mirror of
https://github.com/Fabio286/antares.git
synced 2025-02-09 08:18:43 +01:00
Merge pull request #248 from antares-sql/ts-renderer
TypeScript in renderer process
This commit is contained in:
commit
174579bf8c
@ -1,4 +1,5 @@
|
||||
node_modules
|
||||
assets
|
||||
out
|
||||
dist
|
||||
dist
|
||||
build
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,5 +7,4 @@ node_modules
|
||||
thumbs.db
|
||||
NOTES.md
|
||||
*.txt
|
||||
package-lock.json
|
||||
*.heapsnapshot
|
30996
package-lock.json
generated
Normal file
30996
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -22,8 +22,8 @@
|
||||
"postinstall": "electron-builder install-app-deps && npm run devtools:install",
|
||||
"test:e2e": "npm run compile && npm run test:e2e-dry",
|
||||
"test:e2e-dry": "xvfb-maybe -- playwright test",
|
||||
"lint": "eslint . --ext .js,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
|
||||
"lint:fix": "eslint . --ext .js,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
|
||||
"lint": "eslint . --ext .js,.ts,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
|
||||
"lint:fix": "eslint . --ext .js,.ts,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
|
||||
"contributors:add": "all-contributors add",
|
||||
"contributors:generate": "all-contributors generate"
|
||||
},
|
||||
@ -150,6 +150,8 @@
|
||||
"@babel/preset-typescript": "~7.16.7",
|
||||
"@playwright/test": "~1.21.1",
|
||||
"@types/better-sqlite3": "~7.5.0",
|
||||
"@types/leaflet": "~1.7.9",
|
||||
"@types/marked": "~4.0.3",
|
||||
"@types/node": "~17.0.23",
|
||||
"@types/pg": "~8.6.5",
|
||||
"@typescript-eslint/eslint-plugin": "~5.18.0",
|
||||
@ -189,7 +191,7 @@
|
||||
"unzip-crx-3": "~0.2.0",
|
||||
"vue-eslint-parser": "~8.3.0",
|
||||
"vue-loader": "~16.8.3",
|
||||
"webpack": "~5.60.0",
|
||||
"webpack": "~5.72.0",
|
||||
"webpack-cli": "~4.9.1",
|
||||
"webpack-dev-server": "~4.4.0",
|
||||
"xvfb-maybe": "~0.2.1"
|
||||
|
@ -59,7 +59,7 @@ async function restartElectron () {
|
||||
console.error(chalk.red(data.toString()));
|
||||
});
|
||||
|
||||
electronProcess.on('exit', (code, signal) => {
|
||||
electronProcess.on('exit', () => {
|
||||
if (!manualRestart) process.exit(0);
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
module.exports = {
|
||||
import { Customizations } from '../interfaces/customizations';
|
||||
|
||||
export const defaults: Customizations = {
|
||||
// Defaults
|
||||
defaultPort: null,
|
||||
defaultUser: null,
|
||||
@ -68,24 +70,24 @@ module.exports = {
|
||||
viewUpdateOption: false,
|
||||
procedureDeterministic: false,
|
||||
procedureDataAccess: false,
|
||||
procedureSql: false,
|
||||
procedureSql: null,
|
||||
procedureContext: false,
|
||||
procedureLanguage: false,
|
||||
functionDeterministic: false,
|
||||
functionDataAccess: false,
|
||||
functionSql: false,
|
||||
functionSql: null,
|
||||
functionContext: false,
|
||||
functionLanguage: false,
|
||||
triggerSql: false,
|
||||
triggerSql: null,
|
||||
triggerStatementInCreation: false,
|
||||
triggerMultipleEvents: false,
|
||||
triggerTableInName: false,
|
||||
triggerUpdateColumns: false,
|
||||
triggerOnlyRename: false,
|
||||
triggerEnableDisable: false,
|
||||
triggerFunctionSql: false,
|
||||
triggerFunctionlanguages: false,
|
||||
triggerFunctionSql: null,
|
||||
triggerFunctionlanguages: null,
|
||||
parametersLength: false,
|
||||
languages: false,
|
||||
languages: null,
|
||||
readOnlyMode: false
|
||||
};
|
@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
maria: require('./mysql'),
|
||||
mysql: require('./mysql'),
|
||||
pg: require('./postgresql'),
|
||||
sqlite: require('./sqlite')
|
||||
};
|
16
src/common/customizations/index.ts
Normal file
16
src/common/customizations/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import * as mysql from 'common/customizations/mysql';
|
||||
import * as postgresql from 'common/customizations/postgresql';
|
||||
import * as sqlite from 'common/customizations/sqlite';
|
||||
import { Customizations } from 'common/interfaces/customizations';
|
||||
|
||||
export default {
|
||||
maria: mysql.customizations,
|
||||
mysql: mysql.customizations,
|
||||
pg: postgresql.customizations,
|
||||
sqlite: sqlite.customizations
|
||||
} as {
|
||||
maria: Customizations;
|
||||
mysql: Customizations;
|
||||
pg: Customizations;
|
||||
sqlite: Customizations;
|
||||
};
|
@ -1,6 +1,7 @@
|
||||
const defaults = require('./defaults');
|
||||
import { Customizations } from '../interfaces/customizations';
|
||||
import { defaults } from './defaults';
|
||||
|
||||
module.exports = {
|
||||
export const customizations: Customizations = {
|
||||
...defaults,
|
||||
// Defaults
|
||||
defaultPort: 3306,
|
@ -1,6 +1,7 @@
|
||||
const defaults = require('./defaults');
|
||||
import { Customizations } from '../interfaces/customizations';
|
||||
import { defaults } from './defaults';
|
||||
|
||||
module.exports = {
|
||||
export const customizations: Customizations = {
|
||||
...defaults,
|
||||
// Defaults
|
||||
defaultPort: 5432,
|
@ -1,4 +1,8 @@
|
||||
module.exports = {
|
||||
import { Customizations } from '../interfaces/customizations';
|
||||
import { defaults } from './defaults';
|
||||
|
||||
export const customizations: Customizations = {
|
||||
...defaults,
|
||||
// Core
|
||||
fileConnection: true,
|
||||
// Structure
|
@ -1,4 +1,6 @@
|
||||
module.exports = [
|
||||
import { TypesGroup } from 'common/interfaces/antares';
|
||||
|
||||
export default [
|
||||
{
|
||||
group: 'integer',
|
||||
types: [
|
||||
@ -306,4 +308,4 @@ module.exports = [
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
] as TypesGroup[];
|
@ -1,4 +1,6 @@
|
||||
module.exports = [
|
||||
import { TypesGroup } from 'common/interfaces/antares';
|
||||
|
||||
export default [
|
||||
{
|
||||
group: 'integer',
|
||||
types: [
|
||||
@ -290,4 +292,4 @@ module.exports = [
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
] as TypesGroup[];
|
@ -1,4 +1,6 @@
|
||||
module.exports = [
|
||||
import { TypesGroup } from 'common/interfaces/antares';
|
||||
|
||||
export default [
|
||||
{
|
||||
group: 'integer',
|
||||
types: [
|
||||
@ -134,4 +136,4 @@ module.exports = [
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
] as TypesGroup[];
|
@ -1,4 +1,4 @@
|
||||
module.exports = [
|
||||
export default [
|
||||
'PRIMARY',
|
||||
'INDEX',
|
||||
'UNIQUE',
|
@ -1,4 +1,4 @@
|
||||
module.exports = [
|
||||
export default [
|
||||
'PRIMARY',
|
||||
'INDEX',
|
||||
'UNIQUE'
|
@ -1,4 +1,4 @@
|
||||
module.exports = [
|
||||
export default [
|
||||
'PRIMARY',
|
||||
'INDEX',
|
||||
'UNIQUE'
|
@ -14,6 +14,12 @@ export type ClientCode = 'mysql' | 'maria' | 'pg' | 'sqlite'
|
||||
export type Exporter = MysqlExporter | PostgreSQLExporter
|
||||
export type Importer = MySQLImporter | PostgreSQLImporter
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export interface IpcResponse<T = any> {
|
||||
status: 'success' | 'error';
|
||||
response?: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pasameters needed to create a new Antares connection to a database
|
||||
*/
|
||||
@ -67,12 +73,34 @@ export interface TypeInformations {
|
||||
zerofill: boolean;
|
||||
}
|
||||
|
||||
export interface TypesGroup {
|
||||
group: string;
|
||||
types: TypeInformations[];
|
||||
}
|
||||
|
||||
// Tables
|
||||
export interface TableField {
|
||||
export interface TableInfos {
|
||||
name: string;
|
||||
type: string;
|
||||
rows: number;
|
||||
created: Date;
|
||||
updated: Date;
|
||||
engine: string;
|
||||
comment: string;
|
||||
size: number | false;
|
||||
autoIncrement: number;
|
||||
collation: string;
|
||||
}
|
||||
|
||||
export type TableOptions = Partial<TableInfos>;
|
||||
|
||||
export interface TableField {
|
||||
// eslint-disable-next-line camelcase
|
||||
_antares_id?: string;
|
||||
name: string;
|
||||
key: string;
|
||||
type: string;
|
||||
schema: string;
|
||||
table?: string;
|
||||
numPrecision?: number;
|
||||
numLength?: number;
|
||||
datePrecision?: number;
|
||||
@ -82,7 +110,8 @@ export interface TableField {
|
||||
unsigned?: boolean;
|
||||
zerofill?: boolean;
|
||||
order?: number;
|
||||
default?: number | string;
|
||||
default?: string;
|
||||
defaultType?: string;
|
||||
enumValues?: string;
|
||||
charset?: string;
|
||||
collation?: string;
|
||||
@ -92,9 +121,16 @@ export interface TableField {
|
||||
comment?: string;
|
||||
after?: string;
|
||||
orgName?: string;
|
||||
length?: number | false;
|
||||
alias: string;
|
||||
tableAlias: string;
|
||||
orgTable: string;
|
||||
key?: 'pri' | 'uni';
|
||||
}
|
||||
|
||||
export interface TableIndex {
|
||||
// eslint-disable-next-line camelcase
|
||||
_antares_id?: string;
|
||||
name: string;
|
||||
fields: string[];
|
||||
type: string;
|
||||
@ -107,6 +143,8 @@ export interface TableIndex {
|
||||
}
|
||||
|
||||
export interface TableForeign {
|
||||
// eslint-disable-next-line camelcase
|
||||
_antares_id?: string;
|
||||
constraintName: string;
|
||||
refSchema: string;
|
||||
table: string;
|
||||
@ -118,15 +156,6 @@ export interface TableForeign {
|
||||
oldName?: string;
|
||||
}
|
||||
|
||||
export interface TableOptions {
|
||||
name: string;
|
||||
type?: 'table' | 'view';
|
||||
engine?: string;
|
||||
comment?: string;
|
||||
collation?: string;
|
||||
autoIncrement?: number;
|
||||
}
|
||||
|
||||
export interface CreateTableParams {
|
||||
/** Connection UID */
|
||||
uid?: string;
|
||||
@ -165,6 +194,7 @@ export interface AlterTableParams {
|
||||
}
|
||||
|
||||
// Views
|
||||
export type ViewInfos = TableInfos
|
||||
export interface CreateViewParams {
|
||||
schema: string;
|
||||
name: string;
|
||||
@ -180,6 +210,19 @@ export interface AlterViewParams extends CreateViewParams {
|
||||
}
|
||||
|
||||
// Triggers
|
||||
export interface TriggerInfos {
|
||||
name: string;
|
||||
statement: string;
|
||||
timing: string;
|
||||
definer: string;
|
||||
event: string;
|
||||
table: string;
|
||||
sqlMode: string;
|
||||
created: Date;
|
||||
charset: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface CreateTriggerParams {
|
||||
definer?: string;
|
||||
schema: string;
|
||||
@ -195,13 +238,38 @@ export interface AlterTriggerParams extends CreateTriggerParams {
|
||||
}
|
||||
|
||||
// Routines & Functions
|
||||
|
||||
export interface FunctionParam {
|
||||
// eslint-disable-next-line camelcase
|
||||
_antares_id: string;
|
||||
context: string;
|
||||
name: string;
|
||||
type: string;
|
||||
length: number;
|
||||
}
|
||||
|
||||
export interface RoutineInfos {
|
||||
name: string;
|
||||
type?: string;
|
||||
definer: string;
|
||||
created?: string;
|
||||
sql?: string;
|
||||
updated?: string;
|
||||
comment?: string;
|
||||
charset?: string;
|
||||
security?: string;
|
||||
language?: string;
|
||||
dataAccess?: string;
|
||||
deterministic?: boolean;
|
||||
parameters?: FunctionParam[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
returns?: any;
|
||||
returnsLength?: number;
|
||||
}
|
||||
|
||||
export type FunctionInfos = RoutineInfos
|
||||
export type TriggerFunctionInfos = FunctionInfos
|
||||
|
||||
export interface CreateRoutineParams {
|
||||
name: string;
|
||||
parameters?: FunctionParam[];
|
||||
@ -239,7 +307,7 @@ export interface AlterFunctionParams extends CreateFunctionParams {
|
||||
}
|
||||
|
||||
// Events
|
||||
export interface CreateEventParams {
|
||||
export interface EventInfos {
|
||||
definer?: string;
|
||||
schema: string;
|
||||
name: string;
|
||||
@ -248,16 +316,39 @@ export interface CreateEventParams {
|
||||
starts: string;
|
||||
ends: string;
|
||||
at: string;
|
||||
preserve: string;
|
||||
preserve: boolean;
|
||||
state: string;
|
||||
comment: string;
|
||||
enabled?: boolean;
|
||||
sql: string;
|
||||
}
|
||||
|
||||
export type CreateEventParams = EventInfos;
|
||||
|
||||
export interface AlterEventParams extends CreateEventParams {
|
||||
oldName?: string;
|
||||
}
|
||||
|
||||
// Schema
|
||||
export interface SchemaInfos {
|
||||
name: string;
|
||||
size: number;
|
||||
tables: TableInfos[];
|
||||
functions: FunctionInfos[];
|
||||
procedures: RoutineInfos[];
|
||||
triggers: TriggerInfos[];
|
||||
schedulers: EventInfos[];
|
||||
}
|
||||
|
||||
export interface CollationInfos {
|
||||
charset: string;
|
||||
collation: string;
|
||||
compiled: boolean;
|
||||
default: boolean;
|
||||
id: string | number;
|
||||
sortLen: number;
|
||||
}
|
||||
|
||||
// Query
|
||||
export interface QueryBuilderObject {
|
||||
schema: string;
|
||||
@ -285,17 +376,10 @@ export interface QueryParams {
|
||||
tabUid?: string;
|
||||
}
|
||||
|
||||
export interface QueryField {
|
||||
name: string;
|
||||
alias: string;
|
||||
orgName: string;
|
||||
schema: string;
|
||||
table: string;
|
||||
tableAlias: string;
|
||||
orgTable: string;
|
||||
type: string;
|
||||
length: number;
|
||||
}
|
||||
/**
|
||||
* @deprecated Use TableFIeld
|
||||
*/
|
||||
export type QueryField = TableField
|
||||
|
||||
export interface QueryForeign {
|
||||
schema: string;
|
||||
|
91
src/common/interfaces/customizations.ts
Normal file
91
src/common/interfaces/customizations.ts
Normal file
@ -0,0 +1,91 @@
|
||||
export interface Customizations {
|
||||
// Defaults
|
||||
defaultPort?: number;
|
||||
defaultUser?: string;
|
||||
defaultDatabase?: string;
|
||||
// Core
|
||||
database?: boolean;
|
||||
collations?: boolean;
|
||||
engines?: boolean;
|
||||
connectionSchema?: boolean;
|
||||
sslConnection?: boolean;
|
||||
sshConnection?: boolean;
|
||||
fileConnection?: boolean;
|
||||
cancelQueries?: boolean;
|
||||
// Tools
|
||||
processesList?: boolean;
|
||||
usersManagement?: boolean;
|
||||
variables?: boolean;
|
||||
// Structure
|
||||
schemas?: boolean;
|
||||
tables?: boolean;
|
||||
views?: boolean;
|
||||
triggers?: boolean;
|
||||
triggerFunctions?: boolean;
|
||||
routines?: boolean;
|
||||
functions?: boolean;
|
||||
schedulers?: boolean;
|
||||
// Settings
|
||||
elementsWrapper: string;
|
||||
stringsWrapper: string;
|
||||
tableAdd?: boolean;
|
||||
viewAdd?: boolean;
|
||||
triggerAdd?: boolean;
|
||||
triggerFunctionAdd?: boolean;
|
||||
routineAdd?: boolean;
|
||||
functionAdd?: boolean;
|
||||
schedulerAdd?: boolean;
|
||||
databaseEdit?: boolean;
|
||||
schemaEdit?: boolean;
|
||||
schemaDrop?: boolean;
|
||||
schemaExport?: boolean;
|
||||
exportByChunks?: boolean;
|
||||
schemaImport?: boolean;
|
||||
tableSettings?: boolean;
|
||||
tableOptions?: boolean;
|
||||
tableArray?: boolean;
|
||||
tableRealCount?: boolean;
|
||||
viewSettings?: boolean;
|
||||
triggerSettings?: boolean;
|
||||
triggerFunctionSettings?: boolean;
|
||||
routineSettings?: boolean;
|
||||
functionSettings?: boolean;
|
||||
schedulerSettings?: boolean;
|
||||
indexes?: boolean;
|
||||
foreigns?: boolean;
|
||||
sortableFields?: boolean;
|
||||
unsigned?: boolean;
|
||||
nullable?: boolean;
|
||||
nullablePrimary?: boolean;
|
||||
zerofill?: boolean;
|
||||
autoIncrement?: boolean;
|
||||
comment?: boolean;
|
||||
collation?: boolean;
|
||||
definer?: boolean;
|
||||
onUpdate?: boolean;
|
||||
viewAlgorithm?: boolean;
|
||||
viewSqlSecurity?: boolean;
|
||||
viewUpdateOption?: boolean;
|
||||
procedureDeterministic?: boolean;
|
||||
procedureDataAccess?: boolean;
|
||||
procedureSql?: string;
|
||||
procedureContext?: boolean;
|
||||
procedureLanguage?: boolean;
|
||||
functionDeterministic?: boolean;
|
||||
functionDataAccess?: boolean;
|
||||
functionSql?: string;
|
||||
functionContext?: boolean;
|
||||
functionLanguage?: boolean;
|
||||
triggerSql?: string;
|
||||
triggerStatementInCreation?: boolean;
|
||||
triggerMultipleEvents?: boolean;
|
||||
triggerTableInName?: boolean;
|
||||
triggerUpdateColumns?: boolean;
|
||||
triggerOnlyRename?: boolean;
|
||||
triggerEnableDisable?: boolean;
|
||||
triggerFunctionSql?: string;
|
||||
triggerFunctionlanguages?: string[];
|
||||
parametersLength?: boolean;
|
||||
languages?: string[];
|
||||
readOnlyMode?: boolean;
|
||||
}
|
@ -1,5 +1,34 @@
|
||||
import { UsableLocale } from '@faker-js/faker';
|
||||
|
||||
export interface TableUpdateParams {
|
||||
uid: string;
|
||||
schema: string;
|
||||
table: string;
|
||||
primary?: string;
|
||||
id: number | string;
|
||||
content: number | string | boolean | Date | Blob | null;
|
||||
type: string;
|
||||
field: string;
|
||||
}
|
||||
|
||||
export interface TableDeleteParams {
|
||||
uid: string;
|
||||
schema: string;
|
||||
table: string;
|
||||
primary?: string;
|
||||
field: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
rows: {[key: string]: any};
|
||||
}
|
||||
|
||||
export interface TableFilterClausole {
|
||||
active: boolean;
|
||||
field: string;
|
||||
op: '=' | '!=' | '>' | '<' | '>=' | '<=' | 'IN' | 'NOT IN' | 'LIKE' | 'BETWEEN' | 'IS NULL' | 'IS NOT NULL';
|
||||
value: '';
|
||||
value2: '';
|
||||
}
|
||||
|
||||
export interface InsertRowsParams {
|
||||
uid: string;
|
||||
schema: string;
|
||||
|
@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
export function bufferToBase64 (buf) {
|
||||
export function bufferToBase64 (buf: Buffer) {
|
||||
const binstr = Array.prototype.map.call(buf, ch => {
|
||||
return String.fromCharCode(ch);
|
||||
}).join('');
|
||||
return btoa(binstr);
|
||||
return Buffer.from(binstr, 'binary').toString('base64');
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
'use strict';
|
||||
export function formatBytes (bytes, decimals = 2) {
|
||||
export function formatBytes (bytes: number, decimals = 2) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
|
||||
const k = 1024;
|
@ -1,10 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @param {any[]} array
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getArrayDepth (array) {
|
||||
return Array.isArray(array)
|
||||
? 1 + Math.max(0, ...array.map(getArrayDepth))
|
||||
: 0;
|
||||
}
|
6
src/common/libs/getArrayDepth.ts
Normal file
6
src/common/libs/getArrayDepth.ts
Normal file
@ -0,0 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
export function getArrayDepth (array: any[]): number {
|
||||
return Array.isArray(array)
|
||||
? 1 + Math.max(0, ...array.map(getArrayDepth))
|
||||
: 0;
|
||||
}
|
@ -1,5 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
const lookup = {
|
||||
0: '0000',
|
||||
1: '0001',
|
||||
@ -23,15 +21,11 @@ const lookup = {
|
||||
D: '1101',
|
||||
E: '1110',
|
||||
F: '1111'
|
||||
};
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Converts hexadecimal string to binary string
|
||||
*
|
||||
* @param {string} hex Hexadecimal string
|
||||
* @returns {string} Binary string
|
||||
*/
|
||||
export default function hexToBinary (hex) {
|
||||
export type HexChar = keyof typeof lookup
|
||||
|
||||
export default function hexToBinary (hex: HexChar[]) {
|
||||
let binary = '';
|
||||
for (let i = 0; i < hex.length; i++)
|
||||
binary += lookup[hex[i]];
|
@ -1,5 +1,4 @@
|
||||
'use strict';
|
||||
export function mimeFromHex (hex) {
|
||||
export function mimeFromHex (hex: string) {
|
||||
switch (hex.substring(0, 4)) { // 2 bytes
|
||||
case '424D':
|
||||
return { ext: 'bmp', mime: 'image/bmp' };
|
||||
@ -23,7 +22,7 @@ export function mimeFromHex (hex) {
|
||||
case '425A68':
|
||||
return { ext: 'bz2', mime: 'application/x-bzip2' };
|
||||
default:
|
||||
switch (hex) { // 4 bytes
|
||||
switch (hex) { // 4 bites
|
||||
case '89504E47':
|
||||
return { ext: 'png', mime: 'image/png' };
|
||||
case '47494638':
|
@ -3,13 +3,7 @@
|
||||
const pattern = /[\0\x08\x09\x1a\n\r"'\\\%]/gm;
|
||||
const regex = new RegExp(pattern);
|
||||
|
||||
/**
|
||||
* Escapes a string
|
||||
*
|
||||
* @param {String} string
|
||||
* @returns {String}
|
||||
*/
|
||||
function sqlEscaper (string) {
|
||||
function sqlEscaper (string: string) {
|
||||
return string.replace(regex, char => {
|
||||
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '\"', '\\', '\\\\', '%'];
|
||||
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\\\'', '\\\"', '\\\\', '\\\\\\\\', '\%'];
|
@ -1,8 +0,0 @@
|
||||
/**
|
||||
* @export
|
||||
* @param {String} [prefix]
|
||||
* @returns {String} Unique ID
|
||||
*/
|
||||
export function uidGen (prefix) {
|
||||
return (prefix ? `${prefix}:` : '') + Math.random().toString(36).substr(2, 9).toUpperCase();
|
||||
}
|
3
src/common/libs/uidGen.ts
Normal file
3
src/common/libs/uidGen.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export function uidGen (prefix?: string) {
|
||||
return (prefix ? `${prefix}:` : '') + Math.random().toString(36).substr(2, 9).toUpperCase();
|
||||
}
|
@ -5,11 +5,6 @@ export default () => {
|
||||
app.exit();
|
||||
});
|
||||
|
||||
ipcMain.on('get-key', async event => {
|
||||
const key = false;
|
||||
event.returnValue = key;
|
||||
});
|
||||
|
||||
ipcMain.handle('show-open-dialog', (event, options) => {
|
||||
return dialog.showOpenDialog(options);
|
||||
});
|
||||
|
@ -172,7 +172,10 @@ export default (connections: {[key: string]: antares.Client}) => {
|
||||
});
|
||||
|
||||
ipcMain.handle('export', (event, { uid, type, tables, ...rest }) => {
|
||||
if (exporter !== null) return;
|
||||
if (exporter !== null) {
|
||||
exporter.kill();
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise((resolve/*, reject */) => {
|
||||
(async () => {
|
||||
@ -265,7 +268,10 @@ export default (connections: {[key: string]: antares.Client}) => {
|
||||
});
|
||||
|
||||
ipcMain.handle('import-sql', async (event, options) => {
|
||||
if (importer !== null) return;
|
||||
if (importer !== null) {
|
||||
importer.kill();
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise((resolve/*, reject */) => {
|
||||
(async () => {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import * as fs from 'fs';
|
||||
import * as antares from 'common/interfaces/antares';
|
||||
import { InsertRowsParams } from 'common/interfaces/tableApis';
|
||||
import { ipcMain } from 'electron';
|
||||
@ -5,8 +6,7 @@ import { faker } from '@faker-js/faker';
|
||||
import * as moment from 'moment';
|
||||
import { sqlEscaper } from 'common/libs/sqlEscaper';
|
||||
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes';
|
||||
import * as customizations from 'common/customizations';
|
||||
import fs from 'fs';
|
||||
import customizations from 'common/customizations';
|
||||
|
||||
export default (connections: {[key: string]: antares.Client}) => {
|
||||
ipcMain.handle('get-table-columns', async (event, params) => {
|
||||
@ -246,84 +246,12 @@ export default (connections: {[key: string]: antares.Client}) => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('insert-table-rows', async (event, params) => {
|
||||
try { // TODO: move to client classes
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const insertObj: {[key: string]: any} = {};
|
||||
for (const key in params.row) {
|
||||
const type = params.fields[key];
|
||||
let escapedParam;
|
||||
|
||||
if (params.row[key] === null)
|
||||
escapedParam = 'NULL';
|
||||
else if ([...NUMBER, ...FLOAT].includes(type))
|
||||
escapedParam = +params.row[key];
|
||||
else if ([...TEXT, ...LONG_TEXT].includes(type)) {
|
||||
switch (connections[params.uid]._client) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
escapedParam = `"${sqlEscaper(params.row[key].value)}"`;
|
||||
break;
|
||||
case 'pg':
|
||||
escapedParam = `'${params.row[key].value.replaceAll('\'', '\'\'')}'`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (BLOB.includes(type)) {
|
||||
if (params.row[key].value) {
|
||||
let fileBlob;
|
||||
|
||||
switch (connections[params.uid]._client) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
fileBlob = fs.readFileSync(params.row[key].value);
|
||||
escapedParam = `0x${fileBlob.toString('hex')}`;
|
||||
break;
|
||||
case 'pg':
|
||||
fileBlob = fs.readFileSync(params.row[key].value);
|
||||
escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (connections[params.uid]._client) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
escapedParam = '""';
|
||||
break;
|
||||
case 'pg':
|
||||
escapedParam = 'decode(\'\', \'hex\')';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
insertObj[key] = escapedParam;
|
||||
}
|
||||
|
||||
const rows = new Array(+params.repeat).fill(insertObj);
|
||||
|
||||
await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.into(params.table)
|
||||
.insert(rows)
|
||||
.run();
|
||||
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('insert-table-fake-rows', async (event, params: InsertRowsParams) => {
|
||||
try { // TODO: move to client classes
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const rows: {[key: string]: any}[] = [];
|
||||
const rows: {[key: string]: string | number | boolean | Date | Buffer}[] = [];
|
||||
|
||||
for (let i = 0; i < +params.repeat; i++) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const insertObj: {[key: string]: any} = {};
|
||||
const insertObj: {[key: string]: string | number | boolean | Date | Buffer} = {};
|
||||
|
||||
for (const key in params.row) {
|
||||
const type = params.fields[key];
|
||||
@ -382,8 +310,7 @@ export default (connections: {[key: string]: antares.Client}) => {
|
||||
insertObj[key] = escapedParam;
|
||||
}
|
||||
else { // Faker value
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const parsedParams: {[key: string]: any} = {};
|
||||
const parsedParams: {[key: string]: string | number | boolean | Date | Buffer} = {};
|
||||
let fakeValue;
|
||||
|
||||
if (params.locale)
|
||||
@ -403,7 +330,7 @@ export default (connections: {[key: string]: antares.Client}) => {
|
||||
|
||||
if (typeof fakeValue === 'string') {
|
||||
if (params.row[key].length)
|
||||
fakeValue = fakeValue.substr(0, params.row[key].length);
|
||||
fakeValue = fakeValue.substring(0, params.row[key].length);
|
||||
fakeValue = `'${sqlEscaper(fakeValue)}'`;
|
||||
}
|
||||
else if ([...DATE, ...DATETIME].includes(type))
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import Store from 'electron-store';
|
||||
const persistentStore = new Store({ name: 'settings' });
|
||||
import * as Store from 'electron-store';
|
||||
const persistentStore = new Store({ name: 'settings', clearInvalidConfig: true });
|
||||
const isMacOS = process.platform === 'darwin';
|
||||
|
||||
let mainWindow: Electron.IpcMainEvent;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as antares from 'common/interfaces/antares';
|
||||
import * as mysql from 'mysql2/promise';
|
||||
import { AntaresCore } from '../AntaresCore';
|
||||
import * as dataTypes from 'common/data-types/mysql';
|
||||
import dataTypes from 'common/data-types/mysql';
|
||||
import SSH2Promise = require('ssh2-promise');
|
||||
import SSHConfig from 'ssh2-promise/lib/sshConfig';
|
||||
|
||||
@ -321,7 +321,7 @@ export class MySQLClient extends AntaresCore {
|
||||
return filteredDatabases.map(db => {
|
||||
if (schemas.has(db.Database)) {
|
||||
// TABLES
|
||||
const remappedTables = tablesArr.filter(table => table.Db === db.Database).map(table => {
|
||||
const remappedTables: antares.TableInfos[] = tablesArr.filter(table => table.Db === db.Database).map(table => {
|
||||
let tableType;
|
||||
switch (table.Comment) {
|
||||
case 'VIEW':
|
||||
@ -350,7 +350,7 @@ export class MySQLClient extends AntaresCore {
|
||||
});
|
||||
|
||||
// PROCEDURES
|
||||
const remappedProcedures = procedures.filter(procedure => procedure.Db === db.Database).map(procedure => {
|
||||
const remappedProcedures: antares.RoutineInfos[] = procedures.filter(procedure => procedure.Db === db.Database).map(procedure => {
|
||||
return {
|
||||
name: procedure.Name,
|
||||
type: procedure.Type,
|
||||
@ -364,7 +364,7 @@ export class MySQLClient extends AntaresCore {
|
||||
});
|
||||
|
||||
// FUNCTIONS
|
||||
const remappedFunctions = functions.filter(func => func.Db === db.Database).map(func => {
|
||||
const remappedFunctions: antares.FunctionInfos[] = functions.filter(func => func.Db === db.Database).map(func => {
|
||||
return {
|
||||
name: func.Name,
|
||||
type: func.Type,
|
||||
@ -378,33 +378,26 @@ export class MySQLClient extends AntaresCore {
|
||||
});
|
||||
|
||||
// SCHEDULERS
|
||||
const remappedSchedulers = schedulers.filter(scheduler => scheduler.Db === db.Database).map(scheduler => {
|
||||
const remappedSchedulers: antares.EventInfos[] = schedulers.filter(scheduler => scheduler.Db === db.Database).map(scheduler => {
|
||||
return {
|
||||
name: scheduler.EVENT_NAME,
|
||||
definition: scheduler.EVENT_DEFINITION,
|
||||
type: scheduler.EVENT_TYPE,
|
||||
schema: scheduler.Db,
|
||||
sql: scheduler.EVENT_DEFINITION,
|
||||
execution: scheduler.EVENT_TYPE === 'RECURRING' ? 'EVERY' : 'ONCE',
|
||||
definer: scheduler.DEFINER,
|
||||
body: scheduler.EVENT_BODY,
|
||||
starts: scheduler.STARTS,
|
||||
ends: scheduler.ENDS,
|
||||
state: scheduler.STATUS === 'ENABLED' ? 'ENABLE' : scheduler.STATE === 'DISABLED' ? 'DISABLE' : 'DISABLE ON SLAVE',
|
||||
enabled: scheduler.STATUS === 'ENABLED',
|
||||
executeAt: scheduler.EXECUTE_AT,
|
||||
intervalField: scheduler.INTERVAL_FIELD,
|
||||
intervalValue: scheduler.INTERVAL_VALUE,
|
||||
onCompletion: scheduler.ON_COMPLETION,
|
||||
originator: scheduler.ORIGINATOR,
|
||||
sqlMode: scheduler.SQL_MODE,
|
||||
created: scheduler.CREATED,
|
||||
updated: scheduler.LAST_ALTERED,
|
||||
lastExecuted: scheduler.LAST_EXECUTED,
|
||||
comment: scheduler.EVENT_COMMENT,
|
||||
charset: scheduler.CHARACTER_SET_CLIENT,
|
||||
timezone: scheduler.TIME_ZONE
|
||||
at: scheduler.EXECUTE_AT,
|
||||
every: [scheduler.INTERVAL_FIELD, scheduler.INTERVAL_VALUE],
|
||||
preserve: scheduler.ON_COMPLETION.includes('PRESERVE'),
|
||||
comment: scheduler.EVENT_COMMENT
|
||||
};
|
||||
});
|
||||
|
||||
// TRIGGERS
|
||||
const remappedTriggers = triggersArr.filter(trigger => trigger.Db === db.Database).map(trigger => {
|
||||
const remappedTriggers: antares.TriggerInfos[] = triggersArr.filter(trigger => trigger.Db === db.Database).map(trigger => {
|
||||
return {
|
||||
name: trigger.Trigger,
|
||||
statement: trigger.Statement,
|
||||
@ -930,19 +923,22 @@ export class MySQLClient extends AntaresCore {
|
||||
}
|
||||
|
||||
async getViewInformations ({ schema, view }: { schema: string; view: string }) {
|
||||
const sql = `SHOW CREATE VIEW \`${schema}\`.\`${view}\``;
|
||||
const results = await this.raw(sql);
|
||||
const { rows: algorithm } = await this.raw(`SHOW CREATE VIEW \`${schema}\`.\`${view}\``);
|
||||
const { rows: viewInfo } = await this.raw(`
|
||||
SELECT *
|
||||
FROM INFORMATION_SCHEMA.VIEWS
|
||||
WHERE TABLE_SCHEMA = '${schema}'
|
||||
AND TABLE_NAME = '${view}'
|
||||
`);
|
||||
|
||||
return results.rows.map(row => {
|
||||
return {
|
||||
algorithm: row['Create View'].match(/(?<=CREATE ALGORITHM=).*?(?=\s)/gs)[0],
|
||||
definer: row['Create View'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0],
|
||||
security: row['Create View'].match(/(?<=SQL SECURITY ).*?(?=\s)/gs)[0],
|
||||
updateOption: row['Create View'].match(/(?<=WITH ).*?(?=\s)/gs) ? row['Create View'].match(/(?<=WITH ).*?(?=\s)/gs)[0] : '',
|
||||
sql: row['Create View'].match(/(?<=AS ).*?$/gs)[0],
|
||||
name: row.View
|
||||
};
|
||||
})[0];
|
||||
return {
|
||||
algorithm: algorithm[0]['Create View'].match(/(?<=CREATE ALGORITHM=).*?(?=\s)/gs)[0],
|
||||
definer: viewInfo[0].DEFINER,
|
||||
security: viewInfo[0].SECURITY_TYPE,
|
||||
updateOption: viewInfo[0].CHECK_OPTION === 'NONE' ? '' : viewInfo[0].CHECK_OPTION,
|
||||
sql: viewInfo[0].VIEW_DEFINITION,
|
||||
name: viewInfo[0].TABLE_NAME
|
||||
};
|
||||
}
|
||||
|
||||
async dropView (params: { schema: string; view: string }) {
|
||||
@ -955,7 +951,7 @@ export class MySQLClient extends AntaresCore {
|
||||
USE \`${view.schema}\`;
|
||||
ALTER ALGORITHM = ${view.algorithm}${view.definer ? ` DEFINER=${view.definer}` : ''}
|
||||
SQL SECURITY ${view.security}
|
||||
params \`${view.schema}\`.\`${view.oldName}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''}
|
||||
VIEW \`${view.schema}\`.\`${view.oldName}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''}
|
||||
`;
|
||||
|
||||
if (view.name !== view.oldName)
|
||||
@ -1381,6 +1377,14 @@ export class MySQLClient extends AntaresCore {
|
||||
xa: row.XA,
|
||||
savepoints: row.Savepoints,
|
||||
isDefault: row.Support.includes('DEFAULT')
|
||||
} as {
|
||||
name: string;
|
||||
support: string;
|
||||
comment: string;
|
||||
transactions: string;
|
||||
xa: string;
|
||||
savepoints: string;
|
||||
isDefault: boolean;
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -1405,7 +1409,12 @@ export class MySQLClient extends AntaresCore {
|
||||
break;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}, {}) as {
|
||||
number: string;
|
||||
name: string;
|
||||
arch: string;
|
||||
os: string;
|
||||
};
|
||||
}
|
||||
|
||||
async getProcesses () {
|
||||
@ -1423,6 +1432,15 @@ export class MySQLClient extends AntaresCore {
|
||||
time: row.TIME,
|
||||
state: row.STATE,
|
||||
info: row.INFO
|
||||
} as {
|
||||
id: number;
|
||||
user: string;
|
||||
host: string;
|
||||
db: string;
|
||||
command: string;
|
||||
time: number;
|
||||
state: string;
|
||||
info: string;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { builtinsTypes } from 'pg-types';
|
||||
import * as pg from 'pg';
|
||||
import * as pgAst from 'pgsql-ast-parser';
|
||||
import { AntaresCore } from '../AntaresCore';
|
||||
import * as dataTypes from 'common/data-types/postgresql';
|
||||
import dataTypes from 'common/data-types/postgresql';
|
||||
import SSH2Promise = require('ssh2-promise');
|
||||
import SSHConfig from 'ssh2-promise/lib/sshConfig';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as antares from 'common/interfaces/antares';
|
||||
import * as sqlite from 'better-sqlite3';
|
||||
import { AntaresCore } from '../AntaresCore';
|
||||
import * as dataTypes from 'common/data-types/sqlite';
|
||||
import dataTypes from 'common/data-types/sqlite';
|
||||
import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes';
|
||||
|
||||
export class SQLiteClient extends AntaresCore {
|
||||
|
@ -2,7 +2,7 @@ import * as exporter from 'common/interfaces/exporter';
|
||||
import * as mysql from 'mysql2/promise';
|
||||
import { SqlExporter } from './SqlExporter';
|
||||
import { BLOB, BIT, DATE, DATETIME, FLOAT, SPATIAL, IS_MULTI_SPATIAL, NUMBER } from 'common/fieldTypes';
|
||||
import hexToBinary from 'common/libs/hexToBinary';
|
||||
import hexToBinary, { HexChar } from 'common/libs/hexToBinary';
|
||||
import { getArrayDepth } from 'common/libs/getArrayDepth';
|
||||
import * as moment from 'moment';
|
||||
import { lineString, point, polygon } from '@turf/helpers';
|
||||
@ -138,7 +138,7 @@ ${footer}
|
||||
: this.escapeAndQuote(val);
|
||||
}
|
||||
else if (BIT.includes(column.type))
|
||||
sqlInsertString += `b'${hexToBinary(Buffer.from(val).toString('hex'))}'`;
|
||||
sqlInsertString += `b'${hexToBinary(Buffer.from(val).toString('hex') as undefined as HexChar[])}'`;
|
||||
else if (BLOB.includes(column.type))
|
||||
sqlInsertString += `X'${val.toString('hex').toUpperCase()}'`;
|
||||
else if (NUMBER.includes(column.type))
|
||||
|
@ -2,7 +2,7 @@ import * as antares from 'common/interfaces/antares';
|
||||
import * as exporter from 'common/interfaces/exporter';
|
||||
import { SqlExporter } from './SqlExporter';
|
||||
import { BLOB, BIT, DATE, DATETIME, FLOAT, NUMBER, TEXT_SEARCH } from 'common/fieldTypes';
|
||||
import hexToBinary from 'common/libs/hexToBinary';
|
||||
import hexToBinary, { HexChar } from 'common/libs/hexToBinary';
|
||||
import * as moment from 'moment';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
@ -249,7 +249,7 @@ SET row_security = off;\n\n\n`;
|
||||
else if (TEXT_SEARCH.includes(column.type))
|
||||
sqlInsertString += `'${val.replaceAll('\'', '\'\'')}'`;
|
||||
else if (BIT.includes(column.type))
|
||||
sqlInsertString += `b'${hexToBinary(Buffer.from(val).toString('hex'))}'`;
|
||||
sqlInsertString += `b'${hexToBinary(Buffer.from(val).toString('hex') as undefined as HexChar[])}'`;
|
||||
else if (BLOB.includes(column.type))
|
||||
sqlInsertString += `decode('${val.toString('hex').toUpperCase()}', 'hex')`;
|
||||
else if (NUMBER.includes(column.type))
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as pg from 'pg';
|
||||
import * as importer from 'common/interfaces/importer';
|
||||
import fs from 'fs/promises';
|
||||
import * as fs from 'fs/promises';
|
||||
import PostgreSQLParser from '../../parsers/PostgreSQLParser';
|
||||
import { BaseImporter } from '../BaseImporter';
|
||||
|
||||
|
@ -147,7 +147,7 @@ export default {
|
||||
height: calc(100vh - #{$footer-height});
|
||||
}
|
||||
|
||||
.connection-panel-wrapper{
|
||||
.connection-panel-wrapper {
|
||||
height: calc(100vh - #{$excluding-size});
|
||||
width: 100%;
|
||||
padding-top: 15vh;
|
||||
@ -155,6 +155,6 @@ export default {
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -46,65 +46,58 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BaseConfirmModal',
|
||||
props: {
|
||||
size: {
|
||||
type: String,
|
||||
validator: prop => ['small', 'medium', '400', 'large'].includes(prop),
|
||||
default: 'small'
|
||||
},
|
||||
hideFooter: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
confirmText: String,
|
||||
cancelText: String
|
||||
},
|
||||
emits: ['confirm', 'hide'],
|
||||
computed: {
|
||||
hasHeader () {
|
||||
return !!this.$slots.header;
|
||||
},
|
||||
hasBody () {
|
||||
return !!this.$slots.body;
|
||||
},
|
||||
hasDefault () {
|
||||
return !!this.$slots.default;
|
||||
},
|
||||
modalSizeClass () {
|
||||
if (this.size === 'small')
|
||||
return 'modal-sm';
|
||||
if (this.size === '400')
|
||||
return 'modal-400';
|
||||
else if (this.size === 'large')
|
||||
return 'modal-lg';
|
||||
else return '';
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
confirmModal () {
|
||||
this.$emit('confirm');
|
||||
this.hideModal();
|
||||
},
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, PropType, useSlots } from 'vue';
|
||||
|
||||
hideModal () {
|
||||
this.$emit('hide');
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.hideModal();
|
||||
}
|
||||
}
|
||||
const props = defineProps({
|
||||
size: {
|
||||
type: String as PropType<'small' | 'medium' | '400' | 'large'>,
|
||||
validator: (prop: string) => ['small', 'medium', '400', 'large'].includes(prop),
|
||||
default: 'small'
|
||||
},
|
||||
hideFooter: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
confirmText: String,
|
||||
cancelText: String
|
||||
});
|
||||
const emit = defineEmits(['confirm', 'hide']);
|
||||
const slots = useSlots();
|
||||
|
||||
const hasHeader = computed(() => !!slots.header);
|
||||
const hasBody = computed(() => !!slots.body);
|
||||
const hasDefault = computed(() => !!slots.default);
|
||||
const modalSizeClass = computed(() => {
|
||||
if (props.size === 'small')
|
||||
return 'modal-sm';
|
||||
if (props.size === '400')
|
||||
return 'modal-400';
|
||||
else if (props.size === 'large')
|
||||
return 'modal-lg';
|
||||
else return '';
|
||||
});
|
||||
|
||||
const confirmModal = () => {
|
||||
emit('confirm');
|
||||
hideModal();
|
||||
};
|
||||
|
||||
const hideModal = () => {
|
||||
emit('hide');
|
||||
};
|
||||
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
hideModal();
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', onKey);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -15,67 +15,60 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BaseContextMenu',
|
||||
props: {
|
||||
contextEvent: MouseEvent
|
||||
},
|
||||
emits: ['close-context'],
|
||||
data () {
|
||||
return {
|
||||
contextSize: null,
|
||||
isBottom: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
position () {
|
||||
let topCord = 0;
|
||||
let leftCord = 0;
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, Ref, ref } from 'vue';
|
||||
|
||||
if (this.contextEvent) {
|
||||
const { clientY, clientX } = this.contextEvent;
|
||||
topCord = `${clientY + 2}px`;
|
||||
leftCord = `${clientX + 5}px`;
|
||||
const contextContent: Ref<HTMLDivElement> = ref(null);
|
||||
const contextSize: Ref<DOMRect> = ref(null);
|
||||
const isBottom: Ref<boolean> = ref(false);
|
||||
const props = defineProps<{contextEvent: MouseEvent}>();
|
||||
const emit = defineEmits(['close-context']);
|
||||
|
||||
if (this.contextSize) {
|
||||
if (clientY + (this.contextSize.height < 200 ? 200 : this.contextSize.height) + 5 >= window.innerHeight) {
|
||||
topCord = `${clientY + 3 - this.contextSize.height}px`;
|
||||
this.isBottom = true;
|
||||
}
|
||||
const position = computed(() => {
|
||||
let topCord = '0px';
|
||||
let leftCord = '0px';
|
||||
|
||||
if (clientX + this.contextSize.width + 5 >= window.innerWidth)
|
||||
leftCord = `${clientX - this.contextSize.width}px`;
|
||||
}
|
||||
if (props.contextEvent) {
|
||||
const { clientY, clientX } = props.contextEvent;
|
||||
topCord = `${clientY + 2}px`;
|
||||
leftCord = `${clientX + 5}px`;
|
||||
|
||||
if (contextSize.value) {
|
||||
if (clientY + (contextSize.value.height < 200 ? 200 : contextSize.value.height) + 5 >= window.innerHeight) {
|
||||
topCord = `${clientY + 3 - contextSize.value.height}px`;
|
||||
isBottom.value = true;
|
||||
}
|
||||
|
||||
return {
|
||||
top: topCord,
|
||||
left: leftCord
|
||||
};
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
mounted () {
|
||||
if (this.$refs.contextContent)
|
||||
this.contextSize = this.$refs.contextContent.getBoundingClientRect();
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.$emit('close-context');
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.close();
|
||||
if (clientX + contextSize.value.width + 5 >= window.innerWidth)
|
||||
leftCord = `${clientX - contextSize.value.width}px`;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
top: topCord,
|
||||
left: leftCord
|
||||
};
|
||||
});
|
||||
|
||||
const close = () => {
|
||||
emit('close-context');
|
||||
};
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
close();
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', onKey);
|
||||
|
||||
onMounted(() => {
|
||||
if (contextContent.value)
|
||||
contextSize.value = contextContent.value.getBoundingClientRect();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -4,11 +4,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BaseLoader'
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.empty {
|
||||
position: absolute;
|
||||
|
@ -1,95 +1,93 @@
|
||||
<template>
|
||||
<div id="map" class="map" />
|
||||
</template>
|
||||
<script>
|
||||
import L from 'leaflet';
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, PropType, Ref, ref } from 'vue';
|
||||
import * as L from 'leaflet';
|
||||
import {
|
||||
point,
|
||||
lineString,
|
||||
polygon
|
||||
} from '@turf/helpers';
|
||||
import { GeoJsonObject } from 'geojson';
|
||||
import { getArrayDepth } from 'common/libs/getArrayDepth';
|
||||
|
||||
export default {
|
||||
name: 'BaseMap',
|
||||
props: {
|
||||
points: [Object, Array],
|
||||
isMultiSpatial: Boolean
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
map: null,
|
||||
markers: [],
|
||||
center: null
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
if (this.isMultiSpatial) {
|
||||
for (const element of this.points)
|
||||
this.markers.push(this.getMarkers(element));
|
||||
}
|
||||
else {
|
||||
this.markers = this.getMarkers(this.points);
|
||||
interface Coordinates { x: number; y: number }
|
||||
|
||||
if (!Array.isArray(this.points))
|
||||
this.center = [this.points.y, this.points.x];
|
||||
}
|
||||
const props = defineProps({
|
||||
points: [Object, Array] as PropType<Coordinates | Coordinates[]>,
|
||||
isMultiSpatial: Boolean
|
||||
});
|
||||
const map: Ref<L.Map> = ref(null);
|
||||
const markers: Ref<GeoJsonObject | GeoJsonObject[]> = ref(null);
|
||||
const center: Ref<[number, number]> = ref(null);
|
||||
|
||||
this.map = L.map('map', {
|
||||
center: this.center || [0, 0],
|
||||
zoom: 15,
|
||||
minZoom: 1,
|
||||
attributionControl: false
|
||||
});
|
||||
|
||||
L.control.attribution({ prefix: '<b>Leaflet</b>' }).addTo(this.map);
|
||||
|
||||
const geoJsonObj = L.geoJSON(this.markers, {
|
||||
style: function () {
|
||||
return {
|
||||
weight: 2,
|
||||
fillColor: '#ff7800',
|
||||
color: '#ff7800',
|
||||
opacity: 0.8,
|
||||
fillOpacity: 0.4
|
||||
};
|
||||
},
|
||||
pointToLayer: function (feature, latlng) {
|
||||
return L.circleMarker(latlng, {
|
||||
radius: 7,
|
||||
weight: 2,
|
||||
fillColor: '#ff7800',
|
||||
color: '#ff7800',
|
||||
opacity: 0.8,
|
||||
fillOpacity: 0.4
|
||||
});
|
||||
}
|
||||
}).addTo(this.map);
|
||||
|
||||
const southWest = L.latLng(-90, -180);
|
||||
const northEast = L.latLng(90, 180);
|
||||
const bounds = L.latLngBounds(southWest, northEast);
|
||||
this.map.setMaxBounds(bounds);
|
||||
|
||||
if (!this.center) this.map.fitBounds(geoJsonObj.getBounds());
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <b>OpenStreetMap</b>'
|
||||
}).addTo(this.map);
|
||||
},
|
||||
methods: {
|
||||
getMarkers (points) {
|
||||
if (Array.isArray(points)) {
|
||||
if (getArrayDepth(points) === 1)
|
||||
return lineString(points.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []));
|
||||
else
|
||||
return polygon(points.map(arr => arr.reduce((acc, curr) => [...acc, [curr.x, curr.y]], [])));
|
||||
}
|
||||
else
|
||||
return point([points.x, points.y]);
|
||||
}
|
||||
const getMarkers = (points: Coordinates) => {
|
||||
if (Array.isArray(points)) {
|
||||
if (getArrayDepth(points) === 1)
|
||||
return lineString(points.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []));
|
||||
else
|
||||
return polygon(points.map(arr => arr.reduce((acc: Coordinates[], curr: Coordinates) => [...acc, [curr.x, curr.y]], [])));
|
||||
}
|
||||
else
|
||||
return point([points.x, points.y]);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (props.isMultiSpatial) {
|
||||
for (const element of props.points as Coordinates[])
|
||||
(markers.value as GeoJsonObject[]).push(getMarkers(element));
|
||||
}
|
||||
else {
|
||||
markers.value = getMarkers(props.points as Coordinates);
|
||||
|
||||
if (!Array.isArray(props.points))
|
||||
center.value = [props.points.y, props.points.x];
|
||||
}
|
||||
|
||||
map.value = L.map('map', {
|
||||
center: center.value || [0, 0],
|
||||
zoom: 15,
|
||||
minZoom: 1,
|
||||
attributionControl: false
|
||||
});
|
||||
|
||||
L.control.attribution({ prefix: '<b>Leaflet</b>' }).addTo(map.value);
|
||||
|
||||
const geoJsonObj = L.geoJSON((markers.value as GeoJsonObject), {
|
||||
style: function () {
|
||||
return {
|
||||
weight: 2,
|
||||
fillColor: '#ff7800',
|
||||
color: '#ff7800',
|
||||
opacity: 0.8,
|
||||
fillOpacity: 0.4
|
||||
};
|
||||
},
|
||||
pointToLayer: function (feature, latlng) {
|
||||
return L.circleMarker(latlng, {
|
||||
radius: 7,
|
||||
weight: 2,
|
||||
fillColor: '#ff7800',
|
||||
color: '#ff7800',
|
||||
opacity: 0.8,
|
||||
fillOpacity: 0.4
|
||||
});
|
||||
}
|
||||
}).addTo(map.value);
|
||||
|
||||
const southWest = L.latLng(-90, -180);
|
||||
const northEast = L.latLng(90, 180);
|
||||
const bounds = L.latLngBounds(southWest, northEast);
|
||||
map.value.setMaxBounds(bounds);
|
||||
|
||||
if (!center.value) map.value.fitBounds(geoJsonObj.getBounds());
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <b>OpenStreetMap</b>'
|
||||
}).addTo(map.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -14,64 +14,58 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BaseNotification',
|
||||
props: {
|
||||
message: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
emits: ['close'],
|
||||
data () {
|
||||
return {
|
||||
isExpanded: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
notificationStatus () {
|
||||
let className = '';
|
||||
let iconName = '';
|
||||
switch (this.status) {
|
||||
case 'success':
|
||||
className = 'toast-success';
|
||||
iconName = 'mdi-check';
|
||||
break;
|
||||
case 'error':
|
||||
className = 'toast-error';
|
||||
iconName = 'mdi-alert-rhombus';
|
||||
break;
|
||||
case 'warning':
|
||||
className = 'toast-warning';
|
||||
iconName = 'mdi-alert';
|
||||
break;
|
||||
case 'primary':
|
||||
className = 'toast-primary';
|
||||
iconName = 'mdi-information-outline';
|
||||
break;
|
||||
}
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
return { className, iconName };
|
||||
},
|
||||
isExpandable () {
|
||||
return this.message.length > 80;
|
||||
}
|
||||
const props = defineProps({
|
||||
message: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
methods: {
|
||||
hideToast () {
|
||||
this.$emit('close');
|
||||
},
|
||||
toggleExpand () {
|
||||
this.isExpanded = !this.isExpanded;
|
||||
}
|
||||
status: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
const isExpanded = ref(false);
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
const notificationStatus = computed(() => {
|
||||
let className = '';
|
||||
let iconName = '';
|
||||
switch (props.status) {
|
||||
case 'success':
|
||||
className = 'toast-success';
|
||||
iconName = 'mdi-check';
|
||||
break;
|
||||
case 'error':
|
||||
className = 'toast-error';
|
||||
iconName = 'mdi-alert-rhombus';
|
||||
break;
|
||||
case 'warning':
|
||||
className = 'toast-warning';
|
||||
iconName = 'mdi-alert';
|
||||
break;
|
||||
case 'primary':
|
||||
className = 'toast-primary';
|
||||
iconName = 'mdi-information-outline';
|
||||
break;
|
||||
}
|
||||
|
||||
return { className, iconName };
|
||||
});
|
||||
|
||||
const isExpandable = computed(() => props.message.length > 80);
|
||||
|
||||
const hideToast = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const toggleExpand = () => {
|
||||
isExpanded.value = !isExpanded.value;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.toast {
|
||||
display: flex;
|
||||
|
@ -143,7 +143,7 @@ export default defineComponent({
|
||||
const hightlightedIndex = ref(0);
|
||||
const isOpen = ref(false);
|
||||
const isMouseDown = ref(false);
|
||||
const internalValue = ref(props.modelValue || props.value);
|
||||
const internalValue = ref(props.modelValue !== false ? props.modelValue : props.value);
|
||||
const el = ref(null);
|
||||
const searchInput = ref(null);
|
||||
const optionList = ref(null);
|
||||
@ -403,7 +403,8 @@ export default defineComponent({
|
||||
optionList,
|
||||
optionRefs,
|
||||
handleBlurEvent,
|
||||
handleMouseUpEvent
|
||||
handleMouseUpEvent,
|
||||
internalValue
|
||||
};
|
||||
}
|
||||
});
|
||||
|
@ -9,121 +9,111 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { onMounted, watch } from 'vue';
|
||||
import * as ace from 'ace-builds';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import 'ace-builds/webpack-resolver';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
|
||||
export default {
|
||||
name: 'BaseTextEditor',
|
||||
props: {
|
||||
modelValue: String,
|
||||
mode: { type: String, default: 'text' },
|
||||
editorClass: { type: String, default: '' },
|
||||
autoFocus: { type: Boolean, default: false },
|
||||
readOnly: { type: Boolean, default: false },
|
||||
showLineNumbers: { type: Boolean, default: true },
|
||||
height: { type: Number, default: 200 }
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup () {
|
||||
const settingsStore = useSettingsStore();
|
||||
const props = defineProps({
|
||||
modelValue: String,
|
||||
mode: { type: String, default: 'text' },
|
||||
editorClass: { type: String, default: '' },
|
||||
autoFocus: { type: Boolean, default: false },
|
||||
readOnly: { type: Boolean, default: false },
|
||||
showLineNumbers: { type: Boolean, default: true },
|
||||
height: { type: Number, default: 200 }
|
||||
});
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const {
|
||||
editorTheme,
|
||||
editorFontSize,
|
||||
autoComplete,
|
||||
lineWrap
|
||||
} = storeToRefs(settingsStore);
|
||||
const {
|
||||
editorTheme,
|
||||
editorFontSize,
|
||||
autoComplete,
|
||||
lineWrap
|
||||
} = storeToRefs(settingsStore);
|
||||
|
||||
return {
|
||||
editorTheme,
|
||||
editorFontSize,
|
||||
autoComplete,
|
||||
lineWrap
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
editor: null,
|
||||
id: uidGen()
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
mode () {
|
||||
if (this.editor)
|
||||
this.editor.session.setMode(`ace/mode/${this.mode}`);
|
||||
},
|
||||
editorTheme () {
|
||||
if (this.editor)
|
||||
this.editor.setTheme(`ace/theme/${this.editorTheme}`);
|
||||
},
|
||||
editorFontSize () {
|
||||
const sizes = {
|
||||
small: '12px',
|
||||
medium: '14px',
|
||||
large: '16px'
|
||||
};
|
||||
let editor: ace.Ace.Editor;
|
||||
const id = uidGen();
|
||||
|
||||
if (this.editor) {
|
||||
this.editor.setOptions({
|
||||
fontSize: sizes[this.editorFontSize]
|
||||
});
|
||||
}
|
||||
},
|
||||
autoComplete () {
|
||||
if (this.editor) {
|
||||
this.editor.setOptions({
|
||||
enableLiveAutocompletion: this.autoComplete
|
||||
});
|
||||
}
|
||||
},
|
||||
lineWrap () {
|
||||
if (this.editor) {
|
||||
this.editor.setOptions({
|
||||
wrap: this.lineWrap
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.editor = ace.edit(`editor-${this.id}`, {
|
||||
mode: `ace/mode/${this.mode}`,
|
||||
theme: `ace/theme/${this.editorTheme}`,
|
||||
value: this.modelValue || '',
|
||||
fontSize: '14px',
|
||||
printMargin: false,
|
||||
readOnly: this.readOnly,
|
||||
showLineNumbers: this.showLineNumbers,
|
||||
showGutter: this.showLineNumbers
|
||||
watch(() => props.mode, () => {
|
||||
if (editor)
|
||||
editor.session.setMode(`ace/mode/${props.mode}`);
|
||||
});
|
||||
|
||||
watch(editorTheme, () => {
|
||||
if (editor)
|
||||
editor.setTheme(`ace/theme/${editorTheme.value}`);
|
||||
});
|
||||
|
||||
watch(editorFontSize, () => {
|
||||
const sizes = {
|
||||
small: 12,
|
||||
medium: 14,
|
||||
large: 16
|
||||
};
|
||||
|
||||
if (editor) {
|
||||
editor.setOptions({
|
||||
fontSize: sizes[editorFontSize.value as undefined as 'small' | 'medium' | 'large']
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.setOptions({
|
||||
enableBasicAutocompletion: false,
|
||||
wrap: this.lineWrap,
|
||||
enableSnippets: false,
|
||||
enableLiveAutocompletion: false
|
||||
watch(autoComplete, () => {
|
||||
if (editor) {
|
||||
editor.setOptions({
|
||||
enableLiveAutocompletion: autoComplete.value
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.session.on('change', () => {
|
||||
const content = this.editor.getValue();
|
||||
this.$emit('update:modelValue', content);
|
||||
watch(lineWrap, () => {
|
||||
if (editor) {
|
||||
editor.setOptions({
|
||||
wrap: lineWrap.value
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (this.autoFocus) {
|
||||
setTimeout(() => {
|
||||
this.editor.focus();
|
||||
this.editor.resize();
|
||||
}, 20);
|
||||
}
|
||||
onMounted(() => {
|
||||
editor = ace.edit(`editor-${id}`, {
|
||||
mode: `ace/mode/${props.mode}`,
|
||||
theme: `ace/theme/${editorTheme.value}`,
|
||||
value: props.modelValue || '',
|
||||
fontSize: 14,
|
||||
printMargin: false,
|
||||
readOnly: props.readOnly,
|
||||
showLineNumbers: props.showLineNumbers,
|
||||
showGutter: props.showLineNumbers
|
||||
});
|
||||
|
||||
editor.setOptions({
|
||||
enableBasicAutocompletion: false,
|
||||
wrap: lineWrap,
|
||||
enableSnippets: false,
|
||||
enableLiveAutocompletion: false
|
||||
});
|
||||
|
||||
(editor.session as unknown as ace.Ace.Editor).on('change', () => {
|
||||
const content = editor.getValue();
|
||||
emit('update:modelValue', content);
|
||||
});
|
||||
|
||||
if (props.autoFocus) {
|
||||
setTimeout(() => {
|
||||
this.editor.resize();
|
||||
editor.focus();
|
||||
editor.resize();
|
||||
}, 20);
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
editor.resize();
|
||||
}, 20);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -9,67 +9,63 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BaseToast',
|
||||
props: {
|
||||
message: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
emits: ['close'],
|
||||
data () {
|
||||
return {
|
||||
isVisible: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
toastStatus () {
|
||||
let className = '';
|
||||
let iconName = '';
|
||||
switch (this.status) {
|
||||
case 'success':
|
||||
className = 'toast-success';
|
||||
iconName = 'mdi-check';
|
||||
break;
|
||||
case 'error':
|
||||
className = 'toast-error';
|
||||
iconName = 'mdi-alert-rhombus';
|
||||
break;
|
||||
case 'warning':
|
||||
className = 'toast-warning';
|
||||
iconName = 'mdi-alert';
|
||||
break;
|
||||
case 'primary':
|
||||
className = 'toast-primary';
|
||||
iconName = 'mdi-information-outline';
|
||||
break;
|
||||
}
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
return { className, iconName };
|
||||
}
|
||||
const props = defineProps({
|
||||
message: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
watch: {
|
||||
message: function () {
|
||||
if (this.message)
|
||||
this.isVisible = true;
|
||||
else
|
||||
this.isVisible = false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hideToast () {
|
||||
this.isVisible = false;
|
||||
this.$emit('close');
|
||||
}
|
||||
status: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
const isVisible = ref(false);
|
||||
const message = ref(props.message);
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
const toastStatus = computed(() => {
|
||||
let className = '';
|
||||
let iconName = '';
|
||||
switch (props.status) {
|
||||
case 'success':
|
||||
className = 'toast-success';
|
||||
iconName = 'mdi-check';
|
||||
break;
|
||||
case 'error':
|
||||
className = 'toast-error';
|
||||
iconName = 'mdi-alert-rhombus';
|
||||
break;
|
||||
case 'warning':
|
||||
className = 'toast-warning';
|
||||
iconName = 'mdi-alert';
|
||||
break;
|
||||
case 'primary':
|
||||
className = 'toast-primary';
|
||||
iconName = 'mdi-information-outline';
|
||||
break;
|
||||
}
|
||||
|
||||
return { className, iconName };
|
||||
});
|
||||
|
||||
watch(message, () => {
|
||||
if (message.value)
|
||||
isVisible.value = true;
|
||||
else
|
||||
isVisible.value = false;
|
||||
});
|
||||
|
||||
const hideToast = () => {
|
||||
isVisible.value = false;
|
||||
emit('close');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.toast {
|
||||
display: flex;
|
||||
|
@ -7,7 +7,7 @@
|
||||
{{ lastPart(modelValue) }}
|
||||
</span>
|
||||
<i
|
||||
v-if="modelValue.length"
|
||||
v-if="modelValue"
|
||||
class="file-uploader-reset mdi mdi-close"
|
||||
@click.prevent="clear"
|
||||
/>
|
||||
@ -22,40 +22,35 @@
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
|
||||
export default {
|
||||
name: 'BaseUploadInput',
|
||||
props: {
|
||||
message: {
|
||||
default: 'Browse',
|
||||
type: String
|
||||
},
|
||||
modelValue: {
|
||||
default: '',
|
||||
type: String
|
||||
}
|
||||
defineProps({
|
||||
message: {
|
||||
default: 'Browse',
|
||||
type: String
|
||||
},
|
||||
emits: ['change', 'clear'],
|
||||
data () {
|
||||
return {
|
||||
id: uidGen()
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
clear () {
|
||||
this.$emit('clear');
|
||||
},
|
||||
lastPart (string) {
|
||||
if (!string) return '';
|
||||
|
||||
string = string.split(/[/\\]+/).pop();
|
||||
if (string.length >= 19)
|
||||
string = `...${string.slice(-19)}`;
|
||||
return string;
|
||||
}
|
||||
modelValue: {
|
||||
default: '',
|
||||
type: String
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['change', 'clear']);
|
||||
|
||||
const id = uidGen();
|
||||
|
||||
const clear = () => {
|
||||
emit('clear');
|
||||
};
|
||||
|
||||
const lastPart = (string: string) => {
|
||||
if (!string) return '';
|
||||
|
||||
string = string.split(/[/\\]+/).pop();
|
||||
if (string.length >= 19)
|
||||
string = `...${string.slice(-19)}`;
|
||||
return string;
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="vscroll-holder">
|
||||
<div ref="root" class="vscroll-holder">
|
||||
<div
|
||||
class="vscroll-spacer"
|
||||
:style="{
|
||||
@ -20,71 +20,76 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BaseVirtualScroll',
|
||||
props: {
|
||||
items: Array,
|
||||
itemHeight: Number,
|
||||
visibleHeight: Number,
|
||||
scrollElement: {
|
||||
type: HTMLDivElement,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
topHeight: 0,
|
||||
bottomHeight: 0,
|
||||
visibleItems: [],
|
||||
renderTimeout: null,
|
||||
localScrollElement: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
scrollElement () {
|
||||
this.setScrollElement();
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.setScrollElement();
|
||||
},
|
||||
beforeUnmount () {
|
||||
this.localScrollElement.removeEventListener('scroll', this.checkScrollPosition);
|
||||
},
|
||||
methods: {
|
||||
checkScrollPosition (e) {
|
||||
clearTimeout(this.renderTimeout);
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, Ref, ref, watch } from 'vue';
|
||||
|
||||
this.renderTimeout = setTimeout(() => {
|
||||
this.updateWindow(e);
|
||||
}, 200);
|
||||
},
|
||||
updateWindow () {
|
||||
const visibleItemsCount = Math.ceil(this.visibleHeight / this.itemHeight);
|
||||
const totalScrollHeight = this.items.length * this.itemHeight;
|
||||
const offset = 50;
|
||||
|
||||
const scrollTop = this.localScrollElement.scrollTop;
|
||||
|
||||
const firstVisibleIndex = Math.floor(scrollTop / this.itemHeight);
|
||||
const lastVisibleIndex = firstVisibleIndex + visibleItemsCount;
|
||||
const firstCutIndex = Math.max(firstVisibleIndex - offset, 0);
|
||||
const lastCutIndex = lastVisibleIndex + offset;
|
||||
|
||||
this.visibleItems = this.items.slice(firstCutIndex, lastCutIndex);
|
||||
|
||||
this.topHeight = firstCutIndex * this.itemHeight;
|
||||
this.bottomHeight = totalScrollHeight - this.visibleItems.length * this.itemHeight - this.topHeight;
|
||||
},
|
||||
setScrollElement () {
|
||||
if (this.localScrollElement)
|
||||
this.localScrollElement.removeEventListener('scroll', this.checkScrollPosition);
|
||||
|
||||
this.localScrollElement = this.scrollElement ? this.scrollElement : this.$el;
|
||||
this.updateWindow();
|
||||
this.localScrollElement.addEventListener('scroll', this.checkScrollPosition);
|
||||
}
|
||||
const props = defineProps({
|
||||
items: Array,
|
||||
itemHeight: Number,
|
||||
visibleHeight: Number,
|
||||
scrollElement: {
|
||||
type: HTMLDivElement,
|
||||
default: null
|
||||
}
|
||||
});
|
||||
|
||||
const root = ref(null);
|
||||
const topHeight: Ref<number> = ref(0);
|
||||
const bottomHeight: Ref<number> = ref(0);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const visibleItems: Ref<any[]> = ref([]);
|
||||
const renderTimeout: Ref<NodeJS.Timeout> = ref(null);
|
||||
const localScrollElement: Ref<HTMLDivElement> = ref(null);
|
||||
|
||||
const checkScrollPosition = () => {
|
||||
clearTimeout(renderTimeout.value);
|
||||
|
||||
renderTimeout.value = setTimeout(() => {
|
||||
updateWindow();
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const updateWindow = () => {
|
||||
const visibleItemsCount = Math.ceil(props.visibleHeight / props.itemHeight);
|
||||
const totalScrollHeight = props.items.length * props.itemHeight;
|
||||
const offset = 50;
|
||||
|
||||
const scrollTop = localScrollElement.value.scrollTop;
|
||||
|
||||
const firstVisibleIndex = Math.floor(scrollTop / props.itemHeight);
|
||||
const lastVisibleIndex = firstVisibleIndex + visibleItemsCount;
|
||||
const firstCutIndex = Math.max(firstVisibleIndex - offset, 0);
|
||||
const lastCutIndex = lastVisibleIndex + offset;
|
||||
|
||||
visibleItems.value = props.items.slice(firstCutIndex, lastCutIndex);
|
||||
|
||||
topHeight.value = firstCutIndex * props.itemHeight;
|
||||
bottomHeight.value = totalScrollHeight - visibleItems.value.length * props.itemHeight - topHeight.value;
|
||||
};
|
||||
|
||||
const setScrollElement = () => {
|
||||
if (localScrollElement.value)
|
||||
localScrollElement.value.removeEventListener('scroll', checkScrollPosition);
|
||||
|
||||
localScrollElement.value = props.scrollElement ? props.scrollElement : root.value;
|
||||
updateWindow();
|
||||
localScrollElement.value.addEventListener('scroll', checkScrollPosition);
|
||||
};
|
||||
|
||||
watch(() => props.scrollElement, () => {
|
||||
setScrollElement();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
setScrollElement();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
localScrollElement.value.removeEventListener('scroll', checkScrollPosition);
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
updateWindow
|
||||
});
|
||||
|
||||
</script>
|
||||
|
@ -4,7 +4,7 @@
|
||||
v-model="selectedGroup"
|
||||
class="form-select"
|
||||
:options="[{name: 'manual'}, ...fakerGroups]"
|
||||
:option-label="(opt) => opt.name === 'manual' ? $t('message.manualValue') : $t(`faker.${opt.name}`)"
|
||||
:option-label="(opt: any) => opt.name === 'manual' ? $t('message.manualValue') : $t(`faker.${opt.name}`)"
|
||||
option-track-by="name"
|
||||
:disabled="!isChecked"
|
||||
style="flex-grow: 0;"
|
||||
@ -15,7 +15,7 @@
|
||||
v-if="selectedGroup !== 'manual'"
|
||||
v-model="selectedMethod"
|
||||
:options="fakerMethods"
|
||||
:option-label="(opt) => $t(`faker.${opt.name}`)"
|
||||
:option-label="(opt: any) => $t(`faker.${opt.name}`)"
|
||||
option-track-by="name"
|
||||
class="form-select"
|
||||
:disabled="!isChecked"
|
||||
@ -85,153 +85,149 @@
|
||||
</fieldset>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { computed, PropType, Ref, ref, watch } from 'vue';
|
||||
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
|
||||
import BaseUploadInput from '@/components/BaseUploadInput';
|
||||
import ForeignKeySelect from '@/components/ForeignKeySelect';
|
||||
import BaseUploadInput from '@/components/BaseUploadInput.vue';
|
||||
import ForeignKeySelect from '@/components/ForeignKeySelect.vue';
|
||||
import FakerMethods from 'common/FakerMethods';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
|
||||
export default {
|
||||
name: 'FakerSelect',
|
||||
components: {
|
||||
ForeignKeySelect,
|
||||
BaseUploadInput,
|
||||
BaseSelect
|
||||
},
|
||||
props: {
|
||||
type: String,
|
||||
field: Object,
|
||||
isChecked: Boolean,
|
||||
foreignKeys: Array,
|
||||
keyUsage: Array,
|
||||
fieldLength: Number,
|
||||
fieldObj: Object
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
data () {
|
||||
return {
|
||||
localType: null,
|
||||
selectedGroup: 'manual',
|
||||
selectedMethod: '',
|
||||
selectedValue: '',
|
||||
debounceTimeout: null,
|
||||
methodParams: {},
|
||||
enumArray: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
fakerGroups () {
|
||||
if ([...TEXT, ...LONG_TEXT].includes(this.type))
|
||||
this.localType = 'string';
|
||||
else if (NUMBER.includes(this.type))
|
||||
this.localType = 'number';
|
||||
else if (FLOAT.includes(this.type))
|
||||
this.localType = 'float';
|
||||
else if ([...DATE, ...DATETIME].includes(this.type))
|
||||
this.localType = 'datetime';
|
||||
else if (TIME.includes(this.type))
|
||||
this.localType = 'time';
|
||||
else
|
||||
this.localType = 'none';
|
||||
const props = defineProps({
|
||||
type: String,
|
||||
field: Object,
|
||||
isChecked: Boolean,
|
||||
foreignKeys: Array,
|
||||
keyUsage: Array as PropType<{field: string}[]>,
|
||||
fieldLength: Number,
|
||||
fieldObj: Object
|
||||
});
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
return FakerMethods.getGroupsByType(this.localType);
|
||||
},
|
||||
fakerMethods () {
|
||||
return FakerMethods.getMethods({ type: this.localType, group: this.selectedGroup });
|
||||
},
|
||||
methodData () {
|
||||
return this.fakerMethods.find(method => method.name === this.selectedMethod);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
fieldObj () {
|
||||
if (this.fieldObj) {
|
||||
if (Array.isArray(this.fieldObj.value)) {
|
||||
this.enumArray = this.fieldObj.value;
|
||||
this.selectedValue = this.fieldObj.value[0];
|
||||
}
|
||||
else
|
||||
this.selectedValue = this.fieldObj.value;
|
||||
}
|
||||
},
|
||||
selectedGroup () {
|
||||
if (this.fakerMethods.length)
|
||||
this.selectedMethod = this.fakerMethods[0].name;
|
||||
else
|
||||
this.selectedMethod = '';
|
||||
},
|
||||
selectedMethod () {
|
||||
this.onChange();
|
||||
},
|
||||
selectedValue () {
|
||||
clearTimeout(this.debounceTimeout);
|
||||
this.debounceTimeout = null;
|
||||
this.debounceTimeout = setTimeout(() => {
|
||||
this.onChange();
|
||||
}, 200);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
inputProps () {
|
||||
if ([...TEXT, ...LONG_TEXT].includes(this.type))
|
||||
return { type: 'text', mask: false };
|
||||
const localType: Ref<string> = ref(null);
|
||||
const selectedGroup: Ref<string> = ref('manual');
|
||||
const selectedMethod: Ref<string> = ref('');
|
||||
const selectedValue: Ref<string> = ref('');
|
||||
const debounceTimeout: Ref<NodeJS.Timeout> = ref(null);
|
||||
const methodParams: Ref<{[key: string]: string}> = ref({});
|
||||
const enumArray: Ref<string[]> = ref(null);
|
||||
|
||||
if ([...NUMBER, ...FLOAT].includes(this.type))
|
||||
return { type: 'number', mask: false };
|
||||
const fakerGroups = computed(() => {
|
||||
if ([...TEXT, ...LONG_TEXT].includes(props.type))
|
||||
localType.value = 'string';
|
||||
else if (NUMBER.includes(props.type))
|
||||
localType.value = 'number';
|
||||
else if (FLOAT.includes(props.type))
|
||||
localType.value = 'float';
|
||||
else if ([...DATE, ...DATETIME].includes(props.type))
|
||||
localType.value = 'datetime';
|
||||
else if (TIME.includes(props.type))
|
||||
localType.value = 'time';
|
||||
else
|
||||
localType.value = 'none';
|
||||
|
||||
if (TIME.includes(this.type)) {
|
||||
let timeMask = '##:##:##';
|
||||
const precision = this.fieldLength;
|
||||
return FakerMethods.getGroupsByType(localType.value);
|
||||
});
|
||||
|
||||
for (let i = 0; i < precision; i++)
|
||||
timeMask += i === 0 ? '.#' : '#';
|
||||
const fakerMethods = computed(() => {
|
||||
return FakerMethods.getMethods({ type: localType.value, group: selectedGroup.value });
|
||||
});
|
||||
|
||||
return { type: 'text', mask: timeMask };
|
||||
}
|
||||
const methodData = computed(() => {
|
||||
return fakerMethods.value.find(method => method.name === selectedMethod.value);
|
||||
});
|
||||
|
||||
if (DATE.includes(this.type))
|
||||
return { type: 'text', mask: '####-##-##' };
|
||||
const inputProps = () => {
|
||||
if ([...TEXT, ...LONG_TEXT].includes(props.type))
|
||||
return { type: 'text', mask: false };
|
||||
|
||||
if (DATETIME.includes(this.type)) {
|
||||
let datetimeMask = '####-##-## ##:##:##';
|
||||
const precision = this.fieldLength;
|
||||
if ([...NUMBER, ...FLOAT].includes(props.type))
|
||||
return { type: 'number', mask: false };
|
||||
|
||||
for (let i = 0; i < precision; i++)
|
||||
datetimeMask += i === 0 ? '.#' : '#';
|
||||
if (TIME.includes(props.type)) {
|
||||
let timeMask = '##:##:##';
|
||||
const precision = props.fieldLength;
|
||||
|
||||
return { type: 'text', mask: datetimeMask };
|
||||
}
|
||||
for (let i = 0; i < precision; i++)
|
||||
timeMask += i === 0 ? '.#' : '#';
|
||||
|
||||
if (BLOB.includes(this.type))
|
||||
return { type: 'file', mask: false };
|
||||
|
||||
if (BIT.includes(this.type))
|
||||
return { type: 'text', mask: false };
|
||||
|
||||
return { type: 'text', mask: false };
|
||||
},
|
||||
getKeyUsage (keyName) {
|
||||
return this.keyUsage.find(key => key.field === keyName);
|
||||
},
|
||||
filesChange (event) {
|
||||
const { files } = event.target;
|
||||
if (!files.length) return;
|
||||
|
||||
this.selectedValue = files[0].path;
|
||||
},
|
||||
clearValue () {
|
||||
this.selectedValue = '';
|
||||
},
|
||||
onChange () {
|
||||
this.$emit('update:modelValue', {
|
||||
group: this.selectedGroup,
|
||||
method: this.selectedMethod,
|
||||
params: this.methodParams,
|
||||
value: this.selectedValue,
|
||||
length: this.fieldLength
|
||||
});
|
||||
}
|
||||
return { type: 'text', mask: timeMask };
|
||||
}
|
||||
|
||||
if (DATE.includes(props.type))
|
||||
return { type: 'text', mask: '####-##-##' };
|
||||
|
||||
if (DATETIME.includes(props.type)) {
|
||||
let datetimeMask = '####-##-## ##:##:##';
|
||||
const precision = props.fieldLength;
|
||||
|
||||
for (let i = 0; i < precision; i++)
|
||||
datetimeMask += i === 0 ? '.#' : '#';
|
||||
|
||||
return { type: 'text', mask: datetimeMask };
|
||||
}
|
||||
|
||||
if (BLOB.includes(props.type))
|
||||
return { type: 'file', mask: false };
|
||||
|
||||
if (BIT.includes(props.type))
|
||||
return { type: 'text', mask: false };
|
||||
|
||||
return { type: 'text', mask: false };
|
||||
};
|
||||
|
||||
const getKeyUsage = (keyName: string) => {
|
||||
return props.keyUsage.find(key => key.field === keyName);
|
||||
};
|
||||
|
||||
const filesChange = ({ target } : {target: HTMLInputElement }) => {
|
||||
const { files } = target;
|
||||
if (!files.length) return;
|
||||
|
||||
selectedValue.value = files[0].path;
|
||||
};
|
||||
|
||||
const clearValue = () => {
|
||||
selectedValue.value = '';
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
emit('update:modelValue', {
|
||||
group: selectedGroup.value,
|
||||
method: selectedMethod.value,
|
||||
params: methodParams.value,
|
||||
value: selectedValue.value,
|
||||
length: props.fieldLength
|
||||
});
|
||||
};
|
||||
|
||||
watch(() => props.fieldObj, () => {
|
||||
if (props.fieldObj) {
|
||||
if (Array.isArray(props.fieldObj.value)) {
|
||||
enumArray.value = props.fieldObj.value;
|
||||
selectedValue.value = props.fieldObj.value[0];
|
||||
}
|
||||
else
|
||||
selectedValue.value = props.fieldObj.value;
|
||||
}
|
||||
});
|
||||
|
||||
watch(selectedGroup, () => {
|
||||
if (fakerMethods.value.length)
|
||||
selectedMethod.value = fakerMethods.value[0].name;
|
||||
else
|
||||
selectedMethod.value = '';
|
||||
});
|
||||
|
||||
watch(selectedMethod, () => {
|
||||
onChange();
|
||||
});
|
||||
|
||||
watch(selectedValue, () => {
|
||||
clearTimeout(debounceTimeout.value);
|
||||
debounceTimeout.value = null;
|
||||
debounceTimeout.value = setTimeout(() => {
|
||||
onChange();
|
||||
}, 200);
|
||||
});
|
||||
</script>
|
||||
|
@ -8,107 +8,100 @@
|
||||
dropdown-class="select-sm"
|
||||
dropdown-container=".workspace-query-results > .vscroll"
|
||||
@change="onChange"
|
||||
@blur="$emit('blur')"
|
||||
@blur="emit('blur')"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { computed, Ref, ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import { TEXT, LONG_TEXT } from 'common/fieldTypes';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
import { TableField } from 'common/interfaces/antares';
|
||||
|
||||
export default {
|
||||
name: 'ForeignKeySelect',
|
||||
components: { BaseSelect },
|
||||
props: {
|
||||
modelValue: [String, Number],
|
||||
keyUsage: Object,
|
||||
size: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'blur'],
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
return { addNotification, selectedWorkspace };
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
foreignList: [],
|
||||
currentValue: this.modelValue || null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isValidDefault () {
|
||||
if (!this.foreignList.length) return true;
|
||||
if (this.modelValue === null) return false;
|
||||
return this.foreignList.some(foreign => foreign.foreign_column.toString() === this.modelValue.toString());
|
||||
},
|
||||
foreigns () {
|
||||
const list = [];
|
||||
|
||||
if (!this.isValidDefault)
|
||||
list.push({ value: this.modelValue, label: this.modelValue === null ? 'NULL' : this.modelValue });
|
||||
|
||||
for (const row of this.foreignList)
|
||||
list.push({ value: row.foreign_column, label: `${row.foreign_column} ${this.cutText('foreign_description' in row ? ` - ${row.foreign_description}` : '')}` });
|
||||
|
||||
return list;
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
let foreignDesc;
|
||||
const params = {
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.keyUsage.refSchema,
|
||||
table: this.keyUsage.refTable
|
||||
};
|
||||
|
||||
try { // Field data
|
||||
const { status, response } = await Tables.getTableColumns(params);
|
||||
if (status === 'success') {
|
||||
const textField = response.find(field => [...TEXT, ...LONG_TEXT].includes(field.type) && field.name !== this.keyUsage.refField);
|
||||
foreignDesc = textField ? textField.name : false;
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
try { // Foregn list
|
||||
const { status, response } = await Tables.getForeignList({
|
||||
...params,
|
||||
column: this.keyUsage.refField,
|
||||
description: foreignDesc
|
||||
});
|
||||
|
||||
if (status === 'success')
|
||||
this.foreignList = response.rows;
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange (opt) {
|
||||
this.$emit('update:modelValue', opt.value);
|
||||
},
|
||||
cutText (val) {
|
||||
if (typeof val !== 'string') return val;
|
||||
return val.length > 15 ? `${val.substring(0, 15)}...` : val;
|
||||
}
|
||||
const props = defineProps({
|
||||
modelValue: [String, Number],
|
||||
keyUsage: Object,
|
||||
size: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'blur']);
|
||||
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
const editField: Ref<HTMLSelectElement> = ref(null);
|
||||
const foreignList = ref([]);
|
||||
const currentValue = ref(props.modelValue);
|
||||
|
||||
const isValidDefault = computed(() => {
|
||||
if (!foreignList.value.length) return true;
|
||||
if (props.modelValue === null) return false;
|
||||
return foreignList.value.some(foreign => foreign.foreign_column.toString() === props.modelValue.toString());
|
||||
});
|
||||
|
||||
const foreigns = computed(() => {
|
||||
const list = [];
|
||||
if (!isValidDefault.value)
|
||||
list.push({ value: props.modelValue, label: props.modelValue === null ? 'NULL' : props.modelValue });
|
||||
for (const row of foreignList.value)
|
||||
list.push({ value: row.foreign_column, label: `${row.foreign_column} ${cutText('foreign_description' in row ? ` - ${row.foreign_description}` : '')}` });
|
||||
return list;
|
||||
});
|
||||
|
||||
const onChange = (opt: HTMLSelectElement) => {
|
||||
emit('update:modelValue', opt.value);
|
||||
};
|
||||
|
||||
const cutText = (val: string) => {
|
||||
if (typeof val !== 'string') return val;
|
||||
return val.length > 15 ? `${val.substring(0, 15)}...` : val;
|
||||
};
|
||||
|
||||
let foreignDesc: string | false;
|
||||
const params = {
|
||||
uid: selectedWorkspace.value,
|
||||
schema: props.keyUsage.refSchema,
|
||||
table: props.keyUsage.refTable
|
||||
};
|
||||
|
||||
(async () => {
|
||||
try { // Field data
|
||||
const { status, response } = await Tables.getTableColumns(params);
|
||||
|
||||
if (status === 'success') {
|
||||
const textField = (response as TableField[]).find((field: {type: string; name: string}) => [...TEXT, ...LONG_TEXT].includes(field.type) && field.name !== props.keyUsage.refField);
|
||||
foreignDesc = textField ? textField.name : false;
|
||||
}
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
try { // Foregn list
|
||||
const { status, response } = await Tables.getForeignList({
|
||||
...params,
|
||||
column: props.keyUsage.refField,
|
||||
description: foreignDesc
|
||||
});
|
||||
|
||||
if (status === 'success')
|
||||
foreignList.value = response.rows;
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
@ -55,30 +55,25 @@
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ModalAskCredentials',
|
||||
emits: ['close-asking', 'credentials'],
|
||||
data () {
|
||||
return {
|
||||
credentials: {
|
||||
user: '',
|
||||
password: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
created () {
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
closeModal () {
|
||||
this.$emit('close-asking');
|
||||
},
|
||||
sendCredentials () {
|
||||
this.$emit('credentials', this.credentials);
|
||||
}
|
||||
}
|
||||
<script setup lang="ts">
|
||||
import { Ref, ref } from 'vue';
|
||||
|
||||
const credentials = ref({
|
||||
user: '',
|
||||
password: ''
|
||||
});
|
||||
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||
const emit = defineEmits(['close-asking', 'credentials']);
|
||||
|
||||
const closeModal = () => {
|
||||
emit('close-asking');
|
||||
};
|
||||
|
||||
const sendCredentials = () => {
|
||||
emit('credentials', credentials.value);
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
firstInput.value.focus();
|
||||
}, 20);
|
||||
</script>
|
||||
|
@ -47,83 +47,75 @@
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { computed, PropType, Ref, ref } from 'vue';
|
||||
import { NUMBER, FLOAT } from 'common/fieldTypes';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import { FunctionInfos, RoutineInfos } from 'common/interfaces/antares';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||
|
||||
export default {
|
||||
name: 'ModalAskParameters',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
localRoutine: Object,
|
||||
client: String
|
||||
},
|
||||
emits: ['confirm', 'close'],
|
||||
data () {
|
||||
return {
|
||||
values: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
inParameters () {
|
||||
return this.localRoutine.parameters.filter(param => param.context === 'IN');
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
const props = defineProps({
|
||||
localRoutine: Object as PropType<RoutineInfos | FunctionInfos>,
|
||||
client: String
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput[0].focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
typeClass (type) {
|
||||
if (type)
|
||||
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
|
||||
return '';
|
||||
},
|
||||
runRoutine () {
|
||||
const valArr = Object.keys(this.values).reduce((acc, curr, i) => {
|
||||
let qc;
|
||||
switch (this.client) {
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
qc = '"';
|
||||
break;
|
||||
case 'pg':
|
||||
qc = '\'';
|
||||
break;
|
||||
default:
|
||||
qc = '"';
|
||||
}
|
||||
const emit = defineEmits(['confirm', 'close']);
|
||||
|
||||
const param = this.localRoutine.parameters.find(param => `${i}-${param.name}` === curr);
|
||||
const firstInput: Ref<HTMLInputElement[]> = ref(null);
|
||||
const values: Ref<{[key: string]: string}> = ref({});
|
||||
|
||||
const value = [...NUMBER, ...FLOAT].includes(param.type) ? this.values[curr] : `${qc}${this.values[curr]}${qc}`;
|
||||
acc.push(value);
|
||||
return acc;
|
||||
}, []);
|
||||
this.$emit('confirm', valArr);
|
||||
},
|
||||
closeModal () {
|
||||
this.$emit('close');
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
},
|
||||
wrapNumber (num) {
|
||||
if (!num) return '';
|
||||
return `(${num})`;
|
||||
}
|
||||
}
|
||||
const inParameters = computed(() => {
|
||||
return props.localRoutine.parameters.filter(param => param.context === 'IN');
|
||||
});
|
||||
|
||||
const typeClass = (type: string) => {
|
||||
if (type)
|
||||
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
|
||||
return '';
|
||||
};
|
||||
|
||||
const runRoutine = () => {
|
||||
const valArr = Object.keys(values.value).reduce((acc, curr, i) => {
|
||||
let qc;
|
||||
switch (props.client) {
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
qc = '"';
|
||||
break;
|
||||
case 'pg':
|
||||
qc = '\'';
|
||||
break;
|
||||
default:
|
||||
qc = '"';
|
||||
}
|
||||
|
||||
const param = props.localRoutine.parameters.find(param => `${i}-${param.name}` === curr);
|
||||
|
||||
const value = [...NUMBER, ...FLOAT].includes(param.type) ? values.value[curr] : `${qc}${values.value[curr]}${qc}`;
|
||||
acc.push(value);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
emit('confirm', valArr);
|
||||
};
|
||||
|
||||
const closeModal = () => emit('close');
|
||||
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const wrapNumber = (num: number) => {
|
||||
if (!num) return '';
|
||||
return `(${num})`;
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', onKey);
|
||||
|
||||
setTimeout(() => {
|
||||
firstInput.value[0].focus();
|
||||
}, 20);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -2,8 +2,8 @@
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.discard')"
|
||||
:cancel-text="$t('word.stay')"
|
||||
@confirm="$emit('confirm')"
|
||||
@hide="$emit('close')"
|
||||
@confirm="emit('confirm')"
|
||||
@hide="emit('close')"
|
||||
>
|
||||
<template #header>
|
||||
<div class="d-flex">
|
||||
@ -18,29 +18,23 @@
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
<script setup lang="ts">
|
||||
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||
import { onBeforeUnmount } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'ModalDiscardChanges',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
emits: ['confirm', 'close'],
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
const emit = defineEmits(['confirm', 'close']);
|
||||
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
emit('close');
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', onKey);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -62,116 +62,99 @@
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, Ref, ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import Schema from '@/ipc-api/Schema';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
|
||||
export default {
|
||||
name: 'ModalEditSchema',
|
||||
components: {
|
||||
BaseSelect
|
||||
},
|
||||
props: {
|
||||
selectedSchema: String
|
||||
},
|
||||
emits: ['close'],
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const props = defineProps({
|
||||
selectedSchema: String
|
||||
});
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
const { getWorkspace, getDatabaseVariable } = workspacesStore;
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
getDatabaseVariable
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
database: {
|
||||
name: '',
|
||||
prevName: '',
|
||||
collation: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
collations () {
|
||||
return this.getWorkspace(this.selectedWorkspace).collations;
|
||||
},
|
||||
defaultCollation () {
|
||||
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value || '';
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
let actualCollation;
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
const { getWorkspace, getDatabaseVariable } = workspacesStore;
|
||||
|
||||
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||
const database = ref({
|
||||
name: '',
|
||||
prevName: '',
|
||||
collation: '',
|
||||
prevCollation: null
|
||||
});
|
||||
|
||||
const collations = computed(() => getWorkspace(selectedWorkspace.value).collations);
|
||||
const defaultCollation = computed(() => (getDatabaseVariable(selectedWorkspace.value, 'collation_server').value || ''));
|
||||
|
||||
const updateSchema = async () => {
|
||||
if (database.value.collation !== database.value.prevCollation) {
|
||||
try {
|
||||
const { status, response } = await Schema.getDatabaseCollation({ uid: this.selectedWorkspace, database: this.selectedSchema });
|
||||
const { status, response } = await Schema.updateSchema({
|
||||
uid: selectedWorkspace.value,
|
||||
...database.value
|
||||
});
|
||||
|
||||
if (status === 'success')
|
||||
actualCollation = response;
|
||||
|
||||
closeModal();
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.database = {
|
||||
name: this.selectedSchema,
|
||||
prevName: this.selectedSchema,
|
||||
collation: actualCollation || this.defaultCollation,
|
||||
prevCollation: actualCollation || this.defaultCollation
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
async updateSchema () {
|
||||
if (this.database.collation !== this.database.prevCollation) {
|
||||
try {
|
||||
const { status, response } = await Schema.updateSchema({
|
||||
uid: this.selectedWorkspace,
|
||||
...this.database
|
||||
});
|
||||
|
||||
if (status === 'success')
|
||||
this.closeModal();
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
}
|
||||
else
|
||||
this.closeModal();
|
||||
},
|
||||
closeModal () {
|
||||
this.$emit('close');
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
}
|
||||
else closeModal();
|
||||
};
|
||||
|
||||
const closeModal = () => emit('close');
|
||||
|
||||
const onKey =(e: KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
closeModal();
|
||||
};
|
||||
|
||||
(async () => {
|
||||
let actualCollation;
|
||||
try {
|
||||
const { status, response } = await Schema.getDatabaseCollation({ uid: selectedWorkspace.value, database: props.selectedSchema });
|
||||
|
||||
if (status === 'success')
|
||||
actualCollation = response;
|
||||
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
database.value = {
|
||||
name: props.selectedSchema,
|
||||
prevName: props.selectedSchema,
|
||||
collation: actualCollation || defaultCollation.value,
|
||||
prevCollation: actualCollation || defaultCollation.value
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', onKey);
|
||||
|
||||
setTimeout(() => {
|
||||
firstInput.value.focus();
|
||||
}, 20);
|
||||
})();
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -146,7 +146,7 @@
|
||||
<div class="tbody">
|
||||
<div
|
||||
v-for="item in tables"
|
||||
:key="item.name"
|
||||
:key="item.table"
|
||||
class="tr"
|
||||
>
|
||||
<div class="td">
|
||||
@ -193,7 +193,7 @@
|
||||
>
|
||||
<input v-model="options.includes[key]" type="checkbox"><i class="form-icon" /> {{ $tc(`word.${key}`, 2) }}
|
||||
</label>
|
||||
<div v-if="customizations.exportByChunks">
|
||||
<div v-if="clientCustoms.exportByChunks">
|
||||
<div class="h6 mt-4 mb-2">
|
||||
{{ $t('message.newInserStmtEvery') }}:
|
||||
</div>
|
||||
@ -263,211 +263,204 @@
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, Ref, ref } from 'vue';
|
||||
import * as moment from 'moment';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { SchemaInfos } from 'common/interfaces/antares';
|
||||
import { ExportState, TableParams } from 'common/interfaces/exporter';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import customizations from 'common/customizations';
|
||||
import Application from '@/ipc-api/Application';
|
||||
import Schema from '@/ipc-api/Schema';
|
||||
import { Customizations } from 'common/interfaces/customizations';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
|
||||
export default {
|
||||
name: 'ModalExportSchema',
|
||||
components: {
|
||||
BaseSelect
|
||||
},
|
||||
props: {
|
||||
selectedSchema: String
|
||||
},
|
||||
emits: ['close'],
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const props = defineProps({
|
||||
selectedSchema: String
|
||||
});
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const emit = defineEmits(['close']);
|
||||
const { t } = useI18n();
|
||||
|
||||
const {
|
||||
getWorkspace,
|
||||
getDatabaseVariable,
|
||||
refreshSchema
|
||||
} = workspacesStore;
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
getDatabaseVariable,
|
||||
refreshSchema
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isExporting: false,
|
||||
isRefreshing: false,
|
||||
progressPercentage: 0,
|
||||
progressStatus: '',
|
||||
tables: [],
|
||||
options: {
|
||||
includes: {},
|
||||
outputFormat: 'sql',
|
||||
sqlInsertAfter: 250,
|
||||
sqlInsertDivider: 'bytes'
|
||||
},
|
||||
basePath: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentWorkspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
},
|
||||
customizations () {
|
||||
return this.currentWorkspace.customizations;
|
||||
},
|
||||
schemaItems () {
|
||||
const db = this.currentWorkspace.structure.find(db => db.name === this.selectedSchema);
|
||||
if (db)
|
||||
return db.tables.filter(table => table.type === 'table');
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
return [];
|
||||
},
|
||||
filename () {
|
||||
const date = moment().format('YYYY-MM-DD');
|
||||
return `${this.selectedSchema}_${date}.${this.options.outputFormat}`;
|
||||
},
|
||||
dumpFilePath () {
|
||||
return `${this.basePath}/${this.filename}`;
|
||||
},
|
||||
includeStructureStatus () {
|
||||
if (this.tables.every(item => item.includeStructure)) return 1;
|
||||
else if (this.tables.some(item => item.includeStructure)) return 2;
|
||||
else return 0;
|
||||
},
|
||||
includeContentStatus () {
|
||||
if (this.tables.every(item => item.includeContent)) return 1;
|
||||
else if (this.tables.some(item => item.includeContent)) return 2;
|
||||
else return 0;
|
||||
},
|
||||
includeDropStatementStatus () {
|
||||
if (this.tables.every(item => item.includeDropStatement)) return 1;
|
||||
else if (this.tables.some(item => item.includeDropStatement)) return 2;
|
||||
else return 0;
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
if (!this.schemaItems.length) await this.refresh();
|
||||
const {
|
||||
getWorkspace,
|
||||
refreshSchema
|
||||
} = workspacesStore;
|
||||
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
const isExporting = ref(false);
|
||||
const isRefreshing = ref(false);
|
||||
const progressPercentage = ref(0);
|
||||
const progressStatus = ref('');
|
||||
const tables: Ref<TableParams[]> = ref([]);
|
||||
const options = ref({
|
||||
includes: {} as {[key: string]: boolean},
|
||||
outputFormat: 'sql',
|
||||
sqlInsertAfter: 250,
|
||||
sqlInsertDivider: 'bytes'
|
||||
});
|
||||
const basePath = ref('');
|
||||
|
||||
this.basePath = await Application.getDownloadPathDirectory();
|
||||
this.tables = this.schemaItems.map(item => ({
|
||||
table: item.name,
|
||||
includeStructure: true,
|
||||
includeContent: true,
|
||||
includeDropStatement: true
|
||||
}));
|
||||
const currentWorkspace = computed(() => getWorkspace(selectedWorkspace.value));
|
||||
const clientCustoms: Ref<Customizations> = computed(() => currentWorkspace.value.customizations);
|
||||
const schemaItems = computed(() => {
|
||||
const db: SchemaInfos = currentWorkspace.value.structure.find((db: SchemaInfos) => db.name === props.selectedSchema);
|
||||
if (db)
|
||||
return db.tables.filter(table => table.type === 'table');
|
||||
|
||||
const structure = ['functions', 'views', 'triggers', 'routines', 'schedulers'];
|
||||
return [];
|
||||
});
|
||||
const filename = computed(() => {
|
||||
const date = moment().format('YYYY-MM-DD');
|
||||
return `${props.selectedSchema}_${date}.${options.value.outputFormat}`;
|
||||
});
|
||||
const dumpFilePath = computed(() => `${basePath.value}/${filename.value}`);
|
||||
const includeStructureStatus = computed(() => {
|
||||
if (tables.value.every(item => item.includeStructure)) return 1;
|
||||
else if (tables.value.some(item => item.includeStructure)) return 2;
|
||||
else return 0;
|
||||
});
|
||||
const includeContentStatus = computed(() => {
|
||||
if (tables.value.every(item => item.includeContent)) return 1;
|
||||
else if (tables.value.some(item => item.includeContent)) return 2;
|
||||
else return 0;
|
||||
});
|
||||
const includeDropStatementStatus = computed(() => {
|
||||
if (tables.value.every(item => item.includeDropStatement)) return 1;
|
||||
else if (tables.value.some(item => item.includeDropStatement)) return 2;
|
||||
else return 0;
|
||||
});
|
||||
|
||||
structure.forEach(feat => {
|
||||
const val = customizations[this.currentWorkspace.client][feat];
|
||||
if (val)
|
||||
this.options.includes[feat] = true;
|
||||
});
|
||||
const startExport = async () => {
|
||||
isExporting.value = true;
|
||||
const { uid, client } = currentWorkspace.value;
|
||||
const params = {
|
||||
uid,
|
||||
type: client,
|
||||
schema: props.selectedSchema,
|
||||
outputFile: dumpFilePath.value,
|
||||
tables: [...tables.value],
|
||||
...options.value
|
||||
};
|
||||
|
||||
ipcRenderer.on('export-progress', this.updateProgress);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
ipcRenderer.off('export-progress', this.updateProgress);
|
||||
},
|
||||
methods: {
|
||||
async startExport () {
|
||||
this.isExporting = true;
|
||||
const { uid, client } = this.currentWorkspace;
|
||||
const params = {
|
||||
uid,
|
||||
type: client,
|
||||
schema: this.selectedSchema,
|
||||
outputFile: this.dumpFilePath,
|
||||
tables: [...this.tables],
|
||||
...this.options
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Schema.export(params);
|
||||
if (status === 'success')
|
||||
this.progressStatus = response.cancelled ? this.$t('word.aborted') : this.$t('word.completed');
|
||||
else {
|
||||
this.progressStatus = response;
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isExporting = false;
|
||||
},
|
||||
updateProgress (event, state) {
|
||||
this.progressPercentage = Number((state.currentItemIndex / state.totalItems * 100).toFixed(1));
|
||||
switch (state.op) {
|
||||
case 'PROCESSING':
|
||||
this.progressStatus = this.$t('message.processingTableExport', { table: state.currentItem });
|
||||
break;
|
||||
case 'FETCH':
|
||||
this.progressStatus = this.$t('message.fechingTableExport', { table: state.currentItem });
|
||||
break;
|
||||
case 'WRITE':
|
||||
this.progressStatus = this.$t('message.writingTableExport', { table: state.currentItem });
|
||||
break;
|
||||
}
|
||||
},
|
||||
async closeModal () {
|
||||
let willClose = true;
|
||||
if (this.isExporting) {
|
||||
willClose = false;
|
||||
const { response } = await Schema.abortExport();
|
||||
willClose = response.willAbort;
|
||||
}
|
||||
|
||||
if (willClose)
|
||||
this.$emit('close');
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
},
|
||||
checkAllTables () {
|
||||
this.tables = this.tables.map(item => ({ ...item, includeStructure: true, includeContent: true, includeDropStatement: true }));
|
||||
},
|
||||
uncheckAllTables () {
|
||||
this.tables = this.tables.map(item => ({ ...item, includeStructure: false, includeContent: false, includeDropStatement: false }));
|
||||
},
|
||||
toggleAllTablesOption (option) {
|
||||
const options = ['includeStructure', 'includeContent', 'includeDropStatement'];
|
||||
if (!options.includes(option)) return;
|
||||
|
||||
if (this[`${option}Status`] !== 1)
|
||||
this.tables = this.tables.map(item => ({ ...item, [option]: true }));
|
||||
else
|
||||
this.tables = this.tables.map(item => ({ ...item, [option]: false }));
|
||||
},
|
||||
async refresh () {
|
||||
this.isRefreshing = true;
|
||||
await this.refreshSchema({ uid: this.currentWorkspace.uid, schema: this.selectedSchema });
|
||||
this.isRefreshing = false;
|
||||
},
|
||||
async openPathDialog () {
|
||||
const result = await Application.showOpenDialog({ properties: ['openDirectory'] });
|
||||
if (result && !result.canceled)
|
||||
this.basePath = result.filePaths[0];
|
||||
try {
|
||||
const { status, response } = await Schema.export(params);
|
||||
if (status === 'success')
|
||||
progressStatus.value = response.cancelled ? t('word.aborted') : t('word.completed');
|
||||
else {
|
||||
progressStatus.value = response;
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
isExporting.value = false;
|
||||
};
|
||||
|
||||
const updateProgress = (event: Event, state: ExportState) => {
|
||||
progressPercentage.value = Number((state.currentItemIndex / state.totalItems * 100).toFixed(1));
|
||||
switch (state.op) {
|
||||
case 'PROCESSING':
|
||||
progressStatus.value = t('message.processingTableExport', { table: state.currentItem });
|
||||
break;
|
||||
case 'FETCH':
|
||||
progressStatus.value = t('message.fechingTableExport', { table: state.currentItem });
|
||||
break;
|
||||
case 'WRITE':
|
||||
progressStatus.value = t('message.writingTableExport', { table: state.currentItem });
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const closeModal = async () => {
|
||||
let willClose = true;
|
||||
if (isExporting.value) {
|
||||
willClose = false;
|
||||
const { response } = await Schema.abortExport();
|
||||
willClose = response.willAbort;
|
||||
}
|
||||
|
||||
if (willClose)
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const checkAllTables = () => {
|
||||
tables.value = tables.value.map(item => ({ ...item, includeStructure: true, includeContent: true, includeDropStatement: true }));
|
||||
};
|
||||
|
||||
const uncheckAllTables = () => {
|
||||
tables.value = tables.value.map(item => ({ ...item, includeStructure: false, includeContent: false, includeDropStatement: false }));
|
||||
};
|
||||
|
||||
const toggleAllTablesOption = (option: 'includeStructure' | 'includeContent' |'includeDropStatement') => {
|
||||
const options = {
|
||||
includeStructure: includeStructureStatus.value,
|
||||
includeContent: includeContentStatus.value,
|
||||
includeDropStatement: includeDropStatementStatus.value
|
||||
};
|
||||
|
||||
if (options[option] !== 1)
|
||||
tables.value = tables.value.map(item => ({ ...item, [option]: true }));
|
||||
else
|
||||
tables.value = tables.value.map(item => ({ ...item, [option]: false }));
|
||||
};
|
||||
|
||||
const refresh = async () => {
|
||||
isRefreshing.value = true;
|
||||
await refreshSchema({ uid: currentWorkspace.value.uid, schema: props.selectedSchema });
|
||||
isRefreshing.value = false;
|
||||
};
|
||||
|
||||
const openPathDialog = async () => {
|
||||
const result = await Application.showOpenDialog({ properties: ['openDirectory'] });
|
||||
if (result && !result.canceled)
|
||||
basePath.value = result.filePaths[0];
|
||||
};
|
||||
|
||||
(async () => {
|
||||
if (!schemaItems.value.length) await refresh();
|
||||
|
||||
window.addEventListener('keydown', onKey);
|
||||
|
||||
basePath.value = await Application.getDownloadPathDirectory();
|
||||
tables.value = schemaItems.value.map(item => ({
|
||||
table: item.name,
|
||||
includeStructure: true,
|
||||
includeContent: true,
|
||||
includeDropStatement: true
|
||||
}));
|
||||
|
||||
const structure = ['functions', 'views', 'triggers', 'routines', 'schedulers'];
|
||||
|
||||
structure.forEach((feat: keyof Customizations) => {
|
||||
const val = clientCustoms.value[feat];
|
||||
if (val)
|
||||
options.value.includes[feat] = true;
|
||||
});
|
||||
|
||||
ipcRenderer.on('export-progress', updateProgress);
|
||||
})();
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
ipcRenderer.off('export-progress', updateProgress);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -476,14 +469,15 @@ export default {
|
||||
overflow: hidden;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.workspace-query-results {
|
||||
flex: 1 0 1px;
|
||||
flex: 1 0 1px;
|
||||
|
||||
.table {
|
||||
width: 100% !important;
|
||||
}
|
||||
@ -499,25 +493,24 @@ export default {
|
||||
}
|
||||
|
||||
.modal {
|
||||
|
||||
.modal-container {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
max-height: 60vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.modal-body {
|
||||
max-height: 60vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
}
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-status {
|
||||
font-style: italic;
|
||||
font-size: 80%;
|
||||
font-style: italic;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@ -97,251 +97,228 @@
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeMount, onMounted, Prop, Ref, ref, watch } from 'vue';
|
||||
import * as moment from 'moment';
|
||||
import { TableField, TableForeign } from 'common/interfaces/antares';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import FakerSelect from '@/components/FakerSelect';
|
||||
import FakerSelect from '@/components/FakerSelect.vue';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
|
||||
export default {
|
||||
name: 'ModalFakerRows',
|
||||
components: {
|
||||
FakerSelect,
|
||||
BaseSelect
|
||||
},
|
||||
props: {
|
||||
tabUid: [String, Number],
|
||||
fields: Array,
|
||||
keyUsage: Array
|
||||
},
|
||||
emits: ['reload', 'hide'],
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const props = defineProps({
|
||||
tabUid: [String, Number],
|
||||
fields: Array as Prop<TableField[]>,
|
||||
keyUsage: Array as Prop<TableForeign[]>
|
||||
});
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const { getWorkspace, getWorkspaceTab } = workspacesStore;
|
||||
const locales = [
|
||||
{ value: 'ar', label: 'Arabic' },
|
||||
{ value: 'az', label: 'Azerbaijani' },
|
||||
{ value: 'zh_CN', label: 'Chinese' },
|
||||
{ value: 'zh_TW', label: 'Chinese (Taiwan)' },
|
||||
{ value: 'cz', label: 'Czech' },
|
||||
{ value: 'nl', label: 'Dutch' },
|
||||
{ value: 'nl_BE', label: 'Dutch (Belgium)' },
|
||||
{ value: 'en', label: 'English' },
|
||||
{ value: 'en_AU_ocker', label: 'English (Australia Ocker)' },
|
||||
{ value: 'en_AU', label: 'English (Australia)' },
|
||||
{ value: 'en_BORK', label: 'English (Bork)' },
|
||||
{ value: 'en_CA', label: 'English (Canada)' },
|
||||
{ value: 'en_GB', label: 'English (Great Britain)' },
|
||||
{ value: 'en_IND', label: 'English (India)' },
|
||||
{ value: 'en_IE', label: 'English (Ireland)' },
|
||||
{ value: 'en_ZA', label: 'English (South Africa)' },
|
||||
{ value: 'en_US', label: 'English (United States)' },
|
||||
{ value: 'fa', label: 'Farsi' },
|
||||
{ value: 'fi', label: 'Finnish' },
|
||||
{ value: 'fr', label: 'French' },
|
||||
{ value: 'fr_CA', label: 'French (Canada)' },
|
||||
{ value: 'fr_CH', label: 'French (Switzerland)' },
|
||||
{ value: 'ge', label: 'Georgian' },
|
||||
{ value: 'de', label: 'German' },
|
||||
{ value: 'de_AT', label: 'German (Austria)' },
|
||||
{ value: 'de_CH', label: 'German (Switzerland)' },
|
||||
{ value: 'hr', label: 'Hrvatski' },
|
||||
{ value: 'id_ID', label: 'Indonesia' },
|
||||
{ value: 'it', label: 'Italian' },
|
||||
{ value: 'ja', label: 'Japanese' },
|
||||
{ value: 'ko', label: 'Korean' },
|
||||
{ value: 'nep', label: 'Nepalese' },
|
||||
{ value: 'nb_NO', label: 'Norwegian' },
|
||||
{ value: 'pl', label: 'Polish' },
|
||||
{ value: 'pt_BR', label: 'Portuguese (Brazil)' },
|
||||
{ value: 'pt_PT', label: 'Portuguese (Portugal)' },
|
||||
{ value: 'ro', label: 'Romanian' },
|
||||
{ value: 'ru', label: 'Russian' },
|
||||
{ value: 'sk', label: 'Slovakian' },
|
||||
{ value: 'es', label: 'Spanish' },
|
||||
{ value: 'es_MX', label: 'Spanish (Mexico)' },
|
||||
{ value: 'sv', label: 'Swedish' },
|
||||
{ value: 'tr', label: 'Turkish' },
|
||||
{ value: 'uk', label: 'Ukrainian' },
|
||||
{ value: 'vi', label: 'Vietnamese' }
|
||||
const emit = defineEmits(['reload', 'hide']);
|
||||
|
||||
];
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
getWorkspaceTab,
|
||||
locales
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localRow: {},
|
||||
fieldsToExclude: [],
|
||||
nInserts: 1,
|
||||
isInserting: false,
|
||||
fakerLocale: 'en'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
},
|
||||
foreignKeys () {
|
||||
return this.keyUsage.map(key => key.field);
|
||||
},
|
||||
hasFakes () {
|
||||
return Object.keys(this.localRow).some(field => 'group' in this.localRow[field] && this.localRow[field].group !== 'manual');
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
nInserts (val) {
|
||||
if (!val || val < 1)
|
||||
this.nInserts = 1;
|
||||
else if (val > 1000)
|
||||
this.nInserts = 1000;
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
mounted () {
|
||||
const rowObj = {};
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
for (const field of this.fields) {
|
||||
let fieldDefault;
|
||||
const { getWorkspace } = workspacesStore;
|
||||
|
||||
if (field.default === 'NULL') fieldDefault = null;
|
||||
else {
|
||||
if ([...NUMBER, ...FLOAT].includes(field.type))
|
||||
fieldDefault = !field.default || Number.isNaN(+field.default.replaceAll('\'', '')) ? null : +field.default.replaceAll('\'', '');
|
||||
else if ([...TEXT, ...LONG_TEXT].includes(field.type)) {
|
||||
fieldDefault = field.default
|
||||
? field.default.includes('\'')
|
||||
? field.default.split('\'')[1]
|
||||
: field.default
|
||||
: '';
|
||||
}
|
||||
else if ([...TIME, ...DATE].includes(field.type))
|
||||
fieldDefault = field.default;
|
||||
else if (BIT.includes(field.type))
|
||||
fieldDefault = field.default?.replaceAll('\'', '').replaceAll('b', '');
|
||||
else if (DATETIME.includes(field.type)) {
|
||||
if (field.default && ['current_timestamp', 'now()'].some(term => field.default.toLowerCase().includes(term))) {
|
||||
let datePrecision = '';
|
||||
for (let i = 0; i < field.datePrecision; i++)
|
||||
datePrecision += i === 0 ? '.S' : 'S';
|
||||
fieldDefault = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
|
||||
}
|
||||
else
|
||||
fieldDefault = field.default;
|
||||
}
|
||||
else if (field.enumValues)
|
||||
fieldDefault = field.enumValues.replaceAll('\'', '').split(',');
|
||||
else
|
||||
fieldDefault = field.default;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const localRow: Ref<{[key: string]: any}> = ref({});
|
||||
const fieldsToExclude = ref([]);
|
||||
const nInserts = ref(1);
|
||||
const isInserting = ref(false);
|
||||
const fakerLocale = ref('en');
|
||||
|
||||
rowObj[field.name] = { value: fieldDefault };
|
||||
const workspace = computed(() => getWorkspace(selectedWorkspace.value));
|
||||
const foreignKeys = computed(() => props.keyUsage.map(key => key.field));
|
||||
const hasFakes = computed(() => Object.keys(localRow.value).some(field => 'group' in localRow.value[field] && localRow.value[field].group !== 'manual'));
|
||||
|
||||
if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields
|
||||
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
|
||||
}
|
||||
const locales = [
|
||||
{ value: 'ar', label: 'Arabic' },
|
||||
{ value: 'az', label: 'Azerbaijani' },
|
||||
{ value: 'zh_CN', label: 'Chinese' },
|
||||
{ value: 'zh_TW', label: 'Chinese (Taiwan)' },
|
||||
{ value: 'cz', label: 'Czech' },
|
||||
{ value: 'nl', label: 'Dutch' },
|
||||
{ value: 'nl_BE', label: 'Dutch (Belgium)' },
|
||||
{ value: 'en', label: 'English' },
|
||||
{ value: 'en_AU_ocker', label: 'English (Australia Ocker)' },
|
||||
{ value: 'en_AU', label: 'English (Australia)' },
|
||||
{ value: 'en_BORK', label: 'English (Bork)' },
|
||||
{ value: 'en_CA', label: 'English (Canada)' },
|
||||
{ value: 'en_GB', label: 'English (Great Britain)' },
|
||||
{ value: 'en_IND', label: 'English (India)' },
|
||||
{ value: 'en_IE', label: 'English (Ireland)' },
|
||||
{ value: 'en_ZA', label: 'English (South Africa)' },
|
||||
{ value: 'en_US', label: 'English (United States)' },
|
||||
{ value: 'fa', label: 'Farsi' },
|
||||
{ value: 'fi', label: 'Finnish' },
|
||||
{ value: 'fr', label: 'French' },
|
||||
{ value: 'fr_CA', label: 'French (Canada)' },
|
||||
{ value: 'fr_CH', label: 'French (Switzerland)' },
|
||||
{ value: 'ge', label: 'Georgian' },
|
||||
{ value: 'de', label: 'German' },
|
||||
{ value: 'de_AT', label: 'German (Austria)' },
|
||||
{ value: 'de_CH', label: 'German (Switzerland)' },
|
||||
{ value: 'hr', label: 'Hrvatski' },
|
||||
{ value: 'id_ID', label: 'Indonesia' },
|
||||
{ value: 'it', label: 'Italian' },
|
||||
{ value: 'ja', label: 'Japanese' },
|
||||
{ value: 'ko', label: 'Korean' },
|
||||
{ value: 'nep', label: 'Nepalese' },
|
||||
{ value: 'nb_NO', label: 'Norwegian' },
|
||||
{ value: 'pl', label: 'Polish' },
|
||||
{ value: 'pt_BR', label: 'Portuguese (Brazil)' },
|
||||
{ value: 'pt_PT', label: 'Portuguese (Portugal)' },
|
||||
{ value: 'ro', label: 'Romanian' },
|
||||
{ value: 'ru', label: 'Russian' },
|
||||
{ value: 'sk', label: 'Slovakian' },
|
||||
{ value: 'es', label: 'Spanish' },
|
||||
{ value: 'es_MX', label: 'Spanish (Mexico)' },
|
||||
{ value: 'sv', label: 'Swedish' },
|
||||
{ value: 'tr', label: 'Turkish' },
|
||||
{ value: 'uk', label: 'Ukrainian' },
|
||||
{ value: 'vi', label: 'Vietnamese' }
|
||||
|
||||
this.localRow = { ...rowObj };
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
typeClass (type) {
|
||||
if (type)
|
||||
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
|
||||
return '';
|
||||
},
|
||||
async insertRows () {
|
||||
this.isInserting = true;
|
||||
const rowToInsert = this.localRow;
|
||||
];
|
||||
|
||||
Object.keys(rowToInsert).forEach(key => {
|
||||
if (this.fieldsToExclude.includes(key))
|
||||
delete rowToInsert[key];
|
||||
watch(nInserts, (val) => {
|
||||
if (!val || val < 1)
|
||||
nInserts.value = 1;
|
||||
else if (val > 1000)
|
||||
nInserts.value = 1000;
|
||||
});
|
||||
|
||||
if (typeof rowToInsert[key] === 'undefined')
|
||||
delete rowToInsert[key];
|
||||
});
|
||||
|
||||
const fieldTypes = {};
|
||||
this.fields.forEach(field => {
|
||||
fieldTypes[field.name] = field.type;
|
||||
});
|
||||
|
||||
try {
|
||||
const { status, response } = await Tables.insertTableFakeRows({
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.workspace.breadcrumbs.schema,
|
||||
table: this.workspace.breadcrumbs.table,
|
||||
row: rowToInsert,
|
||||
repeat: this.nInserts,
|
||||
fields: fieldTypes,
|
||||
locale: this.fakerLocale
|
||||
});
|
||||
|
||||
if (status === 'success') {
|
||||
this.closeModal();
|
||||
this.$emit('reload');
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isInserting = false;
|
||||
},
|
||||
closeModal () {
|
||||
this.$emit('hide');
|
||||
},
|
||||
fieldLength (field) {
|
||||
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
|
||||
else if (TEXT.includes(field.type)) return Number(field.charLength);
|
||||
return Number(field.length);
|
||||
},
|
||||
toggleFields (event, field) {
|
||||
if (event.target.checked)
|
||||
this.fieldsToExclude = this.fieldsToExclude.filter(f => f !== field.name);
|
||||
else
|
||||
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
|
||||
},
|
||||
filesChange (event, field) {
|
||||
const { files } = event.target;
|
||||
if (!files.length) return;
|
||||
|
||||
this.localRow[field] = files[0].path;
|
||||
},
|
||||
getKeyUsage (keyName) {
|
||||
return this.keyUsage.find(key => key.field === keyName);
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
},
|
||||
wrapNumber (num) {
|
||||
if (!num) return '';
|
||||
return `(${num})`;
|
||||
}
|
||||
}
|
||||
const typeClass = (type: string) => {
|
||||
if (type)
|
||||
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
|
||||
return '';
|
||||
};
|
||||
|
||||
const insertRows = async () => {
|
||||
isInserting.value = true;
|
||||
const rowToInsert = localRow.value;
|
||||
|
||||
Object.keys(rowToInsert).forEach(key => {
|
||||
if (fieldsToExclude.value.includes(key))
|
||||
delete rowToInsert[key];
|
||||
|
||||
if (typeof rowToInsert[key] === 'undefined')
|
||||
delete rowToInsert[key];
|
||||
});
|
||||
|
||||
const fieldTypes: {[key: string]: string} = {};
|
||||
props.fields.forEach(field => {
|
||||
fieldTypes[field.name] = field.type;
|
||||
});
|
||||
|
||||
try {
|
||||
const { status, response } = await Tables.insertTableFakeRows({
|
||||
uid: selectedWorkspace.value,
|
||||
schema: workspace.value.breadcrumbs.schema,
|
||||
table: workspace.value.breadcrumbs.table,
|
||||
row: rowToInsert,
|
||||
repeat: nInserts.value,
|
||||
fields: fieldTypes,
|
||||
locale: fakerLocale.value
|
||||
});
|
||||
|
||||
if (status === 'success') {
|
||||
closeModal();
|
||||
emit('reload');
|
||||
}
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
isInserting.value = false;
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
emit('hide');
|
||||
};
|
||||
|
||||
const fieldLength = (field: TableField) => {
|
||||
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
|
||||
else if (TEXT.includes(field.type)) return Number(field.charLength);
|
||||
return Number(field.length);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const toggleFields = (event: any, field: TableField) => {
|
||||
if (event.target.checked)
|
||||
fieldsToExclude.value = fieldsToExclude.value.filter(f => f !== field.name);
|
||||
else
|
||||
fieldsToExclude.value = [...fieldsToExclude.value, field.name];
|
||||
};
|
||||
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const wrapNumber = (num: number) => {
|
||||
if (!num) return '';
|
||||
return `(${num})`;
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', onKey);
|
||||
|
||||
onMounted(() => {
|
||||
const rowObj: {[key: string]: unknown} = {};
|
||||
|
||||
for (const field of props.fields) {
|
||||
let fieldDefault;
|
||||
|
||||
if (field.default === 'NULL') fieldDefault = null;
|
||||
else {
|
||||
if ([...NUMBER, ...FLOAT].includes(field.type))
|
||||
fieldDefault = !field.default || Number.isNaN(+field.default.replaceAll('\'', '')) ? null : +field.default.replaceAll('\'', '');
|
||||
else if ([...TEXT, ...LONG_TEXT].includes(field.type)) {
|
||||
fieldDefault = field.default
|
||||
? field.default.includes('\'')
|
||||
? field.default.split('\'')[1]
|
||||
: field.default
|
||||
: '';
|
||||
}
|
||||
else if ([...TIME, ...DATE].includes(field.type))
|
||||
fieldDefault = field.default;
|
||||
else if (BIT.includes(field.type))
|
||||
fieldDefault = field.default?.replaceAll('\'', '').replaceAll('b', '');
|
||||
else if (DATETIME.includes(field.type)) {
|
||||
if (field.default && ['current_timestamp', 'now()'].some(term => field.default.toLowerCase().includes(term))) {
|
||||
let datePrecision = '';
|
||||
for (let i = 0; i < field.datePrecision; i++)
|
||||
datePrecision += i === 0 ? '.S' : 'S';
|
||||
fieldDefault = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
|
||||
}
|
||||
else
|
||||
fieldDefault = field.default;
|
||||
}
|
||||
else if (field.enumValues)
|
||||
fieldDefault = field.enumValues.replaceAll('\'', '').split(',');
|
||||
else
|
||||
fieldDefault = field.default;
|
||||
}
|
||||
|
||||
rowObj[field.name] = { value: fieldDefault };
|
||||
|
||||
if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields
|
||||
fieldsToExclude.value = [...fieldsToExclude.value, field.name];
|
||||
}
|
||||
|
||||
localRow.value = { ...rowObj };
|
||||
});
|
||||
|
||||
onBeforeMount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="modal-title h6">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-history mr-1" />
|
||||
<span class="cut-text">{{ $t('word.history') }}: {{ connectionName }}</span>
|
||||
<span class="cut-text">{{ t('word.history') }}: {{ connectionName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||
@ -22,7 +22,7 @@
|
||||
v-model="searchTerm"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:placeholder="$t('message.searchForQueries')"
|
||||
:placeholder="t('message.searchForQueries')"
|
||||
>
|
||||
<i v-if="!searchTerm" class="form-icon mdi mdi-magnify mdi-18px pr-4" />
|
||||
<i
|
||||
@ -67,13 +67,13 @@
|
||||
<small class="tile-subtitle">{{ query.schema }} · {{ formatDate(query.date) }}</small>
|
||||
<div class="tile-history-buttons">
|
||||
<button class="btn btn-link pl-1" @click.stop="$emit('select-query', query.sql)">
|
||||
<i class="mdi mdi-open-in-app pr-1" /> {{ $t('word.select') }}
|
||||
<i class="mdi mdi-open-in-app pr-1" /> {{ t('word.select') }}
|
||||
</button>
|
||||
<button class="btn btn-link pl-1" @click="copyQuery(query.sql)">
|
||||
<i class="mdi mdi-content-copy pr-1" /> {{ $t('word.copy') }}
|
||||
<i class="mdi mdi-content-copy pr-1" /> {{ t('word.copy') }}
|
||||
</button>
|
||||
<button class="btn btn-link pl-1" @click="deleteQuery(query)">
|
||||
<i class="mdi mdi-delete-forever pr-1" /> {{ $t('word.delete') }}
|
||||
<i class="mdi mdi-delete-forever pr-1" /> {{ t('word.delete') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -88,7 +88,7 @@
|
||||
<i class="mdi mdi-history mdi-48px" />
|
||||
</div>
|
||||
<p class="empty-title h5">
|
||||
{{ $t('message.thereIsNoQueriesYet') }}
|
||||
{{ t('message.thereIsNoQueriesYet') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -97,129 +97,111 @@
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import { useHistoryStore } from '@/stores/history';
|
||||
<script setup lang="ts">
|
||||
import { Component, computed, ComputedRef, onBeforeUnmount, onMounted, onUpdated, Prop, Ref, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import * as moment from 'moment';
|
||||
import { ConnectionParams } from 'common/interfaces/antares';
|
||||
import { HistoryRecord, useHistoryStore } from '@/stores/history';
|
||||
import { useConnectionsStore } from '@/stores/connections';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
|
||||
import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue';
|
||||
|
||||
export default {
|
||||
name: 'ModalHistory',
|
||||
components: {
|
||||
BaseVirtualScroll
|
||||
},
|
||||
props: {
|
||||
connection: Object
|
||||
},
|
||||
emits: ['select-query', 'close'],
|
||||
setup () {
|
||||
const { getHistoryByWorkspace, deleteQueryFromHistory } = useHistoryStore();
|
||||
const { getConnectionName } = useConnectionsStore();
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
return {
|
||||
getHistoryByWorkspace,
|
||||
deleteQueryFromHistory,
|
||||
getConnectionName,
|
||||
addNotification
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
resultsSize: 1000,
|
||||
isQuering: false,
|
||||
scrollElement: null,
|
||||
searchTermInterval: null,
|
||||
searchTerm: '',
|
||||
localSearchTerm: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
connectionName () {
|
||||
return this.getConnectionName(this.connection.uid);
|
||||
},
|
||||
history () {
|
||||
return this.getHistoryByWorkspace(this.connection.uid) || [];
|
||||
},
|
||||
filteredHistory () {
|
||||
return this.history.filter(q => q.sql.toLowerCase().search(this.searchTerm.toLowerCase()) >= 0);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
searchTerm () {
|
||||
clearTimeout(this.searchTermInterval);
|
||||
const { getHistoryByWorkspace, deleteQueryFromHistory } = useHistoryStore();
|
||||
const { getConnectionName } = useConnectionsStore();
|
||||
|
||||
this.searchTermInterval = setTimeout(() => {
|
||||
this.localSearchTerm = this.searchTerm;
|
||||
}, 200);
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey, { capture: true });
|
||||
},
|
||||
updated () {
|
||||
if (this.$refs.table)
|
||||
this.refreshScroller();
|
||||
const props = defineProps({
|
||||
connection: Object as Prop<ConnectionParams>
|
||||
});
|
||||
|
||||
if (this.$refs.tableWrapper)
|
||||
this.scrollElement = this.$refs.tableWrapper;
|
||||
},
|
||||
mounted () {
|
||||
this.resizeResults();
|
||||
window.addEventListener('resize', this.resizeResults);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey, { capture: true });
|
||||
window.removeEventListener('resize', this.resizeResults);
|
||||
clearInterval(this.refreshInterval);
|
||||
},
|
||||
methods: {
|
||||
copyQuery (sql) {
|
||||
navigator.clipboard.writeText(sql);
|
||||
},
|
||||
deleteQuery (query) {
|
||||
this.deleteQueryFromHistory({
|
||||
workspace: this.connection.uid,
|
||||
...query
|
||||
});
|
||||
},
|
||||
resizeResults () {
|
||||
if (this.$refs.resultTable) {
|
||||
const el = this.$refs.tableWrapper.parentElement;
|
||||
const emit = defineEmits(['select-query', 'close']);
|
||||
|
||||
if (el)
|
||||
this.resultsSize = el.offsetHeight - this.$refs.searchForm.offsetHeight;
|
||||
const table: Ref<HTMLDivElement> = ref(null);
|
||||
const resultTable: Ref<Component & { updateWindow: () => void }> = ref(null);
|
||||
const tableWrapper: Ref<HTMLDivElement> = ref(null);
|
||||
const searchForm: Ref<HTMLInputElement> = ref(null);
|
||||
const resultsSize = ref(1000);
|
||||
const scrollElement: Ref<HTMLDivElement> = ref(null);
|
||||
const searchTermInterval: Ref<NodeJS.Timeout> = ref(null);
|
||||
const searchTerm = ref('');
|
||||
const localSearchTerm = ref('');
|
||||
|
||||
this.$refs.resultTable.updateWindow();
|
||||
}
|
||||
},
|
||||
formatDate (date) {
|
||||
return moment(date).isValid() ? moment(date).format('HH:mm:ss - YYYY/MM/DD') : date;
|
||||
},
|
||||
refreshScroller () {
|
||||
this.resizeResults();
|
||||
},
|
||||
closeModal () {
|
||||
this.$emit('close');
|
||||
},
|
||||
highlightWord (string) {
|
||||
string = string.replaceAll('<', '<').replaceAll('>', '>');
|
||||
const connectionName = computed(() => getConnectionName(props.connection.uid));
|
||||
const history: ComputedRef<HistoryRecord[]> = computed(() => (getHistoryByWorkspace(props.connection.uid) || []));
|
||||
const filteredHistory = computed(() => history.value.filter(q => q.sql.toLowerCase().search(searchTerm.value.toLowerCase()) >= 0));
|
||||
|
||||
if (this.searchTerm) {
|
||||
const regexp = new RegExp(`(${this.searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
||||
return string.replace(regexp, '<span class="text-primary text-bold">$1</span>');
|
||||
}
|
||||
else
|
||||
return string;
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
}
|
||||
watch(searchTerm, () => {
|
||||
clearTimeout(searchTermInterval.value);
|
||||
|
||||
searchTermInterval.value = setTimeout(() => {
|
||||
localSearchTerm.value = searchTerm.value;
|
||||
}, 200);
|
||||
});
|
||||
|
||||
const copyQuery = (sql: string) => {
|
||||
navigator.clipboard.writeText(sql);
|
||||
};
|
||||
|
||||
const deleteQuery = (query: HistoryRecord[]) => {
|
||||
deleteQueryFromHistory({
|
||||
workspace: props.connection.uid,
|
||||
...query
|
||||
});
|
||||
};
|
||||
|
||||
const resizeResults = () => {
|
||||
if (resultTable.value) {
|
||||
const el = tableWrapper.value.parentElement;
|
||||
|
||||
if (el)
|
||||
resultsSize.value = el.offsetHeight - searchForm.value.offsetHeight;
|
||||
|
||||
resultTable.value.updateWindow();
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (date: Date) => moment(date).isValid() ? moment(date).format('HH:mm:ss - YYYY/MM/DD') : date;
|
||||
const refreshScroller = () => resizeResults();
|
||||
const closeModal = () => emit('close');
|
||||
|
||||
const highlightWord = (string: string) => {
|
||||
string = string.replaceAll('<', '<').replaceAll('>', '>');
|
||||
|
||||
if (searchTerm.value) {
|
||||
const regexp = new RegExp(`(${searchTerm.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
||||
return string.replace(regexp, '<span class="text-primary text-bold">$1</span>');
|
||||
}
|
||||
else
|
||||
return string;
|
||||
};
|
||||
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
closeModal();
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', onKey, { capture: true });
|
||||
|
||||
onUpdated(() => {
|
||||
if (table.value)
|
||||
refreshScroller();
|
||||
|
||||
if (tableWrapper.value)
|
||||
scrollElement.value = tableWrapper.value;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
resizeResults();
|
||||
window.addEventListener('resize', resizeResults);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey, { capture: true });
|
||||
window.removeEventListener('resize', resizeResults);
|
||||
clearInterval(searchTermInterval.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -49,146 +49,140 @@
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, Ref, ref } from 'vue';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import * as moment from 'moment';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import moment from 'moment';
|
||||
import Schema from '@/ipc-api/Schema';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ImportState } from 'common/interfaces/importer';
|
||||
|
||||
export default {
|
||||
name: 'ModalImportSchema',
|
||||
const { t } = useI18n();
|
||||
|
||||
props: {
|
||||
selectedSchema: String
|
||||
},
|
||||
emits: ['close'],
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
const { getWorkspace, refreshSchema } = workspacesStore;
|
||||
const { getWorkspace, refreshSchema } = workspacesStore;
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
refreshSchema
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
sqlFile: '',
|
||||
isImporting: false,
|
||||
progressPercentage: 0,
|
||||
queryCount: 0,
|
||||
completed: false,
|
||||
progressStatus: 'Reading',
|
||||
queryErrors: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentWorkspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
},
|
||||
formattedQueryErrors () {
|
||||
return this.queryErrors.map(err =>
|
||||
`Time: ${moment(err.time).format('HH:mm:ss.S')} (${err.time})\nError: ${err.message}`
|
||||
).join('\n\n');
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
|
||||
ipcRenderer.on('import-progress', this.updateProgress);
|
||||
ipcRenderer.on('query-error', this.handleQueryError);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
ipcRenderer.off('import-progress', this.updateProgress);
|
||||
ipcRenderer.off('query-error', this.handleQueryError);
|
||||
},
|
||||
methods: {
|
||||
async startImport (sqlFile) {
|
||||
this.isImporting = true;
|
||||
this.sqlFile = sqlFile;
|
||||
|
||||
const { uid, client } = this.currentWorkspace;
|
||||
const params = {
|
||||
uid,
|
||||
type: client,
|
||||
schema: this.selectedSchema,
|
||||
file: sqlFile
|
||||
};
|
||||
|
||||
try {
|
||||
this.completed = false;
|
||||
const { status, response } = await Schema.import(params);
|
||||
if (status === 'success')
|
||||
this.progressStatus = response.cancelled ? this.$t('word.aborted') : this.$t('word.completed');
|
||||
else {
|
||||
this.progressStatus = response;
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
this.refreshSchema({ uid, schema: this.selectedSchema });
|
||||
this.completed = true;
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isImporting = false;
|
||||
},
|
||||
updateProgress (event, state) {
|
||||
this.progressPercentage = Number(state.percentage).toFixed(1);
|
||||
this.queryCount = Number(state.queryCount);
|
||||
},
|
||||
handleQueryError (event, err) {
|
||||
this.queryErrors.push(err);
|
||||
},
|
||||
async closeModal () {
|
||||
let willClose = true;
|
||||
if (this.isImporting) {
|
||||
willClose = false;
|
||||
const { response } = await Schema.abortImport();
|
||||
willClose = response.willAbort;
|
||||
}
|
||||
|
||||
if (willClose)
|
||||
this.$emit('close');
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
const props = defineProps({
|
||||
selectedSchema: String
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
const sqlFile = ref('');
|
||||
const isImporting = ref(false);
|
||||
const progressPercentage = ref(0);
|
||||
const queryCount = ref(0);
|
||||
const completed = ref(false);
|
||||
const progressStatus = ref('Reading');
|
||||
const queryErrors: Ref<{time: string; message: string}[]> = ref([]);
|
||||
|
||||
const currentWorkspace = computed(() => getWorkspace(selectedWorkspace.value));
|
||||
|
||||
const formattedQueryErrors = computed(() => {
|
||||
return queryErrors.value.map(err =>
|
||||
`Time: ${moment(err.time).format('HH:mm:ss.S')} (${err.time})\nError: ${err.message}`
|
||||
).join('\n\n');
|
||||
});
|
||||
|
||||
const startImport = async (file: string) => {
|
||||
isImporting.value = true;
|
||||
sqlFile.value = file;
|
||||
|
||||
const { uid, client } = currentWorkspace.value;
|
||||
const params = {
|
||||
uid,
|
||||
type: client,
|
||||
schema: props.selectedSchema,
|
||||
file: sqlFile.value
|
||||
};
|
||||
|
||||
try {
|
||||
completed.value = false;
|
||||
const { status, response } = await Schema.import(params);
|
||||
|
||||
if (status === 'success')
|
||||
progressStatus.value = response.cancelled ? t('word.aborted') : t('word.completed');
|
||||
else {
|
||||
progressStatus.value = response;
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
refreshSchema({ uid, schema: props.selectedSchema });
|
||||
completed.value = true;
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
isImporting.value = false;
|
||||
};
|
||||
|
||||
const updateProgress = (event: Event, state: ImportState) => {
|
||||
progressPercentage.value = parseFloat(Number(state.percentage).toFixed(1));
|
||||
queryCount.value = Number(state.queryCount);
|
||||
};
|
||||
|
||||
const handleQueryError = (event: Event, err: { time: string; message: string }) => {
|
||||
queryErrors.value.push(err);
|
||||
};
|
||||
|
||||
const closeModal = async () => {
|
||||
let willClose = true;
|
||||
if (isImporting.value) {
|
||||
willClose = false;
|
||||
const { response } = await Schema.abortImport();
|
||||
willClose = response.willAbort;
|
||||
}
|
||||
|
||||
if (willClose)
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
closeModal();
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', onKey);
|
||||
|
||||
ipcRenderer.on('import-progress', updateProgress);
|
||||
ipcRenderer.on('query-error', handleQueryError);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
ipcRenderer.off('import-progress', updateProgress);
|
||||
ipcRenderer.off('query-error', handleQueryError);
|
||||
});
|
||||
|
||||
defineExpose({ startImport });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.modal {
|
||||
.modal-container {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
max-width: 800px;
|
||||
}
|
||||
.modal-body {
|
||||
max-height: 60vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
max-height: 60vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
}
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-status {
|
||||
font-style: italic;
|
||||
font-size: 80%;
|
||||
font-style: italic;
|
||||
font-size: 80%;
|
||||
}
|
||||
</style>
|
||||
|
@ -65,93 +65,75 @@
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, Ref, ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import Schema from '@/ipc-api/Schema';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
|
||||
export default {
|
||||
name: 'ModalNewSchema',
|
||||
components: { BaseSelect },
|
||||
emits: ['reload', 'close'],
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
const { getWorkspace, getDatabaseVariable } = workspacesStore;
|
||||
const { getWorkspace, getDatabaseVariable } = workspacesStore;
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
getDatabaseVariable
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoading: false,
|
||||
database: {
|
||||
name: '',
|
||||
collation: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
collations () {
|
||||
return this.getWorkspace(this.selectedWorkspace).collations;
|
||||
},
|
||||
customizations () {
|
||||
return this.getWorkspace(this.selectedWorkspace).customizations;
|
||||
},
|
||||
defaultCollation () {
|
||||
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server') ? this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value : '';
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.database = { ...this.database, collation: this.defaultCollation };
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
async createSchema () {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const { status, response } = await Schema.createSchema({
|
||||
uid: this.selectedWorkspace,
|
||||
...this.database
|
||||
});
|
||||
|
||||
if (status === 'success') {
|
||||
this.closeModal();
|
||||
this.$emit('reload');
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
this.isLoading = false;
|
||||
},
|
||||
closeModal () {
|
||||
this.$emit('close');
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
const emit = defineEmits(['reload', 'close']);
|
||||
|
||||
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const database = ref({
|
||||
name: '',
|
||||
collation: ''
|
||||
});
|
||||
|
||||
const collations = computed(() => getWorkspace(selectedWorkspace.value).collations);
|
||||
const customizations = computed(() => getWorkspace(selectedWorkspace.value).customizations);
|
||||
const defaultCollation = computed(() => getDatabaseVariable(selectedWorkspace.value, 'collation_server') ? getDatabaseVariable(selectedWorkspace.value, 'collation_server').value : '');
|
||||
|
||||
database.value = { ...database.value, collation: defaultCollation.value };
|
||||
|
||||
const createSchema = async () => {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const { status, response } = await Schema.createSchema({
|
||||
uid: selectedWorkspace.value,
|
||||
...database.value
|
||||
});
|
||||
|
||||
if (status === 'success') {
|
||||
closeModal();
|
||||
emit('reload');
|
||||
}
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
isLoading.value = false;
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
closeModal();
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', onKey);
|
||||
setTimeout(() => {
|
||||
firstInput.value.focus();
|
||||
}, 20);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -1,366 +0,0 @@
|
||||
<template>
|
||||
<Teleport to="#window-content">
|
||||
<div class="modal active">
|
||||
<a class="modal-overlay" @click.stop="closeModal" />
|
||||
<div class="modal-container p-0">
|
||||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
|
||||
<span class="cut-text">{{ $t('message.addNewRow') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body pb-0">
|
||||
<div class="content">
|
||||
<form class="form-horizontal">
|
||||
<fieldset :disabled="isInserting">
|
||||
<div
|
||||
v-for="(field, key) in fields"
|
||||
:key="field.name"
|
||||
class="form-group"
|
||||
>
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label" :title="field.name">{{ field.name }}</label>
|
||||
</div>
|
||||
<div class="input-group col-8 col-sm-12">
|
||||
<ForeignKeySelect
|
||||
v-if="foreignKeys.includes(field.name)"
|
||||
ref="formInput"
|
||||
v-model="localRow[field.name]"
|
||||
class="form-select"
|
||||
:key-usage="getKeyUsage(field.name)"
|
||||
:disabled="fieldsToExclude.includes(field.name)"
|
||||
/>
|
||||
<input
|
||||
v-else-if="inputProps(field).mask"
|
||||
ref="formInput"
|
||||
v-model="localRow[field.name]"
|
||||
v-mask="inputProps(field).mask"
|
||||
class="form-input"
|
||||
:type="inputProps(field).type"
|
||||
:disabled="fieldsToExclude.includes(field.name)"
|
||||
:tabindex="key+1"
|
||||
>
|
||||
<input
|
||||
v-else-if="inputProps(field).type === 'file'"
|
||||
ref="formInput"
|
||||
class="form-input"
|
||||
type="file"
|
||||
:disabled="fieldsToExclude.includes(field.name)"
|
||||
:tabindex="key+1"
|
||||
@change="filesChange($event,field.name)"
|
||||
>
|
||||
<input
|
||||
v-else-if="inputProps(field).type === 'number'"
|
||||
ref="formInput"
|
||||
v-model="localRow[field.name]"
|
||||
class="form-input"
|
||||
step="any"
|
||||
:type="inputProps(field).type"
|
||||
:disabled="fieldsToExclude.includes(field.name)"
|
||||
:tabindex="key+1"
|
||||
>
|
||||
<input
|
||||
v-else
|
||||
ref="formInput"
|
||||
v-model="localRow[field.name]"
|
||||
class="form-input"
|
||||
:type="inputProps(field).type"
|
||||
:disabled="fieldsToExclude.includes(field.name)"
|
||||
:tabindex="key+1"
|
||||
>
|
||||
<span class="input-group-addon" :class="typeCLass(field.type)">
|
||||
{{ field.type }} {{ wrapNumber(fieldLength(field)) }}
|
||||
</span>
|
||||
<label class="form-checkbox ml-3" :title="$t('word.insert')">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="!field.autoIncrement"
|
||||
@change.prevent="toggleFields($event, field)"
|
||||
><i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="input-group col-3 tooltip tooltip-right" :data-tooltip="$t('message.numberOfInserts')">
|
||||
<input
|
||||
v-model="nInserts"
|
||||
type="number"
|
||||
class="form-input"
|
||||
min="1"
|
||||
:disabled="isInserting"
|
||||
>
|
||||
<span class="input-group-addon">
|
||||
<i class="mdi mdi-24px mdi-repeat" />
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-primary mr-2"
|
||||
:class="{'loading': isInserting}"
|
||||
@click.stop="insertRows"
|
||||
>
|
||||
{{ $t('word.insert') }}
|
||||
</button>
|
||||
<button class="btn btn-link" @click.stop="closeModal">
|
||||
{{ $t('word.close') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import ForeignKeySelect from '@/components/ForeignKeySelect';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
export default {
|
||||
name: 'ModalNewTableRow',
|
||||
components: {
|
||||
ForeignKeySelect
|
||||
},
|
||||
props: {
|
||||
tabUid: [String, Number],
|
||||
fields: Array,
|
||||
keyUsage: Array
|
||||
},
|
||||
emits: ['reload', 'hide'],
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
const { getWorkspace, getWorkspaceTab } = workspacesStore;
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
getWorkspaceTab
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localRow: {},
|
||||
fieldsToExclude: [],
|
||||
nInserts: 1,
|
||||
isInserting: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
},
|
||||
foreignKeys () {
|
||||
return this.keyUsage.map(key => key.field);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
nInserts (val) {
|
||||
if (!val || val < 1)
|
||||
this.nInserts = 1;
|
||||
else if (val > 1000)
|
||||
this.nInserts = 1000;
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
mounted () {
|
||||
const rowObj = {};
|
||||
|
||||
for (const field of this.fields) {
|
||||
let fieldDefault;
|
||||
|
||||
if (field.default === 'NULL') fieldDefault = null;
|
||||
else {
|
||||
if ([...NUMBER, ...FLOAT].includes(field.type))
|
||||
fieldDefault = +field.default;
|
||||
|
||||
if ([...TEXT, ...LONG_TEXT].includes(field.type))
|
||||
fieldDefault = field.default ? field.default.substring(1, field.default.length - 1) : '';
|
||||
|
||||
if ([...TIME, ...DATE].includes(field.type))
|
||||
fieldDefault = field.default;
|
||||
|
||||
if (DATETIME.includes(field.type)) {
|
||||
if (field.default && field.default.toLowerCase().includes('current_timestamp')) {
|
||||
let datePrecision = '';
|
||||
for (let i = 0; i < field.datePrecision; i++)
|
||||
datePrecision += i === 0 ? '.S' : 'S';
|
||||
fieldDefault = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rowObj[field.name] = fieldDefault;
|
||||
|
||||
if (field.autoIncrement)// Disable by default auto increment fields
|
||||
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
|
||||
}
|
||||
|
||||
this.localRow = { ...rowObj };
|
||||
|
||||
// Auto focus
|
||||
setTimeout(() => {
|
||||
const firstSelectableInput = this.$refs.formInput.find(input => !input.disabled);
|
||||
firstSelectableInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
typeClass (type) {
|
||||
if (type)
|
||||
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
|
||||
return '';
|
||||
},
|
||||
async insertRows () {
|
||||
this.isInserting = true;
|
||||
const rowToInsert = this.localRow;
|
||||
Object.keys(rowToInsert).forEach(key => {
|
||||
if (this.fieldsToExclude.includes(key))
|
||||
delete rowToInsert[key];
|
||||
if (typeof rowToInsert[key] === 'undefined')
|
||||
delete rowToInsert[key];
|
||||
});
|
||||
|
||||
const fieldTypes = {};
|
||||
this.fields.forEach(field => {
|
||||
fieldTypes[field.name] = field.type;
|
||||
});
|
||||
|
||||
try {
|
||||
const { status, response } = await Tables.insertTableRows({
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.workspace.breadcrumbs.schema,
|
||||
table: this.workspace.breadcrumbs.table,
|
||||
row: rowToInsert,
|
||||
repeat: this.nInserts,
|
||||
fields: fieldTypes
|
||||
});
|
||||
|
||||
if (status === 'success') {
|
||||
this.closeModal();
|
||||
this.$emit('reload');
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isInserting = false;
|
||||
},
|
||||
closeModal () {
|
||||
this.$emit('hide');
|
||||
},
|
||||
fieldLength (field) {
|
||||
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
|
||||
else if (TEXT.includes(field.type)) return field.charLength;
|
||||
return field.length;
|
||||
},
|
||||
inputProps (field) {
|
||||
if ([...TEXT, ...LONG_TEXT].includes(field.type))
|
||||
return { type: 'text', mask: false };
|
||||
|
||||
if ([...NUMBER, ...FLOAT].includes(field.type))
|
||||
return { type: 'number', mask: false };
|
||||
|
||||
if (TIME.includes(field.type)) {
|
||||
let timeMask = '##:##:##';
|
||||
const precision = this.fieldLength(field);
|
||||
|
||||
for (let i = 0; i < precision; i++)
|
||||
timeMask += i === 0 ? '.#' : '#';
|
||||
|
||||
return { type: 'text', mask: timeMask };
|
||||
}
|
||||
|
||||
if (DATE.includes(field.type))
|
||||
return { type: 'text', mask: '####-##-##' };
|
||||
|
||||
if (DATETIME.includes(field.type)) {
|
||||
let datetimeMask = '####-##-## ##:##:##';
|
||||
const precision = this.fieldLength(field);
|
||||
|
||||
for (let i = 0; i < precision; i++)
|
||||
datetimeMask += i === 0 ? '.#' : '#';
|
||||
|
||||
return { type: 'text', mask: datetimeMask };
|
||||
}
|
||||
|
||||
if (BLOB.includes(field.type))
|
||||
return { type: 'file', mask: false };
|
||||
|
||||
if (BIT.includes(field.type))
|
||||
return { type: 'text', mask: false };
|
||||
|
||||
return { type: 'text', mask: false };
|
||||
},
|
||||
toggleFields (event, field) {
|
||||
if (event.target.checked)
|
||||
this.fieldsToExclude = this.fieldsToExclude.filter(f => f !== field.name);
|
||||
else
|
||||
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
|
||||
},
|
||||
filesChange (event, field) {
|
||||
const { files } = event.target;
|
||||
if (!files.length) return;
|
||||
|
||||
this.localRow[field] = files[0].path;
|
||||
},
|
||||
getKeyUsage (keyName) {
|
||||
return this.keyUsage.find(key => key.field === keyName);
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
},
|
||||
wrapNumber (num) {
|
||||
if (!num) return '';
|
||||
return `(${num})`;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-container {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
overflow: hidden;
|
||||
white-space: normal;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.input-group-addon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
@ -133,218 +133,218 @@
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import arrayToFile from '../libs/arrayToFile';
|
||||
<script setup lang="ts">
|
||||
import { Component, computed, onBeforeUnmount, onMounted, onUpdated, Prop, Ref, ref } from 'vue';
|
||||
import { ConnectionParams } from 'common/interfaces/antares';
|
||||
import { arrayToFile } from '../libs/arrayToFile';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import Schema from '@/ipc-api/Schema';
|
||||
import { useConnectionsStore } from '@/stores/connections';
|
||||
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
|
||||
import ModalProcessesListRow from '@/components/ModalProcessesListRow';
|
||||
import ModalProcessesListContext from '@/components/ModalProcessesListContext';
|
||||
import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue';
|
||||
import ModalProcessesListRow from '@/components/ModalProcessesListRow.vue';
|
||||
import ModalProcessesListContext from '@/components/ModalProcessesListContext.vue';
|
||||
|
||||
export default {
|
||||
name: 'ModalProcessesList',
|
||||
components: {
|
||||
BaseVirtualScroll,
|
||||
ModalProcessesListRow,
|
||||
ModalProcessesListContext
|
||||
},
|
||||
props: {
|
||||
connection: Object
|
||||
},
|
||||
emits: ['close'],
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const { getConnectionName } = useConnectionsStore();
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const { getConnectionName } = useConnectionsStore();
|
||||
|
||||
return { addNotification, getConnectionName };
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
resultsSize: 1000,
|
||||
isQuering: false,
|
||||
isContext: false,
|
||||
autorefreshTimer: 0,
|
||||
refreshInterval: null,
|
||||
contextEvent: null,
|
||||
selectedCell: null,
|
||||
selectedRow: null,
|
||||
results: [],
|
||||
fields: [],
|
||||
currentSort: '',
|
||||
currentSortDir: 'asc',
|
||||
scrollElement: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
connectionName () {
|
||||
return this.getConnectionName(this.connection.uid);
|
||||
},
|
||||
sortedResults () {
|
||||
if (this.currentSort) {
|
||||
return [...this.results].sort((a, b) => {
|
||||
let modifier = 1;
|
||||
const valA = typeof a[this.currentSort] === 'string' ? a[this.currentSort].toLowerCase() : a[this.currentSort];
|
||||
const valB = typeof b[this.currentSort] === 'string' ? b[this.currentSort].toLowerCase() : b[this.currentSort];
|
||||
if (this.currentSortDir === 'desc') modifier = -1;
|
||||
if (valA < valB) return -1 * modifier;
|
||||
if (valA > valB) return 1 * modifier;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
else
|
||||
return this.results;
|
||||
const props = defineProps({
|
||||
connection: Object as Prop<ConnectionParams>
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
const tableWrapper: Ref<HTMLDivElement> = ref(null);
|
||||
const table: Ref<HTMLDivElement> = ref(null);
|
||||
const resultTable: Ref<Component & {updateWindow: () => void}> = ref(null);
|
||||
const resultsSize = ref(1000);
|
||||
const isQuering = ref(false);
|
||||
const isContext = ref(false);
|
||||
const autorefreshTimer = ref(0);
|
||||
const refreshInterval: Ref<NodeJS.Timeout> = ref(null);
|
||||
const contextEvent = ref(null);
|
||||
const selectedCell = ref(null);
|
||||
const selectedRow: Ref<number> = ref(null);
|
||||
const results = ref([]);
|
||||
const fields = ref([]);
|
||||
const currentSort = ref('');
|
||||
const currentSortDir = ref('asc');
|
||||
const scrollElement = ref(null);
|
||||
|
||||
const connectionName = computed(() => getConnectionName(props.connection.uid));
|
||||
|
||||
const sortedResults = computed(() => {
|
||||
if (currentSort.value) {
|
||||
return [...results.value].sort((a, b) => {
|
||||
let modifier = 1;
|
||||
const valA = typeof a[currentSort.value] === 'string' ? a[currentSort.value].toLowerCase() : a[currentSort.value];
|
||||
const valB = typeof b[currentSort.value] === 'string' ? b[currentSort.value].toLowerCase() : b[currentSort.value];
|
||||
if (currentSortDir.value === 'desc') modifier = -1;
|
||||
if (valA < valB) return -1 * modifier;
|
||||
if (valA > valB) return 1 * modifier;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
else
|
||||
return results.value;
|
||||
});
|
||||
|
||||
const getProcessesList = async () => {
|
||||
isQuering.value = true;
|
||||
|
||||
try { // Table data
|
||||
const { status, response } = await Schema.getProcesses(props.connection.uid);
|
||||
|
||||
if (status === 'success') {
|
||||
results.value = response;
|
||||
fields.value = response.length ? Object.keys(response[0]) : [];
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey, { capture: true });
|
||||
},
|
||||
updated () {
|
||||
if (this.$refs.table)
|
||||
this.refreshScroller();
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
if (this.$refs.tableWrapper)
|
||||
this.scrollElement = this.$refs.tableWrapper;
|
||||
},
|
||||
mounted () {
|
||||
this.getProcessesList();
|
||||
window.addEventListener('resize', this.resizeResults);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey, { capture: true });
|
||||
window.removeEventListener('resize', this.resizeResults);
|
||||
clearInterval(this.refreshInterval);
|
||||
},
|
||||
methods: {
|
||||
async getProcessesList () {
|
||||
this.isQuering = true;
|
||||
isQuering.value = false;
|
||||
};
|
||||
|
||||
// if table changes clear cached values
|
||||
if (this.lastTable !== this.table)
|
||||
this.results = [];
|
||||
const setRefreshInterval = () => {
|
||||
clearRefresh();
|
||||
|
||||
try { // Table data
|
||||
const { status, response } = await Schema.getProcesses(this.connection.uid);
|
||||
|
||||
if (status === 'success') {
|
||||
this.results = response;
|
||||
this.fields = response.length ? Object.keys(response[0]) : [];
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isQuering = false;
|
||||
},
|
||||
setRefreshInterval () {
|
||||
this.clearRefresh();
|
||||
|
||||
if (+this.autorefreshTimer) {
|
||||
this.refreshInterval = setInterval(() => {
|
||||
if (!this.isQuering)
|
||||
this.getProcessesList();
|
||||
}, this.autorefreshTimer * 1000);
|
||||
}
|
||||
},
|
||||
clearRefresh () {
|
||||
if (this.refreshInterval)
|
||||
clearInterval(this.refreshInterval);
|
||||
},
|
||||
resizeResults () {
|
||||
if (this.$refs.resultTable) {
|
||||
const el = this.$refs.tableWrapper.parentElement;
|
||||
|
||||
if (el) {
|
||||
const size = el.offsetHeight;
|
||||
this.resultsSize = size;
|
||||
}
|
||||
this.$refs.resultTable.updateWindow();
|
||||
}
|
||||
},
|
||||
refreshScroller () {
|
||||
this.resizeResults();
|
||||
},
|
||||
sort (field) {
|
||||
if (field === this.currentSort) {
|
||||
if (this.currentSortDir === 'asc')
|
||||
this.currentSortDir = 'desc';
|
||||
else
|
||||
this.resetSort();
|
||||
}
|
||||
else {
|
||||
this.currentSortDir = 'asc';
|
||||
this.currentSort = field;
|
||||
}
|
||||
},
|
||||
resetSort () {
|
||||
this.currentSort = '';
|
||||
this.currentSortDir = 'asc';
|
||||
},
|
||||
stopRefresh () {
|
||||
this.autorefreshTimer = 0;
|
||||
this.clearRefresh();
|
||||
},
|
||||
selectRow (row) {
|
||||
this.selectedRow = Number(row);
|
||||
},
|
||||
contextMenu (event, cell) {
|
||||
if (event.target.localName === 'input') return;
|
||||
this.stopRefresh();
|
||||
|
||||
this.selectedCell = cell;
|
||||
this.selectedRow = Number(cell.id);
|
||||
this.contextEvent = event;
|
||||
this.isContext = true;
|
||||
},
|
||||
async killProcess () {
|
||||
try { // Table data
|
||||
const { status, response } = await Schema.killProcess({ uid: this.connection.uid, pid: this.selectedRow });
|
||||
|
||||
if (status === 'success')
|
||||
this.getProcessesList();
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
},
|
||||
closeContext () {
|
||||
this.isContext = false;
|
||||
},
|
||||
copyCell () {
|
||||
const row = this.results.find(row => row.id === this.selectedRow);
|
||||
const valueToCopy = row[this.selectedCell.field];
|
||||
navigator.clipboard.writeText(valueToCopy);
|
||||
},
|
||||
copyRow () {
|
||||
const row = this.results.find(row => row.id === this.selectedRow);
|
||||
const rowToCopy = JSON.parse(JSON.stringify(row));
|
||||
navigator.clipboard.writeText(JSON.stringify(rowToCopy));
|
||||
},
|
||||
closeModal () {
|
||||
this.$emit('close');
|
||||
},
|
||||
downloadTable (format) {
|
||||
if (!this.sortedResults) return;
|
||||
arrayToFile({
|
||||
type: format,
|
||||
content: this.sortedResults,
|
||||
filename: 'processes'
|
||||
});
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
if (e.key === 'F5')
|
||||
this.getProcessesList();
|
||||
}
|
||||
if (+autorefreshTimer.value) {
|
||||
refreshInterval.value = setInterval(() => {
|
||||
if (!isQuering.value)
|
||||
getProcessesList();
|
||||
}, autorefreshTimer.value * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
const clearRefresh = () => {
|
||||
if (refreshInterval.value)
|
||||
clearInterval(refreshInterval.value);
|
||||
};
|
||||
|
||||
const resizeResults = () => {
|
||||
if (resultTable.value) {
|
||||
const el = tableWrapper.value.parentElement;
|
||||
|
||||
if (el) {
|
||||
const size = el.offsetHeight;
|
||||
resultsSize.value = size;
|
||||
}
|
||||
resultTable.value.updateWindow();
|
||||
}
|
||||
};
|
||||
|
||||
const refreshScroller = () => resizeResults();
|
||||
|
||||
const sort = (field: string) => {
|
||||
if (field === currentSort.value) {
|
||||
if (currentSortDir.value === 'asc')
|
||||
currentSortDir.value = 'desc';
|
||||
else
|
||||
resetSort();
|
||||
}
|
||||
else {
|
||||
currentSortDir.value = 'asc';
|
||||
currentSort.value = field;
|
||||
}
|
||||
};
|
||||
|
||||
const resetSort = () => {
|
||||
currentSort.value = '';
|
||||
currentSortDir.value = 'asc';
|
||||
};
|
||||
|
||||
const stopRefresh = () => {
|
||||
autorefreshTimer.value = 0;
|
||||
clearRefresh();
|
||||
};
|
||||
|
||||
const selectRow = (row: number) => {
|
||||
selectedRow.value = Number(row);
|
||||
};
|
||||
|
||||
const contextMenu = (event: MouseEvent, cell: { id: number; field: string }) => {
|
||||
if ((event.target as HTMLElement).localName === 'input') return;
|
||||
stopRefresh();
|
||||
|
||||
selectedCell.value = cell;
|
||||
selectedRow.value = Number(cell.id);
|
||||
contextEvent.value = event;
|
||||
isContext.value = true;
|
||||
};
|
||||
|
||||
const killProcess = async () => {
|
||||
try { // Table data
|
||||
const { status, response } = await Schema.killProcess({ uid: props.connection.uid, pid: selectedRow.value });
|
||||
|
||||
if (status === 'success')
|
||||
getProcessesList();
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
};
|
||||
|
||||
const closeContext = () => {
|
||||
isContext.value = false;
|
||||
};
|
||||
|
||||
const copyCell = () => {
|
||||
const row = results.value.find(row => Number(row.id) === selectedRow.value);
|
||||
const valueToCopy = row[selectedCell.value.field];
|
||||
navigator.clipboard.writeText(valueToCopy);
|
||||
};
|
||||
|
||||
const copyRow = () => {
|
||||
const row = results.value.find(row => Number(row.id) === selectedRow.value);
|
||||
const rowToCopy = JSON.parse(JSON.stringify(row));
|
||||
navigator.clipboard.writeText(JSON.stringify(rowToCopy));
|
||||
};
|
||||
|
||||
const closeModal = () => emit('close');
|
||||
|
||||
const downloadTable = (format: 'csv' | 'json') => {
|
||||
if (!sortedResults.value) return;
|
||||
arrayToFile({
|
||||
type: format,
|
||||
content: sortedResults.value,
|
||||
filename: 'processes'
|
||||
});
|
||||
};
|
||||
|
||||
const onKey = (e:KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
closeModal();
|
||||
if (e.key === 'F5')
|
||||
getProcessesList();
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', onKey, { capture: true });
|
||||
|
||||
onMounted(() => {
|
||||
getProcessesList();
|
||||
window.addEventListener('resize', resizeResults);
|
||||
});
|
||||
|
||||
onUpdated(() => {
|
||||
if (table.value)
|
||||
refreshScroller();
|
||||
if (tableWrapper.value)
|
||||
scrollElement.value = tableWrapper.value;
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey, { capture: true });
|
||||
window.removeEventListener('resize', resizeResults);
|
||||
clearInterval(refreshInterval.value);
|
||||
});
|
||||
|
||||
defineExpose({ refreshScroller });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<BaseContextMenu
|
||||
:context-event="contextEvent"
|
||||
:context-event="props.contextEvent"
|
||||
@close-context="closeContext"
|
||||
>
|
||||
<div v-if="selectedRow" class="context-element">
|
||||
<div v-if="props.selectedRow" class="context-element">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-content-copy text-light pr-1" /> {{ $t('word.copy') }}</span>
|
||||
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
|
||||
<div class="context-submenu">
|
||||
<div
|
||||
v-if="selectedRow"
|
||||
v-if="props.selectedRow"
|
||||
class="context-element"
|
||||
@click="copyCell"
|
||||
>
|
||||
@ -17,7 +17,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedRow"
|
||||
v-if="props.selectedRow"
|
||||
class="context-element"
|
||||
@click="copyRow"
|
||||
>
|
||||
@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedRow"
|
||||
v-if="props.selectedRow"
|
||||
class="context-element"
|
||||
@click="killProcess"
|
||||
>
|
||||
@ -39,38 +39,33 @@
|
||||
</BaseContextMenu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
||||
<script setup lang="ts">
|
||||
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
||||
|
||||
export default {
|
||||
name: 'ModalProcessesListContext',
|
||||
components: {
|
||||
BaseContextMenu
|
||||
},
|
||||
props: {
|
||||
contextEvent: MouseEvent,
|
||||
selectedRow: Number,
|
||||
selectedCell: Object
|
||||
},
|
||||
emits: ['close-context', 'copy-cell', 'copy-row', 'kill-process'],
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
closeContext () {
|
||||
this.$emit('close-context');
|
||||
},
|
||||
copyCell () {
|
||||
this.$emit('copy-cell');
|
||||
this.closeContext();
|
||||
},
|
||||
copyRow () {
|
||||
this.$emit('copy-row');
|
||||
this.closeContext();
|
||||
},
|
||||
killProcess () {
|
||||
this.$emit('kill-process');
|
||||
this.closeContext();
|
||||
}
|
||||
}
|
||||
const props = defineProps({
|
||||
contextEvent: MouseEvent,
|
||||
selectedRow: Number,
|
||||
selectedCell: Object
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close-context', 'copy-cell', 'copy-row', 'kill-process']);
|
||||
|
||||
const closeContext = () => {
|
||||
emit('close-context');
|
||||
};
|
||||
|
||||
const copyCell = () => {
|
||||
emit('copy-cell');
|
||||
closeContext();
|
||||
};
|
||||
|
||||
const copyRow = () => {
|
||||
emit('copy-row');
|
||||
closeContext();
|
||||
};
|
||||
|
||||
const killProcess = () => {
|
||||
emit('kill-process');
|
||||
closeContext();
|
||||
};
|
||||
</script>
|
||||
|
@ -31,11 +31,12 @@
|
||||
<div>
|
||||
<div>
|
||||
<TextEditor
|
||||
:value="row.info || ''"
|
||||
:model-value="props.row.info || ''"
|
||||
editor-class="textarea-editor"
|
||||
:mode="editorMode"
|
||||
:read-only="true"
|
||||
/>
|
||||
<div class="mb-4" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -43,60 +44,46 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import TextEditor from '@/components/BaseTextEditor';
|
||||
<script setup lang="ts">
|
||||
import { Ref, ref } from 'vue';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||
import TextEditor from '@/components/BaseTextEditor.vue';
|
||||
|
||||
export default {
|
||||
name: 'ModalProcessesListRow',
|
||||
components: {
|
||||
ConfirmModal,
|
||||
TextEditor
|
||||
},
|
||||
props: {
|
||||
row: Object
|
||||
},
|
||||
emits: ['select-row', 'contextmenu', 'stop-refresh'],
|
||||
data () {
|
||||
return {
|
||||
isInlineEditor: {},
|
||||
isInfoModal: false,
|
||||
editorMode: 'sql'
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
isNull (value) {
|
||||
return value === null ? ' is-null' : '';
|
||||
},
|
||||
selectRow () {
|
||||
this.$emit('select-row');
|
||||
},
|
||||
openContext (event, payload) {
|
||||
this.$emit('contextmenu', event, payload);
|
||||
},
|
||||
hideInfoModal () {
|
||||
this.isInfoModal = false;
|
||||
},
|
||||
dblClick (col) {
|
||||
if (col !== 'info') return;
|
||||
this.$emit('stop-refresh');
|
||||
this.isInfoModal = true;
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape') {
|
||||
this.isInlineEditor[this.editingField] = false;
|
||||
this.editingField = null;
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
}
|
||||
},
|
||||
cutText (val) {
|
||||
if (typeof val !== 'string') return val;
|
||||
return val.length > 250 ? `${val.substring(0, 250)}[...]` : val;
|
||||
}
|
||||
}
|
||||
const props = defineProps({
|
||||
row: Object
|
||||
});
|
||||
|
||||
const emit = defineEmits(['select-row', 'contextmenu', 'stop-refresh']);
|
||||
|
||||
const isInlineEditor: Ref<{[key: string]: boolean}> = ref({});
|
||||
const isInfoModal = ref(false);
|
||||
const editorMode = ref('sql');
|
||||
|
||||
const isNull = (value: string | number) => value === null ? ' is-null' : '';
|
||||
|
||||
const selectRow = () => {
|
||||
emit('select-row');
|
||||
};
|
||||
|
||||
const openContext = (event: MouseEvent, payload: { id: number; field: string }) => {
|
||||
emit('contextmenu', event, payload);
|
||||
};
|
||||
|
||||
const hideInfoModal = () => {
|
||||
isInfoModal.value = false;
|
||||
};
|
||||
|
||||
const dblClick = (col: string) => {
|
||||
if (col !== 'info') return;
|
||||
emit('stop-refresh');
|
||||
isInfoModal.value = true;
|
||||
};
|
||||
|
||||
const cutText = (val: string | number) => {
|
||||
if (typeof val !== 'string') return val;
|
||||
return val.length > 250 ? `${val.substring(0, 250)}[...]` : val;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="modal-title h6">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-cog mr-1" />
|
||||
<span class="cut-text">{{ $t('word.settings') }}</span>
|
||||
<span class="cut-text">{{ t('word.settings') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click="closeModal" />
|
||||
@ -21,14 +21,14 @@
|
||||
:class="{'active': selectedTab === 'general'}"
|
||||
@click="selectTab('general')"
|
||||
>
|
||||
<a class="tab-link">{{ $t('word.general') }}</a>
|
||||
<a class="tab-link">{{ t('word.general') }}</a>
|
||||
</li>
|
||||
<li
|
||||
class="tab-item c-hand"
|
||||
:class="{'active': selectedTab === 'themes'}"
|
||||
@click="selectTab('themes')"
|
||||
>
|
||||
<a class="tab-link">{{ $t('word.themes') }}</a>
|
||||
<a class="tab-link">{{ t('word.themes') }}</a>
|
||||
</li>
|
||||
<li
|
||||
v-if="updateStatus !== 'disabled'"
|
||||
@ -36,21 +36,21 @@
|
||||
:class="{'active': selectedTab === 'update'}"
|
||||
@click="selectTab('update')"
|
||||
>
|
||||
<a class="tab-link" :class="{'badge badge-update': hasUpdates}">{{ $t('word.update') }}</a>
|
||||
<a class="tab-link" :class="{'badge badge-update': hasUpdates}">{{ t('word.update') }}</a>
|
||||
</li>
|
||||
<li
|
||||
class="tab-item c-hand"
|
||||
:class="{'active': selectedTab === 'changelog'}"
|
||||
@click="selectTab('changelog')"
|
||||
>
|
||||
<a class="tab-link">{{ $t('word.changelog') }}</a>
|
||||
<a class="tab-link">{{ t('word.changelog') }}</a>
|
||||
</li>
|
||||
<li
|
||||
class="tab-item c-hand"
|
||||
:class="{'active': selectedTab === 'about'}"
|
||||
@click="selectTab('about')"
|
||||
>
|
||||
<a class="tab-link">{{ $t('word.about') }}</a>
|
||||
<a class="tab-link">{{ t('word.about') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -58,14 +58,14 @@
|
||||
<div class="container">
|
||||
<form class="form-horizontal columns">
|
||||
<div class="column col-12 h6 text-uppercase mb-1">
|
||||
{{ $t('word.application') }}
|
||||
{{ t('word.application') }}
|
||||
</div>
|
||||
<div class="column col-12 col-sm-12 mb-2 columns">
|
||||
<div class="form-group column col-12">
|
||||
<div class="col-5 col-sm-12">
|
||||
<label class="form-label">
|
||||
<i class="mdi mdi-18px mdi-translate mr-1" />
|
||||
{{ $t('word.language') }}
|
||||
{{ t('word.language') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3 col-sm-12">
|
||||
@ -79,16 +79,16 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="col-4 col-sm-12 px-2 p-vcentered">
|
||||
<small class="d-block" style="line-height:1.1; font-size:70%;">
|
||||
{{ $t('message.missingOrIncompleteTranslation') }}<br>
|
||||
<a class="text-bold c-hand" @click="openOutside('https://github.com/antares-sql/antares/wiki/Translate-Antares')">{{ $t('message.findOutHowToContribute') }}</a>
|
||||
<small class="d-block" style="line-height: 1.1; font-size: 70%;">
|
||||
{{ t('message.missingOrIncompleteTranslation') }}<br>
|
||||
<a class="text-bold c-hand" @click="openOutside('https://github.com/antares-sql/antares/wiki/Translate-Antares')">{{ t('message.findOutHowToContribute') }}</a>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group column col-12">
|
||||
<div class="col-5 col-sm-12">
|
||||
<label class="form-label">
|
||||
{{ $t('message.dataTabPageSize') }}
|
||||
{{ t('message.dataTabPageSize') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3 col-sm-12">
|
||||
@ -103,7 +103,7 @@
|
||||
<div class="form-group column col-12 mb-0">
|
||||
<div class="col-5 col-sm-12">
|
||||
<label class="form-label">
|
||||
{{ $t('message.restorePreviourSession') }}
|
||||
{{ t('message.restorePreviourSession') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3 col-sm-12">
|
||||
@ -116,7 +116,7 @@
|
||||
<div class="form-group column col-12 mb-0">
|
||||
<div class="col-5 col-sm-12">
|
||||
<label class="form-label">
|
||||
{{ $t('message.disableBlur') }}
|
||||
{{ t('message.disableBlur') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3 col-sm-12">
|
||||
@ -129,7 +129,7 @@
|
||||
<div class="form-group column col-12">
|
||||
<div class="col-5 col-sm-12">
|
||||
<label class="form-label">
|
||||
{{ $t('message.notificationsTimeout') }}
|
||||
{{ t('message.notificationsTimeout') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3 col-sm-12">
|
||||
@ -141,19 +141,19 @@
|
||||
min="1"
|
||||
@focusout="checkNotificationsTimeout"
|
||||
>
|
||||
<span class="input-group-addon">{{ $t('word.seconds') }}</span>
|
||||
<span class="input-group-addon">{{ t('word.seconds') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-12 h6 mt-4 text-uppercase mb-1">
|
||||
{{ $t('word.editor') }}
|
||||
{{ t('word.editor') }}
|
||||
</div>
|
||||
<div class="column col-12 col-sm-12 columns">
|
||||
<div class="form-group column col-12 mb-0">
|
||||
<div class="col-5 col-sm-12">
|
||||
<label class="form-label">
|
||||
{{ $t('word.autoCompletion') }}
|
||||
{{ t('word.autoCompletion') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3 col-sm-12">
|
||||
@ -166,7 +166,7 @@
|
||||
<div class="form-group column col-12 mb-0">
|
||||
<div class="col-5 col-sm-12">
|
||||
<label class="form-label">
|
||||
{{ $t('message.wrapLongLines') }}
|
||||
{{ t('message.wrapLongLines') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3 col-sm-12">
|
||||
@ -185,18 +185,18 @@
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<div class="column col-12 h6 text-uppercase mb-2">
|
||||
{{ $t('message.applicationTheme') }}
|
||||
{{ t('message.applicationTheme') }}
|
||||
</div>
|
||||
<div
|
||||
class="column col-6 c-hand theme-block"
|
||||
:class="{'selected': applicationTheme === 'dark'}"
|
||||
@click="changeApplicationTheme('dark')"
|
||||
>
|
||||
<img src="../images/dark.png" class="img-responsive img-fit-cover s-rounded">
|
||||
<img :src="darkPreview" class="img-responsive img-fit-cover s-rounded">
|
||||
<div class="theme-name text-light">
|
||||
<i class="mdi mdi-moon-waning-crescent mdi-48px" />
|
||||
<div class="h6 mt-4">
|
||||
{{ $t('word.dark') }}
|
||||
{{ t('word.dark') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -205,11 +205,11 @@
|
||||
:class="{'selected': applicationTheme === 'light'}"
|
||||
@click="changeApplicationTheme('light')"
|
||||
>
|
||||
<img src="../images/light.png" class="img-responsive img-fit-cover s-rounded">
|
||||
<img :src="lightPreview" class="img-responsive img-fit-cover s-rounded">
|
||||
<div class="theme-name text-dark">
|
||||
<i class="mdi mdi-white-balance-sunny mdi-48px" />
|
||||
<div class="h6 mt-4">
|
||||
{{ $t('word.light') }}
|
||||
{{ t('word.light') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -217,7 +217,7 @@
|
||||
|
||||
<div class="columns mt-4">
|
||||
<div class="column col-12 h6 text-uppercase mb-2 mt-4">
|
||||
{{ $t('message.editorTheme') }}
|
||||
{{ t('message.editorTheme') }}
|
||||
</div>
|
||||
<div class="column col-6 h5 mb-4">
|
||||
<BaseSelect
|
||||
@ -238,21 +238,21 @@
|
||||
:class="{'active': editorFontSize === 'small'}"
|
||||
@click="changeEditorFontSize('small')"
|
||||
>
|
||||
{{ $t('word.small') }}
|
||||
{{ t('word.small') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark cut-text"
|
||||
:class="{'active': editorFontSize === 'medium'}"
|
||||
@click="changeEditorFontSize('medium')"
|
||||
>
|
||||
{{ $t('word.medium') }}
|
||||
{{ t('word.medium') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark cut-text"
|
||||
:class="{'active': editorFontSize === 'large'}"
|
||||
@click="changeEditorFontSize('large')"
|
||||
>
|
||||
{{ $t('word.large') }}
|
||||
{{ t('word.large') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -278,19 +278,19 @@
|
||||
|
||||
<div v-show="selectedTab === 'about'" class="panel-body py-4">
|
||||
<div class="text-center">
|
||||
<img src="../images/logo.svg" width="128">
|
||||
<img :src="appLogo" width="128">
|
||||
<h4>{{ appName }}</h4>
|
||||
<p class="mb-2">
|
||||
{{ $t('word.version') }} {{ appVersion }}<br>
|
||||
{{ t('word.version') }} {{ appVersion }}<br>
|
||||
<a class="c-hand" @click="openOutside('https://github.com/antares-sql/antares')"><i class="mdi mdi-github d-inline" /> GitHub</a> • <a class="c-hand" @click="openOutside('https://twitter.com/AntaresSQL')"><i class="mdi mdi-twitter d-inline" /> Twitter</a> • <a class="c-hand" @click="openOutside('https://antares-sql.app/')"><i class="mdi mdi-web d-inline" /> Website</a><br>
|
||||
<small>{{ $t('word.author') }} <a class="c-hand" @click="openOutside('https://github.com/Fabio286')">{{ appAuthor }}</a></small><br>
|
||||
<small>{{ t('word.author') }} <a class="c-hand" @click="openOutside('https://github.com/Fabio286')">{{ appAuthor }}</a></small><br>
|
||||
</p>
|
||||
<div class="mb-2">
|
||||
<small class="d-block text-uppercase">{{ $t('word.contributors') }}:</small>
|
||||
<small class="d-block text-uppercase">{{ t('word.contributors') }}:</small>
|
||||
<div class="d-block py-1">
|
||||
<small v-for="(contributor, i) in otherContributors" :key="i">{{ i !== 0 ? ', ' : '' }}{{ contributor }}</small>
|
||||
</div>
|
||||
<small>{{ $t('message.madeWithJS') }}</small>
|
||||
<small>{{ t('message.madeWithJS') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -301,176 +301,121 @@
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, Ref, ref } from 'vue';
|
||||
import { shell } from 'electron';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useApplicationStore } from '@/stores/application';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import localesNames from '@/i18n/supported-locales';
|
||||
import ModalSettingsUpdate from '@/components/ModalSettingsUpdate';
|
||||
import ModalSettingsChangelog from '@/components/ModalSettingsChangelog';
|
||||
import BaseTextEditor from '@/components/BaseTextEditor';
|
||||
import { localesNames } from '@/i18n/supported-locales';
|
||||
import ModalSettingsUpdate from '@/components/ModalSettingsUpdate.vue';
|
||||
import ModalSettingsChangelog from '@/components/ModalSettingsChangelog.vue';
|
||||
import BaseTextEditor from '@/components/BaseTextEditor.vue';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
import { computed } from '@vue/reactivity';
|
||||
|
||||
export default {
|
||||
name: 'ModalSettings',
|
||||
components: {
|
||||
ModalSettingsUpdate,
|
||||
ModalSettingsChangelog,
|
||||
BaseTextEditor,
|
||||
BaseSelect
|
||||
const { t, availableLocales } = useI18n();
|
||||
|
||||
const applicationStore = useApplicationStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
const {
|
||||
selectedSettingTab,
|
||||
updateStatus
|
||||
} = storeToRefs(applicationStore);
|
||||
const {
|
||||
locale: selectedLocale,
|
||||
dataTabLimit: pageSize,
|
||||
autoComplete: selectedAutoComplete,
|
||||
lineWrap: selectedLineWrap,
|
||||
notificationsTimeout,
|
||||
restoreTabs,
|
||||
disableBlur,
|
||||
applicationTheme,
|
||||
editorTheme,
|
||||
editorFontSize
|
||||
} = storeToRefs(settingsStore);
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
const {
|
||||
changeLocale,
|
||||
changePageSize,
|
||||
changeRestoreTabs,
|
||||
changeDisableBlur,
|
||||
changeAutoComplete,
|
||||
changeLineWrap,
|
||||
changeApplicationTheme,
|
||||
changeEditorTheme,
|
||||
changeEditorFontSize,
|
||||
updateNotificationsTimeout
|
||||
} = settingsStore;
|
||||
const {
|
||||
hideSettingModal: closeModal,
|
||||
appName,
|
||||
appVersion
|
||||
} = applicationStore;
|
||||
const { getWorkspace } = workspacesStore;
|
||||
|
||||
const appAuthor = 'Fabio Di Stasio';
|
||||
const pageSizes = [30, 40, 100, 250, 500, 1000];
|
||||
const contributors = process.env.APP_CONTRIBUTORS;
|
||||
const appLogo = require('../images/logo.svg');
|
||||
const darkPreview = require('../images/dark.png');
|
||||
const lightPreview = require('../images/light.png');
|
||||
const editorThemes= [
|
||||
{
|
||||
group: t('word.light'),
|
||||
themes: [
|
||||
{ code: 'chrome', name: 'Chrome' },
|
||||
{ code: 'clouds', name: 'Clouds' },
|
||||
{ code: 'crimson_editor', name: 'Crimson Editor' },
|
||||
{ code: 'dawn', name: 'Dawn' },
|
||||
{ code: 'dreamweaver', name: 'Dreamweaver' },
|
||||
{ code: 'eclupse', name: 'Eclipse' },
|
||||
{ code: 'github', name: 'GitHub' },
|
||||
{ code: 'iplastic', name: 'IPlastic' },
|
||||
{ code: 'solarized_light', name: 'Solarized Light' },
|
||||
{ code: 'textmate', name: 'TextMate' },
|
||||
{ code: 'tomorrow', name: 'Tomorrow' },
|
||||
{ code: 'xcode', name: 'Xcode' },
|
||||
{ code: 'kuroir', name: 'Kuroir' },
|
||||
{ code: 'katzenmilch', name: 'KatzenMilch' },
|
||||
{ code: 'sqlserver', name: 'SQL Server' }
|
||||
]
|
||||
},
|
||||
setup () {
|
||||
const applicationStore = useApplicationStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
const {
|
||||
selectedSettingTab,
|
||||
updateStatus
|
||||
} = storeToRefs(applicationStore);
|
||||
const {
|
||||
locale: selectedLocale,
|
||||
dataTabLimit: pageSize,
|
||||
autoComplete: selectedAutoComplete,
|
||||
lineWrap: selectedLineWrap,
|
||||
notificationsTimeout,
|
||||
restoreTabs,
|
||||
disableBlur,
|
||||
applicationTheme,
|
||||
editorTheme,
|
||||
editorFontSize
|
||||
} = storeToRefs(settingsStore);
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
const {
|
||||
changeLocale,
|
||||
changePageSize,
|
||||
changeRestoreTabs,
|
||||
changeDisableBlur,
|
||||
changeAutoComplete,
|
||||
changeLineWrap,
|
||||
changeApplicationTheme,
|
||||
changeEditorTheme,
|
||||
changeEditorFontSize,
|
||||
updateNotificationsTimeout
|
||||
} = settingsStore;
|
||||
const {
|
||||
hideSettingModal,
|
||||
appName,
|
||||
appVersion
|
||||
} = applicationStore;
|
||||
const { getWorkspace } = workspacesStore;
|
||||
|
||||
return {
|
||||
appName,
|
||||
appVersion,
|
||||
selectedSettingTab,
|
||||
updateStatus,
|
||||
closeModal: hideSettingModal,
|
||||
selectedLocale,
|
||||
pageSize,
|
||||
selectedAutoComplete,
|
||||
selectedLineWrap,
|
||||
notificationsTimeout,
|
||||
restoreTabs,
|
||||
disableBlur,
|
||||
applicationTheme,
|
||||
editorTheme,
|
||||
editorFontSize,
|
||||
changeLocale,
|
||||
changePageSize,
|
||||
changeRestoreTabs,
|
||||
changeDisableBlur,
|
||||
changeAutoComplete,
|
||||
changeLineWrap,
|
||||
changeApplicationTheme,
|
||||
changeEditorTheme,
|
||||
changeEditorFontSize,
|
||||
updateNotificationsTimeout,
|
||||
selectedWorkspace,
|
||||
getWorkspace
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
appAuthor: 'Fabio Di Stasio',
|
||||
localLocale: null,
|
||||
localPageSize: null,
|
||||
localTimeout: null,
|
||||
localEditorTheme: null,
|
||||
selectedTab: 'general',
|
||||
pageSizes: [30, 40, 100, 250, 500, 1000],
|
||||
editorThemes: [
|
||||
{
|
||||
group: this.$t('word.light'),
|
||||
themes: [
|
||||
{ code: 'chrome', name: 'Chrome' },
|
||||
{ code: 'clouds', name: 'Clouds' },
|
||||
{ code: 'crimson_editor', name: 'Crimson Editor' },
|
||||
{ code: 'dawn', name: 'Dawn' },
|
||||
{ code: 'dreamweaver', name: 'Dreamweaver' },
|
||||
{ code: 'eclupse', name: 'Eclipse' },
|
||||
{ code: 'github', name: 'GitHub' },
|
||||
{ code: 'iplastic', name: 'IPlastic' },
|
||||
{ code: 'solarized_light', name: 'Solarized Light' },
|
||||
{ code: 'textmate', name: 'TextMate' },
|
||||
{ code: 'tomorrow', name: 'Tomorrow' },
|
||||
{ code: 'xcode', name: 'Xcode' },
|
||||
{ code: 'kuroir', name: 'Kuroir' },
|
||||
{ code: 'katzenmilch', name: 'KatzenMilch' },
|
||||
{ code: 'sqlserver', name: 'SQL Server' }
|
||||
]
|
||||
},
|
||||
{
|
||||
group: this.$t('word.dark'),
|
||||
themes: [
|
||||
{ code: 'ambiance', name: 'Ambiance' },
|
||||
{ code: 'chaos', name: 'Chaos' },
|
||||
{ code: 'clouds_midnight', name: 'Clouds Midnight' },
|
||||
{ code: 'dracula', name: 'Dracula' },
|
||||
{ code: 'cobalt', name: 'Cobalt' },
|
||||
{ code: 'gruvbox', name: 'Gruvbox' },
|
||||
{ code: 'gob', name: 'Green on Black' },
|
||||
{ code: 'idle_fingers', name: 'Idle Fingers' },
|
||||
{ code: 'kr_theme', name: 'krTheme' },
|
||||
{ code: 'merbivore', name: 'Merbivore' },
|
||||
{ code: 'mono_industrial', name: 'Mono Industrial' },
|
||||
{ code: 'monokai', name: 'Monokai' },
|
||||
{ code: 'nord_dark', name: 'Nord Dark' },
|
||||
{ code: 'pastel_on_dark', name: 'Pastel on Dark' },
|
||||
{ code: 'solarized_dark', name: 'Solarized Dark' },
|
||||
{ code: 'terminal', name: 'Terminal' },
|
||||
{ code: 'tomorrow_night', name: 'Tomorrow Night' },
|
||||
{ code: 'tomorrow_night_blue', name: 'Tomorrow Night Blue' },
|
||||
{ code: 'tomorrow_night_bright', name: 'Tomorrow Night Bright' },
|
||||
{ code: 'tomorrow_night_eighties', name: 'Tomorrow Night 80s' },
|
||||
{ code: 'twilight', name: 'Twilight' },
|
||||
{ code: 'vibrant_ink', name: 'Vibrant Ink' }
|
||||
]
|
||||
}
|
||||
],
|
||||
contributors: process.env.APP_CONTRIBUTORS
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
locales () {
|
||||
const locales = [];
|
||||
for (const locale of this.$i18n.availableLocales)
|
||||
locales.push({ code: locale, name: localesNames[locale] });
|
||||
|
||||
return locales;
|
||||
},
|
||||
hasUpdates () {
|
||||
return ['available', 'downloading', 'downloaded', 'link'].includes(this.updateStatus);
|
||||
},
|
||||
workspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
},
|
||||
exampleQuery () {
|
||||
return `-- This is an example
|
||||
{
|
||||
group: t('word.dark'),
|
||||
themes: [
|
||||
{ code: 'ambiance', name: 'Ambiance' },
|
||||
{ code: 'chaos', name: 'Chaos' },
|
||||
{ code: 'clouds_midnight', name: 'Clouds Midnight' },
|
||||
{ code: 'dracula', name: 'Dracula' },
|
||||
{ code: 'cobalt', name: 'Cobalt' },
|
||||
{ code: 'gruvbox', name: 'Gruvbox' },
|
||||
{ code: 'gob', name: 'Green on Black' },
|
||||
{ code: 'idle_fingers', name: 'Idle Fingers' },
|
||||
{ code: 'kr_theme', name: 'krTheme' },
|
||||
{ code: 'merbivore', name: 'Merbivore' },
|
||||
{ code: 'mono_industrial', name: 'Mono Industrial' },
|
||||
{ code: 'monokai', name: 'Monokai' },
|
||||
{ code: 'nord_dark', name: 'Nord Dark' },
|
||||
{ code: 'pastel_on_dark', name: 'Pastel on Dark' },
|
||||
{ code: 'solarized_dark', name: 'Solarized Dark' },
|
||||
{ code: 'terminal', name: 'Terminal' },
|
||||
{ code: 'tomorrow_night', name: 'Tomorrow Night' },
|
||||
{ code: 'tomorrow_night_blue', name: 'Tomorrow Night Blue' },
|
||||
{ code: 'tomorrow_night_bright', name: 'Tomorrow Night Bright' },
|
||||
{ code: 'tomorrow_night_eighties', name: 'Tomorrow Night 80s' },
|
||||
{ code: 'twilight', name: 'Twilight' },
|
||||
{ code: 'vibrant_ink', name: 'Vibrant Ink' }
|
||||
]
|
||||
}
|
||||
];
|
||||
const exampleQuery = `-- This is an example
|
||||
SELECT
|
||||
employee.id,
|
||||
employee.first_name,
|
||||
@ -485,57 +430,81 @@ GROUP BY
|
||||
ORDER BY
|
||||
employee.id ASC;
|
||||
`;
|
||||
},
|
||||
otherContributors () {
|
||||
return this.contributors
|
||||
.split(',')
|
||||
.filter(c => !c.includes(this.appAuthor))
|
||||
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.localLocale = this.selectedLocale;
|
||||
this.localPageSize = this.pageSize;
|
||||
this.localTimeout = this.notificationsTimeout;
|
||||
this.localEditorTheme = this.editorTheme;
|
||||
this.selectedTab = this.selectedSettingTab;
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
selectTab (tab) {
|
||||
this.selectedTab = tab;
|
||||
},
|
||||
openOutside (link) {
|
||||
shell.openExternal(link);
|
||||
},
|
||||
checkNotificationsTimeout () {
|
||||
if (!this.localTimeout)
|
||||
this.localTimeout = 10;
|
||||
|
||||
this.updateNotificationsTimeout(+this.localTimeout);
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
},
|
||||
toggleRestoreSession () {
|
||||
this.changeRestoreTabs(!this.restoreTabs);
|
||||
},
|
||||
toggleDisableBlur () {
|
||||
this.changeDisableBlur(!this.disableBlur);
|
||||
},
|
||||
toggleAutoComplete () {
|
||||
this.changeAutoComplete(!this.selectedAutoComplete);
|
||||
},
|
||||
toggleLineWrap () {
|
||||
this.changeLineWrap(!this.selectedLineWrap);
|
||||
}
|
||||
}
|
||||
const localLocale: Ref<string> = ref(null);
|
||||
const localPageSize: Ref<number> = ref(null);
|
||||
const localTimeout: Ref<number> = ref(null);
|
||||
const localEditorTheme: Ref<string> = ref(null);
|
||||
const selectedTab: Ref<string> = ref('general');
|
||||
|
||||
const locales = computed(() => {
|
||||
const locales = [];
|
||||
for (const locale of availableLocales)
|
||||
locales.push({ code: locale, name: localesNames[locale] });
|
||||
|
||||
return locales;
|
||||
});
|
||||
|
||||
const hasUpdates = computed(() => ['available', 'downloading', 'downloaded', 'link'].includes(updateStatus.value));
|
||||
|
||||
const workspace = computed(() => {
|
||||
return getWorkspace(selectedWorkspace.value);
|
||||
});
|
||||
|
||||
const otherContributors = computed(() => {
|
||||
return contributors
|
||||
.split(',')
|
||||
.filter(c => !c.includes(appAuthor))
|
||||
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
||||
});
|
||||
|
||||
const selectTab = (tab: string) => {
|
||||
selectedTab.value = tab;
|
||||
};
|
||||
|
||||
const openOutside = (link: string) => {
|
||||
shell.openExternal(link);
|
||||
};
|
||||
|
||||
const checkNotificationsTimeout = () => {
|
||||
if (!localTimeout.value)
|
||||
localTimeout.value = 10;
|
||||
|
||||
updateNotificationsTimeout(+localTimeout.value);
|
||||
};
|
||||
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const toggleRestoreSession = () => {
|
||||
changeRestoreTabs(!restoreTabs.value);
|
||||
};
|
||||
|
||||
const toggleDisableBlur = () => {
|
||||
changeDisableBlur(!disableBlur.value);
|
||||
};
|
||||
|
||||
const toggleAutoComplete = () => {
|
||||
changeAutoComplete(!selectedAutoComplete.value);
|
||||
};
|
||||
|
||||
const toggleLineWrap = () => {
|
||||
changeLineWrap(!selectedLineWrap.value);
|
||||
};
|
||||
|
||||
localLocale.value = selectedLocale.value as string;
|
||||
localPageSize.value = pageSize.value as number;
|
||||
localTimeout.value = notificationsTimeout.value as number;
|
||||
localEditorTheme.value = editorTheme.value as string;
|
||||
selectedTab.value = selectedSettingTab.value;
|
||||
window.addEventListener('keydown', onKey);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -13,66 +13,53 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { marked } from 'marked';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import BaseLoader from '@/components/BaseLoader.vue';
|
||||
import { useApplicationStore } from '@/stores/application';
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'ModalSettingsChangelog',
|
||||
components: {
|
||||
BaseLoader
|
||||
},
|
||||
setup () {
|
||||
const { appVersion } = useApplicationStore();
|
||||
return { appVersion };
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
changelog: '',
|
||||
isLoading: true,
|
||||
error: '',
|
||||
isError: false
|
||||
const { appVersion } = useApplicationStore();
|
||||
|
||||
const changelog = ref('');
|
||||
const isLoading = ref(true);
|
||||
const error = ref('');
|
||||
const isError = ref(false);
|
||||
|
||||
const getChangelog = async () => {
|
||||
try {
|
||||
const apiRes = await fetch(`https://api.github.com/repos/antares-sql/antares/releases/tags/v${appVersion}`, {
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
const { body } = await apiRes.json();
|
||||
const cutOffset = body.indexOf('### Download');
|
||||
const markdown = cutOffset >= 0
|
||||
? body.substr(0, cutOffset)
|
||||
: body;
|
||||
|
||||
const renderer = {
|
||||
link (href: string, title: string, text: string) {
|
||||
return text;
|
||||
},
|
||||
listitem (text: string) {
|
||||
return `<li>${text.replace(/ *\([^)]*\) */g, '')}</li>`;
|
||||
}
|
||||
};
|
||||
},
|
||||
created () {
|
||||
this.getChangelog();
|
||||
},
|
||||
methods: {
|
||||
async getChangelog () {
|
||||
try {
|
||||
const apiRes = await fetch(`https://api.github.com/repos/antares-sql/antares/releases/tags/v${this.appVersion}`, {
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
const { body } = await apiRes.json();
|
||||
const cutOffset = body.indexOf('### Download');
|
||||
const markdown = cutOffset >= 0
|
||||
? body.substr(0, cutOffset)
|
||||
: body;
|
||||
marked.use({ renderer });
|
||||
|
||||
const renderer = {
|
||||
link (href, title, text) {
|
||||
return text;
|
||||
},
|
||||
listitem (text) {
|
||||
return `<li>${text.replace(/ *\([^)]*\) */g, '')}</li>`;
|
||||
}
|
||||
};
|
||||
|
||||
marked.use({ renderer });
|
||||
|
||||
this.changelog = marked(markdown);
|
||||
}
|
||||
catch (err) {
|
||||
this.error = err.message;
|
||||
this.isError = true;
|
||||
}
|
||||
this.isLoading = false;
|
||||
}
|
||||
changelog.value = marked(markdown);
|
||||
}
|
||||
catch (err) {
|
||||
error.value = err.message;
|
||||
isError.value = true;
|
||||
}
|
||||
|
||||
isLoading.value = false;
|
||||
};
|
||||
|
||||
getChangelog();
|
||||
</script>
|
||||
<style lang="scss">
|
||||
#changelog {
|
||||
|
@ -52,68 +52,61 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ipcRenderer, shell } from 'electron';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useApplicationStore } from '@/stores/application';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
|
||||
export default {
|
||||
name: 'ModalSettingsUpdate',
|
||||
setup () {
|
||||
const applicationStore = useApplicationStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const {
|
||||
updateStatus,
|
||||
getDownloadProgress
|
||||
} = storeToRefs(applicationStore);
|
||||
const { allowPrerelease } = storeToRefs(settingsStore);
|
||||
const applicationStore = useApplicationStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const { changeAllowPrerelease } = settingsStore;
|
||||
const {
|
||||
updateStatus,
|
||||
getDownloadProgress: downloadPercentage
|
||||
} = storeToRefs(applicationStore);
|
||||
const { allowPrerelease } = storeToRefs(settingsStore);
|
||||
|
||||
return {
|
||||
updateStatus,
|
||||
downloadPercentage: getDownloadProgress,
|
||||
allowPrerelease,
|
||||
changeAllowPrerelease
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
updateMessage () {
|
||||
switch (this.updateStatus) {
|
||||
case 'noupdate':
|
||||
return this.$t('message.noUpdatesAvailable');
|
||||
case 'checking':
|
||||
return this.$t('message.checkingForUpdate');
|
||||
case 'nocheck':
|
||||
return this.$t('message.checkFailure');
|
||||
case 'available':
|
||||
return this.$t('message.updateAvailable');
|
||||
case 'downloading':
|
||||
return this.$t('message.downloadingUpdate');
|
||||
case 'downloaded':
|
||||
return this.$t('message.updateDownloaded');
|
||||
case 'link':
|
||||
return this.$t('message.updateAvailable');
|
||||
default:
|
||||
return this.updateStatus;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openOutside (link) {
|
||||
shell.openExternal(link);
|
||||
},
|
||||
checkForUpdates () {
|
||||
ipcRenderer.send('check-for-updates');
|
||||
},
|
||||
restartToUpdate () {
|
||||
ipcRenderer.send('restart-to-update');
|
||||
},
|
||||
toggleAllowPrerelease () {
|
||||
this.changeAllowPrerelease(!this.allowPrerelease);
|
||||
}
|
||||
const { changeAllowPrerelease } = settingsStore;
|
||||
|
||||
const updateMessage = computed(() => {
|
||||
switch (updateStatus.value) {
|
||||
case 'noupdate':
|
||||
return t('message.noUpdatesAvailable');
|
||||
case 'checking':
|
||||
return t('message.checkingForUpdate');
|
||||
case 'nocheck':
|
||||
return t('message.checkFailure');
|
||||
case 'available':
|
||||
return t('message.updateAvailable');
|
||||
case 'downloading':
|
||||
return t('message.downloadingUpdate');
|
||||
case 'downloaded':
|
||||
return t('message.updateDownloaded');
|
||||
case 'link':
|
||||
return t('message.updateAvailable');
|
||||
default:
|
||||
return updateStatus.value;
|
||||
}
|
||||
});
|
||||
|
||||
const openOutside = (link: string) => {
|
||||
shell.openExternal(link);
|
||||
};
|
||||
|
||||
const checkForUpdates = () => {
|
||||
ipcRenderer.send('check-for-updates');
|
||||
};
|
||||
|
||||
const restartToUpdate = () => {
|
||||
ipcRenderer.send('restart-to-update');
|
||||
};
|
||||
|
||||
const toggleAllowPrerelease = () => {
|
||||
changeAllowPrerelease(!allowPrerelease.value);
|
||||
};
|
||||
</script>
|
||||
|
@ -8,7 +8,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, Prop, Ref, ref, toRef, watch } from 'vue';
|
||||
import * as ace from 'ace-builds';
|
||||
import 'ace-builds/webpack-resolver';
|
||||
import '../libs/ext-language_tools';
|
||||
@ -16,329 +17,330 @@ import { storeToRefs } from 'pinia';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import { useApplicationStore } from '@/stores/application';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { Workspace } from '@/stores/workspaces';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
|
||||
export default {
|
||||
name: 'QueryEditor',
|
||||
props: {
|
||||
modelValue: String,
|
||||
workspace: Object,
|
||||
isSelected: Boolean,
|
||||
schema: { type: String, default: '' },
|
||||
autoFocus: { type: Boolean, default: false },
|
||||
readOnly: { type: Boolean, default: false },
|
||||
height: { type: Number, default: 200 }
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup () {
|
||||
const editor = null;
|
||||
const applicationStore = useApplicationStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const editor: Ref<ace.Ace.Editor> = ref(null);
|
||||
const applicationStore = useApplicationStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const { setBaseCompleters } = applicationStore;
|
||||
const { setBaseCompleters } = applicationStore;
|
||||
|
||||
const { baseCompleter } = storeToRefs(applicationStore);
|
||||
const {
|
||||
editorTheme,
|
||||
editorFontSize,
|
||||
autoComplete,
|
||||
lineWrap
|
||||
} = storeToRefs(settingsStore);
|
||||
const { baseCompleter } = storeToRefs(applicationStore);
|
||||
const {
|
||||
editorTheme,
|
||||
editorFontSize,
|
||||
autoComplete,
|
||||
lineWrap
|
||||
} = storeToRefs(settingsStore);
|
||||
|
||||
return {
|
||||
editor,
|
||||
baseCompleter,
|
||||
setBaseCompleters,
|
||||
editorTheme,
|
||||
editorFontSize,
|
||||
autoComplete,
|
||||
lineWrap
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
cursorPosition: 0,
|
||||
fields: [],
|
||||
customCompleter: [],
|
||||
id: uidGen(),
|
||||
lastSchema: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
tables () {
|
||||
return this.workspace
|
||||
? this.workspace.structure.filter(schema => schema.name === this.schema)
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(...curr.tables);
|
||||
return acc;
|
||||
}, []).map(table => {
|
||||
return {
|
||||
name: table.name,
|
||||
type: table.type,
|
||||
fields: []
|
||||
};
|
||||
})
|
||||
: [];
|
||||
},
|
||||
triggers () {
|
||||
return this.workspace
|
||||
? this.workspace.structure.filter(schema => schema.name === this.schema)
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(...curr.triggers);
|
||||
return acc;
|
||||
}, []).map(trigger => {
|
||||
return {
|
||||
name: trigger.name,
|
||||
type: 'trigger'
|
||||
};
|
||||
})
|
||||
: [];
|
||||
},
|
||||
procedures () {
|
||||
return this.workspace
|
||||
? this.workspace.structure.filter(schema => schema.name === this.schema)
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(...curr.procedures);
|
||||
return acc;
|
||||
}, []).map(procedure => {
|
||||
return {
|
||||
name: `${procedure.name}()`,
|
||||
type: 'routine'
|
||||
};
|
||||
})
|
||||
: [];
|
||||
},
|
||||
functions () {
|
||||
return this.workspace
|
||||
? this.workspace.structure.filter(schema => schema.name === this.schema)
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(...curr.functions);
|
||||
return acc;
|
||||
}, []).map(func => {
|
||||
return {
|
||||
name: `${func.name}()`,
|
||||
type: 'function'
|
||||
};
|
||||
})
|
||||
: [];
|
||||
},
|
||||
schedulers () {
|
||||
return this.workspace
|
||||
? this.workspace.structure.filter(schema => schema.name === this.schema)
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(...curr.schedulers);
|
||||
return acc;
|
||||
}, []).map(scheduler => {
|
||||
return {
|
||||
name: scheduler.name,
|
||||
type: 'scheduler'
|
||||
};
|
||||
})
|
||||
: [];
|
||||
},
|
||||
mode () {
|
||||
switch (this.workspace.client) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
return 'mysql';
|
||||
case 'mssql':
|
||||
return 'sqlserver';
|
||||
case 'pg':
|
||||
return 'pgsql';
|
||||
default:
|
||||
return 'sql';
|
||||
}
|
||||
},
|
||||
lastWord () {
|
||||
const charsBefore = this.modelValue.slice(0, this.cursorPosition);
|
||||
const words = charsBefore.replaceAll('\n', ' ').split(' ').filter(Boolean);
|
||||
return words.pop();
|
||||
},
|
||||
isLastWordATable () {
|
||||
return /\w+\.\w*/gm.test(this.lastWord);
|
||||
},
|
||||
fieldsCompleter () {
|
||||
return {
|
||||
getCompletions: (editor, session, pos, prefix, callback) => {
|
||||
const completions = [];
|
||||
this.fields.forEach(field => {
|
||||
completions.push({
|
||||
value: field,
|
||||
meta: 'column',
|
||||
score: 1000
|
||||
});
|
||||
});
|
||||
callback(null, completions);
|
||||
}
|
||||
};
|
||||
const props = defineProps({
|
||||
modelValue: String,
|
||||
workspace: Object as Prop<Workspace>,
|
||||
isSelected: Boolean,
|
||||
schema: { type: String, default: '' },
|
||||
autoFocus: { type: Boolean, default: false },
|
||||
readOnly: { type: Boolean, default: false },
|
||||
height: { type: Number, default: 200 }
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const cursorPosition = ref(0);
|
||||
const fields = ref([]);
|
||||
const customCompleter = ref([]);
|
||||
const id = ref(uidGen());
|
||||
const lastSchema: Ref<string> = ref(null);
|
||||
|
||||
const tables = computed(() => {
|
||||
return props.workspace
|
||||
? props.workspace.structure.filter(schema => schema.name === props.schema)
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(...curr.tables);
|
||||
return acc;
|
||||
}, []).map(table => {
|
||||
return {
|
||||
name: table.name as string,
|
||||
type: table.type as string,
|
||||
fields: []
|
||||
};
|
||||
})
|
||||
: [];
|
||||
});
|
||||
|
||||
const triggers = computed(() => {
|
||||
return props.workspace
|
||||
? props.workspace.structure.filter(schema => schema.name === props.schema)
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(...curr.triggers);
|
||||
return acc;
|
||||
}, []).map(trigger => {
|
||||
return {
|
||||
name: trigger.name as string,
|
||||
type: 'trigger'
|
||||
};
|
||||
})
|
||||
: [];
|
||||
});
|
||||
|
||||
const procedures = computed(() => {
|
||||
return props.workspace
|
||||
? props.workspace.structure.filter(schema => schema.name === props.schema)
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(...curr.procedures);
|
||||
return acc;
|
||||
}, []).map(procedure => {
|
||||
return {
|
||||
name: `${procedure.name}()`,
|
||||
type: 'routine'
|
||||
};
|
||||
})
|
||||
: [];
|
||||
});
|
||||
|
||||
const functions = computed(() => {
|
||||
return props.workspace
|
||||
? props.workspace.structure.filter(schema => schema.name === props.schema)
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(...curr.functions);
|
||||
return acc;
|
||||
}, []).map(func => {
|
||||
return {
|
||||
name: `${func.name}()`,
|
||||
type: 'function'
|
||||
};
|
||||
})
|
||||
: [];
|
||||
});
|
||||
|
||||
const schedulers = computed(() => {
|
||||
return props.workspace
|
||||
? props.workspace.structure.filter(schema => schema.name === props.schema)
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(...curr.schedulers);
|
||||
return acc;
|
||||
}, []).map(scheduler => {
|
||||
return {
|
||||
name: scheduler.name as string,
|
||||
type: 'scheduler'
|
||||
};
|
||||
})
|
||||
: [];
|
||||
});
|
||||
|
||||
const mode = computed(() => {
|
||||
switch (props.workspace.client) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
return 'mysql';
|
||||
// case 'mssql':
|
||||
// return 'sqlserver';
|
||||
case 'pg':
|
||||
return 'pgsql';
|
||||
default:
|
||||
return 'sql';
|
||||
}
|
||||
});
|
||||
|
||||
const lastWord = computed(() => {
|
||||
const charsBefore = props.modelValue.slice(0, cursorPosition.value);
|
||||
const words = charsBefore.replaceAll('\n', ' ').split(' ').filter(Boolean);
|
||||
return words.pop();
|
||||
});
|
||||
|
||||
const isLastWordATable = computed(() => /\w+\.\w*/gm.test(lastWord.value));
|
||||
|
||||
const fieldsCompleter = computed(() => {
|
||||
return {
|
||||
getCompletions: (editor: never, session: never, pos: never, prefix: never, callback: (err: null, response: ace.Ace.Completion[]) => void) => {
|
||||
const completions: ace.Ace.Completion[] = [];
|
||||
fields.value.forEach(field => {
|
||||
completions.push({
|
||||
value: field,
|
||||
meta: 'column',
|
||||
score: 1000
|
||||
});
|
||||
});
|
||||
callback(null, completions);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue () {
|
||||
this.cursorPosition = this.editor.session.doc.positionToIndex(this.editor.getCursorPosition());
|
||||
},
|
||||
editorTheme () {
|
||||
if (this.editor)
|
||||
this.editor.setTheme(`ace/theme/${this.editorTheme}`);
|
||||
},
|
||||
editorFontSize () {
|
||||
const sizes = {
|
||||
small: '12px',
|
||||
medium: '14px',
|
||||
large: '16px'
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
if (this.editor) {
|
||||
this.editor.setOptions({
|
||||
fontSize: sizes[this.editorFontSize]
|
||||
const setCustomCompleter = () => {
|
||||
editor.value.completers.push({
|
||||
getCompletions: (editor, session, pos, prefix, callback: (err: null, response: ace.Ace.Completion[]) => void) => {
|
||||
const completions: ace.Ace.Completion[] = [];
|
||||
[
|
||||
...tables.value,
|
||||
...triggers.value,
|
||||
...procedures.value,
|
||||
...functions.value,
|
||||
...schedulers.value
|
||||
].forEach(el => {
|
||||
completions.push({
|
||||
value: el.name,
|
||||
meta: el.type,
|
||||
score: 1000
|
||||
});
|
||||
}
|
||||
},
|
||||
autoComplete () {
|
||||
if (this.editor) {
|
||||
this.editor.setOptions({
|
||||
enableLiveAutocompletion: this.autoComplete
|
||||
});
|
||||
}
|
||||
},
|
||||
lineWrap () {
|
||||
if (this.editor) {
|
||||
this.editor.setOptions({
|
||||
wrap: this.lineWrap
|
||||
});
|
||||
}
|
||||
},
|
||||
isSelected () {
|
||||
if (this.isSelected) {
|
||||
this.lastSchema = this.schema;
|
||||
this.editor.resize();
|
||||
}
|
||||
},
|
||||
height () {
|
||||
setTimeout(() => {
|
||||
this.editor.resize();
|
||||
}, 20);
|
||||
},
|
||||
lastSchema () {
|
||||
if (this.editor) {
|
||||
this.editor.completers = this.baseCompleter.map(el => Object.assign({}, el));
|
||||
this.setCustomCompleter();
|
||||
}
|
||||
});
|
||||
callback(null, completions);
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.lastSchema = this.schema;
|
||||
},
|
||||
mounted () {
|
||||
this.editor = ace.edit(`editor-${this.id}`, {
|
||||
mode: `ace/mode/${this.mode}`,
|
||||
theme: `ace/theme/${this.editorTheme}`,
|
||||
value: this.modelValue,
|
||||
fontSize: '14px',
|
||||
printMargin: false,
|
||||
readOnly: this.readOnly
|
||||
});
|
||||
|
||||
customCompleter.value = editor.value.completers;
|
||||
};
|
||||
|
||||
watch(() => props.modelValue, () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
cursorPosition.value = (editor.value.session as any).doc.positionToIndex(editor.value.getCursorPosition());
|
||||
});
|
||||
|
||||
watch(editorTheme, () => {
|
||||
if (editor.value)
|
||||
editor.value.setTheme(`ace/theme/${editorTheme.value}`);
|
||||
});
|
||||
|
||||
watch(editorFontSize, () => {
|
||||
const sizes = {
|
||||
small: '12px',
|
||||
medium: '14px',
|
||||
large: '16px'
|
||||
};
|
||||
|
||||
if (editor.value) {
|
||||
editor.value.setOptions({
|
||||
fontSize: sizes[editorFontSize.value]
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.setOptions({
|
||||
enableBasicAutocompletion: true,
|
||||
wrap: this.lineWrap,
|
||||
enableSnippets: true,
|
||||
enableLiveAutocompletion: this.autoComplete
|
||||
watch(autoComplete, () => {
|
||||
if (editor.value) {
|
||||
editor.value.setOptions({
|
||||
enableLiveAutocompletion: autoComplete.value
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (!this.baseCompleter.length)
|
||||
this.setBaseCompleters(this.editor.completers.map(el => Object.assign({}, el)));
|
||||
watch(lineWrap, () => {
|
||||
if (editor.value) {
|
||||
editor.value.setOptions({
|
||||
wrap: lineWrap.value
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.setCustomCompleter();
|
||||
watch(() => props.isSelected, () => {
|
||||
if (props.isSelected) {
|
||||
lastSchema.value = props.schema;
|
||||
editor.value.resize();
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.commands.on('afterExec', e => {
|
||||
if (['insertstring', 'backspace', 'del'].includes(e.command.name)) {
|
||||
if (this.isLastWordATable || e.args === '.') {
|
||||
if (e.args !== ' ') {
|
||||
const table = this.tables.find(t => t.name === this.lastWord.split('.').pop().trim());
|
||||
watch(() => props.height, () => {
|
||||
setTimeout(() => {
|
||||
editor.value.resize();
|
||||
}, 20);
|
||||
});
|
||||
|
||||
if (table) {
|
||||
const params = {
|
||||
uid: this.workspace.uid,
|
||||
schema: this.schema,
|
||||
table: table.name
|
||||
};
|
||||
watch(lastSchema, () => {
|
||||
if (editor.value) {
|
||||
editor.value.completers = baseCompleter.value.map(el => Object.assign({}, el));
|
||||
setCustomCompleter();
|
||||
}
|
||||
});
|
||||
|
||||
Tables.getTableColumns(params).then(res => {
|
||||
if (res.response.length)
|
||||
this.fields = res.response.map(field => field.name);
|
||||
this.editor.completers = [this.fieldsCompleter];
|
||||
this.editor.execCommand('startAutocomplete');
|
||||
}).catch(console.log);
|
||||
}
|
||||
else
|
||||
this.editor.completers = this.customCompleter;
|
||||
lastSchema.value = toRef(props, 'schema').value;
|
||||
|
||||
onMounted(() => {
|
||||
editor.value = ace.edit(`editor-${id.value}`, {
|
||||
mode: `ace/mode/${mode.value}`,
|
||||
theme: `ace/theme/${editorTheme.value}`,
|
||||
value: props.modelValue,
|
||||
fontSize: 14,
|
||||
printMargin: false,
|
||||
readOnly: props.readOnly
|
||||
});
|
||||
|
||||
editor.value.setOptions({
|
||||
enableBasicAutocompletion: true,
|
||||
wrap: lineWrap.value,
|
||||
enableSnippets: true,
|
||||
enableLiveAutocompletion: autoComplete.value
|
||||
});
|
||||
|
||||
if (!baseCompleter.value.length)
|
||||
setBaseCompleters(editor.value.completers.map(el => Object.assign({}, el)));
|
||||
|
||||
setCustomCompleter();
|
||||
|
||||
editor.value.commands.on('afterExec', (e: { args: string; command: { name: string } }) => {
|
||||
if (['insertstring', 'backspace', 'del'].includes(e.command.name)) {
|
||||
if (isLastWordATable.value || e.args === '.') {
|
||||
if (e.args !== ' ') {
|
||||
const table = tables.value.find(t => t.name === lastWord.value.split('.').pop().trim());
|
||||
|
||||
if (table) {
|
||||
const params = {
|
||||
uid: props.workspace.uid,
|
||||
schema: props.schema,
|
||||
table: table.name
|
||||
};
|
||||
|
||||
Tables.getTableColumns(params).then(res => {
|
||||
if (res.response.length)
|
||||
fields.value = res.response.map((field: { name: string }) => field.name);
|
||||
editor.value.completers = [fieldsCompleter.value];
|
||||
editor.value.execCommand('startAutocomplete');
|
||||
}).catch(console.log);
|
||||
}
|
||||
else
|
||||
this.editor.completers = this.customCompleter;
|
||||
editor.value.completers = customCompleter.value;
|
||||
}
|
||||
else
|
||||
this.editor.completers = this.customCompleter;
|
||||
editor.value.completers = customCompleter.value;
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.session.on('change', () => {
|
||||
const content = this.editor.getValue();
|
||||
this.$emit('update:modelValue', content);
|
||||
});
|
||||
|
||||
this.editor.on('guttermousedown', e => {
|
||||
const target = e.domEvent.target;
|
||||
if (target.className.indexOf('ace_gutter-cell') === -1)
|
||||
return;
|
||||
if (e.clientX > 25 + target.getBoundingClientRect().left)
|
||||
return;
|
||||
|
||||
const row = e.getDocumentPosition().row;
|
||||
const breakpoints = e.editor.session.getBreakpoints(row, 0);
|
||||
if (typeof breakpoints[row] === typeof undefined)
|
||||
e.editor.session.setBreakpoint(row);
|
||||
else
|
||||
e.editor.session.clearBreakpoint(row);
|
||||
e.stop();
|
||||
});
|
||||
|
||||
if (this.autoFocus) {
|
||||
setTimeout(() => {
|
||||
this.editor.focus();
|
||||
this.editor.resize();
|
||||
}, 20);
|
||||
editor.value.completers = customCompleter.value;
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(editor.value.session as any).on('change', () => {
|
||||
const content = editor.value.getValue();
|
||||
emit('update:modelValue', content);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(editor.value as any).on('guttermousedown', (e: any) => {
|
||||
const target = e.domEvent.target;
|
||||
if (target.className.indexOf('ace_gutter-cell') === -1)
|
||||
return;
|
||||
if (e.clientX > 25 + target.getBoundingClientRect().left)
|
||||
return;
|
||||
|
||||
const row = e.getDocumentPosition().row;
|
||||
const breakpoints = e.editor.value.session.getBreakpoints(row, 0);
|
||||
if (typeof breakpoints[row] === typeof undefined)
|
||||
e.editor.value.session.setBreakpoint(row);
|
||||
else
|
||||
e.editor.value.session.clearBreakpoint(row);
|
||||
e.stop();
|
||||
});
|
||||
|
||||
if (props.autoFocus) {
|
||||
setTimeout(() => {
|
||||
this.editor.resize();
|
||||
editor.value.focus();
|
||||
editor.value.resize();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
setCustomCompleter () {
|
||||
this.editor.completers.push({
|
||||
getCompletions: (editor, session, pos, prefix, callback) => {
|
||||
const completions = [];
|
||||
[
|
||||
...this.tables,
|
||||
...this.triggers,
|
||||
...this.procedures,
|
||||
...this.functions,
|
||||
...this.schedulers
|
||||
].forEach(el => {
|
||||
completions.push({
|
||||
value: el.name,
|
||||
meta: el.type
|
||||
});
|
||||
});
|
||||
callback(null, completions);
|
||||
}
|
||||
});
|
||||
|
||||
this.customCompleter = this.editor.completers;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
editor.value.resize();
|
||||
}, 20);
|
||||
});
|
||||
|
||||
defineExpose({ editor });
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -29,83 +29,66 @@
|
||||
</BaseContextMenu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { computed, Prop, ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import { useConnectionsStore } from '@/stores/connections';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||
import { ConnectionParams } from 'common/interfaces/antares';
|
||||
|
||||
export default {
|
||||
name: 'SettingBarContext',
|
||||
components: {
|
||||
BaseContextMenu,
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
contextEvent: MouseEvent,
|
||||
contextConnection: Object
|
||||
},
|
||||
emits: ['close-context'],
|
||||
setup () {
|
||||
const {
|
||||
getConnectionName,
|
||||
addConnection,
|
||||
deleteConnection
|
||||
} = useConnectionsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const {
|
||||
getConnectionName,
|
||||
addConnection,
|
||||
deleteConnection
|
||||
} = useConnectionsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
const { selectWorkspace } = workspacesStore;
|
||||
const { selectWorkspace } = workspacesStore;
|
||||
|
||||
return {
|
||||
getConnectionName,
|
||||
addConnection,
|
||||
deleteConnection,
|
||||
selectedWorkspace,
|
||||
selectWorkspace
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isConfirmModal: false,
|
||||
isEditModal: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
connectionName () {
|
||||
return this.getConnectionName(this.contextConnection.uid);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
confirmDeleteConnection () {
|
||||
if (this.selectedWorkspace === this.contextConnection.uid)
|
||||
this.selectWorkspace();
|
||||
this.deleteConnection(this.contextConnection);
|
||||
this.closeContext();
|
||||
},
|
||||
duplicateConnection () {
|
||||
let connectionCopy = Object.assign({}, this.contextConnection);
|
||||
connectionCopy = {
|
||||
...connectionCopy,
|
||||
uid: uidGen('C'),
|
||||
name: connectionCopy.name ? `${connectionCopy?.name}_copy` : ''
|
||||
};
|
||||
const props = defineProps({
|
||||
contextEvent: MouseEvent,
|
||||
contextConnection: Object as Prop<ConnectionParams>
|
||||
});
|
||||
|
||||
this.addConnection(connectionCopy);
|
||||
this.closeContext();
|
||||
},
|
||||
showConfirmModal () {
|
||||
this.isConfirmModal = true;
|
||||
},
|
||||
hideConfirmModal () {
|
||||
this.isConfirmModal = false;
|
||||
this.closeContext();
|
||||
},
|
||||
closeContext () {
|
||||
this.$emit('close-context');
|
||||
}
|
||||
}
|
||||
const emit = defineEmits(['close-context']);
|
||||
|
||||
const isConfirmModal = ref(false);
|
||||
|
||||
const connectionName = computed(() => getConnectionName(props.contextConnection.uid));
|
||||
|
||||
const confirmDeleteConnection = () => {
|
||||
if (selectedWorkspace.value === props.contextConnection.uid)
|
||||
selectWorkspace(null);
|
||||
deleteConnection(props.contextConnection);
|
||||
closeContext();
|
||||
};
|
||||
|
||||
const duplicateConnection = () => {
|
||||
let connectionCopy = Object.assign({}, props.contextConnection);
|
||||
connectionCopy = {
|
||||
...connectionCopy,
|
||||
uid: uidGen('C'),
|
||||
name: connectionCopy.name ? `${connectionCopy?.name}_copy` : ''
|
||||
};
|
||||
|
||||
addConnection(connectionCopy);
|
||||
closeContext();
|
||||
};
|
||||
|
||||
const showConfirmModal = () => {
|
||||
isConfirmModal.value = true;
|
||||
};
|
||||
|
||||
const hideConfirmModal = () => {
|
||||
isConfirmModal.value = false;
|
||||
closeContext();
|
||||
};
|
||||
|
||||
const closeContext = () => {
|
||||
emit('close-context');
|
||||
};
|
||||
</script>
|
||||
|
@ -26,46 +26,39 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { shell } from 'electron';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useApplicationStore } from '@/stores/application';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import { storeToRefs } from 'pinia';
|
||||
const { shell } = require('electron');
|
||||
import { computed, ComputedRef } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'TheFooter',
|
||||
setup () {
|
||||
const applicationStore = useApplicationStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
interface DatabaseInfos {// TODO: temp
|
||||
name: string;
|
||||
number: string;
|
||||
arch: string;
|
||||
os: string;
|
||||
}
|
||||
|
||||
const { getSelected: workspace } = storeToRefs(workspacesStore);
|
||||
const applicationStore = useApplicationStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
const { appVersion, showSettingModal } = applicationStore;
|
||||
const { getWorkspace } = workspacesStore;
|
||||
const { getSelected: workspace } = storeToRefs(workspacesStore);
|
||||
|
||||
return {
|
||||
appVersion,
|
||||
showSettingModal,
|
||||
workspace,
|
||||
getWorkspace
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
version () {
|
||||
return this.getWorkspace(this.workspace) ? this.getWorkspace(this.workspace).version : null;
|
||||
},
|
||||
versionString () {
|
||||
if (this.version)
|
||||
return `${this.version.name} ${this.version.number} (${this.version.arch} ${this.version.os})`;
|
||||
return '';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openOutside (link) {
|
||||
shell.openExternal(link);
|
||||
}
|
||||
}
|
||||
};
|
||||
const { showSettingModal } = applicationStore;
|
||||
const { getWorkspace } = workspacesStore;
|
||||
|
||||
const version: ComputedRef<DatabaseInfos> = computed(() => {
|
||||
return getWorkspace(workspace.value) ? getWorkspace(workspace.value).version : null;
|
||||
});
|
||||
|
||||
const versionString = computed(() => {
|
||||
if (version.value)
|
||||
return `${version.value.name} ${version.value.number} (${version.value.arch} ${version.value.os})`;
|
||||
return '';
|
||||
});
|
||||
|
||||
const openOutside = (link: string) => shell.openExternal(link);
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -16,71 +16,51 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { computed, Ref, ref, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import BaseNotification from '@/components/BaseNotification';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import BaseNotification from '@/components/BaseNotification.vue';
|
||||
|
||||
export default {
|
||||
name: 'TheNotificationsBoard',
|
||||
components: {
|
||||
BaseNotification
|
||||
},
|
||||
setup () {
|
||||
const notificationsStore = useNotificationsStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const notificationsStore = useNotificationsStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const { removeNotification } = notificationsStore;
|
||||
const { removeNotification } = notificationsStore;
|
||||
|
||||
const { notifications } = storeToRefs(notificationsStore);
|
||||
const { notificationsTimeout } = storeToRefs(settingsStore);
|
||||
const { notifications } = storeToRefs(notificationsStore);
|
||||
const { notificationsTimeout } = storeToRefs(settingsStore);
|
||||
|
||||
return {
|
||||
removeNotification,
|
||||
notifications,
|
||||
notificationsTimeout
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
timeouts: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
latestNotifications () {
|
||||
return this.notifications.slice(0, 10);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'notifications.length': function (val) {
|
||||
if (val > 0) {
|
||||
const nUid = this.notifications[0].uid;
|
||||
this.timeouts[nUid] = setTimeout(() => {
|
||||
this.removeNotification(nUid);
|
||||
delete this.timeouts[nUid];
|
||||
}, this.notificationsTimeout * 1000);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clearTimeouts () {
|
||||
for (const uid in this.timeouts) {
|
||||
clearTimeout(this.timeouts[uid]);
|
||||
delete this.timeouts[uid];
|
||||
}
|
||||
},
|
||||
rearmTimeouts () {
|
||||
const delay = 50;
|
||||
let i = this.notifications.length * delay;
|
||||
for (const notification of this.notifications) {
|
||||
this.timeouts[notification.uid] = setTimeout(() => {
|
||||
this.removeNotification(notification.uid);
|
||||
delete this.timeouts[notification.uid];
|
||||
}, (this.notificationsTimeout * 1000) + i);
|
||||
i = i > delay ? i - delay : 0;
|
||||
}
|
||||
}
|
||||
const timeouts: Ref<{[key: string]: NodeJS.Timeout}> = ref({});
|
||||
|
||||
const latestNotifications = computed(() => notifications.value.slice(0, 10));
|
||||
|
||||
watch(() => notifications.value.length, (val) => {
|
||||
if (val > 0) {
|
||||
const nUid: string = notifications.value[0].uid;
|
||||
timeouts.value[nUid] = setTimeout(() => {
|
||||
removeNotification(nUid);
|
||||
delete timeouts.value[nUid];
|
||||
}, notificationsTimeout.value * 1000);
|
||||
}
|
||||
});
|
||||
|
||||
const clearTimeouts = () => {
|
||||
for (const uid in timeouts.value) {
|
||||
clearTimeout(timeouts.value[uid]);
|
||||
delete timeouts.value[uid];
|
||||
}
|
||||
};
|
||||
|
||||
const rearmTimeouts = () => {
|
||||
const delay = 50;
|
||||
let i = notifications.value.length * delay;
|
||||
for (const notification of notifications.value) {
|
||||
timeouts.value[notification.uid] = setTimeout(() => {
|
||||
removeNotification(notification.uid);
|
||||
delete timeouts.value[notification.uid];
|
||||
}, (notificationsTimeout.value * 1000) + i);
|
||||
i = i > delay ? i - delay : 0;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -28,55 +28,30 @@
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { ref, Ref, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useApplicationStore } from '@/stores/application';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import TextEditor from '@/components/BaseTextEditor';
|
||||
import { useScratchpadStore } from '@/stores/scratchpad';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||
import TextEditor from '@/components/BaseTextEditor.vue';
|
||||
|
||||
export default {
|
||||
name: 'TheScratchpad',
|
||||
components: {
|
||||
ConfirmModal,
|
||||
TextEditor
|
||||
},
|
||||
emits: ['hide'],
|
||||
setup () {
|
||||
const applicationStore = useApplicationStore();
|
||||
const scratchpadStore = useScratchpadStore();
|
||||
const applicationStore = useApplicationStore();
|
||||
const scratchpadStore = useScratchpadStore();
|
||||
|
||||
const { notes } = storeToRefs(scratchpadStore);
|
||||
const { changeNotes } = scratchpadStore;
|
||||
const { notes } = storeToRefs(scratchpadStore);
|
||||
const { changeNotes } = scratchpadStore;
|
||||
const { hideScratchpad } = applicationStore;
|
||||
|
||||
return {
|
||||
notes,
|
||||
hideScratchpad: applicationStore.hideScratchpad,
|
||||
changeNotes
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localNotes: '',
|
||||
debounceTimeout: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
localNotes () {
|
||||
clearTimeout(this.debounceTimeout);
|
||||
const localNotes = ref(notes.value);
|
||||
const debounceTimeout: Ref<NodeJS.Timeout> = ref(null);
|
||||
|
||||
watch(localNotes, () => {
|
||||
clearTimeout(debounceTimeout.value);
|
||||
|
||||
debounceTimeout.value = setTimeout(() => {
|
||||
changeNotes(localNotes.value);
|
||||
}, 200);
|
||||
});
|
||||
|
||||
this.debounceTimeout = setTimeout(() => {
|
||||
this.changeNotes(this.localNotes);
|
||||
}, 200);
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.localNotes = this.notes;
|
||||
},
|
||||
methods: {
|
||||
hideModal () {
|
||||
this.$emit('hide');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -55,111 +55,84 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { ref, Ref, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useApplicationStore } from '@/stores/application';
|
||||
import { useConnectionsStore } from '@/stores/connections';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import Draggable from 'vuedraggable';
|
||||
import SettingBarContext from '@/components/SettingBarContext';
|
||||
import * as Draggable from 'vuedraggable';
|
||||
import SettingBarContext from '@/components/SettingBarContext.vue';
|
||||
import { ConnectionParams } from 'common/interfaces/antares';
|
||||
|
||||
export default {
|
||||
name: 'TheSettingBar',
|
||||
components: {
|
||||
Draggable,
|
||||
SettingBarContext
|
||||
const applicationStore = useApplicationStore();
|
||||
const connectionsStore = useConnectionsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
const { updateStatus } = storeToRefs(applicationStore);
|
||||
const { connections: getConnections } = storeToRefs(connectionsStore);
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
const { showSettingModal, showScratchpad } = applicationStore;
|
||||
const { getConnectionName, updateConnections } = connectionsStore;
|
||||
const { getWorkspace, selectWorkspace } = workspacesStore;
|
||||
|
||||
const isLinux = process.platform === 'linux';
|
||||
const isContext: Ref<boolean> = ref(false);
|
||||
const isDragging: Ref<boolean> = ref(false);
|
||||
const contextEvent: Ref<MouseEvent> = ref(null);
|
||||
const contextConnection: Ref<ConnectionParams> = ref(null);
|
||||
|
||||
const connections = computed({
|
||||
get () {
|
||||
return getConnections.value;
|
||||
},
|
||||
setup () {
|
||||
const applicationStore = useApplicationStore();
|
||||
const connectionsStore = useConnectionsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
set (value: ConnectionParams[]) {
|
||||
updateConnections(value);
|
||||
}
|
||||
});
|
||||
|
||||
const { updateStatus } = storeToRefs(applicationStore);
|
||||
const { connections: getConnections } = storeToRefs(connectionsStore);
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const hasUpdates = computed(() => ['available', 'downloading', 'downloaded', 'link'].includes(updateStatus.value));
|
||||
|
||||
const { showSettingModal, showScratchpad } = applicationStore;
|
||||
const { getConnectionName, updateConnections } = connectionsStore;
|
||||
const { getWorkspace, selectWorkspace } = workspacesStore;
|
||||
const contextMenu = (event: MouseEvent, connection: ConnectionParams) => {
|
||||
contextEvent.value = event;
|
||||
contextConnection.value = connection;
|
||||
isContext.value = true;
|
||||
};
|
||||
|
||||
return {
|
||||
applicationStore,
|
||||
updateStatus,
|
||||
showSettingModal,
|
||||
showScratchpad,
|
||||
getConnections,
|
||||
getConnectionName,
|
||||
updateConnections,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
selectWorkspace
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
dragElement: null,
|
||||
isLinux: process.platform === 'linux',
|
||||
isContext: false,
|
||||
isDragging: false,
|
||||
contextEvent: null,
|
||||
contextConnection: {},
|
||||
scale: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
connections: {
|
||||
get () {
|
||||
return this.getConnections;
|
||||
},
|
||||
set (value) {
|
||||
this.updateConnections(value);
|
||||
}
|
||||
},
|
||||
hasUpdates () {
|
||||
return ['available', 'downloading', 'downloaded', 'link'].includes(this.updateStatus);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
contextMenu (event, connection) {
|
||||
this.contextEvent = event;
|
||||
this.contextConnection = connection;
|
||||
this.isContext = true;
|
||||
},
|
||||
workspaceName (connection) {
|
||||
return connection.ask ? '' : `${connection.user + '@'}${connection.host}:${connection.port}`;
|
||||
},
|
||||
tooltipPosition (e) {
|
||||
const el = e.target ? e.target : e;
|
||||
const fromTop = this.isLinux
|
||||
? window.scrollY + el.getBoundingClientRect().top + (el.offsetHeight / 4)
|
||||
: window.scrollY + el.getBoundingClientRect().top - (el.offsetHeight / 4)
|
||||
el.querySelector('.ex-tooltip-content').style.top = `${fromTop}px`;
|
||||
},
|
||||
getStatusBadge (uid) {
|
||||
if (this.getWorkspace(uid)) {
|
||||
const status = this.getWorkspace(uid).connectionStatus;
|
||||
const tooltipPosition = (e: Event) => {
|
||||
const el = e.target ? e.target : e;
|
||||
const fromTop = isLinux
|
||||
? window.scrollY + (el as HTMLElement).getBoundingClientRect().top + ((el as HTMLElement).offsetHeight / 4)
|
||||
: window.scrollY + (el as HTMLElement).getBoundingClientRect().top - ((el as HTMLElement).offsetHeight / 4);
|
||||
(el as HTMLElement).querySelector<HTMLElement>('.ex-tooltip-content').style.top = `${fromTop}px`;
|
||||
};
|
||||
|
||||
switch (status) {
|
||||
case 'connected':
|
||||
return 'badge badge-connected';
|
||||
case 'connecting':
|
||||
return 'badge badge-connecting';
|
||||
case 'failed':
|
||||
return 'badge badge-failed';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
dragStop (e) {
|
||||
this.isDragging = false;
|
||||
const getStatusBadge = (uid: string) => {
|
||||
if (getWorkspace(uid)) {
|
||||
const status = getWorkspace(uid).connectionStatus;
|
||||
|
||||
setTimeout(() => {
|
||||
this.tooltipPosition(e.originalEvent.target.parentNode);
|
||||
}, 200);
|
||||
switch (status) {
|
||||
case 'connected':
|
||||
return 'badge badge-connected';
|
||||
case 'connecting':
|
||||
return 'badge badge-connecting';
|
||||
case 'failed':
|
||||
return 'badge badge-failed';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const dragStop = (e: any) => { // TODO: temp
|
||||
isDragging.value = false;
|
||||
|
||||
setTimeout(() => {
|
||||
tooltipPosition(e.originalEvent.target.parentNode);
|
||||
}, 200);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -9,7 +9,7 @@
|
||||
<img
|
||||
v-if="!isMacOS"
|
||||
class="titlebar-logo"
|
||||
src="@/images/logo.svg"
|
||||
:src="appIcon"
|
||||
>
|
||||
</div>
|
||||
<div class="titlebar-elements titlebar-title">
|
||||
@ -31,112 +31,70 @@
|
||||
<i class="mdi mdi-24px mdi-refresh" />
|
||||
</div>
|
||||
<div v-if="isWindows" style="width: 140px;" />
|
||||
<!-- <div
|
||||
v-if="isLinux"
|
||||
class="titlebar-element"
|
||||
@click="minimizeApp"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-minus" />
|
||||
</div>
|
||||
<div
|
||||
v-if="isLinux"
|
||||
class="titlebar-element"
|
||||
@click="toggleFullScreen"
|
||||
>
|
||||
<i v-if="isMaximized" class="mdi mdi-24px mdi-fullscreen-exit" />
|
||||
<i v-else class="mdi mdi-24px mdi-fullscreen" />
|
||||
</div>
|
||||
<div
|
||||
v-if="isLinux"
|
||||
class="titlebar-element close-button"
|
||||
@click="closeApp"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-close" />
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ipcRenderer } from 'electron';
|
||||
<script setup lang="ts">
|
||||
import { computed, onUnmounted, ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getCurrentWindow } from '@electron/remote';
|
||||
import { useConnectionsStore } from '@/stores/connections';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
export default {
|
||||
name: 'TheTitleBar',
|
||||
setup () {
|
||||
const { getConnectionName } = useConnectionsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const { getConnectionName } = useConnectionsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
const { getWorkspace } = workspacesStore;
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
return {
|
||||
getConnectionName,
|
||||
selectedWorkspace,
|
||||
getWorkspace
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
w: getCurrentWindow(),
|
||||
isMaximized: getCurrentWindow().isMaximized(),
|
||||
isDevelopment: process.env.NODE_ENV === 'development',
|
||||
isMacOS: process.platform === 'darwin',
|
||||
isWindows: process.platform === 'win32',
|
||||
isLinux: process.platform === 'linux'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
windowTitle () {
|
||||
if (!this.selectedWorkspace) return '';
|
||||
if (this.selectedWorkspace === 'NEW') return this.$t('message.createNewConnection');
|
||||
const { getWorkspace } = workspacesStore;
|
||||
|
||||
const connectionName = this.getConnectionName(this.selectedWorkspace);
|
||||
const workspace = this.getWorkspace(this.selectedWorkspace);
|
||||
const breadcrumbs = Object.values(workspace.breadcrumbs).filter(breadcrumb => breadcrumb) || [workspace.client];
|
||||
const appIcon = require('@/images/logo.svg');
|
||||
const w = ref(getCurrentWindow());
|
||||
const isMaximized = ref(getCurrentWindow().isMaximized());
|
||||
const isDevelopment = ref(process.env.NODE_ENV === 'development');
|
||||
const isMacOS = process.platform === 'darwin';
|
||||
const isWindows = process.platform === 'win32';
|
||||
const isLinux = process.platform === 'linux';
|
||||
|
||||
return [connectionName, ...breadcrumbs].join(' • ');
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
windowTitle: function (val) {
|
||||
ipcRenderer.send('change-window-title', val);
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('resize', this.onResize);
|
||||
},
|
||||
unmounted () {
|
||||
window.removeEventListener('resize', this.onResize);
|
||||
},
|
||||
methods: {
|
||||
closeApp () {
|
||||
ipcRenderer.send('close-app');
|
||||
},
|
||||
minimizeApp () {
|
||||
this.w.minimize();
|
||||
},
|
||||
toggleFullScreen () {
|
||||
if (this.isMaximized)
|
||||
this.w.unmaximize();
|
||||
else
|
||||
this.w.maximize();
|
||||
},
|
||||
openDevTools () {
|
||||
this.w.openDevTools();
|
||||
},
|
||||
reload () {
|
||||
this.w.reload();
|
||||
},
|
||||
onResize () {
|
||||
this.isMaximized = this.w.isMaximized();
|
||||
}
|
||||
}
|
||||
const windowTitle = computed(() => {
|
||||
if (!selectedWorkspace.value) return '';
|
||||
if (selectedWorkspace.value === 'NEW') return t('message.createNewConnection');
|
||||
|
||||
const connectionName = getConnectionName(selectedWorkspace.value);
|
||||
const workspace = getWorkspace(selectedWorkspace.value);
|
||||
const breadcrumbs = Object.values(workspace.breadcrumbs).filter(breadcrumb => breadcrumb) || [workspace.client];
|
||||
|
||||
return [connectionName, ...breadcrumbs].join(' • ');
|
||||
});
|
||||
|
||||
const toggleFullScreen = () => {
|
||||
if (isMaximized.value)
|
||||
w.value.unmaximize();
|
||||
else
|
||||
w.value.maximize();
|
||||
};
|
||||
|
||||
const openDevTools = () => {
|
||||
w.value.webContents.openDevTools();
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
w.value.reload();
|
||||
};
|
||||
|
||||
const onResize = () => {
|
||||
isMaximized.value = w.value.isMaximized();
|
||||
};
|
||||
|
||||
window.addEventListener('resize', onResize);
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', onResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -469,266 +469,196 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, Prop, ref, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import Draggable from 'vuedraggable';
|
||||
import * as Draggable from 'vuedraggable';
|
||||
import Connection from '@/ipc-api/Connection';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import { useWorkspacesStore, WorkspaceTab } from '@/stores/workspaces';
|
||||
import { ConnectionParams } from 'common/interfaces/antares';
|
||||
|
||||
import WorkspaceEmptyState from '@/components/WorkspaceEmptyState';
|
||||
import WorkspaceExploreBar from '@/components/WorkspaceExploreBar';
|
||||
import WorkspaceEditConnectionPanel from '@/components/WorkspaceEditConnectionPanel';
|
||||
import WorkspaceTabQuery from '@/components/WorkspaceTabQuery';
|
||||
import WorkspaceTabTable from '@/components/WorkspaceTabTable';
|
||||
import WorkspaceEmptyState from '@/components/WorkspaceEmptyState.vue';
|
||||
import WorkspaceExploreBar from '@/components/WorkspaceExploreBar.vue';
|
||||
import WorkspaceEditConnectionPanel from '@/components/WorkspaceEditConnectionPanel.vue';
|
||||
import WorkspaceTabQuery from '@/components/WorkspaceTabQuery.vue';
|
||||
import WorkspaceTabTable from '@/components/WorkspaceTabTable.vue';
|
||||
|
||||
import WorkspaceTabNewTable from '@/components/WorkspaceTabNewTable';
|
||||
import WorkspaceTabNewView from '@/components/WorkspaceTabNewView';
|
||||
import WorkspaceTabNewTrigger from '@/components/WorkspaceTabNewTrigger';
|
||||
import WorkspaceTabNewRoutine from '@/components/WorkspaceTabNewRoutine';
|
||||
import WorkspaceTabNewFunction from '@/components/WorkspaceTabNewFunction';
|
||||
import WorkspaceTabNewScheduler from '@/components/WorkspaceTabNewScheduler';
|
||||
import WorkspaceTabNewTriggerFunction from '@/components/WorkspaceTabNewTriggerFunction';
|
||||
import WorkspaceTabNewTable from '@/components/WorkspaceTabNewTable.vue';
|
||||
import WorkspaceTabNewView from '@/components/WorkspaceTabNewView.vue';
|
||||
import WorkspaceTabNewTrigger from '@/components/WorkspaceTabNewTrigger.vue';
|
||||
import WorkspaceTabNewRoutine from '@/components/WorkspaceTabNewRoutine.vue';
|
||||
import WorkspaceTabNewFunction from '@/components/WorkspaceTabNewFunction.vue';
|
||||
import WorkspaceTabNewScheduler from '@/components/WorkspaceTabNewScheduler.vue';
|
||||
import WorkspaceTabNewTriggerFunction from '@/components/WorkspaceTabNewTriggerFunction.vue';
|
||||
|
||||
import WorkspaceTabPropsTable from '@/components/WorkspaceTabPropsTable';
|
||||
import WorkspaceTabPropsView from '@/components/WorkspaceTabPropsView';
|
||||
import WorkspaceTabPropsTrigger from '@/components/WorkspaceTabPropsTrigger';
|
||||
import WorkspaceTabPropsTriggerFunction from '@/components/WorkspaceTabPropsTriggerFunction';
|
||||
import WorkspaceTabPropsRoutine from '@/components/WorkspaceTabPropsRoutine';
|
||||
import WorkspaceTabPropsFunction from '@/components/WorkspaceTabPropsFunction';
|
||||
import WorkspaceTabPropsScheduler from '@/components/WorkspaceTabPropsScheduler';
|
||||
import ModalProcessesList from '@/components/ModalProcessesList';
|
||||
import ModalDiscardChanges from '@/components/ModalDiscardChanges';
|
||||
import WorkspaceTabPropsTable from '@/components/WorkspaceTabPropsTable.vue';
|
||||
import WorkspaceTabPropsView from '@/components/WorkspaceTabPropsView.vue';
|
||||
import WorkspaceTabPropsTrigger from '@/components/WorkspaceTabPropsTrigger.vue';
|
||||
import WorkspaceTabPropsTriggerFunction from '@/components/WorkspaceTabPropsTriggerFunction.vue';
|
||||
import WorkspaceTabPropsRoutine from '@/components/WorkspaceTabPropsRoutine.vue';
|
||||
import WorkspaceTabPropsFunction from '@/components/WorkspaceTabPropsFunction.vue';
|
||||
import WorkspaceTabPropsScheduler from '@/components/WorkspaceTabPropsScheduler.vue';
|
||||
import ModalProcessesList from '@/components/ModalProcessesList.vue';
|
||||
import ModalDiscardChanges from '@/components/ModalDiscardChanges.vue';
|
||||
|
||||
export default {
|
||||
name: 'Workspace',
|
||||
components: {
|
||||
Draggable,
|
||||
WorkspaceEmptyState,
|
||||
WorkspaceExploreBar,
|
||||
WorkspaceEditConnectionPanel,
|
||||
WorkspaceTabQuery,
|
||||
WorkspaceTabTable,
|
||||
WorkspaceTabNewTable,
|
||||
WorkspaceTabPropsTable,
|
||||
WorkspaceTabNewView,
|
||||
WorkspaceTabPropsView,
|
||||
WorkspaceTabNewTrigger,
|
||||
WorkspaceTabPropsTrigger,
|
||||
WorkspaceTabNewTriggerFunction,
|
||||
WorkspaceTabPropsTriggerFunction,
|
||||
WorkspaceTabNewRoutine,
|
||||
WorkspaceTabNewFunction,
|
||||
WorkspaceTabPropsRoutine,
|
||||
WorkspaceTabPropsFunction,
|
||||
WorkspaceTabNewScheduler,
|
||||
WorkspaceTabPropsScheduler,
|
||||
ModalProcessesList,
|
||||
ModalDiscardChanges
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
const {
|
||||
getWorkspace,
|
||||
addWorkspace,
|
||||
connectWorkspace,
|
||||
selectTab,
|
||||
newTab,
|
||||
removeTab,
|
||||
updateTabs
|
||||
} = workspacesStore;
|
||||
|
||||
const props = defineProps({
|
||||
connection: Object as Prop<ConnectionParams>
|
||||
});
|
||||
|
||||
const hasWheelEvent = ref(false);
|
||||
const isProcessesModal = ref(false);
|
||||
const unsavedTab = ref(null);
|
||||
const tabWrap = ref(null);
|
||||
|
||||
const workspace = computed(() => getWorkspace(props.connection.uid));
|
||||
|
||||
const draggableTabs = computed<WorkspaceTab[]>({
|
||||
get () {
|
||||
return workspace.value.tabs;
|
||||
},
|
||||
props: {
|
||||
connection: Object
|
||||
},
|
||||
setup () {
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
set (val) {
|
||||
updateTabs({ uid: props.connection.uid, tabs: val });
|
||||
}
|
||||
});
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const isSelected = computed(() => {
|
||||
return selectedWorkspace.value === props.connection.uid;
|
||||
});
|
||||
|
||||
const {
|
||||
getWorkspace,
|
||||
addWorkspace,
|
||||
connectWorkspace,
|
||||
removeConnected,
|
||||
selectTab,
|
||||
newTab,
|
||||
removeTab,
|
||||
updateTabs,
|
||||
selectNextTab,
|
||||
selectPrevTab
|
||||
} = workspacesStore;
|
||||
const selectedTab = computed(() => {
|
||||
return workspace.value ? workspace.value.selectedTab : null;
|
||||
});
|
||||
|
||||
return {
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
addWorkspace,
|
||||
connectWorkspace,
|
||||
removeConnected,
|
||||
selectTab,
|
||||
newTab,
|
||||
removeTab,
|
||||
updateTabs,
|
||||
selectNextTab,
|
||||
selectPrevTab
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
hasWheelEvent: false,
|
||||
isProcessesModal: false,
|
||||
unsavedTab: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
draggableTabs: {
|
||||
get () {
|
||||
return this.workspace.tabs;
|
||||
},
|
||||
set (val) {
|
||||
this.updateTabs({ uid: this.connection.uid, tabs: val });
|
||||
}
|
||||
},
|
||||
isSelected () {
|
||||
return this.selectedWorkspace === this.connection.uid;
|
||||
},
|
||||
isSettingSupported () {
|
||||
if (this.workspace.breadcrumbs.table && this.workspace.customizations.tableSettings) return true;
|
||||
if (this.workspace.breadcrumbs.view && this.workspace.customizations.viewSettings) return true;
|
||||
if (this.workspace.breadcrumbs.trigger && this.workspace.customizations.triggerSettings) return true;
|
||||
if (this.workspace.breadcrumbs.procedure && this.workspace.customizations.routineSettings) return true;
|
||||
if (this.workspace.breadcrumbs.function && this.workspace.customizations.functionSettings) return true;
|
||||
if (this.workspace.breadcrumbs.triggerFunction && this.workspace.customizations.functionSettings) return true;
|
||||
if (this.workspace.breadcrumbs.scheduler && this.workspace.customizations.schedulerSettings) return true;
|
||||
return false;
|
||||
},
|
||||
selectedTab () {
|
||||
return this.workspace ? this.workspace.selectedTab : null;
|
||||
},
|
||||
queryTabs () {
|
||||
return this.workspace ? this.workspace.tabs.filter(tab => tab.type === 'query') : [];
|
||||
},
|
||||
schemaChild () {
|
||||
for (const key in this.workspace.breadcrumbs) {
|
||||
if (key === 'schema') continue;
|
||||
if (this.workspace.breadcrumbs[key]) return this.workspace.breadcrumbs[key];
|
||||
}
|
||||
return false;
|
||||
},
|
||||
hasTools () {
|
||||
if (!this.workspace.customizations) return false;
|
||||
else {
|
||||
return this.workspace.customizations.processesList ||
|
||||
this.workspace.customizations.usersManagement ||
|
||||
this.workspace.customizations.variables;
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
queryTabs: {
|
||||
handler (newVal, oldVal) {
|
||||
if (newVal.length > oldVal.length) {
|
||||
setTimeout(() => {
|
||||
const scroller = this.$refs.tabWrap;
|
||||
if (scroller) scroller.$el.scrollLeft = scroller.$el.scrollWidth;
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
await this.addWorkspace(this.connection.uid);
|
||||
const isInitiated = await Connection.checkConnection(this.connection.uid);
|
||||
if (isInitiated)
|
||||
this.connectWorkspace(this.connection);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
addQueryTab () {
|
||||
this.newTab({ uid: this.connection.uid, type: 'query' });
|
||||
},
|
||||
getSelectedTab () {
|
||||
return this.workspace.tabs.find(tab => tab.uid === this.selectedTab);
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
const queryTabs = computed(() => {
|
||||
return workspace.value ? workspace.value.tabs.filter(tab => tab.type === 'query') : [];
|
||||
});
|
||||
|
||||
if (!this.isSelected)
|
||||
return;
|
||||
const hasTools = computed(() => {
|
||||
if (!workspace.value.customizations) return false;
|
||||
else {
|
||||
return workspace.value.customizations.processesList ||
|
||||
workspace.value.customizations.usersManagement ||
|
||||
workspace.value.customizations.variables;
|
||||
}
|
||||
});
|
||||
|
||||
if ((e.ctrlKey || e.metaKey) && e.keyCode === 84 && !e.altKey) { // CTRL|Command + t
|
||||
this.addQueryTab();
|
||||
}
|
||||
watch(queryTabs, (newVal, oldVal) => {
|
||||
if (newVal.length > oldVal.length) {
|
||||
setTimeout(() => {
|
||||
const scroller = tabWrap.value;
|
||||
if (scroller) scroller.$el.scrollLeft = scroller.$el.scrollWidth;
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
|
||||
if ((e.ctrlKey || e.metaKey) && e.keyCode === 87 && !e.altKey) { // CTRL|Command + w
|
||||
const currentTab = this.getSelectedTab();
|
||||
if (currentTab)
|
||||
this.closeTab(currentTab);
|
||||
}
|
||||
const addQueryTab = () => {
|
||||
newTab({ uid: props.connection.uid, type: 'query' });
|
||||
};
|
||||
|
||||
// select next tab
|
||||
if (e.altKey && (e.ctrlKey || e.metaKey) && e.key === 'ArrowRight')
|
||||
this.selectNextTab({ uid: this.connection.uid });
|
||||
const getSelectedTab = () => {
|
||||
return workspace.value.tabs.find(tab => tab.uid === selectedTab.value);
|
||||
};
|
||||
|
||||
// select prev tab
|
||||
if (e.altKey && (e.ctrlKey || e.metaKey) && e.key === 'ArrowLeft')
|
||||
this.selectPrevTab({ uid: this.connection.uid });
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
|
||||
// select tab by index (range 1-9). CTRL|CMD number
|
||||
if ((e.ctrlKey || e.metaKey) && !e.altKey && e.keyCode >= 49 && e.keyCode <= 57) {
|
||||
const newIndex = parseInt(e.key) - 1;
|
||||
if (!isSelected.value)
|
||||
return;
|
||||
|
||||
if (this.workspace.tabs[newIndex])
|
||||
this.selectTab({ uid: this.connection.uid, tab: this.workspace.tabs[newIndex].uid });
|
||||
}
|
||||
},
|
||||
openAsPermanentTab (tab) {
|
||||
const permanentTabs = {
|
||||
table: 'data',
|
||||
view: 'data',
|
||||
trigger: 'trigger-props',
|
||||
triggerFunction: 'trigger-function-props',
|
||||
function: 'function-props',
|
||||
routine: 'routine-props',
|
||||
scheduler: 'scheduler-props'
|
||||
};
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 't' && !e.altKey) { // CTRL|Command + t
|
||||
addQueryTab();
|
||||
}
|
||||
|
||||
this.newTab({
|
||||
uid: this.connection.uid,
|
||||
schema: tab.schema,
|
||||
elementName: tab.elementName,
|
||||
type: permanentTabs[tab.elementType],
|
||||
elementType: tab.elementType
|
||||
});
|
||||
},
|
||||
closeTab (tab, force) {
|
||||
this.unsavedTab = null;
|
||||
// if (tab.type === 'query' && this.queryTabs.length === 1) return;
|
||||
if (!force && tab.isChanged) {
|
||||
this.unsavedTab = tab;
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeTab({ uid: this.connection.uid, tab: tab.uid });
|
||||
},
|
||||
showProcessesModal () {
|
||||
this.isProcessesModal = true;
|
||||
},
|
||||
hideProcessesModal () {
|
||||
this.isProcessesModal = false;
|
||||
},
|
||||
addWheelEvent () {
|
||||
if (!this.hasWheelEvent) {
|
||||
this.$refs.tabWrap.$el.addEventListener('wheel', e => {
|
||||
if (e.deltaY > 0) this.$refs.tabWrap.$el.scrollLeft += 50;
|
||||
else this.$refs.tabWrap.$el.scrollLeft -= 50;
|
||||
});
|
||||
this.hasWheelEvent = true;
|
||||
}
|
||||
},
|
||||
cutText (string) {
|
||||
const limit = 20;
|
||||
const escapedString = string.replace(/\s{2,}/g, ' ');
|
||||
if (escapedString.length > limit)
|
||||
return `${escapedString.substr(0, limit)}...`;
|
||||
return escapedString;
|
||||
}
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'w' && !e.altKey) { // CTRL|Command + w
|
||||
const currentTab = getSelectedTab();
|
||||
if (currentTab)
|
||||
closeTab(currentTab);
|
||||
}
|
||||
};
|
||||
|
||||
const openAsPermanentTab = (tab: WorkspaceTab) => {
|
||||
const permanentTabs = {
|
||||
table: 'data',
|
||||
view: 'data',
|
||||
trigger: 'trigger-props',
|
||||
triggerFunction: 'trigger-function-props',
|
||||
function: 'function-props',
|
||||
routine: 'routine-props',
|
||||
procedure: 'routine-props',
|
||||
scheduler: 'scheduler-props'
|
||||
} as {[key: string]: string};
|
||||
|
||||
newTab({
|
||||
uid: props.connection.uid,
|
||||
schema: tab.schema,
|
||||
elementName: tab.elementName,
|
||||
type: permanentTabs[tab.elementType],
|
||||
elementType: tab.elementType
|
||||
});
|
||||
};
|
||||
|
||||
const closeTab = (tab: WorkspaceTab, force = false) => {
|
||||
unsavedTab.value = null;
|
||||
// if (tab.type === 'query' && this.queryTabs.length === 1) return;
|
||||
if (!force && tab.isChanged) {
|
||||
unsavedTab.value = tab;
|
||||
return;
|
||||
}
|
||||
|
||||
removeTab({ uid: props.connection.uid, tab: tab.uid });
|
||||
};
|
||||
|
||||
const showProcessesModal = () => {
|
||||
isProcessesModal.value = true;
|
||||
};
|
||||
|
||||
const hideProcessesModal = () => {
|
||||
isProcessesModal.value = false;
|
||||
};
|
||||
|
||||
const addWheelEvent = () => {
|
||||
if (!hasWheelEvent.value) {
|
||||
tabWrap.value.$el.addEventListener('wheel', (e: WheelEvent) => {
|
||||
if (e.deltaY > 0) tabWrap.value.$el.scrollLeft += 50;
|
||||
else tabWrap.value.$el.scrollLeft -= 50;
|
||||
});
|
||||
hasWheelEvent.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const cutText = (string: string) => {
|
||||
const limit = 20;
|
||||
const escapedString = string.replace(/\s{2,}/g, ' ');
|
||||
if (escapedString.length > limit)
|
||||
return `${escapedString.substr(0, limit)}...`;
|
||||
return escapedString;
|
||||
};
|
||||
|
||||
(async () => {
|
||||
window.addEventListener('keydown', onKey);
|
||||
await addWorkspace(props.connection.uid);
|
||||
const isInitiated = await Connection.checkConnection(props.connection.uid);
|
||||
if (isInitiated)
|
||||
connectWorkspace(props.connection);
|
||||
})();
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -8,23 +8,23 @@
|
||||
:class="{'active': selectedTab === 'general'}"
|
||||
@click="selectTab('general')"
|
||||
>
|
||||
<a class="tab-link">{{ $t('word.general') }}</a>
|
||||
<a class="tab-link">{{ t('word.general') }}</a>
|
||||
</li>
|
||||
<li
|
||||
v-if="customizations.sslConnection"
|
||||
v-if="clientCustomizations.sslConnection"
|
||||
class="tab-item c-hand"
|
||||
:class="{'active': selectedTab === 'ssl'}"
|
||||
@click="selectTab('ssl')"
|
||||
>
|
||||
<a class="tab-link">{{ $t('word.ssl') }}</a>
|
||||
<a class="tab-link">{{ t('word.ssl') }}</a>
|
||||
</li>
|
||||
<li
|
||||
v-if="customizations.sshConnection"
|
||||
v-if="clientCustomizations.sshConnection"
|
||||
class="tab-item c-hand"
|
||||
:class="{'active': selectedTab === 'ssh'}"
|
||||
@click="selectTab('ssh')"
|
||||
>
|
||||
<a class="tab-link">{{ $t('word.sshTunnel') }}</a>
|
||||
<a class="tab-link">{{ t('word.sshTunnel') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -34,7 +34,7 @@
|
||||
<fieldset class="m-0" :disabled="isBusy">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.connectionName') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.connectionName') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -47,7 +47,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.client') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.client') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<BaseSelect
|
||||
@ -61,7 +61,7 @@
|
||||
</div>
|
||||
<div v-if="connection.client === 'pg'" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.connectionString') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.connectionString') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -72,9 +72,9 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.hostName') }}/IP</label>
|
||||
<label class="form-label cut-text">{{ t('word.hostName') }}/IP</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -84,22 +84,22 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="customizations.fileConnection" class="form-group columns">
|
||||
<div v-if="clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.database') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.database') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="connection.databasePath"
|
||||
:message="$t('word.browse')"
|
||||
:message="t('word.browse')"
|
||||
@clear="pathClear('databasePath')"
|
||||
@change="pathSelection($event, 'databasePath')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.port') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.port') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -111,9 +111,9 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="customizations.database" class="form-group columns">
|
||||
<div v-if="clientCustomizations.database" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.database') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.database') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -123,9 +123,9 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.user') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.user') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -136,9 +136,9 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.password') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.password') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -149,32 +149,32 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="customizations.connectionSchema" class="form-group columns">
|
||||
<div v-if="clientCustomizations.connectionSchema" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.schema') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.schema') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
v-model="connection.schema"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:placeholder="$t('word.all')"
|
||||
:placeholder="t('word.all')"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="customizations.readOnlyMode" class="form-group columns">
|
||||
<div v-if="clientCustomizations.readOnlyMode" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12" />
|
||||
<div class="column col-8 col-sm-12">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="connection.readonly" type="checkbox"><i class="form-icon" /> {{ $t('message.readOnlyMode') }}
|
||||
<input v-model="connection.readonly" type="checkbox"><i class="form-icon" /> {{ t('message.readOnlyMode') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12" />
|
||||
<div class="column col-8 col-sm-12">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ $t('message.askCredentials') }}
|
||||
<input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ t('message.askCredentials') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -188,7 +188,7 @@
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">
|
||||
{{ $t('message.enableSsl') }}
|
||||
{{ t('message.enableSsl') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
@ -201,12 +201,12 @@
|
||||
<fieldset class="m-0" :disabled="isBusy || !connection.ssl">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.privateKey') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.privateKey') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="connection.key"
|
||||
:message="$t('word.browse')"
|
||||
:message="t('word.browse')"
|
||||
@clear="pathClear('key')"
|
||||
@change="pathSelection($event, 'key')"
|
||||
/>
|
||||
@ -214,12 +214,12 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.certificate') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.certificate') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="connection.cert"
|
||||
:message="$t('word.browse')"
|
||||
:message="t('word.browse')"
|
||||
@clear="pathClear('cert')"
|
||||
@change="pathSelection($event, 'cert')"
|
||||
/>
|
||||
@ -227,12 +227,12 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.caCertificate') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.caCertificate') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="connection.ca"
|
||||
:message="$t('word.browse')"
|
||||
:message="t('word.browse')"
|
||||
@clear="pathClear('ca')"
|
||||
@change="pathSelection($event, 'ca')"
|
||||
/>
|
||||
@ -240,7 +240,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.ciphers') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.ciphers') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -255,7 +255,7 @@
|
||||
<div class="column col-4 col-sm-12" />
|
||||
<div class="column col-8 col-sm-12">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="connection.untrustedConnection" type="checkbox"><i class="form-icon" /> {{ $t('message.untrustedConnection') }}
|
||||
<input v-model="connection.untrustedConnection" type="checkbox"><i class="form-icon" /> {{ t('message.untrustedConnection') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -269,7 +269,7 @@
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">
|
||||
{{ $t('message.enableSsh') }}
|
||||
{{ t('message.enableSsh') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
@ -282,7 +282,7 @@
|
||||
<fieldset class="m-0" :disabled="isBusy || !connection.ssh">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.hostName') }}/IP</label>
|
||||
<label class="form-label cut-text">{{ t('word.hostName') }}/IP</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -294,7 +294,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.user') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.user') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -306,7 +306,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.password') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.password') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -318,7 +318,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.port') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.port') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -332,12 +332,12 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.privateKey') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.privateKey') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="connection.sshKey"
|
||||
:message="$t('word.browse')"
|
||||
:message="t('word.browse')"
|
||||
@clear="pathClear('sshKey')"
|
||||
@change="pathSelection($event, 'sshKey')"
|
||||
/>
|
||||
@ -345,7 +345,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.passphrase') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.passphrase') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -368,7 +368,7 @@
|
||||
@click="startTest"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-lightning-bolt mr-1" />
|
||||
{{ $t('message.testConnection') }}
|
||||
{{ t('message.testConnection') }}
|
||||
</button>
|
||||
<button
|
||||
id="connection-save"
|
||||
@ -377,7 +377,7 @@
|
||||
@click="saveConnection"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||
{{ $t('word.save') }}
|
||||
{{ t('word.save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -389,188 +389,171 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { computed, Ref, ref, watch } from 'vue';
|
||||
import customizations from 'common/customizations';
|
||||
import Connection from '@/ipc-api/Connection';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import { useConnectionsStore } from '@/stores/connections';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import ModalAskCredentials from '@/components/ModalAskCredentials';
|
||||
import BaseUploadInput from '@/components/BaseUploadInput';
|
||||
import ModalAskCredentials from '@/components/ModalAskCredentials.vue';
|
||||
import BaseUploadInput from '@/components/BaseUploadInput.vue';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
import { ConnectionParams } from 'common/interfaces/antares';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceAddConnectionPanel',
|
||||
components: {
|
||||
ModalAskCredentials,
|
||||
BaseUploadInput,
|
||||
BaseSelect
|
||||
},
|
||||
setup () {
|
||||
const { addConnection } = useConnectionsStore();
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { connectWorkspace, selectWorkspace } = workspacesStore;
|
||||
const { addConnection } = useConnectionsStore();
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
return {
|
||||
addConnection,
|
||||
addNotification,
|
||||
connectWorkspace,
|
||||
selectWorkspace
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
clients: [
|
||||
{ name: 'MySQL', slug: 'mysql' },
|
||||
{ name: 'MariaDB', slug: 'maria' },
|
||||
{ name: 'PostgreSQL', slug: 'pg' },
|
||||
{ name: 'SQLite', slug: 'sqlite' }
|
||||
],
|
||||
connection: {
|
||||
name: '',
|
||||
client: 'mysql',
|
||||
host: '127.0.0.1',
|
||||
database: null,
|
||||
databasePath: '',
|
||||
port: null,
|
||||
user: null,
|
||||
password: '',
|
||||
ask: false,
|
||||
readonly: false,
|
||||
uid: uidGen('C'),
|
||||
ssl: false,
|
||||
cert: '',
|
||||
key: '',
|
||||
ca: '',
|
||||
ciphers: '',
|
||||
untrustedConnection: false,
|
||||
ssh: false,
|
||||
sshHost: '',
|
||||
sshUser: '',
|
||||
sshPass: '',
|
||||
sshKey: '',
|
||||
sshPort: 22,
|
||||
pgConnString: ''
|
||||
},
|
||||
isConnecting: false,
|
||||
isTesting: false,
|
||||
isAsking: false,
|
||||
selectedTab: 'general'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
customizations () {
|
||||
return customizations[this.connection.client];
|
||||
},
|
||||
isBusy () {
|
||||
return this.isConnecting || this.isTesting;
|
||||
const { connectWorkspace, selectWorkspace } = workspacesStore;
|
||||
|
||||
const clients = ref([
|
||||
{ name: 'MySQL', slug: 'mysql' },
|
||||
{ name: 'MariaDB', slug: 'maria' },
|
||||
{ name: 'PostgreSQL', slug: 'pg' },
|
||||
{ name: 'SQLite', slug: 'sqlite' }
|
||||
]);
|
||||
|
||||
const connection = ref({
|
||||
name: '',
|
||||
client: 'mysql',
|
||||
host: '127.0.0.1',
|
||||
database: null,
|
||||
databasePath: '',
|
||||
port: null,
|
||||
user: null,
|
||||
password: '',
|
||||
ask: false,
|
||||
readonly: false,
|
||||
uid: uidGen('C'),
|
||||
ssl: false,
|
||||
cert: '',
|
||||
key: '',
|
||||
ca: '',
|
||||
ciphers: '',
|
||||
untrustedConnection: false,
|
||||
ssh: false,
|
||||
sshHost: '',
|
||||
sshUser: '',
|
||||
sshPass: '',
|
||||
sshKey: '',
|
||||
sshPort: 22,
|
||||
pgConnString: ''
|
||||
}) as Ref<ConnectionParams & { pgConnString: string }>;
|
||||
|
||||
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||
const isConnecting = ref(false);
|
||||
const isTesting = ref(false);
|
||||
const isAsking = ref(false);
|
||||
const selectedTab = ref('general');
|
||||
|
||||
const clientCustomizations = computed(() => {
|
||||
return customizations[connection.value.client];
|
||||
});
|
||||
|
||||
const isBusy = computed(() => {
|
||||
return isConnecting.value || isTesting.value;
|
||||
});
|
||||
|
||||
watch(() => connection.value.client, () => {
|
||||
connection.value.user = clientCustomizations.value.defaultUser;
|
||||
connection.value.port = clientCustomizations.value.defaultPort;
|
||||
connection.value.database = clientCustomizations.value.defaultDatabase;
|
||||
});
|
||||
|
||||
const setDefaults = () => {
|
||||
connection.value.user = clientCustomizations.value.defaultUser;
|
||||
connection.value.port = clientCustomizations.value.defaultPort;
|
||||
connection.value.database = clientCustomizations.value.defaultDatabase;
|
||||
};
|
||||
|
||||
const startTest = async () => {
|
||||
isTesting.value = true;
|
||||
|
||||
if (connection.value.ask)
|
||||
isAsking.value = true;
|
||||
else {
|
||||
try {
|
||||
const res = await Connection.makeTest(connection.value);
|
||||
if (res.status === 'error')
|
||||
addNotification({ status: 'error', message: res.response.message || res.response.toString() });
|
||||
else
|
||||
addNotification({ status: 'success', message: t('message.connectionSuccessfullyMade') });
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'connection.client' () {
|
||||
this.connection.user = this.customizations.defaultUser;
|
||||
this.connection.port = this.customizations.defaultPort;
|
||||
this.connection.database = this.customizations.defaultDatabase;
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.setDefaults();
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.$refs.firstInput) this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
setDefaults () {
|
||||
this.connection.user = this.customizations.defaultUser;
|
||||
this.connection.port = this.customizations.defaultPort;
|
||||
this.connection.database = this.customizations.defaultDatabase;
|
||||
},
|
||||
async startConnection () {
|
||||
await this.saveConnection();
|
||||
this.isConnecting = true;
|
||||
|
||||
if (this.connection.ask)
|
||||
this.isAsking = true;
|
||||
else {
|
||||
await this.connectWorkspace(this.connection);
|
||||
this.isConnecting = false;
|
||||
}
|
||||
},
|
||||
async startTest () {
|
||||
this.isTesting = true;
|
||||
|
||||
if (this.connection.ask)
|
||||
this.isAsking = true;
|
||||
else {
|
||||
try {
|
||||
const res = await Connection.makeTest(this.connection);
|
||||
if (res.status === 'error')
|
||||
this.addNotification({ status: 'error', message: res.response.message || res.response.toString() });
|
||||
else
|
||||
this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isTesting = false;
|
||||
}
|
||||
},
|
||||
async continueTest (credentials) { // if "Ask for credentials" is true
|
||||
this.isAsking = false;
|
||||
const params = Object.assign({}, this.connection, credentials);
|
||||
try {
|
||||
if (this.isConnecting) {
|
||||
const params = Object.assign({}, this.connection, credentials);
|
||||
await this.connectWorkspace(params);
|
||||
this.isConnecting = false;
|
||||
}
|
||||
else {
|
||||
const res = await Connection.makeTest(params);
|
||||
if (res.status === 'error')
|
||||
this.addNotification({ status: 'error', message: res.response.message || res.response.toString() });
|
||||
else
|
||||
this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') });
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isTesting = false;
|
||||
},
|
||||
async saveConnection () {
|
||||
await this.addConnection(this.connection);
|
||||
this.selectWorkspace(this.connection.uid);
|
||||
},
|
||||
closeAsking () {
|
||||
this.isTesting = false;
|
||||
this.isAsking = false;
|
||||
},
|
||||
selectTab (tab) {
|
||||
this.selectedTab = tab;
|
||||
},
|
||||
toggleSsl () {
|
||||
this.connection.ssl = !this.connection.ssl;
|
||||
},
|
||||
toggleSsh () {
|
||||
this.connection.ssh = !this.connection.ssh;
|
||||
},
|
||||
pathSelection (event, name) {
|
||||
const { files } = event.target;
|
||||
if (!files.length) return;
|
||||
|
||||
this.connection[name] = files[0].path;
|
||||
},
|
||||
pathClear (name) {
|
||||
this.connection[name] = '';
|
||||
}
|
||||
isTesting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const continueTest = async (credentials: { user: string; password: string }) => { // if "Ask for credentials" is true
|
||||
isAsking.value = false;
|
||||
const params = Object.assign({}, connection.value, credentials);
|
||||
|
||||
try {
|
||||
if (isConnecting.value) {
|
||||
await connectWorkspace(params);
|
||||
isConnecting.value = false;
|
||||
}
|
||||
else {
|
||||
const res = await Connection.makeTest(params);
|
||||
if (res.status === 'error')
|
||||
addNotification({ status: 'error', message: res.response.message || res.response.toString() });
|
||||
else
|
||||
addNotification({ status: 'success', message: t('message.connectionSuccessfullyMade') });
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
isTesting.value = false;
|
||||
};
|
||||
|
||||
const saveConnection = async () => {
|
||||
await addConnection(connection.value);
|
||||
selectWorkspace(connection.value.uid);
|
||||
};
|
||||
|
||||
const closeAsking = () => {
|
||||
isTesting.value = false;
|
||||
isAsking.value = false;
|
||||
};
|
||||
|
||||
const selectTab = (tab: string) => {
|
||||
selectedTab.value = tab;
|
||||
};
|
||||
|
||||
const toggleSsl = () => {
|
||||
connection.value.ssl = !connection.value.ssl;
|
||||
};
|
||||
|
||||
const toggleSsh = () => {
|
||||
connection.value.ssh = !connection.value.ssh;
|
||||
};
|
||||
|
||||
const pathSelection = (event: Event & {target: {files: {path: string}[]}}, name: keyof ConnectionParams) => {
|
||||
const { files } = event.target;
|
||||
if (!files.length) return;
|
||||
|
||||
(connection.value as unknown as {[key: string]: string})[name] = files[0].path as string;
|
||||
};
|
||||
|
||||
const pathClear = (name: keyof ConnectionParams) => {
|
||||
(connection.value as unknown as {[key: string]: string})[name] = '';
|
||||
};
|
||||
|
||||
setDefaults();
|
||||
|
||||
setTimeout(() => {
|
||||
if (firstInput.value) firstInput.value.focus();
|
||||
}, 20);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -8,23 +8,23 @@
|
||||
:class="{'active': selectedTab === 'general'}"
|
||||
@click="selectTab('general')"
|
||||
>
|
||||
<a class="tab-link">{{ $t('word.general') }}</a>
|
||||
<a class="tab-link">{{ t('word.general') }}</a>
|
||||
</li>
|
||||
<li
|
||||
v-if="customizations.sslConnection"
|
||||
v-if="clientCustomizations.sslConnection"
|
||||
class="tab-item c-hand"
|
||||
:class="{'active': selectedTab === 'ssl'}"
|
||||
@click="selectTab('ssl')"
|
||||
>
|
||||
<a class="tab-link">{{ $t('word.ssl') }}</a>
|
||||
<a class="tab-link">{{ t('word.ssl') }}</a>
|
||||
</li>
|
||||
<li
|
||||
v-if="customizations.sshConnection"
|
||||
v-if="clientCustomizations.sshConnection"
|
||||
class="tab-item c-hand"
|
||||
:class="{'active': selectedTab === 'ssh'}"
|
||||
@click="selectTab('ssh')"
|
||||
>
|
||||
<a class="tab-link">{{ $t('word.sshTunnel') }}</a>
|
||||
<a class="tab-link">{{ t('word.sshTunnel') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -34,7 +34,7 @@
|
||||
<fieldset class="m-0" :disabled="isBusy">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.connectionName') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.connectionName') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -47,7 +47,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.client') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.client') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<BaseSelect
|
||||
@ -63,7 +63,7 @@
|
||||
</div>
|
||||
<div v-if="connection.client === 'pg'" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.connectionString') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.connectionString') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -74,9 +74,9 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.hostName') }}/IP</label>
|
||||
<label class="form-label cut-text">{{ t('word.hostName') }}/IP</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -86,22 +86,22 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="customizations.fileConnection" class="form-group columns">
|
||||
<div v-if="clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.database') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.database') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="localConnection.databasePath"
|
||||
:message="$t('word.browse')"
|
||||
:message="t('word.browse')"
|
||||
@clear="pathClear('databasePath')"
|
||||
@change="pathSelection($event, 'databasePath')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.port') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.port') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -113,9 +113,9 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="customizations.database" class="form-group columns">
|
||||
<div v-if="clientCustomizations.database" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.database') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.database') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -125,9 +125,9 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.user') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.user') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -138,9 +138,9 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.password') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.password') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -151,32 +151,32 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="customizations.connectionSchema" class="form-group columns">
|
||||
<div v-if="clientCustomizations.connectionSchema" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.schema') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.schema') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.schema"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:placeholder="$t('word.all')"
|
||||
:placeholder="t('word.all')"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="customizations.readOnlyMode" class="form-group columns">
|
||||
<div v-if="clientCustomizations.readOnlyMode" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12" />
|
||||
<div class="column col-8 col-sm-12">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="localConnection.readonly" type="checkbox"><i class="form-icon" /> {{ $t('message.readOnlyMode') }}
|
||||
<input v-model="localConnection.readonly" type="checkbox"><i class="form-icon" /> {{ t('message.readOnlyMode') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-4 col-sm-12" />
|
||||
<div class="column col-8 col-sm-12">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="localConnection.ask" type="checkbox"><i class="form-icon" /> {{ $t('message.askCredentials') }}
|
||||
<input v-model="localConnection.ask" type="checkbox"><i class="form-icon" /> {{ t('message.askCredentials') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -190,7 +190,7 @@
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">
|
||||
{{ $t('message.enableSsl') }}
|
||||
{{ t('message.enableSsl') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
@ -203,12 +203,12 @@
|
||||
<fieldset class="m-0" :disabled="isBusy || !localConnection.ssl">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.privateKey') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.privateKey') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="localConnection.key"
|
||||
:message="$t('word.browse')"
|
||||
:message="t('word.browse')"
|
||||
@clear="pathClear('key')"
|
||||
@change="pathSelection($event, 'key')"
|
||||
/>
|
||||
@ -216,12 +216,12 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.certificate') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.certificate') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="localConnection.cert"
|
||||
:message="$t('word.browse')"
|
||||
:message="t('word.browse')"
|
||||
@clear="pathClear('cert')"
|
||||
@change="pathSelection($event, 'cert')"
|
||||
/>
|
||||
@ -229,12 +229,12 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.caCertificate') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.caCertificate') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="localConnection.ca"
|
||||
:message="$t('word.browse')"
|
||||
:message="t('word.browse')"
|
||||
@clear="pathClear('ca')"
|
||||
@change="pathSelection($event, 'ca')"
|
||||
/>
|
||||
@ -242,7 +242,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.ciphers') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.ciphers') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -263,7 +263,7 @@
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">
|
||||
{{ $t('message.enableSsh') }}
|
||||
{{ t('message.enableSsh') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
@ -276,7 +276,7 @@
|
||||
<fieldset class="m-0" :disabled="isBusy || !localConnection.ssh">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.hostName') }}/IP</label>
|
||||
<label class="form-label cut-text">{{ t('word.hostName') }}/IP</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -288,7 +288,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.user') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.user') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -300,7 +300,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.password') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.password') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -312,7 +312,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.port') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.port') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -326,12 +326,12 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.privateKey') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.privateKey') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="localConnection.sshKey"
|
||||
:message="$t('word.browse')"
|
||||
:message="t('word.browse')"
|
||||
@clear="pathClear('sshKey')"
|
||||
@change="pathSelection($event, 'sshKey')"
|
||||
/>
|
||||
@ -339,7 +339,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label cut-text">{{ $t('word.passphrase') }}</label>
|
||||
<label class="form-label cut-text">{{ t('word.passphrase') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
@ -362,7 +362,7 @@
|
||||
@click="startTest"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-lightning-bolt mr-1" />
|
||||
{{ $t('message.testConnection') }}
|
||||
{{ t('message.testConnection') }}
|
||||
</button>
|
||||
<button
|
||||
id="connection-save"
|
||||
@ -371,7 +371,7 @@
|
||||
@click="saveConnection"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||
{{ $t('word.save') }}
|
||||
{{ t('word.save') }}
|
||||
</button>
|
||||
<button
|
||||
id="connection-connect"
|
||||
@ -381,7 +381,7 @@
|
||||
@click="startConnection"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-connection mr-1" />
|
||||
{{ $t('word.connect') }}
|
||||
{{ t('word.connect') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -393,154 +393,150 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { computed, Prop, Ref, ref, watch } from 'vue';
|
||||
import customizations from 'common/customizations';
|
||||
import { useConnectionsStore } from '@/stores/connections';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import Connection from '@/ipc-api/Connection';
|
||||
import ModalAskCredentials from '@/components/ModalAskCredentials';
|
||||
import BaseUploadInput from '@/components/BaseUploadInput';
|
||||
import ModalAskCredentials from '@/components/ModalAskCredentials.vue';
|
||||
import BaseUploadInput from '@/components/BaseUploadInput.vue';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
import { ConnectionParams } from 'common/interfaces/antares';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceEditConnectionPanel',
|
||||
components: {
|
||||
ModalAskCredentials,
|
||||
BaseUploadInput,
|
||||
BaseSelect
|
||||
},
|
||||
props: {
|
||||
connection: Object
|
||||
},
|
||||
setup () {
|
||||
const { editConnection } = useConnectionsStore();
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const { connectWorkspace } = useWorkspacesStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
return {
|
||||
editConnection,
|
||||
addNotification,
|
||||
connectWorkspace
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
clients: [
|
||||
{ name: 'MySQL', slug: 'mysql' },
|
||||
{ name: 'MariaDB', slug: 'maria' },
|
||||
{ name: 'PostgreSQL', slug: 'pg' },
|
||||
{ name: 'SQLite', slug: 'sqlite' }
|
||||
],
|
||||
isConnecting: false,
|
||||
isTesting: false,
|
||||
isAsking: false,
|
||||
localConnection: null,
|
||||
selectedTab: 'general'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
customizations () {
|
||||
return customizations[this.localConnection.client];
|
||||
},
|
||||
isBusy () {
|
||||
return this.isConnecting || this.isTesting;
|
||||
},
|
||||
hasChanges () {
|
||||
return JSON.stringify(this.connection) !== JSON.stringify(this.localConnection);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
connection () {
|
||||
this.localConnection = JSON.parse(JSON.stringify(this.connection));
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.localConnection = JSON.parse(JSON.stringify(this.connection));
|
||||
},
|
||||
methods: {
|
||||
async startConnection () {
|
||||
await this.saveConnection();
|
||||
this.isConnecting = true;
|
||||
const props = defineProps({
|
||||
connection: Object as Prop<ConnectionParams>
|
||||
});
|
||||
|
||||
if (this.localConnection.ask)
|
||||
this.isAsking = true;
|
||||
else {
|
||||
await this.connectWorkspace(this.localConnection);
|
||||
this.isConnecting = false;
|
||||
}
|
||||
},
|
||||
async startTest () {
|
||||
this.isTesting = true;
|
||||
const { editConnection } = useConnectionsStore();
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const { connectWorkspace } = useWorkspacesStore();
|
||||
|
||||
if (this.localConnection.ask)
|
||||
this.isAsking = true;
|
||||
else {
|
||||
try {
|
||||
const res = await Connection.makeTest(this.localConnection);
|
||||
if (res.status === 'error')
|
||||
this.addNotification({ status: 'error', message: res.response.message || res.response.toString() });
|
||||
else
|
||||
this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
const clients = ref([
|
||||
{ name: 'MySQL', slug: 'mysql' },
|
||||
{ name: 'MariaDB', slug: 'maria' },
|
||||
{ name: 'PostgreSQL', slug: 'pg' },
|
||||
{ name: 'SQLite', slug: 'sqlite' }
|
||||
]);
|
||||
|
||||
this.isTesting = false;
|
||||
}
|
||||
},
|
||||
async continueTest (credentials) { // if "Ask for credentials" is true
|
||||
this.isAsking = false;
|
||||
const params = Object.assign({}, this.localConnection, credentials);
|
||||
try {
|
||||
if (this.isConnecting) {
|
||||
const params = Object.assign({}, this.connection, credentials);
|
||||
await this.connectWorkspace(params);
|
||||
this.isConnecting = false;
|
||||
}
|
||||
else {
|
||||
const res = await Connection.makeTest(params);
|
||||
if (res.status === 'error')
|
||||
this.addNotification({ status: 'error', message: res.response.message || res.response.toString() });
|
||||
else
|
||||
this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') });
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||
const localConnection: Ref<ConnectionParams & { pgConnString: string }> = ref(null);
|
||||
const isConnecting = ref(false);
|
||||
const isTesting = ref(false);
|
||||
const isAsking = ref(false);
|
||||
const selectedTab = ref('general');
|
||||
|
||||
this.isTesting = false;
|
||||
},
|
||||
saveConnection () {
|
||||
return this.editConnection(this.localConnection);
|
||||
},
|
||||
closeAsking () {
|
||||
this.isTesting = false;
|
||||
this.isAsking = false;
|
||||
this.isConnecting = false;
|
||||
},
|
||||
selectTab (tab) {
|
||||
this.selectedTab = tab;
|
||||
},
|
||||
toggleSsl () {
|
||||
this.localConnection.ssl = !this.localConnection.ssl;
|
||||
},
|
||||
toggleSsh () {
|
||||
this.localConnection.ssh = !this.localConnection.ssh;
|
||||
},
|
||||
pathSelection (event, name) {
|
||||
const { files } = event.target;
|
||||
if (!files.length) return;
|
||||
const clientCustomizations = computed(() => {
|
||||
return customizations[localConnection.value.client];
|
||||
});
|
||||
|
||||
this.localConnection[name] = files[0].path;
|
||||
},
|
||||
pathClear (name) {
|
||||
this.localConnection[name] = '';
|
||||
}
|
||||
const isBusy = computed(() => {
|
||||
return isConnecting.value || isTesting.value;
|
||||
});
|
||||
|
||||
const hasChanges = computed(() => {
|
||||
return JSON.stringify(props.connection) !== JSON.stringify(localConnection.value);
|
||||
});
|
||||
|
||||
watch(() => props.connection, () => {
|
||||
localConnection.value = JSON.parse(JSON.stringify(props.connection));
|
||||
});
|
||||
|
||||
const startConnection = async () => {
|
||||
await saveConnection();
|
||||
isConnecting.value = true;
|
||||
|
||||
if (localConnection.value.ask)
|
||||
isAsking.value = true;
|
||||
else {
|
||||
await connectWorkspace(localConnection.value);
|
||||
isConnecting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const startTest = async () => {
|
||||
isTesting.value = true;
|
||||
|
||||
if (localConnection.value.ask)
|
||||
isAsking.value = true;
|
||||
else {
|
||||
try {
|
||||
const res = await Connection.makeTest(localConnection.value);
|
||||
if (res.status === 'error')
|
||||
addNotification({ status: 'error', message: res.response.message || res.response.toString() });
|
||||
else
|
||||
addNotification({ status: 'success', message: t('message.connectionSuccessfullyMade') });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
isTesting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const continueTest = async (credentials: {user: string; password: string }) => { // if "Ask for credentials" is true
|
||||
isAsking.value = false;
|
||||
const params = Object.assign({}, localConnection.value, credentials);
|
||||
try {
|
||||
if (isConnecting.value) {
|
||||
const params = Object.assign({}, props.connection, credentials);
|
||||
await connectWorkspace(params);
|
||||
isConnecting.value = false;
|
||||
}
|
||||
else {
|
||||
const res = await Connection.makeTest(params);
|
||||
if (res.status === 'error')
|
||||
addNotification({ status: 'error', message: res.response.message || res.response.toString() });
|
||||
else
|
||||
addNotification({ status: 'success', message: t('message.connectionSuccessfullyMade') });
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
isTesting.value = false;
|
||||
};
|
||||
|
||||
const saveConnection = () => {
|
||||
return editConnection(localConnection.value);
|
||||
};
|
||||
|
||||
const closeAsking = () => {
|
||||
isTesting.value = false;
|
||||
isAsking.value = false;
|
||||
isConnecting.value = false;
|
||||
};
|
||||
|
||||
const selectTab = (tab: string) => {
|
||||
selectedTab.value = tab;
|
||||
};
|
||||
|
||||
const toggleSsl = () => {
|
||||
localConnection.value.ssl = !localConnection.value.ssl;
|
||||
};
|
||||
|
||||
const toggleSsh = () => {
|
||||
localConnection.value.ssh = !localConnection.value.ssh;
|
||||
};
|
||||
|
||||
const pathSelection = (event: Event & {target: {files: {path: string}[]}}, name: keyof ConnectionParams) => {
|
||||
const { files } = event.target;
|
||||
if (!files.length) return;
|
||||
|
||||
(localConnection.value as unknown as {[key: string]: string})[name] = files[0].path;
|
||||
};
|
||||
|
||||
const pathClear = (name: keyof ConnectionParams) => {
|
||||
(localConnection.value as unknown as {[key: string]: string})[name] = '';
|
||||
};
|
||||
|
||||
localConnection.value = JSON.parse(JSON.stringify(props.connection));
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -1,61 +1,48 @@
|
||||
<template>
|
||||
<div class="column col-12 empty">
|
||||
<div class="empty-icon">
|
||||
<img
|
||||
v-if="applicationTheme === 'dark'"
|
||||
src="../images/logo-dark.svg"
|
||||
width="200"
|
||||
>
|
||||
<img
|
||||
v-if="applicationTheme === 'light'"
|
||||
src="../images/logo-light.svg"
|
||||
width="200"
|
||||
>
|
||||
<img :src="logos[applicationTheme]" width="200">
|
||||
</div>
|
||||
<p class="h6 empty-subtitle">
|
||||
{{ $t('message.noOpenTabs') }}
|
||||
{{ t('message.noOpenTabs') }}
|
||||
</p>
|
||||
<div class="empty-action">
|
||||
<button class="btn btn-gray d-flex" @click="$emit('new-tab')">
|
||||
<button class="btn btn-gray d-flex" @click="emit('new-tab')">
|
||||
<i class="mdi mdi-24px mdi-tab-plus mr-2" />
|
||||
{{ $t('message.openNewTab') }}
|
||||
{{ t('message.openNewTab') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import { storeToRefs } from 'pinia';
|
||||
export default {
|
||||
name: 'WorkspaceEmptyState',
|
||||
emits: ['new-tab'],
|
||||
setup () {
|
||||
const settingsStore = useSettingsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { applicationTheme } = storeToRefs(settingsStore);
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const { t } = useI18n();
|
||||
|
||||
const { getWorkspace, changeBreadcrumbs } = workspacesStore;
|
||||
const emit = defineEmits(['new-tab']);
|
||||
|
||||
return {
|
||||
applicationTheme,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
changeBreadcrumbs
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.changeBreadcrumbs({ schema: this.workspace.breadcrumbs.schema });
|
||||
}
|
||||
const logos = {
|
||||
light: require('../images/logo-light.svg') as string,
|
||||
dark: require('../images/logo-dark.svg') as string
|
||||
};
|
||||
|
||||
const settingsStore = useSettingsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
const { applicationTheme } = storeToRefs(settingsStore);
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
const { getWorkspace, changeBreadcrumbs } = workspacesStore;
|
||||
|
||||
const workspace = computed(() => {
|
||||
return getWorkspace(selectedWorkspace.value);
|
||||
});
|
||||
|
||||
changeBreadcrumbs({ schema: workspace.value.breadcrumbs.schema });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -48,7 +48,7 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-explorebar-body" @click="$refs.explorebar.focus()">
|
||||
<div class="workspace-explorebar-body" @click="explorebar.focus()">
|
||||
<WorkspaceExploreBarSchema
|
||||
v-for="db of workspace.structure"
|
||||
:key="db.name"
|
||||
@ -115,7 +115,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { Component, computed, onMounted, Ref, ref, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import { useConnectionsStore } from '@/stores/connections';
|
||||
@ -125,428 +126,290 @@ import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import Views from '@/ipc-api/Views';
|
||||
import Functions from '@/ipc-api/Functions';
|
||||
import Schedulers from '@/ipc-api/Schedulers';
|
||||
|
||||
import WorkspaceExploreBarSchema from '@/components/WorkspaceExploreBarSchema';
|
||||
import DatabaseContext from '@/components/WorkspaceExploreBarSchemaContext';
|
||||
import TableContext from '@/components/WorkspaceExploreBarTableContext';
|
||||
import MiscContext from '@/components/WorkspaceExploreBarMiscContext';
|
||||
import MiscFolderContext from '@/components/WorkspaceExploreBarMiscFolderContext';
|
||||
import ModalNewSchema from '@/components/ModalNewSchema';
|
||||
import WorkspaceExploreBarSchema from '@/components/WorkspaceExploreBarSchema.vue';
|
||||
import DatabaseContext from '@/components/WorkspaceExploreBarSchemaContext.vue';
|
||||
import TableContext from '@/components/WorkspaceExploreBarTableContext.vue';
|
||||
import MiscContext from '@/components/WorkspaceExploreBarMiscContext.vue';
|
||||
import MiscFolderContext from '@/components/WorkspaceExploreBarMiscFolderContext.vue';
|
||||
import ModalNewSchema from '@/components/ModalNewSchema.vue';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceExploreBar',
|
||||
components: {
|
||||
WorkspaceExploreBarSchema,
|
||||
DatabaseContext,
|
||||
TableContext,
|
||||
MiscContext,
|
||||
MiscFolderContext,
|
||||
ModalNewSchema
|
||||
},
|
||||
props: {
|
||||
connection: Object,
|
||||
isSelected: Boolean
|
||||
},
|
||||
setup () {
|
||||
const { getConnectionName } = useConnectionsStore();
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const props = defineProps({
|
||||
connection: Object,
|
||||
isSelected: Boolean
|
||||
});
|
||||
|
||||
const { explorebarSize } = storeToRefs(settingsStore);
|
||||
const { getConnectionName } = useConnectionsStore();
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
const { changeExplorebarSize } = settingsStore;
|
||||
const {
|
||||
getWorkspace,
|
||||
removeConnected: disconnectWorkspace,
|
||||
refreshStructure,
|
||||
changeBreadcrumbs,
|
||||
selectTab,
|
||||
newTab,
|
||||
removeTabs,
|
||||
setSearchTerm,
|
||||
addLoadingElement,
|
||||
removeLoadingElement
|
||||
} = workspacesStore;
|
||||
const { explorebarSize } = storeToRefs(settingsStore);
|
||||
|
||||
return {
|
||||
getConnectionName,
|
||||
addNotification,
|
||||
explorebarSize,
|
||||
changeExplorebarSize,
|
||||
getWorkspace,
|
||||
disconnectWorkspace,
|
||||
refreshStructure,
|
||||
changeBreadcrumbs,
|
||||
selectTab,
|
||||
newTab,
|
||||
removeTabs,
|
||||
setSearchTerm,
|
||||
addLoadingElement,
|
||||
removeLoadingElement
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isRefreshing: false,
|
||||
const { changeExplorebarSize } = settingsStore;
|
||||
const {
|
||||
getWorkspace,
|
||||
removeConnected: disconnectWorkspace,
|
||||
refreshStructure,
|
||||
newTab,
|
||||
removeTabs,
|
||||
setSearchTerm,
|
||||
addLoadingElement,
|
||||
removeLoadingElement
|
||||
} = workspacesStore;
|
||||
|
||||
isNewDBModal: false,
|
||||
isNewViewModal: false,
|
||||
isNewTriggerModal: false,
|
||||
isNewRoutineModal: false,
|
||||
isNewFunctionModal: false,
|
||||
isNewTriggerFunctionModal: false,
|
||||
isNewSchedulerModal: false,
|
||||
const searchInput: Ref<HTMLInputElement> = ref(null);
|
||||
const explorebar: Ref<HTMLInputElement> = ref(null);
|
||||
const resizer: Ref<HTMLInputElement> = ref(null);
|
||||
const schema: Ref<Component & { selectSchema: (name: string) => void; $refs: {schemaAccordion: HTMLDetailsElement} }[]> = ref(null);
|
||||
const isRefreshing = ref(false);
|
||||
const isNewDBModal = ref(false);
|
||||
const localWidth = ref(null);
|
||||
const explorebarWidthInterval = ref(null);
|
||||
const searchTermInterval = ref(null);
|
||||
const isDatabaseContext = ref(false);
|
||||
const isTableContext = ref(false);
|
||||
const isMiscContext = ref(false);
|
||||
const isMiscFolderContext = ref(false);
|
||||
const databaseContextEvent = ref(null);
|
||||
const tableContextEvent = ref(null);
|
||||
const miscContextEvent = ref(null);
|
||||
const selectedSchema = ref('');
|
||||
const selectedTable = ref(null);
|
||||
const selectedMisc = ref(null);
|
||||
const searchTerm = ref('');
|
||||
|
||||
localWidth: null,
|
||||
explorebarWidthInterval: null,
|
||||
searchTermInterval: null,
|
||||
isDatabaseContext: false,
|
||||
isTableContext: false,
|
||||
isMiscContext: false,
|
||||
isMiscFolderContext: false,
|
||||
const workspace = computed(() => {
|
||||
return getWorkspace(props.connection.uid);
|
||||
});
|
||||
|
||||
databaseContextEvent: null,
|
||||
tableContextEvent: null,
|
||||
miscContextEvent: null,
|
||||
const connectionName = computed(() => {
|
||||
return getConnectionName(props.connection.uid);
|
||||
});
|
||||
|
||||
selectedSchema: '',
|
||||
selectedTable: null,
|
||||
selectedMisc: null,
|
||||
searchTerm: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
connectionName () {
|
||||
return this.getConnectionName(this.connection.uid);
|
||||
},
|
||||
customizations () {
|
||||
return this.workspace.customizations;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
localWidth (val) {
|
||||
clearTimeout(this.explorebarWidthInterval);
|
||||
const customizations = computed(() => {
|
||||
return workspace.value.customizations;
|
||||
});
|
||||
|
||||
this.explorebarWidthInterval = setTimeout(() => {
|
||||
this.changeExplorebarSize(val);
|
||||
}, 500);
|
||||
},
|
||||
isSelected (val) {
|
||||
if (val) this.localWidth = this.explorebarSize;
|
||||
},
|
||||
searchTerm () {
|
||||
clearTimeout(this.searchTermInterval);
|
||||
watch(localWidth, (val: number) => {
|
||||
clearTimeout(explorebarWidthInterval.value);
|
||||
|
||||
this.searchTermInterval = setTimeout(() => {
|
||||
this.setSearchTerm(this.searchTerm);
|
||||
}, 200);
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.localWidth = this.explorebarSize;
|
||||
},
|
||||
mounted () {
|
||||
const resizer = this.$refs.resizer;
|
||||
explorebarWidthInterval.value = setTimeout(() => {
|
||||
changeExplorebarSize(val);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
resizer.addEventListener('mousedown', e => {
|
||||
e.preventDefault();
|
||||
watch(() => props.isSelected, (val: boolean) => {
|
||||
if (val) localWidth.value = explorebarSize.value;
|
||||
});
|
||||
|
||||
window.addEventListener('mousemove', this.resize);
|
||||
window.addEventListener('mouseup', this.stopResize);
|
||||
});
|
||||
watch(searchTerm, () => {
|
||||
clearTimeout(searchTermInterval.value);
|
||||
|
||||
if (this.workspace.structure.length === 1) { // Auto-open if juust one schema
|
||||
this.$refs.schema[0].selectSchema(this.workspace.structure[0].name);
|
||||
this.$refs.schema[0].$refs.schemaAccordion.open = true;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async refresh () {
|
||||
if (!this.isRefreshing) {
|
||||
this.isRefreshing = true;
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
this.isRefreshing = false;
|
||||
}
|
||||
},
|
||||
explorebarSearch (e) {
|
||||
if (e.code === 'Backspace') {
|
||||
e.preventDefault();
|
||||
if (this.searchTerm.length)
|
||||
this.searchTerm = this.searchTerm.slice(0, -1);
|
||||
else
|
||||
return;
|
||||
}
|
||||
else if (e.key.length > 1)// Prevent non-alphanumerics
|
||||
return;
|
||||
searchTermInterval.value = setTimeout(() => {
|
||||
setSearchTerm(searchTerm.value);
|
||||
}, 200);
|
||||
});
|
||||
|
||||
this.$refs.searchInput.focus();
|
||||
},
|
||||
resize (e) {
|
||||
const el = this.$refs.explorebar;
|
||||
let explorebarWidth = e.pageX - el.getBoundingClientRect().left;
|
||||
if (explorebarWidth > 500) explorebarWidth = 500;
|
||||
if (explorebarWidth < 150) explorebarWidth = 150;
|
||||
this.localWidth = explorebarWidth;
|
||||
},
|
||||
stopResize () {
|
||||
window.removeEventListener('mousemove', this.resize);
|
||||
},
|
||||
showNewDBModal () {
|
||||
this.isNewDBModal = true;
|
||||
},
|
||||
hideNewDBModal () {
|
||||
this.isNewDBModal = false;
|
||||
},
|
||||
openCreateElementTab (element) {
|
||||
this.closeDatabaseContext();
|
||||
this.closeMiscFolderContext();
|
||||
localWidth.value = explorebarSize.value;
|
||||
|
||||
this.newTab({
|
||||
uid: this.workspace.uid,
|
||||
schema: this.selectedSchema,
|
||||
elementName: '',
|
||||
elementType: element,
|
||||
type: `new-${element}`
|
||||
});
|
||||
},
|
||||
openSchemaContext (payload) {
|
||||
this.selectedSchema = payload.schema;
|
||||
this.databaseContextEvent = payload.event;
|
||||
this.isDatabaseContext = true;
|
||||
},
|
||||
closeDatabaseContext () {
|
||||
this.isDatabaseContext = false;
|
||||
},
|
||||
openTableContext (payload) {
|
||||
this.selectedTable = payload.table;
|
||||
this.selectedSchema = payload.schema;
|
||||
this.tableContextEvent = payload.event;
|
||||
this.isTableContext = true;
|
||||
},
|
||||
closeTableContext () {
|
||||
this.isTableContext = false;
|
||||
},
|
||||
openMiscContext (payload) {
|
||||
this.selectedMisc = payload.misc;
|
||||
this.selectedSchema = payload.schema;
|
||||
this.miscContextEvent = payload.event;
|
||||
this.isMiscContext = true;
|
||||
},
|
||||
openMiscFolderContext (payload) {
|
||||
this.selectedMisc = payload.type;
|
||||
this.selectedSchema = payload.schema;
|
||||
this.miscContextEvent = payload.event;
|
||||
this.isMiscFolderContext = true;
|
||||
},
|
||||
closeMiscContext () {
|
||||
this.isMiscContext = false;
|
||||
},
|
||||
closeMiscFolderContext () {
|
||||
this.isMiscFolderContext = false;
|
||||
},
|
||||
showCreateTriggerModal () {
|
||||
this.closeDatabaseContext();
|
||||
this.closeMiscFolderContext();
|
||||
this.isNewTriggerModal = true;
|
||||
},
|
||||
hideCreateTriggerModal () {
|
||||
this.isNewTriggerModal = false;
|
||||
},
|
||||
showCreateRoutineModal () {
|
||||
this.closeDatabaseContext();
|
||||
this.closeMiscFolderContext();
|
||||
this.isNewRoutineModal = true;
|
||||
},
|
||||
hideCreateRoutineModal () {
|
||||
this.isNewRoutineModal = false;
|
||||
},
|
||||
showCreateFunctionModal () {
|
||||
this.closeDatabaseContext();
|
||||
this.closeMiscFolderContext();
|
||||
this.isNewFunctionModal = true;
|
||||
},
|
||||
hideCreateFunctionModal () {
|
||||
this.isNewFunctionModal = false;
|
||||
},
|
||||
showCreateTriggerFunctionModal () {
|
||||
this.closeDatabaseContext();
|
||||
this.closeMiscFolderContext();
|
||||
this.isNewTriggerFunctionModal = true;
|
||||
},
|
||||
hideCreateTriggerFunctionModal () {
|
||||
this.isNewTriggerFunctionModal = false;
|
||||
},
|
||||
showCreateSchedulerModal () {
|
||||
this.closeDatabaseContext();
|
||||
this.closeMiscFolderContext();
|
||||
this.isNewSchedulerModal = true;
|
||||
},
|
||||
hideCreateSchedulerModal () {
|
||||
this.isNewSchedulerModal = false;
|
||||
},
|
||||
async deleteTable (payload) {
|
||||
this.closeTableContext();
|
||||
onMounted(() => {
|
||||
resizer.value.addEventListener('mousedown', (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
this.addLoadingElement({
|
||||
name: payload.table.name,
|
||||
schema: payload.schema,
|
||||
type: 'table'
|
||||
});
|
||||
window.addEventListener('mousemove', resize);
|
||||
window.addEventListener('mouseup', stopResize);
|
||||
});
|
||||
|
||||
try {
|
||||
let res;
|
||||
if (workspace.value.structure.length === 1) { // Auto-open if juust one schema
|
||||
schema.value[0].selectSchema(workspace.value.structure[0].name);
|
||||
schema.value[0].$refs.schemaAccordion.open = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (payload.table.type === 'table') {
|
||||
res = await Tables.dropTable({
|
||||
uid: this.connection.uid,
|
||||
table: payload.table.name,
|
||||
schema: payload.schema
|
||||
});
|
||||
}
|
||||
else if (payload.table.type === 'view') {
|
||||
res = await Views.dropView({
|
||||
uid: this.connection.uid,
|
||||
view: payload.table.name,
|
||||
schema: payload.schema
|
||||
});
|
||||
}
|
||||
|
||||
const { status, response } = res;
|
||||
|
||||
if (status === 'success') {
|
||||
this.refresh();
|
||||
|
||||
this.removeTabs({
|
||||
uid: this.connection.uid,
|
||||
elementName: payload.table.name,
|
||||
elementType: payload.table.type,
|
||||
schema: payload.schema
|
||||
});
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.removeLoadingElement({
|
||||
name: payload.table.name,
|
||||
schema: payload.schema,
|
||||
type: 'table'
|
||||
});
|
||||
},
|
||||
async duplicateTable (payload) {
|
||||
this.closeTableContext();
|
||||
|
||||
this.addLoadingElement({
|
||||
name: payload.table.name,
|
||||
schema: payload.schema,
|
||||
type: 'table'
|
||||
});
|
||||
|
||||
try {
|
||||
const { status, response } = await Tables.duplicateTable({
|
||||
uid: this.connection.uid,
|
||||
table: payload.table.name,
|
||||
schema: payload.schema
|
||||
});
|
||||
|
||||
if (status === 'success')
|
||||
this.refresh();
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.removeLoadingElement({
|
||||
name: payload.table.name,
|
||||
schema: payload.schema,
|
||||
type: 'table'
|
||||
});
|
||||
},
|
||||
async openCreateFunctionEditor (payload) {
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.selectedSchema,
|
||||
...payload
|
||||
};
|
||||
|
||||
const { status, response } = await Functions.createFunction(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refresh();
|
||||
this.changeBreadcrumbs({ schema: this.selectedSchema, function: payload.name });
|
||||
|
||||
this.newTab({
|
||||
uid: this.workspace.uid,
|
||||
schema: this.selectedSchema,
|
||||
elementName: payload.name,
|
||||
elementType: 'function',
|
||||
type: 'function-props'
|
||||
});
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
},
|
||||
async openCreateTriggerFunctionEditor (payload) {
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.selectedSchema,
|
||||
...payload
|
||||
};
|
||||
|
||||
const { status, response } = await Functions.createTriggerFunction(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refresh();
|
||||
this.changeBreadcrumbs({ schema: this.selectedSchema, triggerFunction: payload.name });
|
||||
|
||||
this.newTab({
|
||||
uid: this.workspace.uid,
|
||||
schema: this.selectedSchema,
|
||||
elementName: payload.name,
|
||||
elementType: 'triggerFunction',
|
||||
type: 'trigger-function-props'
|
||||
});
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
},
|
||||
async openCreateSchedulerEditor (payload) {
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.selectedSchema,
|
||||
...payload
|
||||
};
|
||||
|
||||
const { status, response } = await Schedulers.createScheduler(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refresh();
|
||||
this.changeBreadcrumbs({ schema: this.selectedSchema, scheduler: payload.name });
|
||||
|
||||
this.newTab({
|
||||
uid: this.workspace.uid,
|
||||
schema: this.selectedSchema,
|
||||
elementName: payload.name,
|
||||
elementType: 'scheduler',
|
||||
type: 'scheduler-props'
|
||||
});
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
const refresh = async () => {
|
||||
if (!isRefreshing.value) {
|
||||
isRefreshing.value = true;
|
||||
await refreshStructure(props.connection.uid);
|
||||
isRefreshing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const explorebarSearch = (e: KeyboardEvent) => {
|
||||
if (e.code === 'Backspace') {
|
||||
e.preventDefault();
|
||||
if (searchTerm.value.length)
|
||||
searchTerm.value = searchTerm.value.slice(0, -1);
|
||||
else
|
||||
return;
|
||||
}
|
||||
else if (e.key.length > 1)// Prevent non-alphanumerics
|
||||
return;
|
||||
|
||||
searchInput.value.focus();
|
||||
};
|
||||
|
||||
const resize = (e: MouseEvent) => {
|
||||
const el = explorebar.value;
|
||||
let explorebarWidth = e.pageX - el.getBoundingClientRect().left;
|
||||
if (explorebarWidth > 500) explorebarWidth = 500;
|
||||
if (explorebarWidth < 150) explorebarWidth = 150;
|
||||
localWidth.value = explorebarWidth;
|
||||
};
|
||||
|
||||
const stopResize = () => {
|
||||
window.removeEventListener('mousemove', resize);
|
||||
};
|
||||
|
||||
const showNewDBModal = () => {
|
||||
isNewDBModal.value = true;
|
||||
};
|
||||
|
||||
const hideNewDBModal = () => {
|
||||
isNewDBModal.value = false;
|
||||
};
|
||||
|
||||
const openCreateElementTab = (element: string) => {
|
||||
closeDatabaseContext();
|
||||
closeMiscFolderContext();
|
||||
|
||||
newTab({
|
||||
uid: workspace.value.uid,
|
||||
schema: selectedSchema.value,
|
||||
elementName: '',
|
||||
elementType: element,
|
||||
type: `new-${element}`
|
||||
});
|
||||
};
|
||||
|
||||
const openSchemaContext = (payload: { schema: string; event: PointerEvent }) => {
|
||||
selectedSchema.value = payload.schema;
|
||||
databaseContextEvent.value = payload.event;
|
||||
isDatabaseContext.value = true;
|
||||
};
|
||||
|
||||
const closeDatabaseContext = () => {
|
||||
isDatabaseContext.value = false;
|
||||
};
|
||||
|
||||
const openTableContext = (payload: { schema: string; table: string; event: PointerEvent }) => {
|
||||
selectedTable.value = payload.table;
|
||||
selectedSchema.value = payload.schema;
|
||||
tableContextEvent.value = payload.event;
|
||||
isTableContext.value = true;
|
||||
};
|
||||
|
||||
const closeTableContext = () => {
|
||||
isTableContext.value = false;
|
||||
};
|
||||
|
||||
const openMiscContext = (payload: { schema: string; misc: string; event: PointerEvent }) => {
|
||||
selectedMisc.value = payload.misc;
|
||||
selectedSchema.value = payload.schema;
|
||||
miscContextEvent.value = payload.event;
|
||||
isMiscContext.value = true;
|
||||
};
|
||||
|
||||
const openMiscFolderContext = (payload: { schema: string; type: string; event: PointerEvent }) => {
|
||||
selectedMisc.value = payload.type;
|
||||
selectedSchema.value = payload.schema;
|
||||
miscContextEvent.value = payload.event;
|
||||
isMiscFolderContext.value = true;
|
||||
};
|
||||
|
||||
const closeMiscContext = () => {
|
||||
isMiscContext.value = false;
|
||||
};
|
||||
|
||||
const closeMiscFolderContext = () => {
|
||||
isMiscFolderContext.value = false;
|
||||
};
|
||||
|
||||
const deleteTable = async (payload: { schema: string; table: { name: string; type: string }; event: PointerEvent }) => {
|
||||
closeTableContext();
|
||||
|
||||
addLoadingElement({
|
||||
name: payload.table.name,
|
||||
schema: payload.schema,
|
||||
type: 'table'
|
||||
});
|
||||
|
||||
try {
|
||||
let res;
|
||||
|
||||
if (payload.table.type === 'table') {
|
||||
res = await Tables.dropTable({
|
||||
uid: props.connection.uid,
|
||||
table: payload.table.name,
|
||||
schema: payload.schema
|
||||
});
|
||||
}
|
||||
else if (payload.table.type === 'view') {
|
||||
res = await Views.dropView({
|
||||
uid: props.connection.uid,
|
||||
view: payload.table.name,
|
||||
schema: payload.schema
|
||||
});
|
||||
}
|
||||
|
||||
const { status, response } = res;
|
||||
|
||||
if (status === 'success') {
|
||||
refresh();
|
||||
|
||||
removeTabs({
|
||||
uid: props.connection.uid as string,
|
||||
elementName: payload.table.name as string,
|
||||
elementType: payload.table.type,
|
||||
schema: payload.schema as string
|
||||
});
|
||||
}
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
removeLoadingElement({
|
||||
name: payload.table.name,
|
||||
schema: payload.schema,
|
||||
type: 'table'
|
||||
});
|
||||
};
|
||||
|
||||
const duplicateTable = async (payload: { schema: string; table: { name: string }; event: PointerEvent }) => {
|
||||
closeTableContext();
|
||||
|
||||
addLoadingElement({
|
||||
name: payload.table.name,
|
||||
schema: payload.schema,
|
||||
type: 'table'
|
||||
});
|
||||
|
||||
try {
|
||||
const { status, response } = await Tables.duplicateTable({
|
||||
uid: props.connection.uid,
|
||||
table: payload.table.name,
|
||||
schema: payload.schema
|
||||
});
|
||||
|
||||
if (status === 'success')
|
||||
refresh();
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
removeLoadingElement({
|
||||
name: payload.table.name,
|
||||
schema: payload.schema,
|
||||
type: 'table'
|
||||
});
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -4,7 +4,7 @@
|
||||
@close-context="closeContext"
|
||||
>
|
||||
<div
|
||||
v-if="['procedure', 'function'].includes(selectedMisc.type)"
|
||||
v-if="['procedure', 'routine', 'function'].includes(selectedMisc.type)"
|
||||
class="context-element"
|
||||
@click="runElementCheck"
|
||||
>
|
||||
@ -56,7 +56,7 @@
|
||||
</ConfirmModal>
|
||||
<ModalAskParameters
|
||||
v-if="isAskingParameters"
|
||||
:local-routine="localElement"
|
||||
:local-routine="(localElement as any)"
|
||||
:client="workspace.client"
|
||||
@confirm="runElement"
|
||||
@close="hideAskParamsModal"
|
||||
@ -64,324 +64,333 @@
|
||||
</BaseContextMenu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { computed, Prop, Ref, ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import ModalAskParameters from '@/components/ModalAskParameters';
|
||||
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||
import ModalAskParameters from '@/components/ModalAskParameters.vue';
|
||||
import Triggers from '@/ipc-api/Triggers';
|
||||
import Routines from '@/ipc-api/Routines';
|
||||
import Functions from '@/ipc-api/Functions';
|
||||
import Schedulers from '@/ipc-api/Schedulers';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { EventInfos, FunctionInfos, IpcResponse, RoutineInfos, TriggerInfos } from 'common/interfaces/antares';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceExploreBarMiscContext',
|
||||
components: {
|
||||
BaseContextMenu,
|
||||
ConfirmModal,
|
||||
ModalAskParameters
|
||||
},
|
||||
props: {
|
||||
contextEvent: MouseEvent,
|
||||
selectedMisc: Object,
|
||||
selectedSchema: String
|
||||
},
|
||||
emits: ['close-context', 'reload'],
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const props = defineProps({
|
||||
contextEvent: MouseEvent,
|
||||
selectedMisc: Object as Prop<{ name:string; type:string; enabled?: boolean }>,
|
||||
selectedSchema: String
|
||||
});
|
||||
|
||||
const {
|
||||
getWorkspace,
|
||||
changeBreadcrumbs,
|
||||
addLoadingElement,
|
||||
removeLoadingElement,
|
||||
removeTabs,
|
||||
newTab
|
||||
} = workspacesStore;
|
||||
const emit = defineEmits(['close-context', 'reload']);
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
changeBreadcrumbs,
|
||||
addLoadingElement,
|
||||
removeLoadingElement,
|
||||
removeTabs,
|
||||
newTab
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isDeleteModal: false,
|
||||
isRunModal: false,
|
||||
isAskingParameters: false,
|
||||
localElement: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
},
|
||||
customizations () {
|
||||
return this.getWorkspace(this.selectedWorkspace).customizations;
|
||||
},
|
||||
deleteMessage () {
|
||||
switch (this.selectedMisc.type) {
|
||||
case 'trigger':
|
||||
return this.$t('message.deleteTrigger');
|
||||
case 'procedure':
|
||||
return this.$t('message.deleteRoutine');
|
||||
case 'function':
|
||||
case 'triggerFunction':
|
||||
return this.$t('message.deleteFunction');
|
||||
case 'scheduler':
|
||||
return this.$t('message.deleteScheduler');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showDeleteModal () {
|
||||
this.isDeleteModal = true;
|
||||
},
|
||||
hideDeleteModal () {
|
||||
this.isDeleteModal = false;
|
||||
},
|
||||
showAskParamsModal () {
|
||||
this.isAskingParameters = true;
|
||||
},
|
||||
hideAskParamsModal () {
|
||||
this.isAskingParameters = false;
|
||||
this.closeContext();
|
||||
},
|
||||
closeContext () {
|
||||
this.$emit('close-context');
|
||||
},
|
||||
async deleteMisc () {
|
||||
try {
|
||||
let res;
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
switch (this.selectedMisc.type) {
|
||||
case 'trigger':
|
||||
res = await Triggers.dropTrigger({
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.selectedSchema,
|
||||
trigger: this.selectedMisc.name
|
||||
});
|
||||
break;
|
||||
case 'procedure':
|
||||
res = await Routines.dropRoutine({
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.selectedSchema,
|
||||
routine: this.selectedMisc.name
|
||||
});
|
||||
break;
|
||||
case 'function':
|
||||
case 'triggerFunction':
|
||||
res = await Functions.dropFunction({
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.selectedSchema,
|
||||
func: this.selectedMisc.name
|
||||
});
|
||||
break;
|
||||
case 'scheduler':
|
||||
res = await Schedulers.dropScheduler({
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.selectedSchema,
|
||||
scheduler: this.selectedMisc.name
|
||||
});
|
||||
break;
|
||||
}
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
const { status, response } = res;
|
||||
const {
|
||||
getWorkspace,
|
||||
addLoadingElement,
|
||||
removeLoadingElement,
|
||||
removeTabs,
|
||||
newTab
|
||||
} = workspacesStore;
|
||||
|
||||
if (status === 'success') {
|
||||
this.removeTabs({
|
||||
uid: this.selectedWorkspace,
|
||||
elementName: this.selectedMisc.name,
|
||||
elementType: this.selectedMisc.type,
|
||||
schema: this.selectedSchema
|
||||
});
|
||||
const isDeleteModal = ref(false);
|
||||
const isAskingParameters = ref(false);
|
||||
const localElement: Ref<TriggerInfos | RoutineInfos | FunctionInfos | EventInfos> = ref(null);
|
||||
|
||||
this.closeContext();
|
||||
this.$emit('reload');
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
},
|
||||
runElementCheck () {
|
||||
if (this.selectedMisc.type === 'procedure')
|
||||
this.runRoutineCheck();
|
||||
else if (this.selectedMisc.type === 'function')
|
||||
this.runFunctionCheck();
|
||||
},
|
||||
runElement (params) {
|
||||
if (this.selectedMisc.type === 'procedure')
|
||||
this.runRoutine(params);
|
||||
else if (this.selectedMisc.type === 'function')
|
||||
this.runFunction(params);
|
||||
},
|
||||
async runRoutineCheck () {
|
||||
const params = {
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.selectedSchema,
|
||||
routine: this.selectedMisc.name
|
||||
};
|
||||
const workspace = computed(() => {
|
||||
return getWorkspace(selectedWorkspace.value);
|
||||
});
|
||||
|
||||
try {
|
||||
const { status, response } = await Routines.getRoutineInformations(params);
|
||||
if (status === 'success')
|
||||
this.localElement = response;
|
||||
const customizations = computed(() => {
|
||||
return getWorkspace(selectedWorkspace.value).customizations;
|
||||
});
|
||||
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
const deleteMessage = computed(() => {
|
||||
switch (props.selectedMisc.type) {
|
||||
case 'trigger':
|
||||
return t('message.deleteTrigger');
|
||||
case 'procedure':
|
||||
return t('message.deleteRoutine');
|
||||
case 'function':
|
||||
case 'triggerFunction':
|
||||
return t('message.deleteFunction');
|
||||
case 'scheduler':
|
||||
return t('message.deleteScheduler');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
if (this.localElement.parameters.length)
|
||||
this.showAskParamsModal();
|
||||
else
|
||||
this.runRoutine();
|
||||
},
|
||||
runRoutine (params) {
|
||||
if (!params) params = [];
|
||||
const showDeleteModal = () => {
|
||||
isDeleteModal.value = true;
|
||||
};
|
||||
|
||||
let sql;
|
||||
switch (this.workspace.client) { // TODO: move in a better place
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
case 'pg':
|
||||
sql = `CALL ${this.localElement.name}(${params.join(',')})`;
|
||||
break;
|
||||
case 'mssql':
|
||||
sql = `EXEC ${this.localElement.name} ${params.join(',')}`;
|
||||
break;
|
||||
default:
|
||||
sql = `CALL \`${this.localElement.name}\`(${params.join(',')})`;
|
||||
}
|
||||
const hideDeleteModal = () => {
|
||||
isDeleteModal.value = false;
|
||||
};
|
||||
|
||||
this.newTab({ uid: this.workspace.uid, content: sql, type: 'query', autorun: true });
|
||||
this.closeContext();
|
||||
},
|
||||
async runFunctionCheck () {
|
||||
const params = {
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.selectedSchema,
|
||||
func: this.selectedMisc.name
|
||||
};
|
||||
const showAskParamsModal = () => {
|
||||
isAskingParameters.value = true;
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Functions.getFunctionInformations(params);
|
||||
if (status === 'success')
|
||||
this.localElement = response;
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
const hideAskParamsModal = () => {
|
||||
isAskingParameters.value = false;
|
||||
closeContext();
|
||||
};
|
||||
|
||||
if (this.localElement.parameters.length)
|
||||
this.showAskParamsModal();
|
||||
else
|
||||
this.runFunction();
|
||||
},
|
||||
runFunction (params) {
|
||||
if (!params) params = [];
|
||||
const closeContext = () => {
|
||||
emit('close-context');
|
||||
};
|
||||
|
||||
let sql;
|
||||
switch (this.workspace.client) { // TODO: move in a better place
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
sql = `SELECT \`${this.localElement.name}\` (${params.join(',')})`;
|
||||
break;
|
||||
case 'pg':
|
||||
sql = `SELECT ${this.localElement.name}(${params.join(',')})`;
|
||||
break;
|
||||
case 'mssql':
|
||||
sql = `SELECT ${this.localElement.name} ${params.join(',')}`;
|
||||
break;
|
||||
default:
|
||||
sql = `SELECT \`${this.localElement.name}\` (${params.join(',')})`;
|
||||
}
|
||||
const deleteMisc = async () => {
|
||||
try {
|
||||
let res: IpcResponse;
|
||||
|
||||
this.newTab({ uid: this.workspace.uid, content: sql, type: 'query', autorun: true });
|
||||
this.closeContext();
|
||||
},
|
||||
async toggleTrigger () {
|
||||
this.addLoadingElement({
|
||||
name: this.selectedMisc.name,
|
||||
schema: this.selectedSchema,
|
||||
type: 'trigger'
|
||||
});
|
||||
|
||||
try {
|
||||
const { status, response } = await Triggers.toggleTrigger({
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.selectedSchema,
|
||||
trigger: this.selectedMisc.name,
|
||||
enabled: this.selectedMisc.enabled
|
||||
switch (props.selectedMisc.type) {
|
||||
case 'trigger':
|
||||
res = await Triggers.dropTrigger({
|
||||
uid: selectedWorkspace.value,
|
||||
schema: props.selectedSchema,
|
||||
trigger: props.selectedMisc.name
|
||||
});
|
||||
|
||||
if (status !== 'success')
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.removeLoadingElement({
|
||||
name: this.selectedMisc.name,
|
||||
schema: this.selectedSchema,
|
||||
type: 'trigger'
|
||||
});
|
||||
|
||||
this.closeContext();
|
||||
this.$emit('reload');
|
||||
},
|
||||
async toggleScheduler () {
|
||||
this.addLoadingElement({
|
||||
name: this.selectedMisc.name,
|
||||
schema: this.selectedSchema,
|
||||
type: 'scheduler'
|
||||
});
|
||||
|
||||
try {
|
||||
const { status, response } = await Schedulers.toggleScheduler({
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.selectedSchema,
|
||||
scheduler: this.selectedMisc.name,
|
||||
enabled: this.selectedMisc.enabled
|
||||
break;
|
||||
case 'routine':
|
||||
case 'procedure':
|
||||
res = await Routines.dropRoutine({
|
||||
uid: selectedWorkspace.value,
|
||||
schema: props.selectedSchema,
|
||||
routine: props.selectedMisc.name
|
||||
});
|
||||
break;
|
||||
case 'function':
|
||||
case 'triggerFunction':
|
||||
res = await Functions.dropFunction({
|
||||
uid: selectedWorkspace.value,
|
||||
schema: props.selectedSchema,
|
||||
func: props.selectedMisc.name
|
||||
});
|
||||
break;
|
||||
case 'scheduler':
|
||||
res = await Schedulers.dropScheduler({
|
||||
uid: selectedWorkspace.value,
|
||||
schema: props.selectedSchema,
|
||||
scheduler: props.selectedMisc.name
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
if (status !== 'success')
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
console.log(res);
|
||||
|
||||
this.removeLoadingElement({
|
||||
name: this.selectedMisc.name,
|
||||
schema: this.selectedSchema,
|
||||
type: 'scheduler'
|
||||
const { status, response } = res;
|
||||
|
||||
if (status === 'success') {
|
||||
removeTabs({
|
||||
uid: selectedWorkspace.value,
|
||||
elementName: props.selectedMisc.name,
|
||||
elementType: props.selectedMisc.type,
|
||||
schema: props.selectedSchema
|
||||
});
|
||||
|
||||
this.closeContext();
|
||||
this.$emit('reload');
|
||||
closeContext();
|
||||
emit('reload');
|
||||
}
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
};
|
||||
|
||||
const runElementCheck = () => {
|
||||
if (['procedure', 'routine'].includes(props.selectedMisc.type))
|
||||
runRoutineCheck();
|
||||
else if (props.selectedMisc.type === 'function')
|
||||
runFunctionCheck();
|
||||
};
|
||||
|
||||
const runElement = (params: string[]) => {
|
||||
if (props.selectedMisc.type === 'procedure')
|
||||
runRoutine(params);
|
||||
else if (props.selectedMisc.type === 'function')
|
||||
runFunction(params);
|
||||
};
|
||||
|
||||
const runRoutineCheck = async () => {
|
||||
const params = {
|
||||
uid: selectedWorkspace.value,
|
||||
schema: props.selectedSchema,
|
||||
routine: props.selectedMisc.name
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Routines.getRoutineInformations(params);
|
||||
if (status === 'success')
|
||||
localElement.value = response;
|
||||
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
if ((localElement.value as RoutineInfos).parameters.length)
|
||||
showAskParamsModal();
|
||||
else
|
||||
runRoutine();
|
||||
};
|
||||
|
||||
const runRoutine = (params?: string[]) => {
|
||||
if (!params) params = [];
|
||||
|
||||
let sql;
|
||||
switch (workspace.value.client) { // TODO: move in a better place
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
case 'pg':
|
||||
sql = `CALL ${localElement.value.name}(${params.join(',')})`;
|
||||
break;
|
||||
// case 'mssql':
|
||||
// sql = `EXEC ${localElement.value.name} ${params.join(',')}`;
|
||||
// break;
|
||||
default:
|
||||
sql = `CALL \`${localElement.value.name}\`(${params.join(',')})`;
|
||||
}
|
||||
|
||||
newTab({
|
||||
uid: workspace.value.uid,
|
||||
content: sql,
|
||||
type: 'query',
|
||||
schema: props.selectedSchema,
|
||||
autorun: true
|
||||
});
|
||||
closeContext();
|
||||
};
|
||||
|
||||
const runFunctionCheck = async () => {
|
||||
const params = {
|
||||
uid: selectedWorkspace.value,
|
||||
schema: props.selectedSchema,
|
||||
func: props.selectedMisc.name
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Functions.getFunctionInformations(params);
|
||||
if (status === 'success')
|
||||
localElement.value = response;
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
if ((localElement.value as FunctionInfos).parameters.length)
|
||||
showAskParamsModal();
|
||||
else
|
||||
runFunction();
|
||||
};
|
||||
|
||||
const runFunction = (params?: string[]) => {
|
||||
if (!params) params = [];
|
||||
|
||||
let sql;
|
||||
switch (workspace.value.client) { // TODO: move in a better place
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
sql = `SELECT \`${localElement.value.name}\` (${params.join(',')})`;
|
||||
break;
|
||||
case 'pg':
|
||||
sql = `SELECT ${localElement.value.name}(${params.join(',')})`;
|
||||
break;
|
||||
// case 'mssql':
|
||||
// sql = `SELECT ${localElement.value.name} ${params.join(',')}`;
|
||||
// break;
|
||||
default:
|
||||
sql = `SELECT \`${localElement.value.name}\` (${params.join(',')})`;
|
||||
}
|
||||
|
||||
newTab({
|
||||
uid: workspace.value.uid,
|
||||
content: sql,
|
||||
type: 'query',
|
||||
schema: props.selectedSchema,
|
||||
autorun: true
|
||||
});
|
||||
closeContext();
|
||||
};
|
||||
|
||||
const toggleTrigger = async () => {
|
||||
addLoadingElement({
|
||||
name: props.selectedMisc.name,
|
||||
schema: props.selectedSchema,
|
||||
type: 'trigger'
|
||||
});
|
||||
|
||||
try {
|
||||
const { status, response } = await Triggers.toggleTrigger({
|
||||
uid: selectedWorkspace.value,
|
||||
schema: props.selectedSchema,
|
||||
trigger: props.selectedMisc.name,
|
||||
enabled: props.selectedMisc.enabled
|
||||
});
|
||||
|
||||
if (status !== 'success')
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
removeLoadingElement({
|
||||
name: props.selectedMisc.name,
|
||||
schema: props.selectedSchema,
|
||||
type: 'trigger'
|
||||
});
|
||||
|
||||
closeContext();
|
||||
emit('reload');
|
||||
};
|
||||
|
||||
const toggleScheduler = async () => {
|
||||
addLoadingElement({
|
||||
name: props.selectedMisc.name,
|
||||
schema: props.selectedSchema,
|
||||
type: 'scheduler'
|
||||
});
|
||||
|
||||
try {
|
||||
const { status, response } = await Schedulers.toggleScheduler({
|
||||
uid: selectedWorkspace.value,
|
||||
schema: props.selectedSchema,
|
||||
scheduler: props.selectedMisc.name,
|
||||
enabled: props.selectedMisc.enabled
|
||||
});
|
||||
|
||||
if (status !== 'success')
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
removeLoadingElement({
|
||||
name: props.selectedMisc.name,
|
||||
schema: props.selectedSchema,
|
||||
type: 'scheduler'
|
||||
});
|
||||
|
||||
closeContext();
|
||||
emit('reload');
|
||||
};
|
||||
</script>
|
||||
|
@ -1,112 +1,68 @@
|
||||
<template>
|
||||
<BaseContextMenu
|
||||
:context-event="contextEvent"
|
||||
:context-event="props.contextEvent"
|
||||
@close-context="closeContext"
|
||||
>
|
||||
<div
|
||||
v-if="selectedMisc === 'trigger'"
|
||||
v-if="props.selectedMisc === 'trigger'"
|
||||
class="context-element"
|
||||
@click="$emit('open-create-trigger-tab')"
|
||||
@click="emit('open-create-trigger-tab')"
|
||||
>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-table-cog text-light pr-1" /> {{ $t('message.createNewTrigger') }}</span>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-table-cog text-light pr-1" /> {{ t('message.createNewTrigger') }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedMisc === 'procedure'"
|
||||
v-if="['procedure', 'routine'].includes(props.selectedMisc)"
|
||||
class="context-element"
|
||||
@click="$emit('open-create-routine-tab')"
|
||||
@click="emit('open-create-routine-tab')"
|
||||
>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-sync-circle text-light pr-1" /> {{ $t('message.createNewRoutine') }}</span>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-sync-circle text-light pr-1" /> {{ t('message.createNewRoutine') }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedMisc === 'function'"
|
||||
v-if="props.selectedMisc === 'function'"
|
||||
class="context-element"
|
||||
@click="$emit('open-create-function-tab')"
|
||||
@click="emit('open-create-function-tab')"
|
||||
>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-arrow-right-bold-box text-light pr-1" /> {{ $t('message.createNewFunction') }}</span>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-arrow-right-bold-box text-light pr-1" /> {{ t('message.createNewFunction') }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedMisc === 'triggerFunction'"
|
||||
v-if="props.selectedMisc === 'triggerFunction'"
|
||||
class="context-element"
|
||||
@click="$emit('open-create-trigger-function-tab')"
|
||||
@click="emit('open-create-trigger-function-tab')"
|
||||
>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-cog-clockwise text-light pr-1" /> {{ $t('message.createNewFunction') }}</span>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-cog-clockwise text-light pr-1" /> {{ t('message.createNewFunction') }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedMisc === 'scheduler'"
|
||||
v-if="props.selectedMisc === 'scheduler'"
|
||||
class="context-element"
|
||||
@click="$emit('open-create-scheduler-tab')"
|
||||
@click="emit('open-create-scheduler-tab')"
|
||||
>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-calendar-clock text-light pr-1" /> {{ $t('message.createNewScheduler') }}</span>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-calendar-clock text-light pr-1" /> {{ t('message.createNewScheduler') }}</span>
|
||||
</div>
|
||||
</BaseContextMenu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
||||
import { storeToRefs } from 'pinia';
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceExploreBarMiscContext',
|
||||
components: {
|
||||
BaseContextMenu
|
||||
},
|
||||
props: {
|
||||
contextEvent: MouseEvent,
|
||||
selectedMisc: String,
|
||||
selectedSchema: String
|
||||
},
|
||||
emits: [
|
||||
'open-create-trigger-tab',
|
||||
'open-create-routine-tab',
|
||||
'open-create-function-tab',
|
||||
'open-create-trigger-function-tab',
|
||||
'open-create-scheduler-tab',
|
||||
'close-context'
|
||||
],
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const props = defineProps({
|
||||
contextEvent: MouseEvent,
|
||||
selectedMisc: String,
|
||||
selectedSchema: String
|
||||
});
|
||||
|
||||
const { getWorkspace, changeBreadcrumbs } = workspacesStore;
|
||||
const emit = defineEmits([
|
||||
'open-create-trigger-tab',
|
||||
'open-create-routine-tab',
|
||||
'open-create-function-tab',
|
||||
'open-create-trigger-function-tab',
|
||||
'open-create-scheduler-tab',
|
||||
'close-context'
|
||||
]);
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
changeBreadcrumbs
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localElement: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showDeleteModal () {
|
||||
this.isDeleteModal = true;
|
||||
},
|
||||
hideDeleteModal () {
|
||||
this.isDeleteModal = false;
|
||||
},
|
||||
showAskParamsModal () {
|
||||
this.isAskingParameters = true;
|
||||
},
|
||||
hideAskParamsModal () {
|
||||
this.isAskingParameters = false;
|
||||
this.closeContext();
|
||||
},
|
||||
closeContext () {
|
||||
this.$emit('close-context');
|
||||
}
|
||||
}
|
||||
const closeContext = () => {
|
||||
emit('close-context');
|
||||
};
|
||||
</script>
|
||||
|
@ -25,7 +25,7 @@
|
||||
<ul class="menu menu-nav pt-0">
|
||||
<li
|
||||
v-for="table of filteredTables"
|
||||
:ref="breadcrumbs.schema === database.name && [breadcrumbs.table, breadcrumbs.view].includes(table.name) ? 'explorebar-selected' : ''"
|
||||
:ref="breadcrumbs.schema === database.name && [breadcrumbs.table, breadcrumbs.view].includes(table.name) ? 'explorebarSelected' : ''"
|
||||
:key="table.name"
|
||||
class="menu-item"
|
||||
:class="{'selected': breadcrumbs.schema === database.name && [breadcrumbs.table, breadcrumbs.view].includes(table.name)}"
|
||||
@ -61,7 +61,7 @@
|
||||
@contextmenu.prevent="showMiscFolderContext($event, 'trigger')"
|
||||
>
|
||||
<i class="misc-icon mdi mdi-18px mdi-folder-cog mr-1" />
|
||||
{{ $tc('word.trigger', 2) }}
|
||||
{{ t('word.trigger', 2) }}
|
||||
</summary>
|
||||
<div class="accordion-body">
|
||||
<div>
|
||||
@ -69,7 +69,7 @@
|
||||
<li
|
||||
v-for="trigger of filteredTriggers"
|
||||
:key="trigger.name"
|
||||
:ref="breadcrumbs.schema === database.name && breadcrumbs.trigger === trigger.name ? 'explorebar-selected' : ''"
|
||||
:ref="breadcrumbs.schema === database.name && breadcrumbs.trigger === trigger.name ? 'explorebarSelected' : ''"
|
||||
class="menu-item"
|
||||
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.trigger === trigger.name}"
|
||||
@mousedown.left="selectMisc({schema: database.name, misc: trigger, type: 'trigger'})"
|
||||
@ -84,7 +84,7 @@
|
||||
<div
|
||||
v-if="trigger.enabled === false"
|
||||
class="tooltip tooltip-left disabled-indicator"
|
||||
:data-tooltip="$t('word.disabled')"
|
||||
:data-tooltip="t('word.disabled')"
|
||||
>
|
||||
<i class="table-icon mdi mdi-pause mdi-18px mr-1" />
|
||||
</div>
|
||||
@ -100,27 +100,27 @@
|
||||
<summary
|
||||
class="accordion-header misc-name"
|
||||
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.routine}"
|
||||
@contextmenu.prevent="showMiscFolderContext($event, 'procedure')"
|
||||
@contextmenu.prevent="showMiscFolderContext($event, 'routine')"
|
||||
>
|
||||
<i class="misc-icon mdi mdi-18px mdi-folder-sync mr-1" />
|
||||
{{ $tc('word.storedRoutine', 2) }}
|
||||
{{ t('word.storedRoutine', 2) }}
|
||||
</summary>
|
||||
<div class="accordion-body">
|
||||
<div>
|
||||
<ul class="menu menu-nav pt-0">
|
||||
<li
|
||||
v-for="(procedure, i) of filteredProcedures"
|
||||
:key="`${procedure.name}-${i}`"
|
||||
:ref="breadcrumbs.schema === database.name && breadcrumbs.routine === procedure.name ? 'explorebar-selected' : ''"
|
||||
v-for="(routine, i) of filteredProcedures"
|
||||
:key="`${routine.name}-${i}`"
|
||||
:ref="breadcrumbs.schema === database.name && breadcrumbs.routine === routine.name ? 'explorebarSelected' : ''"
|
||||
class="menu-item"
|
||||
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.routine === procedure.name}"
|
||||
@mousedown.left="selectMisc({schema: database.name, misc: procedure, type: 'routine'})"
|
||||
@dblclick="openMiscPermanentTab({schema: database.name, misc: procedure, type: 'routine'})"
|
||||
@contextmenu.prevent="showMiscContext($event, {...procedure, type: 'procedure'})"
|
||||
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.routine === routine.name}"
|
||||
@mousedown.left="selectMisc({schema: database.name, misc: routine, type: 'routine'})"
|
||||
@dblclick="openMiscPermanentTab({schema: database.name, misc: routine, type: 'routine'})"
|
||||
@contextmenu.prevent="showMiscContext($event, {...routine, type: 'routine'})"
|
||||
>
|
||||
<a class="table-name">
|
||||
<i class="table-icon mdi mdi-sync-circle mdi-18px mr-1" />
|
||||
<span v-html="highlightWord(procedure.name)" />
|
||||
<span v-html="highlightWord(routine.name)" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@ -137,7 +137,7 @@
|
||||
@contextmenu.prevent="showMiscFolderContext($event, 'triggerFunction')"
|
||||
>
|
||||
<i class="misc-icon mdi mdi-18px mdi-folder-refresh mr-1" />
|
||||
{{ $tc('word.triggerFunction', 2) }}
|
||||
{{ t('word.triggerFunction', 2) }}
|
||||
</summary>
|
||||
<div class="accordion-body">
|
||||
<div>
|
||||
@ -145,7 +145,7 @@
|
||||
<li
|
||||
v-for="(func, i) of filteredTriggerFunctions"
|
||||
:key="`${func.name}-${i}`"
|
||||
:ref="breadcrumbs.schema === database.name && breadcrumbs.triggerFunction === func.name ? 'explorebar-selected' : ''"
|
||||
:ref="breadcrumbs.schema === database.name && breadcrumbs.triggerFunction === func.name ? 'explorebarSelected' : ''"
|
||||
class="menu-item"
|
||||
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.triggerFunction === func.name}"
|
||||
@mousedown.left="selectMisc({schema: database.name, misc: func, type: 'triggerFunction'})"
|
||||
@ -171,7 +171,7 @@
|
||||
@contextmenu.prevent="showMiscFolderContext($event, 'function')"
|
||||
>
|
||||
<i class="misc-icon mdi mdi-18px mdi-folder-move mr-1" />
|
||||
{{ $tc('word.function', 2) }}
|
||||
{{ t('word.function', 2) }}
|
||||
</summary>
|
||||
<div class="accordion-body">
|
||||
<div>
|
||||
@ -179,7 +179,7 @@
|
||||
<li
|
||||
v-for="(func, i) of filteredFunctions"
|
||||
:key="`${func.name}-${i}`"
|
||||
:ref="breadcrumbs.schema === database.name && breadcrumbs.function === func.name ? 'explorebar-selected' : ''"
|
||||
:ref="breadcrumbs.schema === database.name && breadcrumbs.function === func.name ? 'explorebarSelected' : ''"
|
||||
class="menu-item"
|
||||
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.function === func.name}"
|
||||
@mousedown.left="selectMisc({schema: database.name, misc: func, type: 'function'})"
|
||||
@ -205,7 +205,7 @@
|
||||
@contextmenu.prevent="showMiscFolderContext($event, 'scheduler')"
|
||||
>
|
||||
<i class="misc-icon mdi mdi-18px mdi-folder-clock mr-1" />
|
||||
{{ $tc('word.scheduler', 2) }}
|
||||
{{ t('word.scheduler', 2) }}
|
||||
</summary>
|
||||
<div class="accordion-body">
|
||||
<div>
|
||||
@ -213,7 +213,7 @@
|
||||
<li
|
||||
v-for="scheduler of filteredSchedulers"
|
||||
:key="scheduler.name"
|
||||
:ref="breadcrumbs.schema === database.name && breadcrumbs.scheduler === scheduler.name ? 'explorebar-selected' : ''"
|
||||
:ref="breadcrumbs.schema === database.name && breadcrumbs.scheduler === scheduler.name ? 'explorebarSelected' : ''"
|
||||
class="menu-item"
|
||||
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.scheduler === scheduler.name}"
|
||||
@mousedown.left="selectMisc({schema: database.name, misc: scheduler, type: 'scheduler'})"
|
||||
@ -228,7 +228,7 @@
|
||||
<div
|
||||
v-if="scheduler.enabled === false"
|
||||
class="tooltip tooltip-left disabled-indicator"
|
||||
:data-tooltip="$t('word.disabled')"
|
||||
:data-tooltip="t('word.disabled')"
|
||||
>
|
||||
<i class="table-icon mdi mdi-pause mdi-18px mr-1" />
|
||||
</div>
|
||||
@ -242,226 +242,235 @@
|
||||
</details>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import { formatBytes } from 'common/libs/formatBytes';
|
||||
<script setup lang="ts">
|
||||
import { computed, Prop, Ref, ref, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { Breadcrumb, useWorkspacesStore, WorkspaceStructure } from '@/stores/workspaces';
|
||||
import { formatBytes } from 'common/libs/formatBytes';
|
||||
import { EventInfos, FunctionInfos, RoutineInfos, TableInfos, TriggerFunctionInfos, TriggerInfos } from 'common/interfaces/antares';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceExploreBarSchema',
|
||||
props: {
|
||||
database: Object,
|
||||
connection: Object
|
||||
},
|
||||
emits: [
|
||||
'show-schema-context',
|
||||
'show-table-context',
|
||||
'show-misc-context',
|
||||
'show-misc-folder-context'
|
||||
],
|
||||
setup () {
|
||||
const settingsStore = useSettingsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { applicationTheme } = storeToRefs(settingsStore);
|
||||
const props = defineProps({
|
||||
database: Object as Prop<WorkspaceStructure>,
|
||||
connection: Object
|
||||
});
|
||||
|
||||
const {
|
||||
getLoadedSchemas,
|
||||
getWorkspace,
|
||||
getSearchTerm,
|
||||
changeBreadcrumbs,
|
||||
addLoadedSchema,
|
||||
newTab,
|
||||
refreshSchema
|
||||
} = workspacesStore;
|
||||
const emit = defineEmits([
|
||||
'show-schema-context',
|
||||
'show-table-context',
|
||||
'show-misc-context',
|
||||
'show-misc-folder-context'
|
||||
]);
|
||||
|
||||
return {
|
||||
applicationTheme,
|
||||
getLoadedSchemas,
|
||||
getWorkspace,
|
||||
getSearchTerm,
|
||||
changeBreadcrumbs,
|
||||
addLoadedSchema,
|
||||
newTab,
|
||||
refreshSchema
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoading: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
searchTerm () {
|
||||
return this.getSearchTerm(this.connection.uid);
|
||||
},
|
||||
filteredTables () {
|
||||
return this.database.tables.filter(table => table.name.search(this.searchTerm) >= 0);
|
||||
},
|
||||
filteredTriggers () {
|
||||
return this.database.triggers.filter(trigger => trigger.name.search(this.searchTerm) >= 0);
|
||||
},
|
||||
filteredProcedures () {
|
||||
return this.database.procedures.filter(procedure => procedure.name.search(this.searchTerm) >= 0);
|
||||
},
|
||||
filteredFunctions () {
|
||||
return this.database.functions.filter(func => func.name.search(this.searchTerm) >= 0);
|
||||
},
|
||||
filteredTriggerFunctions () {
|
||||
return this.database.triggerFunctions
|
||||
? this.database.triggerFunctions.filter(func => func.name.search(this.searchTerm) >= 0)
|
||||
: [];
|
||||
},
|
||||
filteredSchedulers () {
|
||||
return this.database.schedulers.filter(scheduler => scheduler.name.search(this.searchTerm) >= 0);
|
||||
},
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
breadcrumbs () {
|
||||
return this.workspace.breadcrumbs;
|
||||
},
|
||||
customizations () {
|
||||
return this.workspace.customizations;
|
||||
},
|
||||
loadedSchemas () {
|
||||
return this.getLoadedSchemas(this.connection.uid);
|
||||
},
|
||||
maxSize () {
|
||||
return this.database.tables.reduce((acc, curr) => {
|
||||
if (curr.size > acc) acc = curr.size;
|
||||
return acc;
|
||||
}, 0);
|
||||
},
|
||||
totalSize () {
|
||||
return this.database.tables.reduce((acc, curr) => acc + curr.size, 0);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
breadcrumbs (newVal, oldVal) {
|
||||
if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
|
||||
setTimeout(() => {
|
||||
const element = this.$refs['explorebar-selected'] ? this.$refs['explorebar-selected'][0] : null;
|
||||
if (element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const elemTop = rect.top;
|
||||
const elemBottom = rect.bottom;
|
||||
const isVisible = (elemTop >= 0) && (elemBottom <= window.innerHeight);
|
||||
const settingsStore = useSettingsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
if (!isVisible) {
|
||||
element.setAttribute('tabindex', '-1');
|
||||
element.focus();
|
||||
element.removeAttribute('tabindex');
|
||||
}
|
||||
}
|
||||
}, 50);
|
||||
const { applicationTheme } = storeToRefs(settingsStore);
|
||||
|
||||
const {
|
||||
getLoadedSchemas,
|
||||
getWorkspace,
|
||||
getSearchTerm,
|
||||
changeBreadcrumbs,
|
||||
addLoadedSchema,
|
||||
newTab,
|
||||
refreshSchema
|
||||
} = workspacesStore;
|
||||
|
||||
const schemaAccordion: Ref<HTMLDetailsElement> = ref(null);
|
||||
const explorebarSelected: Ref<HTMLElement[]> = ref(null);
|
||||
const isLoading = ref(false);
|
||||
|
||||
const searchTerm = computed(() => {
|
||||
return getSearchTerm(props.connection.uid);
|
||||
});
|
||||
|
||||
const filteredTables = computed(() => {
|
||||
return props.database.tables.filter(table => table.name.search(searchTerm.value) >= 0);
|
||||
});
|
||||
|
||||
const filteredTriggers = computed(() => {
|
||||
return props.database.triggers.filter(trigger => trigger.name.search(searchTerm.value) >= 0);
|
||||
});
|
||||
|
||||
const filteredProcedures = computed(() => {
|
||||
return props.database.procedures.filter(procedure => procedure.name.search(searchTerm.value) >= 0);
|
||||
});
|
||||
|
||||
const filteredFunctions = computed(() => {
|
||||
return props.database.functions.filter(func => func.name.search(searchTerm.value) >= 0);
|
||||
});
|
||||
|
||||
const filteredTriggerFunctions = computed(() => {
|
||||
return props.database.triggerFunctions
|
||||
? props.database.triggerFunctions.filter(func => func.name.search(searchTerm.value) >= 0)
|
||||
: [];
|
||||
});
|
||||
|
||||
const filteredSchedulers = computed(() => {
|
||||
return props.database.schedulers.filter(scheduler => scheduler.name.search(searchTerm.value) >= 0);
|
||||
});
|
||||
|
||||
const workspace = computed(() => {
|
||||
return getWorkspace(props.connection.uid);
|
||||
});
|
||||
|
||||
const breadcrumbs = computed(() => {
|
||||
return workspace.value.breadcrumbs;
|
||||
});
|
||||
|
||||
const customizations = computed(() => {
|
||||
return workspace.value.customizations;
|
||||
});
|
||||
|
||||
const loadedSchemas = computed(() => {
|
||||
return getLoadedSchemas(props.connection.uid);
|
||||
});
|
||||
|
||||
const maxSize = computed(() => {
|
||||
return props.database.tables.reduce((acc: number, curr) => {
|
||||
if (curr.size && curr.size > acc) acc = curr.size;
|
||||
return acc;
|
||||
}, 0);
|
||||
});
|
||||
|
||||
watch(breadcrumbs, (newVal, oldVal) => {
|
||||
if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
|
||||
setTimeout(() => {
|
||||
const element = explorebarSelected.value ? explorebarSelected.value[0] : null;
|
||||
if (element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const elemTop = rect.top;
|
||||
const elemBottom = rect.bottom;
|
||||
const isVisible = (elemTop >= 0) && (elemBottom <= window.innerHeight);
|
||||
|
||||
if (!isVisible) {
|
||||
element.setAttribute('tabindex', '-1');
|
||||
element.focus();
|
||||
element.removeAttribute('tabindex');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatBytes,
|
||||
async selectSchema (schema) {
|
||||
if (!this.loadedSchemas.has(schema) && !this.isLoading) {
|
||||
this.isLoading = true;
|
||||
await this.refreshSchema({ uid: this.connection.uid, schema });
|
||||
this.addLoadedSchema(schema);
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
selectTable ({ schema, table }) {
|
||||
this.newTab({
|
||||
uid: this.connection.uid,
|
||||
elementName: table.name,
|
||||
schema: this.database.name,
|
||||
type: 'temp-data',
|
||||
elementType: table.type
|
||||
});
|
||||
}, 50);
|
||||
}
|
||||
});
|
||||
|
||||
this.setBreadcrumbs({ schema, [table.type]: table.name });
|
||||
},
|
||||
selectMisc ({ schema, misc, type }) {
|
||||
const miscTempTabs = {
|
||||
trigger: 'temp-trigger-props',
|
||||
triggerFunction: 'temp-trigger-function-props',
|
||||
function: 'temp-function-props',
|
||||
routine: 'temp-routine-props',
|
||||
scheduler: 'temp-scheduler-props'
|
||||
};
|
||||
|
||||
this.newTab({
|
||||
uid: this.connection.uid,
|
||||
elementName: misc.name,
|
||||
schema: this.database.name,
|
||||
type: miscTempTabs[type],
|
||||
elementType: type
|
||||
});
|
||||
|
||||
this.setBreadcrumbs({ schema, [type]: misc.name });
|
||||
},
|
||||
openDataTab ({ schema, table }) {
|
||||
this.newTab({ uid: this.connection.uid, elementName: table.name, schema: this.database.name, type: 'data', elementType: table.type });
|
||||
this.setBreadcrumbs({ schema, [table.type]: table.name });
|
||||
},
|
||||
openMiscPermanentTab ({ schema, misc, type }) {
|
||||
const miscTabs = {
|
||||
trigger: 'trigger-props',
|
||||
triggerFunction: 'trigger-function-props',
|
||||
function: 'function-props',
|
||||
routine: 'routine-props',
|
||||
scheduler: 'scheduler-props'
|
||||
};
|
||||
|
||||
this.newTab({
|
||||
uid: this.connection.uid,
|
||||
elementName: misc.name,
|
||||
schema: this.database.name,
|
||||
type: miscTabs[type],
|
||||
elementType: type
|
||||
});
|
||||
this.setBreadcrumbs({ schema, [type]: misc.name });
|
||||
},
|
||||
showSchemaContext (event, schema) {
|
||||
this.$emit('show-schema-context', { event, schema });
|
||||
},
|
||||
showTableContext (event, table) {
|
||||
this.$emit('show-table-context', { event, schema: this.database.name, table });
|
||||
},
|
||||
showMiscContext (event, misc) {
|
||||
this.$emit('show-misc-context', { event, schema: this.database.name, misc });
|
||||
},
|
||||
showMiscFolderContext (event, type) {
|
||||
this.$emit('show-misc-folder-context', { event, schema: this.database.name, type });
|
||||
},
|
||||
piePercentage (val) {
|
||||
const perc = val / this.maxSize * 100;
|
||||
if (this.applicationTheme === 'dark')
|
||||
return { background: `conic-gradient(lime ${perc}%, white 0)` };
|
||||
else
|
||||
return { background: `conic-gradient(teal ${perc}%, silver 0)` };
|
||||
},
|
||||
setBreadcrumbs (payload) {
|
||||
if (this.breadcrumbs.schema === payload.schema && this.breadcrumbs.table === payload.table) return;
|
||||
this.changeBreadcrumbs(payload);
|
||||
},
|
||||
highlightWord (string) {
|
||||
string = string.replaceAll('<', '<').replaceAll('>', '>');
|
||||
|
||||
if (this.searchTerm) {
|
||||
const regexp = new RegExp(`(${this.searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
||||
return string.replace(regexp, '<span class="text-primary">$1</span>');
|
||||
}
|
||||
else
|
||||
return string;
|
||||
},
|
||||
checkLoadingStatus (name, type) {
|
||||
return this.workspace.loadingElements.some(el =>
|
||||
el.name === name &&
|
||||
el.type === type &&
|
||||
el.schema === this.database.name);
|
||||
}
|
||||
const selectSchema = async (schema: string) => {
|
||||
if (!loadedSchemas.value.has(schema) && !isLoading.value) {
|
||||
isLoading.value = true;
|
||||
setBreadcrumbs({ schema });
|
||||
await refreshSchema({ uid: props.connection.uid, schema });
|
||||
addLoadedSchema(schema);
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const selectTable = ({ schema, table }: { schema: string; table: TableInfos }) => {
|
||||
newTab({
|
||||
uid: props.connection.uid,
|
||||
elementName: table.name,
|
||||
schema: props.database.name,
|
||||
type: 'temp-data',
|
||||
elementType: table.type
|
||||
});
|
||||
|
||||
setBreadcrumbs({ schema, [table.type]: table.name });
|
||||
};
|
||||
|
||||
const selectMisc = ({ schema, misc, type }: { schema: string; misc: { name: string }; type: 'trigger' | 'triggerFunction' | 'function' | 'routine' | 'scheduler' }) => {
|
||||
const miscTempTabs = {
|
||||
trigger: 'temp-trigger-props',
|
||||
triggerFunction: 'temp-trigger-function-props',
|
||||
function: 'temp-function-props',
|
||||
routine: 'temp-routine-props',
|
||||
scheduler: 'temp-scheduler-props'
|
||||
};
|
||||
|
||||
newTab({
|
||||
uid: props.connection.uid,
|
||||
elementName: misc.name,
|
||||
schema: props.database.name,
|
||||
type: miscTempTabs[type],
|
||||
elementType: type
|
||||
});
|
||||
|
||||
setBreadcrumbs({ schema, [type]: misc.name });
|
||||
};
|
||||
|
||||
const openDataTab = ({ schema, table }: { schema: string; table: TableInfos }) => {
|
||||
newTab({ uid: props.connection.uid, elementName: table.name, schema: props.database.name, type: 'data', elementType: table.type });
|
||||
setBreadcrumbs({ schema, [table.type]: table.name });
|
||||
};
|
||||
|
||||
const openMiscPermanentTab = ({ schema, misc, type }: { schema: string; misc: { name: string }; type: 'trigger' | 'triggerFunction' | 'function' | 'routine' | 'scheduler' }) => {
|
||||
const miscTabs = {
|
||||
trigger: 'trigger-props',
|
||||
triggerFunction: 'trigger-function-props',
|
||||
function: 'function-props',
|
||||
routine: 'routine-props',
|
||||
scheduler: 'scheduler-props'
|
||||
};
|
||||
|
||||
newTab({
|
||||
uid: props.connection.uid,
|
||||
elementName: misc.name,
|
||||
schema: props.database.name,
|
||||
type: miscTabs[type],
|
||||
elementType: type
|
||||
});
|
||||
setBreadcrumbs({ schema, [type]: misc.name });
|
||||
};
|
||||
|
||||
const showSchemaContext = (event: MouseEvent, schema: string) => {
|
||||
emit('show-schema-context', { event, schema });
|
||||
};
|
||||
|
||||
const showTableContext = (event: MouseEvent, table: TableInfos) => {
|
||||
emit('show-table-context', { event, schema: props.database.name, table });
|
||||
};
|
||||
|
||||
const showMiscContext = (event: MouseEvent, misc: TriggerInfos | TriggerFunctionInfos | RoutineInfos | FunctionInfos | EventInfos) => {
|
||||
emit('show-misc-context', { event, schema: props.database.name, misc });
|
||||
};
|
||||
|
||||
const showMiscFolderContext = (event: MouseEvent, type: string) => {
|
||||
emit('show-misc-folder-context', { event, schema: props.database.name, type });
|
||||
};
|
||||
|
||||
const piePercentage = (val: number) => {
|
||||
const perc = val / maxSize.value * 100;
|
||||
if (applicationTheme.value === 'dark')
|
||||
return { background: `conic-gradient(lime ${perc}%, white 0)` };
|
||||
else
|
||||
return { background: `conic-gradient(teal ${perc}%, silver 0)` };
|
||||
};
|
||||
|
||||
const setBreadcrumbs = (payload: Breadcrumb) => {
|
||||
if (breadcrumbs.value.schema === payload.schema && breadcrumbs.value.table === payload.table) return;
|
||||
changeBreadcrumbs(payload);
|
||||
};
|
||||
|
||||
const highlightWord = (string: string) => {
|
||||
string = string.replaceAll('<', '<').replaceAll('>', '>');
|
||||
|
||||
if (searchTerm.value) {
|
||||
const regexp = new RegExp(`(${searchTerm.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
||||
return string.replace(regexp, '<span class="text-primary">$1</span>');
|
||||
}
|
||||
else
|
||||
return string;
|
||||
};
|
||||
|
||||
const checkLoadingStatus = (name: string, type: string) => {
|
||||
return workspace.value.loadingElements.some(el =>
|
||||
el.name === name &&
|
||||
el.type === type &&
|
||||
el.schema === props.database.name);
|
||||
};
|
||||
|
||||
defineExpose({ selectSchema, schemaAccordion });
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -123,159 +123,154 @@
|
||||
</BaseContextMenu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { Component, computed, nextTick, Ref, ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import ModalEditSchema from '@/components/ModalEditSchema';
|
||||
import ModalExportSchema from '@/components/ModalExportSchema';
|
||||
import ModalImportSchema from '@/components/ModalImportSchema';
|
||||
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||
import ModalEditSchema from '@/components/ModalEditSchema.vue';
|
||||
import ModalExportSchema from '@/components/ModalExportSchema.vue';
|
||||
import ModalImportSchema from '@/components/ModalImportSchema.vue';
|
||||
import Schema from '@/ipc-api/Schema';
|
||||
import Application from '@/ipc-api/Application';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceExploreBarSchemaContext',
|
||||
components: {
|
||||
BaseContextMenu,
|
||||
ConfirmModal,
|
||||
ModalEditSchema,
|
||||
ModalExportSchema,
|
||||
ModalImportSchema
|
||||
},
|
||||
props: {
|
||||
contextEvent: MouseEvent,
|
||||
selectedSchema: String
|
||||
},
|
||||
emits: [
|
||||
'open-create-table-tab',
|
||||
'open-create-view-tab',
|
||||
'open-create-trigger-tab',
|
||||
'open-create-routine-tab',
|
||||
'open-create-function-tab',
|
||||
'open-create-trigger-function-tab',
|
||||
'open-create-scheduler-tab',
|
||||
'close-context',
|
||||
'reload'
|
||||
],
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const props = defineProps({
|
||||
contextEvent: MouseEvent,
|
||||
selectedSchema: String
|
||||
});
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const emit = defineEmits([
|
||||
'open-create-table-tab',
|
||||
'open-create-view-tab',
|
||||
'open-create-trigger-tab',
|
||||
'open-create-routine-tab',
|
||||
'open-create-function-tab',
|
||||
'open-create-trigger-function-tab',
|
||||
'open-create-scheduler-tab',
|
||||
'close-context',
|
||||
'reload'
|
||||
]);
|
||||
|
||||
const {
|
||||
getWorkspace,
|
||||
changeBreadcrumbs
|
||||
} = workspacesStore;
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
changeBreadcrumbs
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isDeleteModal: false,
|
||||
isEditModal: false,
|
||||
isExportSchemaModal: false,
|
||||
isImportSchemaModal: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openCreateTableTab () {
|
||||
this.$emit('open-create-table-tab');
|
||||
},
|
||||
openCreateViewTab () {
|
||||
this.$emit('open-create-view-tab');
|
||||
},
|
||||
openCreateTriggerTab () {
|
||||
this.$emit('open-create-trigger-tab');
|
||||
},
|
||||
openCreateRoutineTab () {
|
||||
this.$emit('open-create-routine-tab');
|
||||
},
|
||||
openCreateFunctionTab () {
|
||||
this.$emit('open-create-function-tab');
|
||||
},
|
||||
openCreateTriggerFunctionTab () {
|
||||
this.$emit('open-create-trigger-function-tab');
|
||||
},
|
||||
openCreateSchedulerTab () {
|
||||
this.$emit('open-create-scheduler-tab');
|
||||
},
|
||||
showDeleteModal () {
|
||||
this.isDeleteModal = true;
|
||||
},
|
||||
hideDeleteModal () {
|
||||
this.isDeleteModal = false;
|
||||
},
|
||||
showEditModal () {
|
||||
this.isEditModal = true;
|
||||
},
|
||||
hideEditModal () {
|
||||
this.isEditModal = false;
|
||||
this.closeContext();
|
||||
},
|
||||
showExportSchemaModal () {
|
||||
this.isExportSchemaModal = true;
|
||||
},
|
||||
hideExportSchemaModal () {
|
||||
this.isExportSchemaModal = false;
|
||||
this.closeContext();
|
||||
},
|
||||
showImportSchemaModal () {
|
||||
this.isImportSchemaModal = true;
|
||||
},
|
||||
hideImportSchemaModal () {
|
||||
this.isImportSchemaModal = false;
|
||||
this.closeContext();
|
||||
},
|
||||
async initImport () {
|
||||
const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: 'SQL', extensions: ['sql'] }] });
|
||||
if (result && !result.canceled) {
|
||||
const file = result.filePaths[0];
|
||||
this.showImportSchemaModal();
|
||||
this.$nextTick(() => {
|
||||
this.$refs.importModalRef.startImport(file);
|
||||
});
|
||||
}
|
||||
},
|
||||
closeContext () {
|
||||
this.$emit('close-context');
|
||||
},
|
||||
async deleteSchema () {
|
||||
try {
|
||||
const { status, response } = await Schema.deleteSchema({
|
||||
uid: this.selectedWorkspace,
|
||||
database: this.selectedSchema
|
||||
});
|
||||
|
||||
if (status === 'success') {
|
||||
if (this.selectedSchema === this.workspace.breadcrumbs.schema)
|
||||
this.changeBreadcrumbs({ schema: null });
|
||||
|
||||
this.closeContext();
|
||||
this.$emit('reload');
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
const {
|
||||
getWorkspace,
|
||||
changeBreadcrumbs
|
||||
} = workspacesStore;
|
||||
|
||||
const importModalRef: Ref<Component & {startImport: (file: string) => void}> = ref(null);
|
||||
const isDeleteModal = ref(false);
|
||||
const isEditModal = ref(false);
|
||||
const isExportSchemaModal = ref(false);
|
||||
const isImportSchemaModal = ref(false);
|
||||
|
||||
const workspace = computed(() => getWorkspace(selectedWorkspace.value));
|
||||
|
||||
const openCreateTableTab = () => {
|
||||
emit('open-create-table-tab');
|
||||
};
|
||||
|
||||
const openCreateViewTab = () => {
|
||||
emit('open-create-view-tab');
|
||||
};
|
||||
|
||||
const openCreateTriggerTab = () => {
|
||||
emit('open-create-trigger-tab');
|
||||
};
|
||||
|
||||
const openCreateRoutineTab = () => {
|
||||
emit('open-create-routine-tab');
|
||||
};
|
||||
|
||||
const openCreateFunctionTab = () => {
|
||||
emit('open-create-function-tab');
|
||||
};
|
||||
|
||||
const openCreateTriggerFunctionTab = () => {
|
||||
emit('open-create-trigger-function-tab');
|
||||
};
|
||||
|
||||
const openCreateSchedulerTab = () => {
|
||||
emit('open-create-scheduler-tab');
|
||||
};
|
||||
|
||||
const showDeleteModal = () => {
|
||||
isDeleteModal.value = true;
|
||||
};
|
||||
|
||||
const hideDeleteModal = () => {
|
||||
isDeleteModal.value = false;
|
||||
};
|
||||
|
||||
const showEditModal = () => {
|
||||
isEditModal.value = true;
|
||||
};
|
||||
|
||||
const hideEditModal = () => {
|
||||
isEditModal.value = false;
|
||||
closeContext();
|
||||
};
|
||||
|
||||
const showExportSchemaModal = () => {
|
||||
isExportSchemaModal.value = true;
|
||||
};
|
||||
|
||||
const hideExportSchemaModal = () => {
|
||||
isExportSchemaModal.value = false;
|
||||
closeContext();
|
||||
};
|
||||
|
||||
const showImportSchemaModal = () => {
|
||||
isImportSchemaModal.value = true;
|
||||
};
|
||||
|
||||
const hideImportSchemaModal = () => {
|
||||
isImportSchemaModal.value = false;
|
||||
closeContext();
|
||||
};
|
||||
|
||||
const initImport = async () => {
|
||||
const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: 'SQL', extensions: ['sql'] }] });
|
||||
if (result && !result.canceled) {
|
||||
const file = result.filePaths[0];
|
||||
showImportSchemaModal();
|
||||
await nextTick();
|
||||
importModalRef.value.startImport(file);
|
||||
}
|
||||
};
|
||||
|
||||
const closeContext = () => {
|
||||
emit('close-context');
|
||||
};
|
||||
|
||||
const deleteSchema = async () => {
|
||||
try {
|
||||
const { status, response } = await Schema.deleteSchema({
|
||||
uid: selectedWorkspace.value,
|
||||
database: props.selectedSchema
|
||||
});
|
||||
|
||||
if (status === 'success') {
|
||||
if (props.selectedSchema === workspace.value.breadcrumbs.schema)
|
||||
changeBreadcrumbs({ schema: null });
|
||||
|
||||
closeContext();
|
||||
emit('reload');
|
||||
}
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.context-submenu {
|
||||
min-width: 150px !important;
|
||||
|
@ -71,151 +71,133 @@
|
||||
</BaseContextMenu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceExploreBarTableContext',
|
||||
components: {
|
||||
BaseContextMenu,
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
contextEvent: MouseEvent,
|
||||
selectedTable: Object,
|
||||
selectedSchema: String
|
||||
},
|
||||
emits: ['close-context', 'duplicate-table', 'reload', 'delete-table'],
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const props = defineProps({
|
||||
contextEvent: MouseEvent,
|
||||
selectedTable: Object,
|
||||
selectedSchema: String
|
||||
});
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const emit = defineEmits(['close-context', 'duplicate-table', 'reload', 'delete-table']);
|
||||
|
||||
const {
|
||||
getWorkspace,
|
||||
newTab,
|
||||
removeTabs,
|
||||
addLoadingElement,
|
||||
removeLoadingElement,
|
||||
changeBreadcrumbs
|
||||
} = workspacesStore;
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
getWorkspace,
|
||||
newTab,
|
||||
removeTabs,
|
||||
addLoadingElement,
|
||||
removeLoadingElement,
|
||||
changeBreadcrumbs,
|
||||
selectedWorkspace
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isDeleteModal: false,
|
||||
isEmptyModal: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
},
|
||||
customizations () {
|
||||
return this.workspace && this.workspace.customizations ? this.workspace.customizations : {};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showDeleteModal () {
|
||||
this.isDeleteModal = true;
|
||||
},
|
||||
hideDeleteModal () {
|
||||
this.isDeleteModal = false;
|
||||
},
|
||||
showEmptyModal () {
|
||||
this.isEmptyModal = true;
|
||||
},
|
||||
hideEmptyModal () {
|
||||
this.isEmptyModal = false;
|
||||
},
|
||||
closeContext () {
|
||||
this.$emit('close-context');
|
||||
},
|
||||
openTableSettingTab () {
|
||||
this.newTab({
|
||||
uid: this.selectedWorkspace,
|
||||
elementName: this.selectedTable.name,
|
||||
schema: this.selectedSchema,
|
||||
type: 'table-props',
|
||||
elementType: 'table'
|
||||
});
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
this.changeBreadcrumbs({
|
||||
schema: this.selectedSchema,
|
||||
table: this.selectedTable.name
|
||||
});
|
||||
const {
|
||||
getWorkspace,
|
||||
newTab,
|
||||
addLoadingElement,
|
||||
removeLoadingElement,
|
||||
changeBreadcrumbs
|
||||
} = workspacesStore;
|
||||
|
||||
this.closeContext();
|
||||
},
|
||||
openViewSettingTab () {
|
||||
this.newTab({
|
||||
uid: this.selectedWorkspace,
|
||||
elementType: 'table',
|
||||
elementName: this.selectedTable.name,
|
||||
schema: this.selectedSchema,
|
||||
type: 'view-props'
|
||||
});
|
||||
const isDeleteModal = ref(false);
|
||||
const isEmptyModal = ref(false);
|
||||
|
||||
this.changeBreadcrumbs({
|
||||
schema: this.selectedSchema,
|
||||
view: this.selectedTable.name
|
||||
});
|
||||
const workspace = computed(() => getWorkspace(selectedWorkspace.value));
|
||||
const customizations = computed(() => workspace.value && workspace.value.customizations ? workspace.value.customizations : null);
|
||||
|
||||
this.closeContext();
|
||||
},
|
||||
duplicateTable () {
|
||||
this.$emit('duplicate-table', { schema: this.selectedSchema, table: this.selectedTable });
|
||||
},
|
||||
async emptyTable () {
|
||||
this.closeContext();
|
||||
const showDeleteModal = () => {
|
||||
isDeleteModal.value = true;
|
||||
};
|
||||
|
||||
this.addLoadingElement({
|
||||
name: this.selectedTable.name,
|
||||
schema: this.selectedSchema,
|
||||
type: 'table'
|
||||
});
|
||||
const hideDeleteModal = () => {
|
||||
isDeleteModal.value = false;
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Tables.truncateTable({
|
||||
uid: this.selectedWorkspace,
|
||||
table: this.selectedTable.name,
|
||||
schema: this.selectedSchema
|
||||
});
|
||||
const showEmptyModal = () => {
|
||||
isEmptyModal.value = true;
|
||||
};
|
||||
|
||||
if (status === 'success')
|
||||
this.$emit('reload');
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
const hideEmptyModal = () => {
|
||||
isEmptyModal.value = false;
|
||||
};
|
||||
|
||||
this.removeLoadingElement({
|
||||
name: this.selectedTable.name,
|
||||
schema: this.selectedSchema,
|
||||
type: 'table'
|
||||
});
|
||||
},
|
||||
deleteTable () {
|
||||
this.$emit('delete-table', { schema: this.selectedSchema, table: this.selectedTable });
|
||||
}
|
||||
const closeContext = () => {
|
||||
emit('close-context');
|
||||
};
|
||||
|
||||
const openTableSettingTab = () => {
|
||||
newTab({
|
||||
uid: selectedWorkspace.value,
|
||||
elementName: props.selectedTable.name,
|
||||
schema: props.selectedSchema,
|
||||
type: 'table-props',
|
||||
elementType: 'table'
|
||||
});
|
||||
|
||||
changeBreadcrumbs({
|
||||
schema: props.selectedSchema,
|
||||
table: props.selectedTable.name
|
||||
});
|
||||
|
||||
closeContext();
|
||||
};
|
||||
|
||||
const openViewSettingTab = () => {
|
||||
newTab({
|
||||
uid: selectedWorkspace.value,
|
||||
elementType: 'table',
|
||||
elementName: props.selectedTable.name,
|
||||
schema: props.selectedSchema,
|
||||
type: 'view-props'
|
||||
});
|
||||
|
||||
changeBreadcrumbs({
|
||||
schema: props.selectedSchema,
|
||||
view: props.selectedTable.name
|
||||
});
|
||||
|
||||
closeContext();
|
||||
};
|
||||
|
||||
const duplicateTable = () => {
|
||||
emit('duplicate-table', { schema: props.selectedSchema, table: props.selectedTable });
|
||||
};
|
||||
|
||||
const emptyTable = async () => {
|
||||
closeContext();
|
||||
|
||||
addLoadingElement({
|
||||
name: props.selectedTable.name,
|
||||
schema: props.selectedSchema,
|
||||
type: 'table'
|
||||
});
|
||||
|
||||
try {
|
||||
const { status, response } = await Tables.truncateTable({
|
||||
uid: selectedWorkspace.value,
|
||||
table: props.selectedTable.name,
|
||||
schema: props.selectedSchema
|
||||
});
|
||||
|
||||
if (status === 'success')
|
||||
emit('reload');
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
removeLoadingElement({
|
||||
name: props.selectedTable.name,
|
||||
schema: props.selectedSchema,
|
||||
type: 'table'
|
||||
});
|
||||
};
|
||||
|
||||
const deleteTable = () => {
|
||||
emit('delete-table', { schema: props.selectedSchema, table: props.selectedTable });
|
||||
};
|
||||
</script>
|
||||
|
@ -11,27 +11,27 @@
|
||||
@click="saveChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
<span>{{ t('word.save') }}</span>
|
||||
</button>
|
||||
<button
|
||||
:disabled="!isChanged"
|
||||
class="btn btn-link btn-sm mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
:title="t('message.clearChanges')"
|
||||
@click="clearChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<span>{{ t('word.clear') }}</span>
|
||||
</button>
|
||||
|
||||
<div class="divider-vert py-3" />
|
||||
|
||||
<button class="btn btn-dark btn-sm" @click="showParamsModal">
|
||||
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
|
||||
<span>{{ $t('word.parameters') }}</span>
|
||||
<span>{{ t('word.parameters') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="workspace-query-info">
|
||||
<div class="d-flex" :title="$t('word.schema')">
|
||||
<div class="d-flex" :title="t('word.schema')">
|
||||
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||
</div>
|
||||
</div>
|
||||
@ -42,7 +42,7 @@
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.name') }}
|
||||
{{ t('word.name') }}
|
||||
</label>
|
||||
<input
|
||||
ref="firstInput"
|
||||
@ -55,7 +55,7 @@
|
||||
<div v-if="customizations.languages" class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.language') }}
|
||||
{{ t('word.language') }}
|
||||
</label>
|
||||
<BaseSelect
|
||||
v-model="localFunction.language"
|
||||
@ -67,13 +67,13 @@
|
||||
<div v-if="customizations.definer" class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.definer') }}
|
||||
{{ t('word.definer') }}
|
||||
</label>
|
||||
<BaseSelect
|
||||
v-model="localFunction.definer"
|
||||
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]"
|
||||
:option-label="(user) => user.value === '' ? user.name : `${user.name}@${user.host}`"
|
||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||
:options="[{value: '', name:t('message.currentUser')}, ...workspace.users]"
|
||||
:option-label="(user: any) => user.value === '' ? user.name : `${user.name}@${user.host}`"
|
||||
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||
class="form-select"
|
||||
/>
|
||||
</div>
|
||||
@ -81,13 +81,13 @@
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.returns') }}
|
||||
{{ t('word.returns') }}
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<BaseSelect
|
||||
v-model="localFunction.returns"
|
||||
class="form-select text-uppercase"
|
||||
:options="[{ name: 'VOID' }, ...workspace.dataTypes]"
|
||||
:options="[{ name: 'VOID' }, ...(workspace.dataTypes as any)]"
|
||||
group-label="group"
|
||||
group-values="types"
|
||||
option-label="name"
|
||||
@ -101,7 +101,7 @@
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="0"
|
||||
:placeholder="$t('word.length')"
|
||||
:placeholder="t('word.length')"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@ -109,7 +109,7 @@
|
||||
<div v-if="customizations.comment" class="column">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.comment') }}
|
||||
{{ t('word.comment') }}
|
||||
</label>
|
||||
<input
|
||||
v-model="localFunction.comment"
|
||||
@ -121,7 +121,7 @@
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('message.sqlSecurity') }}
|
||||
{{ t('message.sqlSecurity') }}
|
||||
</label>
|
||||
<BaseSelect
|
||||
v-model="localFunction.security"
|
||||
@ -133,7 +133,7 @@
|
||||
<div v-if="customizations.functionDataAccess" class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('message.dataAccess') }}
|
||||
{{ t('message.dataAccess') }}
|
||||
</label>
|
||||
<BaseSelect
|
||||
v-model="localFunction.dataAccess"
|
||||
@ -146,7 +146,7 @@
|
||||
<div class="form-group">
|
||||
<label class="form-label d-invisible">.</label>
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="localFunction.deterministic" type="checkbox"><i class="form-icon" /> {{ $t('word.deterministic') }}
|
||||
<input v-model="localFunction.deterministic" type="checkbox"><i class="form-icon" /> {{ t('word.deterministic') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -154,7 +154,7 @@
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<label class="form-label ml-2">{{ $t('message.functionBody') }}</label>
|
||||
<label class="form-label ml-2">{{ t('message.functionBody') }}</label>
|
||||
<QueryEditor
|
||||
v-show="isSelected"
|
||||
ref="queryEditor"
|
||||
@ -175,213 +175,177 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
||||
import { Ace } from 'ace-builds';
|
||||
import Functions from '@/ipc-api/Functions';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import WorkspaceTabPropsFunctionParamsModal from '@/components/WorkspaceTabPropsFunctionParamsModal';
|
||||
import Functions from '@/ipc-api/Functions';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import BaseLoader from '@/components/BaseLoader.vue';
|
||||
import QueryEditor from '@/components/QueryEditor.vue';
|
||||
import WorkspaceTabPropsFunctionParamsModal from '@/components/WorkspaceTabPropsFunctionParamsModal.vue';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
import { FunctionInfos, FunctionParam } from 'common/interfaces/antares';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceTabNewFunction',
|
||||
components: {
|
||||
BaseLoader,
|
||||
QueryEditor,
|
||||
WorkspaceTabPropsFunctionParamsModal,
|
||||
BaseSelect
|
||||
},
|
||||
props: {
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
tab: Object,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
},
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const props = defineProps({
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
tab: Object,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
});
|
||||
|
||||
const {
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges,
|
||||
newTab,
|
||||
removeTab,
|
||||
renameTabs
|
||||
} = workspacesStore;
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges,
|
||||
newTab,
|
||||
removeTab,
|
||||
renameTabs
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
isParamsModal: false,
|
||||
originalFunction: {},
|
||||
localFunction: {},
|
||||
lastFunction: null,
|
||||
sqlProxy: '',
|
||||
editorHeight: 300
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
customizations () {
|
||||
return this.workspace.customizations;
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.originalFunction) !== JSON.stringify(this.localFunction);
|
||||
},
|
||||
isDefinerInUsers () {
|
||||
return this.originalFunction
|
||||
? this.workspace.users.some(user => this.originalFunction.definer === `\`${user.name}\`@\`${user.host}\``)
|
||||
: true;
|
||||
},
|
||||
schemaTables () {
|
||||
const schemaTables = this.workspace.structure
|
||||
.filter(schema => schema.name === this.schema)
|
||||
.map(schema => schema.tables);
|
||||
const {
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges,
|
||||
newTab,
|
||||
removeTab
|
||||
} = workspacesStore;
|
||||
|
||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||
const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
|
||||
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const isParamsModal = ref(false);
|
||||
const originalFunction: Ref<FunctionInfos> = ref(null);
|
||||
const localFunction = ref(null);
|
||||
const editorHeight = ref(300);
|
||||
|
||||
const workspace = computed(() => {
|
||||
return getWorkspace(props.connection.uid);
|
||||
});
|
||||
|
||||
const customizations = computed(() => {
|
||||
return workspace.value.customizations;
|
||||
});
|
||||
|
||||
const isChanged = computed(() => {
|
||||
return JSON.stringify(originalFunction.value) !== JSON.stringify(localFunction.value);
|
||||
});
|
||||
|
||||
const saveChanges = async () => {
|
||||
if (isSaving.value) return;
|
||||
isSaving.value = true;
|
||||
const params = {
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
...localFunction.value
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Functions.createFunction(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await refreshStructure(props.connection.uid);
|
||||
|
||||
newTab({
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
elementName: localFunction.value.name,
|
||||
elementType: 'function',
|
||||
type: 'function-props'
|
||||
});
|
||||
|
||||
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
|
||||
changeBreadcrumbs({ schema: props.schema, function: localFunction.value.name });
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isSelected (val) {
|
||||
if (val)
|
||||
this.changeBreadcrumbs({ schema: this.schema });
|
||||
},
|
||||
isChanged (val) {
|
||||
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.originalFunction = {
|
||||
sql: this.customizations.functionSql,
|
||||
language: this.customizations.languages ? this.customizations.languages[0] : null,
|
||||
name: '',
|
||||
definer: '',
|
||||
comment: '',
|
||||
security: 'DEFINER',
|
||||
dataAccess: 'CONTAINS SQL',
|
||||
deterministic: false,
|
||||
returns: this.workspace.dataTypes.length ? this.workspace.dataTypes[0].types[0].name : null
|
||||
};
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction));
|
||||
isSaving.value = false;
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
this.resizeQueryEditor();
|
||||
}, 50);
|
||||
const clearChanges = () => {
|
||||
localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
|
||||
queryEditor.value.editor.session.setValue(localFunction.value.sql);
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
mounted () {
|
||||
if (this.isSelected)
|
||||
this.changeBreadcrumbs({ schema: this.schema });
|
||||
const resizeQueryEditor = () => {
|
||||
if (queryEditor.value) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
editorHeight.value = size;
|
||||
queryEditor.value.editor.resize();
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 100);
|
||||
},
|
||||
unmounted () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
this.isSaving = true;
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
...this.localFunction
|
||||
};
|
||||
const parametersUpdate = (parameters: FunctionParam[]) => {
|
||||
localFunction.value = { ...localFunction.value, parameters };
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Functions.createFunction(params);
|
||||
const showParamsModal = () => {
|
||||
isParamsModal.value = true;
|
||||
};
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
const hideParamsModal = () => {
|
||||
isParamsModal.value = false;
|
||||
};
|
||||
|
||||
this.newTab({
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
elementName: this.localFunction.name,
|
||||
elementType: 'function',
|
||||
type: 'function-props'
|
||||
});
|
||||
|
||||
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
|
||||
this.changeBreadcrumbs({ schema: this.schema, function: this.localFunction.name });
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isSaving = false;
|
||||
},
|
||||
clearChanges () {
|
||||
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction));
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
|
||||
},
|
||||
resizeQueryEditor () {
|
||||
if (this.$refs.queryEditor) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
this.editorHeight = size;
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
}
|
||||
},
|
||||
optionsUpdate (options) {
|
||||
this.localFunction = options;
|
||||
},
|
||||
parametersUpdate (parameters) {
|
||||
this.localFunction = { ...this.localFunction, parameters };
|
||||
},
|
||||
showParamsModal () {
|
||||
this.isParamsModal = true;
|
||||
},
|
||||
hideParamsModal () {
|
||||
this.isParamsModal = false;
|
||||
},
|
||||
showAskParamsModal () {
|
||||
this.isAskingParameters = true;
|
||||
},
|
||||
hideAskParamsModal () {
|
||||
this.isAskingParameters = false;
|
||||
},
|
||||
onKey (e) {
|
||||
if (this.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
||||
if (this.isChanged)
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (props.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.key === 's') { // CTRL + S
|
||||
if (isChanged.value)
|
||||
saveChanges();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.isSelected, (val) => {
|
||||
if (val) changeBreadcrumbs({ schema: props.schema });
|
||||
});
|
||||
|
||||
watch(isChanged, (val) => {
|
||||
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
|
||||
});
|
||||
|
||||
originalFunction.value = {
|
||||
sql: customizations.value.functionSql,
|
||||
language: customizations.value.languages ? customizations.value.languages[0] : null,
|
||||
name: '',
|
||||
definer: '',
|
||||
comment: '',
|
||||
security: 'DEFINER',
|
||||
dataAccess: 'CONTAINS SQL',
|
||||
deterministic: false,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
returns: workspace.value.dataTypes.length ? (workspace.value.dataTypes[0] as any).types[0].name : null
|
||||
};
|
||||
|
||||
localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
|
||||
|
||||
setTimeout(() => {
|
||||
resizeQueryEditor();
|
||||
}, 50);
|
||||
|
||||
window.addEventListener('keydown', onKey);
|
||||
|
||||
onMounted(() => {
|
||||
if (props.isSelected)
|
||||
changeBreadcrumbs({ schema: props.schema });
|
||||
|
||||
setTimeout(() => {
|
||||
firstInput.value.focus();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', resizeQueryEditor);
|
||||
});
|
||||
</script>
|
||||
|
@ -72,8 +72,8 @@
|
||||
<BaseSelect
|
||||
v-model="localRoutine.definer"
|
||||
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]"
|
||||
:option-label="(user) => user.value === '' ? user.name : `${user.name}@${user.host}`"
|
||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||
:option-label="(user: any) => user.value === '' ? user.name : `${user.name}@${user.host}`"
|
||||
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||
class="form-select"
|
||||
/>
|
||||
</div>
|
||||
@ -129,7 +129,6 @@
|
||||
<label class="form-label ml-2">{{ $t('message.routineBody') }}</label>
|
||||
<QueryEditor
|
||||
v-show="isSelected"
|
||||
:key="`new-${_uid}`"
|
||||
ref="queryEditor"
|
||||
v-model="localRoutine.sql"
|
||||
:workspace="workspace"
|
||||
@ -148,209 +147,174 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
||||
import { Ace } from 'ace-builds';
|
||||
import Routines from '@/ipc-api/Routines';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal';
|
||||
import Routines from '@/ipc-api/Routines';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import QueryEditor from '@/components/QueryEditor.vue';
|
||||
import BaseLoader from '@/components/BaseLoader.vue';
|
||||
import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal.vue';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
import { FunctionParam } from 'common/interfaces/antares';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceTabNewRoutine',
|
||||
components: {
|
||||
QueryEditor,
|
||||
BaseLoader,
|
||||
WorkspaceTabPropsRoutineParamsModal,
|
||||
BaseSelect
|
||||
},
|
||||
props: {
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
tab: Object,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
},
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const props = defineProps({
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
tab: Object,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
});
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
const {
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges,
|
||||
newTab,
|
||||
removeTab,
|
||||
renameTabs
|
||||
} = workspacesStore;
|
||||
const {
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges,
|
||||
newTab,
|
||||
removeTab
|
||||
} = workspacesStore;
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges,
|
||||
newTab,
|
||||
removeTab,
|
||||
renameTabs
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
isParamsModal: false,
|
||||
originalRoutine: {},
|
||||
localRoutine: {},
|
||||
lastRoutine: null,
|
||||
sqlProxy: '',
|
||||
editorHeight: 300
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
customizations () {
|
||||
return this.workspace.customizations;
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.originalRoutine) !== JSON.stringify(this.localRoutine);
|
||||
},
|
||||
isDefinerInUsers () {
|
||||
return this.originalRoutine ? this.workspace.users.some(user => this.originalRoutine.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
||||
},
|
||||
schemaTables () {
|
||||
const schemaTables = this.workspace.structure
|
||||
.filter(schema => schema.name === this.schema)
|
||||
.map(schema => schema.tables);
|
||||
const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
|
||||
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const isParamsModal = ref(false);
|
||||
const originalRoutine = ref(null);
|
||||
const localRoutine = ref(null);
|
||||
const editorHeight = ref(300);
|
||||
|
||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||
const workspace = computed(() => {
|
||||
return getWorkspace(props.connection.uid);
|
||||
});
|
||||
|
||||
const customizations = computed(() => {
|
||||
return workspace.value.customizations;
|
||||
});
|
||||
|
||||
const isChanged = computed(() => {
|
||||
return JSON.stringify(originalRoutine.value) !== JSON.stringify(localRoutine.value);
|
||||
});
|
||||
|
||||
const saveChanges = async () => {
|
||||
if (isSaving.value) return;
|
||||
isSaving.value = true;
|
||||
const params = {
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
...localRoutine.value
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Routines.createRoutine(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await refreshStructure(props.connection.uid);
|
||||
|
||||
newTab({
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
elementName: localRoutine.value.name,
|
||||
elementType: 'routine',
|
||||
type: 'routine-props'
|
||||
});
|
||||
|
||||
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
|
||||
changeBreadcrumbs({ schema: props.schema, routine: localRoutine.value.name });
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isSelected (val) {
|
||||
if (val)
|
||||
this.changeBreadcrumbs({ schema: this.schema });
|
||||
},
|
||||
isChanged (val) {
|
||||
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.originalRoutine = {
|
||||
sql: this.customizations.procedureSql,
|
||||
language: this.customizations.languages ? this.customizations.languages[0] : null,
|
||||
name: '',
|
||||
definer: '',
|
||||
comment: '',
|
||||
security: 'DEFINER',
|
||||
dataAccess: 'CONTAINS SQL',
|
||||
deterministic: false
|
||||
};
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.localRoutine = JSON.parse(JSON.stringify(this.originalRoutine));
|
||||
isSaving.value = false;
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
this.resizeQueryEditor();
|
||||
}, 50);
|
||||
const clearChanges = () => {
|
||||
localRoutine.value = JSON.parse(JSON.stringify(originalRoutine.value));
|
||||
queryEditor.value.editor.session.setValue(localRoutine.value.sql);
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
mounted () {
|
||||
if (this.isSelected)
|
||||
this.changeBreadcrumbs({ schema: this.schema });
|
||||
const resizeQueryEditor = () => {
|
||||
if (queryEditor.value) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
editorHeight.value = size;
|
||||
queryEditor.value.editor.resize();
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 100);
|
||||
const parametersUpdate = (parameters: FunctionParam[]) => {
|
||||
localRoutine.value = { ...localRoutine.value, parameters };
|
||||
};
|
||||
|
||||
window.addEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
unmounted () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
this.isSaving = true;
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
...this.localRoutine
|
||||
};
|
||||
const showParamsModal = () => {
|
||||
isParamsModal.value = true;
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Routines.createRoutine(params);
|
||||
const hideParamsModal = () => {
|
||||
isParamsModal.value = false;
|
||||
};
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
|
||||
this.newTab({
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
elementName: this.localRoutine.name,
|
||||
elementType: 'routine',
|
||||
type: 'routine-props'
|
||||
});
|
||||
|
||||
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
|
||||
this.changeBreadcrumbs({ schema: this.schema, routine: this.localRoutine.name });
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isSaving = false;
|
||||
},
|
||||
clearChanges () {
|
||||
this.localRoutine = JSON.parse(JSON.stringify(this.originalRoutine));
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localRoutine.sql);
|
||||
},
|
||||
resizeQueryEditor () {
|
||||
if (this.$refs.queryEditor) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
this.editorHeight = size;
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
}
|
||||
},
|
||||
parametersUpdate (parameters) {
|
||||
this.localRoutine = { ...this.localRoutine, parameters };
|
||||
},
|
||||
showParamsModal () {
|
||||
this.isParamsModal = true;
|
||||
},
|
||||
hideParamsModal () {
|
||||
this.isParamsModal = false;
|
||||
},
|
||||
showAskParamsModal () {
|
||||
this.isAskingParameters = true;
|
||||
},
|
||||
hideAskParamsModal () {
|
||||
this.isAskingParameters = false;
|
||||
},
|
||||
onKey (e) {
|
||||
if (this.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
||||
if (this.isChanged)
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (props.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.key === 's') { // CTRL + S
|
||||
if (isChanged.value)
|
||||
saveChanges();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.isSelected, (val) => {
|
||||
if (val) changeBreadcrumbs({ schema: props.schema });
|
||||
});
|
||||
|
||||
watch(isChanged, (val) => {
|
||||
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
|
||||
});
|
||||
|
||||
originalRoutine.value = {
|
||||
sql: customizations.value.functionSql,
|
||||
language: customizations.value.languages ? customizations.value.languages[0] : null,
|
||||
name: '',
|
||||
definer: '',
|
||||
comment: '',
|
||||
security: 'DEFINER',
|
||||
dataAccess: 'CONTAINS SQL',
|
||||
deterministic: false,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
returns: workspace.value.dataTypes.length ? (workspace.value.dataTypes[0] as any).types[0].name : null
|
||||
};
|
||||
|
||||
localRoutine.value = JSON.parse(JSON.stringify(originalRoutine.value));
|
||||
|
||||
setTimeout(() => {
|
||||
resizeQueryEditor();
|
||||
}, 50);
|
||||
|
||||
window.addEventListener('keydown', onKey);
|
||||
|
||||
onMounted(() => {
|
||||
if (props.isSelected)
|
||||
changeBreadcrumbs({ schema: props.schema });
|
||||
|
||||
setTimeout(() => {
|
||||
firstInput.value.focus();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', resizeQueryEditor);
|
||||
});
|
||||
</script>
|
||||
|
@ -11,26 +11,26 @@
|
||||
@click="saveChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
<span>{{ t('word.save') }}</span>
|
||||
</button>
|
||||
<button
|
||||
:disabled="!isChanged"
|
||||
class="btn btn-link btn-sm mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
:title="t('message.clearChanges')"
|
||||
@click="clearChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<span>{{ t('word.clear') }}</span>
|
||||
</button>
|
||||
|
||||
<div class="divider-vert py-3" />
|
||||
<button class="btn btn-dark btn-sm" @click="showTimingModal">
|
||||
<i class="mdi mdi-24px mdi-timer mr-1" />
|
||||
<span>{{ $t('word.timing') }}</span>
|
||||
<span>{{ t('word.timing') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="workspace-query-info">
|
||||
<div class="d-flex" :title="$t('word.schema')">
|
||||
<div class="d-flex" :title="t('word.schema')">
|
||||
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||
</div>
|
||||
</div>
|
||||
@ -40,7 +40,7 @@
|
||||
<div class="columns">
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
<label class="form-label">{{ t('word.name') }}</label>
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="localScheduler.name"
|
||||
@ -51,19 +51,19 @@
|
||||
</div>
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.definer') }}</label>
|
||||
<label class="form-label">{{ t('word.definer') }}</label>
|
||||
<BaseSelect
|
||||
v-model="localScheduler.definer"
|
||||
:options="users"
|
||||
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
|
||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||
:option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
|
||||
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||
class="form-select"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.comment') }}</label>
|
||||
<label class="form-label">{{ t('word.comment') }}</label>
|
||||
<input
|
||||
v-model="localScheduler.comment"
|
||||
class="form-input"
|
||||
@ -73,7 +73,7 @@
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="form-group">
|
||||
<label class="form-label mr-2">{{ $t('word.state') }}</label>
|
||||
<label class="form-label mr-2">{{ t('word.state') }}</label>
|
||||
<label class="form-radio form-inline">
|
||||
<input
|
||||
v-model="localScheduler.state"
|
||||
@ -104,7 +104,7 @@
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<label class="form-label ml-2">{{ $t('message.schedulerBody') }}</label>
|
||||
<label class="form-label ml-2">{{ t('message.schedulerBody') }}</label>
|
||||
<QueryEditor
|
||||
v-show="isSelected"
|
||||
ref="queryEditor"
|
||||
@ -124,209 +124,186 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { storeToRefs } from 'pinia';
|
||||
<script setup lang="ts">
|
||||
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
||||
import { Ace } from 'ace-builds';
|
||||
import { EventInfos } from 'common/interfaces/antares';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal';
|
||||
import BaseLoader from '@/components/BaseLoader.vue';
|
||||
import QueryEditor from '@/components/QueryEditor.vue';
|
||||
import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal.vue';
|
||||
import Schedulers from '@/ipc-api/Schedulers';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceTabNewScheduler',
|
||||
components: {
|
||||
BaseLoader,
|
||||
QueryEditor,
|
||||
WorkspaceTabPropsSchedulerTimingModal,
|
||||
BaseSelect
|
||||
},
|
||||
props: {
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
tab: Object,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
},
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const props = defineProps({
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
tab: Object,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
});
|
||||
|
||||
const {
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges,
|
||||
newTab,
|
||||
removeTab,
|
||||
renameTabs
|
||||
} = workspacesStore;
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges,
|
||||
newTab,
|
||||
removeTab,
|
||||
renameTabs
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
isTimingModal: false,
|
||||
originalScheduler: {},
|
||||
localScheduler: {},
|
||||
lastScheduler: null,
|
||||
sqlProxy: '',
|
||||
editorHeight: 300
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.originalScheduler) !== JSON.stringify(this.localScheduler);
|
||||
},
|
||||
isDefinerInUsers () {
|
||||
return this.originalScheduler ? this.workspace.users.some(user => this.originalScheduler.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
||||
},
|
||||
schemaTables () {
|
||||
const schemaTables = this.workspace.structure
|
||||
.filter(schema => schema.name === this.schema)
|
||||
.map(schema => schema.tables);
|
||||
const {
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges,
|
||||
newTab,
|
||||
removeTab
|
||||
} = workspacesStore;
|
||||
|
||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||
},
|
||||
users () {
|
||||
const users = [{ value: '' }, ...this.workspace.users];
|
||||
if (!this.isDefinerInUsers) {
|
||||
const [name, host] = this.originalScheduler.definer.replaceAll('`', '').split('@');
|
||||
users.unshift({ name, host });
|
||||
}
|
||||
const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
|
||||
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const isTimingModal = ref(false);
|
||||
const originalScheduler = ref(null);
|
||||
const localScheduler = ref(null);
|
||||
const editorHeight = ref(300);
|
||||
|
||||
return users;
|
||||
const workspace = computed(() => {
|
||||
return getWorkspace(props.connection.uid);
|
||||
});
|
||||
|
||||
const isChanged = computed(() => {
|
||||
return JSON.stringify(originalScheduler.value) !== JSON.stringify(localScheduler.value);
|
||||
});
|
||||
|
||||
const isDefinerInUsers = computed(() => {
|
||||
return originalScheduler.value ? workspace.value.users.some(user => originalScheduler.value.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
||||
});
|
||||
|
||||
const users = computed(() => {
|
||||
const users = [{ value: '' }, ...workspace.value.users];
|
||||
if (!isDefinerInUsers.value) {
|
||||
const [name, host] = originalScheduler.value.definer.replaceAll('`', '').split('@');
|
||||
users.unshift({ name, host });
|
||||
}
|
||||
|
||||
return users;
|
||||
});
|
||||
|
||||
const saveChanges = async () => {
|
||||
if (isSaving.value) return;
|
||||
isSaving.value = true;
|
||||
const params = {
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
...localScheduler.value
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Schedulers.createScheduler(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await refreshStructure(props.connection.uid);
|
||||
|
||||
newTab({
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
elementName: localScheduler.value.name,
|
||||
elementType: 'scheduler',
|
||||
type: 'scheduler-props'
|
||||
});
|
||||
|
||||
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
|
||||
changeBreadcrumbs({ schema: props.schema, scheduler: localScheduler.value.name });
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isSelected (val) {
|
||||
if (val)
|
||||
this.changeBreadcrumbs({ schema: this.schema });
|
||||
},
|
||||
isChanged (val) {
|
||||
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
this.originalScheduler = {
|
||||
definer: '',
|
||||
sql: 'BEGIN\r\n\r\nEND',
|
||||
name: '',
|
||||
comment: '',
|
||||
execution: 'EVERY',
|
||||
every: ['1', 'DAY'],
|
||||
preserve: true,
|
||||
state: 'DISABLE'
|
||||
};
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.localScheduler = JSON.parse(JSON.stringify(this.originalScheduler));
|
||||
isSaving.value = false;
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
this.resizeQueryEditor();
|
||||
}, 50);
|
||||
const clearChanges = () => {
|
||||
localScheduler.value = JSON.parse(JSON.stringify(originalScheduler.value));
|
||||
queryEditor.value.editor.session.setValue(localScheduler.value.sql);
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
mounted () {
|
||||
if (this.isSelected)
|
||||
this.changeBreadcrumbs({ schema: this.schema });
|
||||
const resizeQueryEditor = () => {
|
||||
if (queryEditor.value) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
editorHeight.value = size;
|
||||
queryEditor.value.editor.resize();
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 100);
|
||||
const showTimingModal = () => {
|
||||
isTimingModal.value = true;
|
||||
};
|
||||
|
||||
window.addEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
unmounted () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
this.isSaving = true;
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
...this.localScheduler
|
||||
};
|
||||
const hideTimingModal = () => {
|
||||
isTimingModal.value = false;
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Schedulers.createScheduler(params);
|
||||
const timingUpdate = (options: EventInfos) => {
|
||||
localScheduler.value = options;
|
||||
};
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
|
||||
this.newTab({
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
elementName: this.localScheduler.name,
|
||||
elementType: 'scheduler',
|
||||
type: 'scheduler-props'
|
||||
});
|
||||
|
||||
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
|
||||
this.changeBreadcrumbs({ schema: this.schema, scheduler: this.localScheduler.name });
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isSaving = false;
|
||||
},
|
||||
clearChanges () {
|
||||
this.localScheduler = JSON.parse(JSON.stringify(this.originalScheduler));
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localScheduler.sql);
|
||||
},
|
||||
resizeQueryEditor () {
|
||||
if (this.$refs.queryEditor) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
this.editorHeight = size;
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
}
|
||||
},
|
||||
showTimingModal () {
|
||||
this.isTimingModal = true;
|
||||
},
|
||||
hideTimingModal () {
|
||||
this.isTimingModal = false;
|
||||
},
|
||||
timingUpdate (options) {
|
||||
this.localScheduler = options;
|
||||
},
|
||||
onKey (e) {
|
||||
if (this.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
||||
if (this.isChanged)
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (props.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.key === 's') { // CTRL + S
|
||||
if (isChanged.value)
|
||||
saveChanges();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.isSelected, (val) => {
|
||||
if (val) changeBreadcrumbs({ schema: props.schema });
|
||||
});
|
||||
|
||||
watch(isChanged, (val) => {
|
||||
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
|
||||
});
|
||||
|
||||
originalScheduler.value = {
|
||||
definer: '',
|
||||
sql: 'BEGIN\r\n\r\nEND',
|
||||
name: '',
|
||||
comment: '',
|
||||
execution: 'EVERY',
|
||||
every: ['1', 'DAY'],
|
||||
preserve: true,
|
||||
state: 'DISABLE'
|
||||
};
|
||||
|
||||
originalScheduler.value = JSON.parse(JSON.stringify(originalScheduler.value));
|
||||
|
||||
setTimeout(() => {
|
||||
resizeQueryEditor();
|
||||
}, 50);
|
||||
|
||||
window.addEventListener('keydown', onKey);
|
||||
|
||||
onMounted(() => {
|
||||
if (props.isSelected)
|
||||
changeBreadcrumbs({ schema: props.schema });
|
||||
|
||||
setTimeout(() => {
|
||||
firstInput.value.focus();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', resizeQueryEditor);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
@ -11,16 +11,16 @@
|
||||
@click="saveChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
<span>{{ t('word.save') }}</span>
|
||||
</button>
|
||||
<button
|
||||
:disabled="!isChanged || isSaving"
|
||||
class="btn btn-link btn-sm mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
:title="t('message.clearChanges')"
|
||||
@click="clearChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<span>{{ t('word.clear') }}</span>
|
||||
</button>
|
||||
|
||||
<div class="divider-vert py-3" />
|
||||
@ -28,20 +28,20 @@
|
||||
<button
|
||||
:disabled="isSaving"
|
||||
class="btn btn-dark btn-sm"
|
||||
:title="$t('message.addNewField')"
|
||||
:title="t('message.addNewField')"
|
||||
@click="addField"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
|
||||
<span>{{ $t('word.add') }}</span>
|
||||
<span>{{ t('word.add') }}</span>
|
||||
</button>
|
||||
<button
|
||||
:disabled="isSaving || !localFields.length"
|
||||
class="btn btn-dark btn-sm"
|
||||
:title="$t('message.manageIndexes')"
|
||||
:title="t('message.manageIndexes')"
|
||||
@click="showIntdexesModal"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" />
|
||||
<span>{{ $t('word.indexes') }}</span>
|
||||
<span>{{ t('word.indexes') }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm"
|
||||
@ -49,11 +49,11 @@
|
||||
@click="showForeignModal"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-key-link mr-1" />
|
||||
<span>{{ $t('word.foreignKeys') }}</span>
|
||||
<span>{{ t('word.foreignKeys') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="workspace-query-info">
|
||||
<div class="d-flex" :title="$t('word.schema')">
|
||||
<div class="d-flex" :title="t('word.schema')">
|
||||
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||
</div>
|
||||
</div>
|
||||
@ -63,7 +63,7 @@
|
||||
<div class="columns mb-4">
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
<label class="form-label">{{ t('word.name') }}</label>
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="localOptions.name"
|
||||
@ -74,7 +74,7 @@
|
||||
</div>
|
||||
<div v-if="workspace.customizations.comment" class="column">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.comment') }}</label>
|
||||
<label class="form-label">{{ t('word.comment') }}</label>
|
||||
<input
|
||||
v-model="localOptions.comment"
|
||||
class="form-input"
|
||||
@ -86,7 +86,7 @@
|
||||
<div v-if="workspace.customizations.collations" class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.collation') }}
|
||||
{{ t('word.collation') }}
|
||||
</label>
|
||||
<BaseSelect
|
||||
v-model="localOptions.collation"
|
||||
@ -100,7 +100,7 @@
|
||||
<div v-if="workspace.customizations.engines" class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.engine') }}
|
||||
{{ t('word.engine') }}
|
||||
</label>
|
||||
<BaseSelect
|
||||
v-model="localOptions.engine"
|
||||
@ -160,310 +160,302 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { Component, computed, onBeforeUnmount, onMounted, Prop, Ref, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields';
|
||||
import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal';
|
||||
import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal';
|
||||
import WorkspaceTabNewTableEmptyState from '@/components/WorkspaceTabNewTableEmptyState';
|
||||
import BaseLoader from '@/components/BaseLoader.vue';
|
||||
import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields.vue';
|
||||
import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal.vue';
|
||||
import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal.vue';
|
||||
import WorkspaceTabNewTableEmptyState from '@/components/WorkspaceTabNewTableEmptyState.vue';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
import { ConnectionParams, TableField, TableForeign, TableIndex, TableOptions } from 'common/interfaces/antares';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceTabNewTable',
|
||||
components: {
|
||||
BaseLoader,
|
||||
WorkspaceTabPropsTableFields,
|
||||
WorkspaceTabPropsTableIndexesModal,
|
||||
WorkspaceTabPropsTableForeignModal,
|
||||
WorkspaceTabNewTableEmptyState,
|
||||
BaseSelect
|
||||
},
|
||||
props: {
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
tab: Object,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
},
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const props = defineProps({
|
||||
tabUid: String,
|
||||
connection: Object as Prop<ConnectionParams>,
|
||||
tab: Object,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
});
|
||||
|
||||
const {
|
||||
getWorkspace,
|
||||
getDatabaseVariable,
|
||||
refreshStructure,
|
||||
setUnsavedChanges,
|
||||
newTab,
|
||||
renameTabs,
|
||||
removeTab,
|
||||
changeBreadcrumbs
|
||||
} = workspacesStore;
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
getWorkspace,
|
||||
getDatabaseVariable,
|
||||
refreshStructure,
|
||||
setUnsavedChanges,
|
||||
newTab,
|
||||
renameTabs,
|
||||
removeTab,
|
||||
changeBreadcrumbs,
|
||||
selectedWorkspace
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
isIndexesModal: false,
|
||||
isForeignModal: false,
|
||||
isOptionsChanging: false,
|
||||
originalFields: [],
|
||||
localFields: [],
|
||||
originalKeyUsage: [],
|
||||
localKeyUsage: [],
|
||||
originalIndexes: [],
|
||||
localIndexes: [],
|
||||
tableOptions: {},
|
||||
localOptions: {},
|
||||
lastTable: null,
|
||||
newFieldsCounter: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
defaultCollation () {
|
||||
if (this.workspace.customizations.collations)
|
||||
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server')?.value || '';
|
||||
return '';
|
||||
},
|
||||
defaultEngine () {
|
||||
if (this.workspace.customizations.engines)
|
||||
return this.workspace.engines?.find(engine => engine.isDefault)?.name || '';
|
||||
return '';
|
||||
},
|
||||
schemaTables () {
|
||||
const schemaTables = this.workspace.structure
|
||||
.filter(schema => schema.name === this.schema)
|
||||
.map(schema => schema.tables);
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.originalFields) !== JSON.stringify(this.localFields) ||
|
||||
JSON.stringify(this.originalKeyUsage) !== JSON.stringify(this.localKeyUsage) ||
|
||||
JSON.stringify(this.originalIndexes) !== JSON.stringify(this.localIndexes) ||
|
||||
JSON.stringify(this.tableOptions) !== JSON.stringify(this.localOptions);
|
||||
},
|
||||
isValid () {
|
||||
return !!this.localFields.length && !!this.localOptions.name.trim().length;
|
||||
const {
|
||||
getWorkspace,
|
||||
getDatabaseVariable,
|
||||
refreshStructure,
|
||||
setUnsavedChanges,
|
||||
newTab,
|
||||
removeTab,
|
||||
changeBreadcrumbs
|
||||
} = workspacesStore;
|
||||
|
||||
const indexTable: Ref<Component & { tableWrapper: HTMLDivElement }> = ref(null);
|
||||
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const isIndexesModal = ref(false);
|
||||
const isForeignModal = ref(false);
|
||||
const originalFields: Ref<TableField[]> = ref([]);
|
||||
const localFields: Ref<TableField[]> = ref([]);
|
||||
const originalKeyUsage: Ref<TableForeign[]> = ref([]);
|
||||
const localKeyUsage: Ref<TableForeign[]> = ref([]);
|
||||
const originalIndexes: Ref<TableIndex[]> = ref([]);
|
||||
const localIndexes: Ref<TableIndex[]> = ref([]);
|
||||
const tableOptions: Ref<TableOptions> = ref(null);
|
||||
const localOptions: Ref<TableOptions> = ref(null);
|
||||
const newFieldsCounter = ref(0);
|
||||
|
||||
const workspace = computed(() => {
|
||||
return getWorkspace(props.connection.uid);
|
||||
});
|
||||
|
||||
const defaultCollation = computed(() => {
|
||||
if (workspace.value.customizations.collations)
|
||||
return getDatabaseVariable(selectedWorkspace.value, 'collation_server')?.value || '';
|
||||
return '';
|
||||
});
|
||||
|
||||
const defaultEngine = computed(() => {
|
||||
if (workspace.value.customizations.engines)
|
||||
return workspace.value.engines?.find(engine => engine.isDefault)?.name as string || '';
|
||||
return '';
|
||||
});
|
||||
|
||||
const schemaTables = computed(() => {
|
||||
const schemaTables = workspace.value.structure
|
||||
.filter(schema => schema.name === props.schema)
|
||||
.map(schema => schema.tables);
|
||||
|
||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||
});
|
||||
|
||||
const isChanged = computed(() => {
|
||||
return JSON.stringify(originalFields.value) !== JSON.stringify(localFields.value) ||
|
||||
JSON.stringify(originalKeyUsage.value) !== JSON.stringify(localKeyUsage.value) ||
|
||||
JSON.stringify(originalIndexes.value) !== JSON.stringify(localIndexes.value) ||
|
||||
JSON.stringify(tableOptions.value) !== JSON.stringify(localOptions.value);
|
||||
});
|
||||
|
||||
const isValid = computed(() => {
|
||||
return !!localFields.value.length && !!localOptions.value.name.trim().length;
|
||||
});
|
||||
|
||||
const saveChanges = async () => {
|
||||
if (isSaving.value || !isValid.value) return;
|
||||
isSaving.value = true;
|
||||
|
||||
const params = {
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
fields: localFields.value,
|
||||
foreigns: localKeyUsage.value,
|
||||
indexes: localIndexes.value,
|
||||
options: localOptions.value
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Tables.createTable(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await refreshStructure(props.connection.uid);
|
||||
|
||||
newTab({
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
elementName: localOptions.value.name,
|
||||
elementType: 'table',
|
||||
type: 'table-props'
|
||||
});
|
||||
|
||||
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
|
||||
changeBreadcrumbs({ schema: props.schema, table: localOptions.value.name });
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isSelected (val) {
|
||||
if (val)
|
||||
this.changeBreadcrumbs({ schema: this.schema });
|
||||
},
|
||||
isChanged (val) {
|
||||
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.tableOptions = {
|
||||
name: '',
|
||||
type: 'table',
|
||||
engine: this.defaultEngine,
|
||||
comment: '',
|
||||
collation: this.defaultCollation
|
||||
};
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions));
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
mounted () {
|
||||
if (this.isSelected)
|
||||
this.changeBreadcrumbs({ schema: this.schema });
|
||||
isSaving.value = false;
|
||||
newFieldsCounter.value = 0;
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 100);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
async saveChanges () {
|
||||
if (this.isSaving || !this.isValid) return;
|
||||
this.isSaving = true;
|
||||
const clearChanges = () => {
|
||||
localFields.value = JSON.parse(JSON.stringify(originalFields.value));
|
||||
localIndexes.value = JSON.parse(JSON.stringify(originalIndexes.value));
|
||||
localKeyUsage.value = JSON.parse(JSON.stringify(originalKeyUsage.value));
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
fields: this.localFields,
|
||||
foreigns: this.localKeyUsage,
|
||||
indexes: this.localIndexes,
|
||||
options: this.localOptions
|
||||
};
|
||||
tableOptions.value = {
|
||||
name: '',
|
||||
type: 'table',
|
||||
engine: defaultEngine.value,
|
||||
comment: '',
|
||||
collation: defaultCollation.value
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Tables.createTable(params);
|
||||
localOptions.value = JSON.parse(JSON.stringify(tableOptions.value));
|
||||
newFieldsCounter.value = 0;
|
||||
};
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
const addField = () => {
|
||||
localFields.value.push({
|
||||
_antares_id: uidGen(),
|
||||
name: `${t('word.field', 1)}_${++newFieldsCounter.value}`,
|
||||
key: '',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type: (workspace.value.dataTypes[0] as any).types[0].name,
|
||||
schema: props.schema,
|
||||
numPrecision: null,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
numLength: (workspace.value.dataTypes[0] as any).types[0].length,
|
||||
datePrecision: null,
|
||||
charLength: null,
|
||||
nullable: false,
|
||||
unsigned: false,
|
||||
zerofill: false,
|
||||
order: localFields.value.length + 1,
|
||||
default: null,
|
||||
charset: null,
|
||||
collation: defaultCollation.value,
|
||||
autoIncrement: false,
|
||||
onUpdate: '',
|
||||
comment: ''
|
||||
});
|
||||
|
||||
this.newTab({
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
elementName: this.localOptions.name,
|
||||
elementType: 'table',
|
||||
type: 'table-props'
|
||||
});
|
||||
setTimeout(() => {
|
||||
const scrollable = indexTable.value.tableWrapper;
|
||||
scrollable.scrollTop = scrollable.scrollHeight + 30;
|
||||
}, 20);
|
||||
};
|
||||
|
||||
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
|
||||
this.changeBreadcrumbs({ schema: this.schema, table: this.localOptions.name });
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
const renameField = (payload: {index: string; new: string; old: string}) => {
|
||||
localIndexes.value = localIndexes.value.map(index => {
|
||||
const fi = index.fields.findIndex(field => field === payload.old);
|
||||
if (fi !== -1)
|
||||
index.fields[fi] = payload.new;
|
||||
return index;
|
||||
});
|
||||
|
||||
this.isSaving = false;
|
||||
this.newFieldsCounter = 0;
|
||||
},
|
||||
clearChanges () {
|
||||
this.localFields = JSON.parse(JSON.stringify(this.originalFields));
|
||||
this.localIndexes = JSON.parse(JSON.stringify(this.originalIndexes));
|
||||
this.localKeyUsage = JSON.parse(JSON.stringify(this.originalKeyUsage));
|
||||
localKeyUsage.value = localKeyUsage.value.map(key => {
|
||||
if (key.field === payload.old)
|
||||
key.field = payload.new;
|
||||
return key;
|
||||
});
|
||||
};
|
||||
|
||||
this.tableOptions = {
|
||||
name: '',
|
||||
type: 'table',
|
||||
engine: this.defaultEngine,
|
||||
comment: '',
|
||||
collation: this.defaultCollation
|
||||
};
|
||||
const duplicateField = (uid: string) => {
|
||||
const fieldToClone = Object.assign({}, localFields.value.find(field => field._antares_id === uid));
|
||||
fieldToClone._antares_id = uidGen();
|
||||
fieldToClone.name = `${fieldToClone.name}_copy`;
|
||||
fieldToClone.order = localFields.value.length + 1;
|
||||
localFields.value = [...localFields.value, fieldToClone];
|
||||
|
||||
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions));
|
||||
this.newFieldsCounter = 0;
|
||||
},
|
||||
addField () {
|
||||
this.localFields.push({
|
||||
_antares_id: uidGen(),
|
||||
name: `${this.$tc('word.field', 1)}_${++this.newFieldsCounter}`,
|
||||
key: '',
|
||||
type: this.workspace.dataTypes[0].types[0].name,
|
||||
schema: this.schema,
|
||||
numPrecision: null,
|
||||
numLength: this.workspace.dataTypes[0].types[0].length,
|
||||
datePrecision: null,
|
||||
charLength: null,
|
||||
nullable: false,
|
||||
unsigned: false,
|
||||
zerofill: false,
|
||||
order: this.localFields.length + 1,
|
||||
default: null,
|
||||
charset: null,
|
||||
collation: null,
|
||||
autoIncrement: false,
|
||||
onUpdate: '',
|
||||
comment: ''
|
||||
});
|
||||
setTimeout(() => {
|
||||
const scrollable = indexTable.value.tableWrapper;
|
||||
scrollable.scrollTop = scrollable.scrollHeight + 30;
|
||||
}, 20);
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
const scrollable = this.$refs.indexTable.$refs.tableWrapper;
|
||||
scrollable.scrollTop = scrollable.scrollHeight + 30;
|
||||
}, 20);
|
||||
},
|
||||
renameField (payload) {
|
||||
this.localIndexes = this.localIndexes.map(index => {
|
||||
const fi = index.fields.findIndex(field => field === payload.old);
|
||||
if (fi !== -1)
|
||||
index.fields[fi] = payload.new;
|
||||
return index;
|
||||
});
|
||||
const removeField = (uid: string) => {
|
||||
localFields.value = localFields.value.filter(field => field._antares_id !== uid);
|
||||
};
|
||||
|
||||
this.localKeyUsage = this.localKeyUsage.map(key => {
|
||||
if (key.field === payload.old)
|
||||
key.field = payload.new;
|
||||
return key;
|
||||
});
|
||||
},
|
||||
duplicateField (uid) {
|
||||
const fieldToClone = Object.assign({}, this.localFields.find(field => field._antares_id === uid));
|
||||
fieldToClone._antares_id = uidGen();
|
||||
fieldToClone.name = `${fieldToClone.name}_copy`;
|
||||
fieldToClone.order = this.localFields.length + 1;
|
||||
this.localFields = [...this.localFields, fieldToClone];
|
||||
const addNewIndex = (payload: { index: string; field: string }) => {
|
||||
localIndexes.value = [...localIndexes.value, {
|
||||
_antares_id: uidGen(),
|
||||
name: payload.index === 'PRIMARY' ? 'PRIMARY' : payload.field,
|
||||
fields: [payload.field],
|
||||
type: payload.index,
|
||||
comment: '',
|
||||
indexType: 'BTREE',
|
||||
indexComment: '',
|
||||
cardinality: 0
|
||||
}];
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
const scrollable = this.$refs.indexTable.$refs.tableWrapper;
|
||||
scrollable.scrollTop = scrollable.scrollHeight + 30;
|
||||
}, 20);
|
||||
},
|
||||
removeField (uid) {
|
||||
this.localFields = this.localFields.filter(field => field._antares_id !== uid);
|
||||
},
|
||||
addNewIndex (payload) {
|
||||
this.localIndexes = [...this.localIndexes, {
|
||||
_antares_id: uidGen(),
|
||||
name: payload.index === 'PRIMARY' ? 'PRIMARY' : payload.field,
|
||||
fields: [payload.field],
|
||||
type: payload.index,
|
||||
comment: '',
|
||||
indexType: 'BTREE',
|
||||
indexComment: '',
|
||||
cardinality: 0
|
||||
}];
|
||||
},
|
||||
addToIndex (payload) {
|
||||
this.localIndexes = this.localIndexes.map(index => {
|
||||
if (index._antares_id === payload.index) index.fields.push(payload.field);
|
||||
return index;
|
||||
});
|
||||
},
|
||||
optionsUpdate (options) {
|
||||
this.localOptions = options;
|
||||
},
|
||||
showIntdexesModal () {
|
||||
this.isIndexesModal = true;
|
||||
},
|
||||
hideIndexesModal () {
|
||||
this.isIndexesModal = false;
|
||||
},
|
||||
indexesUpdate (indexes) {
|
||||
this.localIndexes = indexes;
|
||||
},
|
||||
showForeignModal () {
|
||||
this.isForeignModal = true;
|
||||
},
|
||||
hideForeignModal () {
|
||||
this.isForeignModal = false;
|
||||
},
|
||||
foreignsUpdate (foreigns) {
|
||||
this.localKeyUsage = foreigns;
|
||||
},
|
||||
onKey (e) {
|
||||
if (this.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
||||
if (this.isChanged)
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
const addToIndex = (payload: { index: string; field: string }) => {
|
||||
localIndexes.value = localIndexes.value.map(index => {
|
||||
if (index._antares_id === payload.index) index.fields.push(payload.field);
|
||||
return index;
|
||||
});
|
||||
};
|
||||
|
||||
const showIntdexesModal = () => {
|
||||
isIndexesModal.value = true;
|
||||
};
|
||||
|
||||
const hideIndexesModal = () => {
|
||||
isIndexesModal.value = false;
|
||||
};
|
||||
|
||||
const indexesUpdate = (indexes: TableIndex[]) => {
|
||||
localIndexes.value = indexes;
|
||||
};
|
||||
|
||||
const showForeignModal = () => {
|
||||
isForeignModal.value = true;
|
||||
};
|
||||
|
||||
const hideForeignModal = () => {
|
||||
isForeignModal.value = false;
|
||||
};
|
||||
|
||||
const foreignsUpdate = (foreigns: TableForeign[]) => {
|
||||
localKeyUsage.value = foreigns;
|
||||
};
|
||||
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (props.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.key === 's') { // CTRL + S
|
||||
if (isChanged.value)
|
||||
saveChanges();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.isSelected, (val) => {
|
||||
if (val) changeBreadcrumbs({ schema: props.schema });
|
||||
});
|
||||
|
||||
watch(isChanged, (val) => {
|
||||
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
|
||||
});
|
||||
|
||||
tableOptions.value = {
|
||||
name: '',
|
||||
type: 'table',
|
||||
engine: defaultEngine.value,
|
||||
comment: '',
|
||||
collation: defaultCollation.value
|
||||
};
|
||||
|
||||
localOptions.value = JSON.parse(JSON.stringify(tableOptions.value));
|
||||
window.addEventListener('keydown', onKey);
|
||||
|
||||
onMounted(() => {
|
||||
if (props.isSelected)
|
||||
changeBreadcrumbs({ schema: props.schema });
|
||||
|
||||
setTimeout(() => {
|
||||
firstInput.value.focus();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
</script>
|
||||
|
@ -1,22 +1,22 @@
|
||||
<template>
|
||||
<div class="column col-12 empty">
|
||||
<p class="h6 empty-subtitle">
|
||||
{{ $t('message.thereAreNoTableFields') }}
|
||||
{{ t('message.thereAreNoTableFields') }}
|
||||
</p>
|
||||
<div class="empty-action">
|
||||
<button class="btn btn-gray d-flex" @click="$emit('new-field')">
|
||||
<button class="btn btn-gray d-flex" @click="emit('new-field')">
|
||||
<i class="mdi mdi-24px mdi-playlist-plus mr-2" />
|
||||
{{ $t('message.addNewField') }}
|
||||
{{ t('message.addNewField') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'WorkspaceTabNewTableEmptyState',
|
||||
emits: ['new-field']
|
||||
};
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['new-field']);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -11,20 +11,20 @@
|
||||
@click="saveChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
<span>{{ t('word.save') }}</span>
|
||||
</button>
|
||||
<button
|
||||
:disabled="!isChanged"
|
||||
class="btn btn-link btn-sm mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
:title="t('message.clearChanges')"
|
||||
@click="clearChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<span>{{ t('word.clear') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="workspace-query-info">
|
||||
<div class="d-flex" :title="$t('word.schema')">
|
||||
<div class="d-flex" :title="t('word.schema')">
|
||||
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||
</div>
|
||||
</div>
|
||||
@ -34,7 +34,7 @@
|
||||
<div class="columns">
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
<label class="form-label">{{ t('word.name') }}</label>
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="localTrigger.name"
|
||||
@ -45,12 +45,12 @@
|
||||
</div>
|
||||
<div v-if="customizations.definer" class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.definer') }}</label>
|
||||
<label class="form-label">{{ t('word.definer') }}</label>
|
||||
<BaseSelect
|
||||
v-model="localTrigger.definer"
|
||||
:options="users"
|
||||
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
|
||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||
:option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
|
||||
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||
class="form-select"
|
||||
/>
|
||||
</div>
|
||||
@ -58,7 +58,7 @@
|
||||
<fieldset class="column columns mb-0" :disabled="customizations.triggerOnlyRename">
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.table') }}</label>
|
||||
<label class="form-label">{{ t('word.table') }}</label>
|
||||
<BaseSelect
|
||||
v-model="localTrigger.table"
|
||||
:options="schemaTables"
|
||||
@ -70,7 +70,7 @@
|
||||
</div>
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.event') }}</label>
|
||||
<label class="form-label">{{ t('word.event') }}</label>
|
||||
<div class="input-group">
|
||||
<BaseSelect
|
||||
v-model="localTrigger.activation"
|
||||
@ -85,7 +85,7 @@
|
||||
/>
|
||||
<div v-if="customizations.triggerMultipleEvents" class="px-4">
|
||||
<label
|
||||
v-for="event in Object.keys(localEvents)"
|
||||
v-for="event in Object.keys(localEvents) as ('INSERT' | 'UPDATE' | 'DELETE')[]"
|
||||
:key="event"
|
||||
class="form-checkbox form-inline"
|
||||
@change.prevent="changeEvents(event)"
|
||||
@ -101,7 +101,7 @@
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<label class="form-label ml-2">{{ $t('message.triggerStatement') }}</label>
|
||||
<label class="form-label ml-2">{{ t('message.triggerStatement') }}</label>
|
||||
<QueryEditor
|
||||
v-show="isSelected"
|
||||
ref="queryEditor"
|
||||
@ -114,219 +114,204 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { storeToRefs } from 'pinia';
|
||||
<script setup lang="ts">
|
||||
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
||||
import { Ace } from 'ace-builds';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import QueryEditor from '@/components/QueryEditor.vue';
|
||||
import BaseLoader from '@/components/BaseLoader.vue';
|
||||
import Triggers from '@/ipc-api/Triggers';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceTabNewTrigger',
|
||||
components: {
|
||||
BaseLoader,
|
||||
QueryEditor,
|
||||
BaseSelect
|
||||
},
|
||||
props: {
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
tab: Object,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
},
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const props = defineProps({
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
tab: Object,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
});
|
||||
|
||||
const {
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges,
|
||||
newTab,
|
||||
removeTab,
|
||||
renameTabs
|
||||
} = workspacesStore;
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges,
|
||||
newTab,
|
||||
removeTab,
|
||||
renameTabs
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
originalTrigger: {},
|
||||
localTrigger: {},
|
||||
lastTrigger: null,
|
||||
sqlProxy: '',
|
||||
editorHeight: 300,
|
||||
localEvents: { INSERT: false, UPDATE: false, DELETE: false }
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
customizations () {
|
||||
return this.workspace.customizations;
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.originalTrigger) !== JSON.stringify(this.localTrigger);
|
||||
},
|
||||
isDefinerInUsers () {
|
||||
return this.originalTrigger ? this.workspace.users.some(user => this.originalTrigger.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
||||
},
|
||||
schemaTables () {
|
||||
const schemaTables = this.workspace.structure
|
||||
.filter(schema => schema.name === this.schema)
|
||||
.map(schema => schema.tables);
|
||||
const {
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges,
|
||||
newTab,
|
||||
removeTab
|
||||
} = workspacesStore;
|
||||
|
||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||
},
|
||||
users () {
|
||||
const users = [{ value: '' }, ...this.workspace.users];
|
||||
if (!this.isDefinerInUsers) {
|
||||
const [name, host] = this.originalTrigger.definer.replaceAll('`', '').split('@');
|
||||
users.unshift({ name, host });
|
||||
}
|
||||
const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
|
||||
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const originalTrigger = ref(null);
|
||||
const localTrigger = ref(null);
|
||||
const editorHeight = ref(300);
|
||||
const localEvents = ref({ INSERT: false, UPDATE: false, DELETE: false });
|
||||
|
||||
return users;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isSelected (val) {
|
||||
if (val)
|
||||
this.changeBreadcrumbs({ schema: this.schema });
|
||||
},
|
||||
isChanged (val) {
|
||||
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.originalTrigger = {
|
||||
sql: this.customizations.triggerSql,
|
||||
definer: '',
|
||||
table: this.schemaTables.length ? this.schemaTables[0].name : null,
|
||||
activation: 'BEFORE',
|
||||
event: this.customizations.triggerMultipleEvents ? ['INSERT'] : 'INSERT',
|
||||
name: ''
|
||||
};
|
||||
const workspace = computed(() => {
|
||||
return getWorkspace(props.connection.uid);
|
||||
});
|
||||
|
||||
this.localTrigger = JSON.parse(JSON.stringify(this.originalTrigger));
|
||||
const customizations = computed(() => {
|
||||
return workspace.value.customizations;
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.resizeQueryEditor();
|
||||
}, 50);
|
||||
const isChanged = computed(() => {
|
||||
return JSON.stringify(originalTrigger.value) !== JSON.stringify(localTrigger.value);
|
||||
});
|
||||
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
mounted () {
|
||||
if (this.isSelected)
|
||||
this.changeBreadcrumbs({ schema: this.schema });
|
||||
const isDefinerInUsers = computed(() => {
|
||||
return originalTrigger.value ? workspace.value.users.some(user => originalTrigger.value.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 100);
|
||||
const schemaTables = computed(() => {
|
||||
const schemaTables = workspace.value.structure
|
||||
.filter(schema => schema.name === props.schema)
|
||||
.map(schema => schema.tables);
|
||||
|
||||
window.addEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
unmounted () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
changeEvents (event) {
|
||||
if (this.customizations.triggerMultipleEvents) {
|
||||
this.localEvents[event] = !this.localEvents[event];
|
||||
this.localTrigger.event = [];
|
||||
for (const key in this.localEvents) {
|
||||
if (this.localEvents[key])
|
||||
this.localTrigger.event.push(key);
|
||||
}
|
||||
}
|
||||
},
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
this.isSaving = true;
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
...this.localTrigger
|
||||
};
|
||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||
});
|
||||
|
||||
try {
|
||||
const { status, response } = await Triggers.createTrigger(params);
|
||||
const users = computed(() => {
|
||||
const users = [{ value: '' }, ...workspace.value.users];
|
||||
if (!isDefinerInUsers.value) {
|
||||
const [name, host] = originalTrigger.value.definer.replaceAll('`', '').split('@');
|
||||
users.unshift({ name, host });
|
||||
}
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
return users;
|
||||
});
|
||||
|
||||
this.newTab({
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
elementName: this.localTrigger.name,
|
||||
elementType: 'trigger',
|
||||
type: 'trigger-props'
|
||||
});
|
||||
|
||||
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
|
||||
this.changeBreadcrumbs({ schema: this.schema, trigger: this.localTrigger.name });
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isSaving = false;
|
||||
},
|
||||
clearChanges () {
|
||||
this.localTrigger = JSON.parse(JSON.stringify(this.originalTrigger));
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localTrigger.sql);
|
||||
|
||||
Object.keys(this.localEvents).forEach(event => {
|
||||
this.localEvents[event] = false;
|
||||
});
|
||||
|
||||
if (this.customizations.triggerMultipleEvents) {
|
||||
this.originalTrigger.event.forEach(e => {
|
||||
this.localEvents[e] = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
resizeQueryEditor () {
|
||||
if (this.$refs.queryEditor) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
this.editorHeight = size;
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
}
|
||||
},
|
||||
onKey (e) {
|
||||
if (this.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
||||
if (this.isChanged)
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
const changeEvents = (event: 'INSERT' | 'UPDATE' | 'DELETE') => {
|
||||
if (customizations.value.triggerMultipleEvents) {
|
||||
localEvents.value[event] = !localEvents.value[event];
|
||||
localTrigger.value.event = [];
|
||||
for (const key in localEvents.value) {
|
||||
if (localEvents.value[key as 'INSERT' | 'UPDATE' | 'DELETE'])
|
||||
localTrigger.value.event.push(key);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const saveChanges = async () => {
|
||||
if (isSaving.value) return;
|
||||
isSaving.value = true;
|
||||
const params = {
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
...localTrigger.value
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Triggers.createTrigger(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await refreshStructure(props.connection.uid);
|
||||
|
||||
newTab({
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
elementName: localTrigger.value.name,
|
||||
elementType: 'trigger',
|
||||
type: 'trigger-props'
|
||||
});
|
||||
|
||||
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
|
||||
changeBreadcrumbs({ schema: props.schema, trigger: localTrigger.value.name });
|
||||
}
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
isSaving.value = false;
|
||||
};
|
||||
|
||||
const clearChanges = () => {
|
||||
localTrigger.value = JSON.parse(JSON.stringify(originalTrigger.value));
|
||||
queryEditor.value.editor.session.setValue(localTrigger.value.sql);
|
||||
|
||||
Object.keys(localEvents.value).forEach((event: 'INSERT' | 'UPDATE' | 'DELETE') => {
|
||||
localEvents.value[event] = false;
|
||||
});
|
||||
|
||||
if (customizations.value.triggerMultipleEvents) {
|
||||
originalTrigger.value.event.forEach((e: 'INSERT' | 'UPDATE' | 'DELETE') => {
|
||||
localEvents.value[e] = true;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const resizeQueryEditor = () => {
|
||||
if (queryEditor.value) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
editorHeight.value = size;
|
||||
queryEditor.value.editor.resize();
|
||||
}
|
||||
};
|
||||
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (props.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
||||
if (isChanged.value)
|
||||
saveChanges();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.isSelected, (val) => {
|
||||
if (val) changeBreadcrumbs({ schema: props.schema });
|
||||
});
|
||||
|
||||
watch(isChanged, (val) => {
|
||||
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
|
||||
});
|
||||
|
||||
originalTrigger.value = {
|
||||
sql: customizations.value.triggerSql,
|
||||
definer: '',
|
||||
table: schemaTables.value.length ? schemaTables.value[0].name : null,
|
||||
activation: 'BEFORE',
|
||||
event: customizations.value.triggerMultipleEvents ? ['INSERT'] : 'INSERT',
|
||||
name: ''
|
||||
};
|
||||
|
||||
localTrigger.value = JSON.parse(JSON.stringify(originalTrigger.value));
|
||||
|
||||
setTimeout(() => {
|
||||
resizeQueryEditor();
|
||||
}, 50);
|
||||
|
||||
window.addEventListener('keydown', onKey);
|
||||
|
||||
onMounted(() => {
|
||||
if (props.isSelected)
|
||||
changeBreadcrumbs({ schema: props.schema });
|
||||
|
||||
setTimeout(() => {
|
||||
firstInput.value.focus();
|
||||
}, 100);
|
||||
|
||||
window.addEventListener('resize', resizeQueryEditor);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', resizeQueryEditor);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
</script>
|
||||
|
@ -11,16 +11,16 @@
|
||||
@click="saveChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
<span>{{ t('word.save') }}</span>
|
||||
</button>
|
||||
<button
|
||||
:disabled="!isChanged"
|
||||
class="btn btn-link btn-sm mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
:title="t('message.clearChanges')"
|
||||
@click="clearChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<span>{{ t('word.clear') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -30,7 +30,7 @@
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.name') }}
|
||||
{{ t('word.name') }}
|
||||
</label>
|
||||
<input
|
||||
ref="firstInput"
|
||||
@ -43,7 +43,7 @@
|
||||
<div v-if="customizations.triggerFunctionlanguages" class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.language') }}
|
||||
{{ t('word.language') }}
|
||||
</label>
|
||||
<BaseSelect
|
||||
v-model="localFunction.language"
|
||||
@ -55,20 +55,20 @@
|
||||
<div v-if="customizations.definer" class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.definer') }}
|
||||
{{ t('word.definer') }}
|
||||
</label>
|
||||
<BaseSelect
|
||||
v-model="localFunction.definer"
|
||||
:options="workspace.users"
|
||||
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
|
||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||
:option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
|
||||
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||
class="form-select"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="customizations.comment" class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.comment') }}
|
||||
{{ t('word.comment') }}
|
||||
</label>
|
||||
<input
|
||||
v-model="localFunction.comment"
|
||||
@ -80,7 +80,7 @@
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<label class="form-label ml-2">{{ $t('message.functionBody') }}</label>
|
||||
<label class="form-label ml-2">{{ t('message.functionBody') }}</label>
|
||||
<QueryEditor
|
||||
v-show="isSelected"
|
||||
ref="queryEditor"
|
||||
@ -93,190 +93,157 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { storeToRefs } from 'pinia';
|
||||
<script setup lang="ts">
|
||||
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
||||
import { Ace } from 'ace-builds';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import BaseLoader from '@/components/BaseLoader.vue';
|
||||
import QueryEditor from '@/components/QueryEditor.vue';
|
||||
import Functions from '@/ipc-api/Functions';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceTabNewTriggerFunction',
|
||||
components: {
|
||||
BaseLoader,
|
||||
QueryEditor,
|
||||
BaseSelect
|
||||
},
|
||||
props: {
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
tab: Object,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
},
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const props = defineProps({
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
tab: Object,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
});
|
||||
|
||||
const {
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges,
|
||||
newTab,
|
||||
removeTab,
|
||||
renameTabs
|
||||
} = workspacesStore;
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges,
|
||||
newTab,
|
||||
removeTab,
|
||||
renameTabs
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
isParamsModal: false,
|
||||
isAskingParameters: false,
|
||||
originalFunction: {},
|
||||
localFunction: {},
|
||||
lastFunction: null,
|
||||
sqlProxy: '',
|
||||
editorHeight: 300
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
customizations () {
|
||||
return this.workspace.customizations;
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.originalFunction) !== JSON.stringify(this.localFunction);
|
||||
},
|
||||
isDefinerInUsers () {
|
||||
return this.originalFunction
|
||||
? this.workspace.users.some(user => this.originalFunction.definer === `\`${user.name}\`@\`${user.host}\``)
|
||||
: true;
|
||||
},
|
||||
schemaTables () {
|
||||
const schemaTables = this.workspace.structure
|
||||
.filter(schema => schema.name === this.schema)
|
||||
.map(schema => schema.tables);
|
||||
const {
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges,
|
||||
newTab,
|
||||
removeTab
|
||||
} = workspacesStore;
|
||||
|
||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||
const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
|
||||
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const originalFunction = ref(null);
|
||||
const localFunction = ref(null);
|
||||
const editorHeight = ref(300);
|
||||
|
||||
const workspace = computed(() => {
|
||||
return getWorkspace(props.connection.uid);
|
||||
});
|
||||
|
||||
const customizations = computed(() => {
|
||||
return workspace.value.customizations;
|
||||
});
|
||||
|
||||
const isChanged = computed(() => {
|
||||
return JSON.stringify(originalFunction.value) !== JSON.stringify(localFunction.value);
|
||||
});
|
||||
|
||||
const saveChanges = async () => {
|
||||
if (isSaving.value) return;
|
||||
isSaving.value = true;
|
||||
const params = {
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
...localFunction.value
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Functions.createTriggerFunction(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await refreshStructure(props.connection.uid);
|
||||
|
||||
newTab({
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
elementName: localFunction.value.name,
|
||||
elementType: 'triggerFunction',
|
||||
type: 'trigger-function-props'
|
||||
});
|
||||
|
||||
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
|
||||
changeBreadcrumbs({ schema: props.schema, triggerFunction: localFunction.value.name });
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isSelected (val) {
|
||||
if (val)
|
||||
this.changeBreadcrumbs({ schema: this.schema });
|
||||
},
|
||||
isChanged (val) {
|
||||
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.originalFunction = {
|
||||
sql: this.customizations.triggerFunctionSql,
|
||||
language: this.customizations.triggerFunctionlanguages.length ? this.customizations.triggerFunctionlanguages[0] : null,
|
||||
name: ''
|
||||
};
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction));
|
||||
isSaving.value = false;
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
this.resizeQueryEditor();
|
||||
}, 50);
|
||||
const clearChanges = () => {
|
||||
localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
|
||||
queryEditor.value.editor.session.setValue(localFunction.value.sql);
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
mounted () {
|
||||
if (this.isSelected)
|
||||
this.changeBreadcrumbs({ schema: this.schema });
|
||||
const resizeQueryEditor = () => {
|
||||
if (queryEditor.value) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
editorHeight.value = size;
|
||||
queryEditor.value.editor.resize();
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 100);
|
||||
|
||||
window.addEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
unmounted () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
this.isSaving = true;
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
...this.localFunction
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Functions.createTriggerFunction(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
|
||||
this.newTab({
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
elementName: this.localFunction.name,
|
||||
elementType: 'triggerFunction',
|
||||
type: 'trigger-function-props'
|
||||
});
|
||||
|
||||
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
|
||||
this.changeBreadcrumbs({ schema: this.schema, triggerFunction: this.localFunction.name });
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isSaving = false;
|
||||
},
|
||||
clearChanges () {
|
||||
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction));
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
|
||||
},
|
||||
resizeQueryEditor () {
|
||||
if (this.$refs.queryEditor) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
this.editorHeight = size;
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
}
|
||||
},
|
||||
onKey (e) {
|
||||
if (this.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
||||
if (this.isChanged)
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (props.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.key === 's') { // CTRL + S
|
||||
if (isChanged.value)
|
||||
saveChanges();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
originalFunction.value = {
|
||||
sql: customizations.value.triggerFunctionSql,
|
||||
language: customizations.value.triggerFunctionlanguages.length ? customizations.value.triggerFunctionlanguages[0] : null,
|
||||
name: ''
|
||||
};
|
||||
|
||||
localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
|
||||
|
||||
setTimeout(() => {
|
||||
resizeQueryEditor();
|
||||
}, 50);
|
||||
|
||||
window.addEventListener('keydown', onKey);
|
||||
|
||||
onMounted(() => {
|
||||
if (props.isSelected)
|
||||
changeBreadcrumbs({ schema: props.schema });
|
||||
|
||||
setTimeout(() => {
|
||||
firstInput.value.focus();
|
||||
}, 100);
|
||||
|
||||
window.addEventListener('resize', resizeQueryEditor);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', resizeQueryEditor);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
|
||||
watch(() => props.isSelected, (val) => {
|
||||
if (val) changeBreadcrumbs({ schema: props.schema });
|
||||
});
|
||||
|
||||
watch(isChanged, (val) => {
|
||||
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
|
||||
});
|
||||
</script>
|
||||
|
@ -11,20 +11,20 @@
|
||||
@click="saveChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
<span>{{ t('word.save') }}</span>
|
||||
</button>
|
||||
<button
|
||||
:disabled="!isChanged"
|
||||
class="btn btn-link btn-sm mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
:title="t('message.clearChanges')"
|
||||
@click="clearChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<span>{{ t('word.clear') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="workspace-query-info">
|
||||
<div class="d-flex" :title="$t('word.schema')">
|
||||
<div class="d-flex" :title="t('word.schema')">
|
||||
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||
</div>
|
||||
</div>
|
||||
@ -34,7 +34,7 @@
|
||||
<div class="columns">
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
<label class="form-label">{{ t('word.name') }}</label>
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="localView.name"
|
||||
@ -45,19 +45,19 @@
|
||||
</div>
|
||||
<div class="column col-auto">
|
||||
<div v-if="workspace.customizations.definer" class="form-group">
|
||||
<label class="form-label">{{ $t('word.definer') }}</label>
|
||||
<label class="form-label">{{ t('word.definer') }}</label>
|
||||
<BaseSelect
|
||||
v-model="localView.definer"
|
||||
:options="users"
|
||||
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
|
||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||
:option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
|
||||
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||
class="form-select"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-auto mr-2">
|
||||
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
|
||||
<label class="form-label">{{ $t('message.sqlSecurity') }}</label>
|
||||
<label class="form-label">{{ t('message.sqlSecurity') }}</label>
|
||||
<BaseSelect
|
||||
v-model="localView.security"
|
||||
:options="['DEFINER', 'INVOKER']"
|
||||
@ -67,7 +67,7 @@
|
||||
</div>
|
||||
<div class="column col-auto mr-2">
|
||||
<div v-if="workspace.customizations.viewAlgorithm" class="form-group">
|
||||
<label class="form-label">{{ $t('word.algorithm') }}</label>
|
||||
<label class="form-label">{{ t('word.algorithm') }}</label>
|
||||
<BaseSelect
|
||||
v-model="localView.algorithm"
|
||||
:options="['UNDEFINED', 'MERGE', 'TEMPTABLE']"
|
||||
@ -77,10 +77,10 @@
|
||||
</div>
|
||||
<div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('message.updateOption') }}</label>
|
||||
<label class="form-label">{{ t('message.updateOption') }}</label>
|
||||
<BaseSelect
|
||||
v-model="localView.updateOption"
|
||||
:option-track-by="(user) => user.value"
|
||||
:option-track-by="(user: any) => user.value"
|
||||
:options="[{label: 'None', value: ''}, {label: 'CASCADED', value: 'CASCADED'}, {label: 'LOCAL', value: 'LOCAL'}]"
|
||||
class="form-select"
|
||||
/>
|
||||
@ -90,7 +90,7 @@
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<label class="form-label ml-2">{{ $t('message.selectStatement') }}</label>
|
||||
<label class="form-label ml-2">{{ t('message.selectStatement') }}</label>
|
||||
<QueryEditor
|
||||
v-show="isSelected"
|
||||
ref="queryEditor"
|
||||
@ -103,194 +103,170 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { storeToRefs } from 'pinia';
|
||||
<script setup lang="ts">
|
||||
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
||||
import { Ace } from 'ace-builds';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import BaseLoader from '@/components/BaseLoader.vue';
|
||||
import QueryEditor from '@/components/QueryEditor.vue';
|
||||
import Views from '@/ipc-api/Views';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceTabNewView',
|
||||
components: {
|
||||
BaseLoader,
|
||||
QueryEditor,
|
||||
BaseSelect
|
||||
},
|
||||
props: {
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
tab: Object,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
},
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const props = defineProps({
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
tab: Object,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
});
|
||||
|
||||
const {
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
setUnsavedChanges,
|
||||
changeBreadcrumbs,
|
||||
newTab,
|
||||
removeTab,
|
||||
renameTabs
|
||||
} = workspacesStore;
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
setUnsavedChanges,
|
||||
changeBreadcrumbs,
|
||||
newTab,
|
||||
removeTab,
|
||||
renameTabs
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
originalView: {},
|
||||
localView: {},
|
||||
lastView: null,
|
||||
sqlProxy: '',
|
||||
editorHeight: 300
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.originalView) !== JSON.stringify(this.localView);
|
||||
},
|
||||
isDefinerInUsers () {
|
||||
return this.originalView ? this.workspace.users.some(user => this.originalView.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
||||
},
|
||||
users () {
|
||||
const users = [{ value: '' }, ...this.workspace.users];
|
||||
if (!this.isDefinerInUsers) {
|
||||
const [name, host] = this.originalView.definer.replaceAll('`', '').split('@');
|
||||
users.unshift({ name, host });
|
||||
}
|
||||
const {
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
setUnsavedChanges,
|
||||
changeBreadcrumbs,
|
||||
newTab,
|
||||
removeTab
|
||||
} = workspacesStore;
|
||||
|
||||
return users;
|
||||
const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
|
||||
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const originalView = ref(null);
|
||||
const localView = ref(null);
|
||||
const editorHeight = ref(300);
|
||||
|
||||
const workspace = computed(() => getWorkspace(props.connection.uid));
|
||||
const isChanged = computed(() => JSON.stringify(originalView.value) !== JSON.stringify(localView.value));
|
||||
const isDefinerInUsers = computed(() => originalView.value ? workspace.value.users.some(user => originalView.value.definer === `\`${user.name}\`@\`${user.host}\``) : true);
|
||||
|
||||
const users = computed(() => {
|
||||
const users = [{ value: '' }, ...workspace.value.users];
|
||||
if (!isDefinerInUsers.value) {
|
||||
const [name, host] = originalView.value.definer.replaceAll('`', '').split('@');
|
||||
users.unshift({ name, host });
|
||||
}
|
||||
|
||||
return users;
|
||||
});
|
||||
|
||||
const saveChanges = async () => {
|
||||
if (isSaving.value) return;
|
||||
isSaving.value = true;
|
||||
|
||||
const params = {
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
...localView.value
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Views.createView(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await refreshStructure(props.connection.uid);
|
||||
|
||||
newTab({
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
elementName: localView.value.name,
|
||||
elementType: 'view',
|
||||
type: 'view-props'
|
||||
});
|
||||
|
||||
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
|
||||
changeBreadcrumbs({ schema: props.schema, view: localView.value.name });
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isSelected (val) {
|
||||
if (val) {
|
||||
this.changeBreadcrumbs({ schema: this.schema, view: this.view });
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.resizeQueryEditor();
|
||||
}, 50);
|
||||
}
|
||||
},
|
||||
isChanged (val) {
|
||||
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
this.originalView = {
|
||||
algorithm: 'UNDEFINED',
|
||||
definer: '',
|
||||
security: 'DEFINER',
|
||||
updateOption: '',
|
||||
sql: '',
|
||||
name: ''
|
||||
};
|
||||
isSaving.value = false;
|
||||
};
|
||||
|
||||
this.localView = JSON.parse(JSON.stringify(this.originalView));
|
||||
const clearChanges = () => {
|
||||
localView.value = JSON.parse(JSON.stringify(originalView.value));
|
||||
queryEditor.value.editor.session.setValue(localView.value.sql);
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
this.resizeQueryEditor();
|
||||
}, 50);
|
||||
const resizeQueryEditor = () => {
|
||||
if (queryEditor.value) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
editorHeight.value = size;
|
||||
queryEditor.value.editor.resize();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
mounted () {
|
||||
if (this.isSelected)
|
||||
this.changeBreadcrumbs({ schema: this.schema });
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 100);
|
||||
|
||||
window.addEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
unmounted () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
this.isSaving = true;
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
...this.localView
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Views.createView(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
|
||||
this.newTab({
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
elementName: this.localView.name,
|
||||
elementType: 'view',
|
||||
type: 'view-props'
|
||||
});
|
||||
|
||||
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
|
||||
this.changeBreadcrumbs({ schema: this.schema, view: this.localView.name });
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isSaving = false;
|
||||
},
|
||||
clearChanges () {
|
||||
this.localView = JSON.parse(JSON.stringify(this.originalView));
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localView.sql);
|
||||
},
|
||||
resizeQueryEditor () {
|
||||
if (this.$refs.queryEditor) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
this.editorHeight = size;
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
}
|
||||
},
|
||||
onKey (e) {
|
||||
if (this.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
||||
if (this.isChanged)
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (props.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.key === 's') { // CTRL + S
|
||||
if (isChanged.value)
|
||||
saveChanges();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.isSelected, (val) => {
|
||||
if (val) {
|
||||
changeBreadcrumbs({ schema: props.schema, view: localView.value.name });
|
||||
|
||||
setTimeout(() => {
|
||||
resizeQueryEditor();
|
||||
}, 50);
|
||||
}
|
||||
});
|
||||
|
||||
watch(isChanged, (val) => {
|
||||
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
|
||||
});
|
||||
|
||||
originalView.value = {
|
||||
algorithm: 'UNDEFINED',
|
||||
definer: '',
|
||||
security: 'DEFINER',
|
||||
updateOption: '',
|
||||
sql: '',
|
||||
name: ''
|
||||
};
|
||||
|
||||
localView.value = JSON.parse(JSON.stringify(originalView.value));
|
||||
|
||||
setTimeout(() => {
|
||||
resizeQueryEditor();
|
||||
}, 50);
|
||||
|
||||
window.addEventListener('keydown', onKey);
|
||||
|
||||
onMounted(() => {
|
||||
if (props.isSelected)
|
||||
changeBreadcrumbs({ schema: props.schema });
|
||||
|
||||
setTimeout(() => {
|
||||
firstInput.value.focus();
|
||||
}, 100);
|
||||
|
||||
window.addEventListener('resize', resizeQueryEditor);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', resizeQueryEditor);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
@ -11,16 +11,16 @@
|
||||
@click="saveChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
<span>{{ t('word.save') }}</span>
|
||||
</button>
|
||||
<button
|
||||
:disabled="!isChanged"
|
||||
class="btn btn-link btn-sm mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
:title="t('message.clearChanges')"
|
||||
@click="clearChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<span>{{ t('word.clear') }}</span>
|
||||
</button>
|
||||
|
||||
<div class="divider-vert py-3" />
|
||||
@ -31,15 +31,15 @@
|
||||
@click="runFunctionCheck"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-play mr-1" />
|
||||
<span>{{ $t('word.run') }}</span>
|
||||
<span>{{ t('word.run') }}</span>
|
||||
</button>
|
||||
<button class="btn btn-dark btn-sm" @click="showParamsModal">
|
||||
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
|
||||
<span>{{ $t('word.parameters') }}</span>
|
||||
<span>{{ t('word.parameters') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="workspace-query-info">
|
||||
<div class="d-flex" :title="$t('word.schema')">
|
||||
<div class="d-flex" :title="t('word.schema')">
|
||||
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||
</div>
|
||||
</div>
|
||||
@ -50,7 +50,7 @@
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.name') }}
|
||||
{{ t('word.name') }}
|
||||
</label>
|
||||
<input
|
||||
ref="firstInput"
|
||||
@ -64,7 +64,7 @@
|
||||
<div v-if="customizations.languages" class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.language') }}
|
||||
{{ t('word.language') }}
|
||||
</label>
|
||||
<BaseSelect
|
||||
v-model="localFunction.language"
|
||||
@ -76,13 +76,13 @@
|
||||
<div v-if="customizations.definer" class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.definer') }}
|
||||
{{ t('word.definer') }}
|
||||
</label>
|
||||
<BaseSelect
|
||||
v-model="localFunction.definer"
|
||||
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]"
|
||||
:option-label="(user) => user.value === '' ? user.name : `${user.name}@${user.host}`"
|
||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||
:options="[{value: '', name:t('message.currentUser')}, ...workspace.users]"
|
||||
:option-label="(user: any) => user.value === '' ? user.name : `${user.name}@${user.host}`"
|
||||
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||
class="form-select"
|
||||
/>
|
||||
</div>
|
||||
@ -90,13 +90,13 @@
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.returns') }}
|
||||
{{ t('word.returns') }}
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<BaseSelect
|
||||
v-model="localFunction.returns"
|
||||
class="form-select text-uppercase"
|
||||
:options="[{ name: 'VOID' }, ...workspace.dataTypes]"
|
||||
:options="[{ name: 'VOID' }, ...(workspace.dataTypes as any)]"
|
||||
group-label="group"
|
||||
group-values="types"
|
||||
option-label="name"
|
||||
@ -110,7 +110,7 @@
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="0"
|
||||
:placeholder="$t('word.length')"
|
||||
:placeholder="t('word.length')"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@ -118,7 +118,7 @@
|
||||
<div v-if="customizations.comment" class="column">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.comment') }}
|
||||
{{ t('word.comment') }}
|
||||
</label>
|
||||
<input
|
||||
v-model="localFunction.comment"
|
||||
@ -130,7 +130,7 @@
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('message.sqlSecurity') }}
|
||||
{{ t('message.sqlSecurity') }}
|
||||
</label>
|
||||
<BaseSelect
|
||||
v-model="localFunction.security"
|
||||
@ -142,7 +142,7 @@
|
||||
<div v-if="customizations.functionDataAccess" class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('message.dataAccess') }}
|
||||
{{ t('message.dataAccess') }}
|
||||
</label>
|
||||
<BaseSelect
|
||||
v-model="localFunction.dataAccess"
|
||||
@ -155,7 +155,7 @@
|
||||
<div class="form-group">
|
||||
<label class="form-label d-invisible">.</label>
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="localFunction.deterministic" type="checkbox"><i class="form-icon" /> {{ $t('word.deterministic') }}
|
||||
<input v-model="localFunction.deterministic" type="checkbox"><i class="form-icon" /> {{ t('word.deterministic') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -163,7 +163,7 @@
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<label class="form-label ml-2">{{ $t('message.functionBody') }}</label>
|
||||
<label class="form-label ml-2">{{ t('message.functionBody') }}</label>
|
||||
<QueryEditor
|
||||
v-show="isSelected"
|
||||
ref="queryEditor"
|
||||
@ -191,300 +191,273 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { storeToRefs } from 'pinia';
|
||||
<script setup lang="ts">
|
||||
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
||||
import { Ace } from 'ace-builds';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import WorkspaceTabPropsFunctionParamsModal from '@/components/WorkspaceTabPropsFunctionParamsModal';
|
||||
import ModalAskParameters from '@/components/ModalAskParameters';
|
||||
import BaseLoader from '@/components/BaseLoader.vue';
|
||||
import QueryEditor from '@/components/QueryEditor.vue';
|
||||
import WorkspaceTabPropsFunctionParamsModal from '@/components/WorkspaceTabPropsFunctionParamsModal.vue';
|
||||
import ModalAskParameters from '@/components/ModalAskParameters.vue';
|
||||
import Functions from '@/ipc-api/Functions';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { AlterFunctionParams, FunctionInfos, FunctionParam } from 'common/interfaces/antares';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceTabPropsFunction',
|
||||
components: {
|
||||
BaseLoader,
|
||||
QueryEditor,
|
||||
WorkspaceTabPropsFunctionParamsModal,
|
||||
ModalAskParameters,
|
||||
BaseSelect
|
||||
},
|
||||
props: {
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
function: String,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
},
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const props = defineProps({
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
function: String,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
});
|
||||
|
||||
const {
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
renameTabs,
|
||||
newTab,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges
|
||||
} = workspacesStore;
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
renameTabs,
|
||||
newTab,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
isParamsModal: false,
|
||||
isAskingParameters: false,
|
||||
originalFunction: null,
|
||||
localFunction: { sql: '' },
|
||||
lastFunction: null,
|
||||
sqlProxy: '',
|
||||
editorHeight: 300
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
customizations () {
|
||||
return this.workspace.customizations;
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.originalFunction) !== JSON.stringify(this.localFunction);
|
||||
},
|
||||
isDefinerInUsers () {
|
||||
return this.originalFunction
|
||||
? this.workspace.users.some(user => this.originalFunction.definer === `\`${user.name}\`@\`${user.host}\``)
|
||||
: true;
|
||||
},
|
||||
isTableNameValid () {
|
||||
return this.localFunction.name !== '';
|
||||
},
|
||||
isInDataTypes () {
|
||||
let typeNames = [];
|
||||
for (const group of this.workspace.dataTypes) {
|
||||
typeNames = group.types.reduce((acc, curr) => {
|
||||
acc.push(curr.name);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
return typeNames.includes(this.localFunction.returns);
|
||||
},
|
||||
schemaTables () {
|
||||
const schemaTables = this.workspace.structure
|
||||
.filter(schema => schema.name === this.schema)
|
||||
.map(schema => schema.tables);
|
||||
const {
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
renameTabs,
|
||||
newTab,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges
|
||||
} = workspacesStore;
|
||||
|
||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||
const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
|
||||
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const isParamsModal = ref(false);
|
||||
const isAskingParameters = ref(false);
|
||||
const originalFunction: Ref<FunctionInfos> = ref(null);
|
||||
const localFunction: Ref<FunctionInfos> = ref({ name: '', sql: '', definer: null });
|
||||
const lastFunction = ref(null);
|
||||
const sqlProxy = ref('');
|
||||
const editorHeight = ref(300);
|
||||
|
||||
const workspace = computed(() => {
|
||||
return getWorkspace(props.connection.uid);
|
||||
});
|
||||
|
||||
const customizations = computed(() => {
|
||||
return workspace.value.customizations;
|
||||
});
|
||||
|
||||
const isChanged = computed(() => {
|
||||
return JSON.stringify(originalFunction.value) !== JSON.stringify(localFunction.value);
|
||||
});
|
||||
|
||||
const isTableNameValid = computed(() => {
|
||||
return localFunction.value.name !== '';
|
||||
});
|
||||
|
||||
const getFunctionData = async () => {
|
||||
if (!props.function) return;
|
||||
|
||||
isLoading.value = true;
|
||||
localFunction.value = { name: '', sql: '', definer: null };
|
||||
lastFunction.value = props.function;
|
||||
|
||||
const params = {
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
func: props.function
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Functions.getFunctionInformations(params);
|
||||
if (status === 'success') {
|
||||
originalFunction.value = response;
|
||||
|
||||
originalFunction.value.parameters = [...originalFunction.value.parameters.map(param => {
|
||||
param._antares_id = uidGen();
|
||||
return param;
|
||||
})];
|
||||
|
||||
localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
|
||||
sqlProxy.value = localFunction.value.sql;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async schema () {
|
||||
if (this.isSelected) {
|
||||
await this.getFunctionData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
|
||||
this.lastFunction = this.function;
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
resizeQueryEditor();
|
||||
isLoading.value = false;
|
||||
};
|
||||
|
||||
const saveChanges = async () => {
|
||||
if (isSaving.value) return;
|
||||
isSaving.value = true;
|
||||
const params = {
|
||||
uid: props.connection.uid,
|
||||
func: {
|
||||
...localFunction.value,
|
||||
schema: props.schema,
|
||||
oldName: originalFunction.value.name
|
||||
} as AlterFunctionParams
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Functions.alterFunction(params);
|
||||
|
||||
if (status === 'success') {
|
||||
const oldName = originalFunction.value.name;
|
||||
|
||||
await refreshStructure(props.connection.uid);
|
||||
|
||||
if (oldName !== localFunction.value.name) {
|
||||
renameTabs({
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
elementName: oldName,
|
||||
elementNewName: localFunction.value.name,
|
||||
elementType: 'function'
|
||||
});
|
||||
|
||||
changeBreadcrumbs({ schema: props.schema, function: localFunction.value.name });
|
||||
}
|
||||
},
|
||||
async function () {
|
||||
if (this.isSelected) {
|
||||
await this.getFunctionData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
|
||||
this.lastFunction = this.function;
|
||||
}
|
||||
},
|
||||
async isSelected (val) {
|
||||
if (val) {
|
||||
this.changeBreadcrumbs({ schema: this.schema, function: this.function });
|
||||
|
||||
setTimeout(() => {
|
||||
this.resizeQueryEditor();
|
||||
}, 200);
|
||||
|
||||
if (this.lastFunction !== this.function)
|
||||
this.getRoutineData();
|
||||
}
|
||||
},
|
||||
isChanged (val) {
|
||||
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
await this.getFunctionData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
unmounted () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
async getFunctionData () {
|
||||
if (!this.function) return;
|
||||
|
||||
this.isLoading = true;
|
||||
this.localFunction = { sql: '' };
|
||||
this.lastFunction = this.function;
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
func: this.function
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Functions.getFunctionInformations(params);
|
||||
if (status === 'success') {
|
||||
this.originalFunction = response;
|
||||
|
||||
this.originalFunction.parameters = [...this.originalFunction.parameters.map(param => {
|
||||
param._antares_id = uidGen();
|
||||
return param;
|
||||
})];
|
||||
|
||||
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction));
|
||||
this.sqlProxy = this.localFunction.sql;
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.resizeQueryEditor();
|
||||
this.isLoading = false;
|
||||
},
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
this.isSaving = true;
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
func: {
|
||||
...this.localFunction,
|
||||
schema: this.schema,
|
||||
oldName: this.originalFunction.name
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Functions.alterFunction(params);
|
||||
|
||||
if (status === 'success') {
|
||||
const oldName = this.originalFunction.name;
|
||||
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
|
||||
if (oldName !== this.localFunction.name) {
|
||||
this.renameTabs({
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
elementName: oldName,
|
||||
elementNewName: this.localFunction.name,
|
||||
elementType: 'function'
|
||||
});
|
||||
|
||||
this.changeBreadcrumbs({ schema: this.schema, function: this.localFunction.name });
|
||||
}
|
||||
else
|
||||
this.getFunctionData();
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isSaving = false;
|
||||
},
|
||||
clearChanges () {
|
||||
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction));
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
|
||||
},
|
||||
resizeQueryEditor () {
|
||||
if (this.$refs.queryEditor) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
this.editorHeight = size;
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
}
|
||||
},
|
||||
optionsUpdate (options) {
|
||||
this.localFunction = options;
|
||||
},
|
||||
parametersUpdate (parameters) {
|
||||
this.localFunction = { ...this.localFunction, parameters };
|
||||
},
|
||||
runFunctionCheck () {
|
||||
if (this.localFunction.parameters.length)
|
||||
this.showAskParamsModal();
|
||||
else
|
||||
this.runFunction();
|
||||
},
|
||||
runFunction (params) {
|
||||
if (!params) params = [];
|
||||
getFunctionData();
|
||||
}
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
let sql;
|
||||
switch (this.connection.client) { // TODO: move in a better place
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
sql = `SELECT \`${this.originalFunction.name}\` (${params.join(',')})`;
|
||||
break;
|
||||
case 'pg':
|
||||
sql = `SELECT ${this.originalFunction.name}(${params.join(',')})`;
|
||||
break;
|
||||
case 'mssql':
|
||||
sql = `SELECT ${this.originalFunction.name} ${params.join(',')}`;
|
||||
break;
|
||||
default:
|
||||
sql = `SELECT \`${this.originalFunction.name}\` (${params.join(',')})`;
|
||||
}
|
||||
isSaving.value = false;
|
||||
};
|
||||
|
||||
this.newTab({ uid: this.connection.uid, content: sql, type: 'query', autorun: true });
|
||||
},
|
||||
showParamsModal () {
|
||||
this.isParamsModal = true;
|
||||
},
|
||||
hideParamsModal () {
|
||||
this.isParamsModal = false;
|
||||
},
|
||||
showAskParamsModal () {
|
||||
this.isAskingParameters = true;
|
||||
},
|
||||
hideAskParamsModal () {
|
||||
this.isAskingParameters = false;
|
||||
},
|
||||
onKey (e) {
|
||||
if (this.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
||||
if (this.isChanged)
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
const clearChanges = () => {
|
||||
localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
|
||||
queryEditor.value.editor.session.setValue(localFunction.value.sql);
|
||||
};
|
||||
|
||||
const resizeQueryEditor = () => {
|
||||
if (queryEditor.value) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
editorHeight.value = size;
|
||||
queryEditor.value.editor.resize();
|
||||
}
|
||||
};
|
||||
|
||||
const parametersUpdate = (parameters: FunctionParam[]) => {
|
||||
localFunction.value = { ...localFunction.value, parameters };
|
||||
};
|
||||
|
||||
const runFunctionCheck = () => {
|
||||
if (localFunction.value.parameters.length)
|
||||
showAskParamsModal();
|
||||
else
|
||||
runFunction();
|
||||
};
|
||||
|
||||
const runFunction = (params?: string[]) => {
|
||||
if (!params) params = [];
|
||||
|
||||
let sql;
|
||||
switch (props.connection.client) { // TODO: move in a better place
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
sql = `SELECT \`${originalFunction.value.name}\` (${params.join(',')})`;
|
||||
break;
|
||||
case 'pg':
|
||||
sql = `SELECT ${originalFunction.value.name}(${params.join(',')})`;
|
||||
break;
|
||||
case 'mssql':
|
||||
sql = `SELECT ${originalFunction.value.name} ${params.join(',')}`;
|
||||
break;
|
||||
default:
|
||||
sql = `SELECT \`${originalFunction.value.name}\` (${params.join(',')})`;
|
||||
}
|
||||
|
||||
newTab({ uid: props.connection.uid, content: sql, type: 'query', autorun: true });
|
||||
};
|
||||
|
||||
const showParamsModal = () => {
|
||||
isParamsModal.value = true;
|
||||
};
|
||||
|
||||
const hideParamsModal = () => {
|
||||
isParamsModal.value = false;
|
||||
};
|
||||
|
||||
const showAskParamsModal = () => {
|
||||
isAskingParameters.value = true;
|
||||
};
|
||||
|
||||
const hideAskParamsModal = () => {
|
||||
isAskingParameters.value = false;
|
||||
};
|
||||
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (props.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
||||
if (isChanged.value)
|
||||
saveChanges();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.schema, async () => {
|
||||
if (props.isSelected) {
|
||||
await getFunctionData();
|
||||
queryEditor.value.editor.session.setValue(localFunction.value.sql);
|
||||
lastFunction.value = props.function;
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => props.function, async () => {
|
||||
if (props.isSelected) {
|
||||
await getFunctionData();
|
||||
queryEditor.value.editor.session.setValue(localFunction.value.sql);
|
||||
lastFunction.value = props.function;
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => props.isSelected, async (val) => {
|
||||
if (val) {
|
||||
changeBreadcrumbs({ schema: props.schema, function: props.function });
|
||||
|
||||
setTimeout(() => {
|
||||
resizeQueryEditor();
|
||||
}, 200);
|
||||
|
||||
if (lastFunction.value !== props.function)
|
||||
getFunctionData();
|
||||
}
|
||||
});
|
||||
|
||||
watch(isChanged, (val) => {
|
||||
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
|
||||
});
|
||||
|
||||
(async () => {
|
||||
await getFunctionData();
|
||||
queryEditor.value.editor.session.setValue(localFunction.value.sql);
|
||||
window.addEventListener('keydown', onKey);
|
||||
})();
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', resizeQueryEditor);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', resizeQueryEditor);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
</script>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
:confirm-text="t('word.confirm')"
|
||||
size="medium"
|
||||
class="options-modal"
|
||||
@confirm="confirmParametersChange"
|
||||
@ -9,7 +9,7 @@
|
||||
<template #header>
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
|
||||
<span class="cut-text">{{ $t('word.parameters') }} "{{ func }}"</span>
|
||||
<span class="cut-text">{{ t('word.parameters') }} "{{ func }}"</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
@ -20,16 +20,16 @@
|
||||
<div class="d-flex">
|
||||
<button class="btn btn-dark btn-sm d-flex" @click="addParameter">
|
||||
<i class="mdi mdi-24px mdi-plus mr-1" />
|
||||
<span>{{ $t('word.add') }}</span>
|
||||
<span>{{ t('word.add') }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
:title="t('message.clearChanges')"
|
||||
:disabled="!isChanged"
|
||||
@click.prevent="clearChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<span>{{ t('word.clear') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -55,7 +55,7 @@
|
||||
<div class="tile-action">
|
||||
<button
|
||||
class="btn btn-link remove-field p-0 mr-2"
|
||||
:title="$t('word.delete')"
|
||||
:title="t('word.delete')"
|
||||
@click.prevent="removeParameter(param._antares_id)"
|
||||
>
|
||||
<i class="mdi mdi-close" />
|
||||
@ -74,7 +74,7 @@
|
||||
>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.name') }}
|
||||
{{ t('word.name') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
@ -86,7 +86,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.type') }}
|
||||
{{ t('word.type') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<BaseSelect
|
||||
@ -102,7 +102,7 @@
|
||||
</div>
|
||||
<div v-if="customizations.parametersLength" class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.length') }}
|
||||
{{ t('word.length') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
@ -115,7 +115,7 @@
|
||||
</div>
|
||||
<div v-if="customizations.functionContext" class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.context') }}
|
||||
{{ t('word.context') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<label class="form-radio">
|
||||
@ -150,11 +150,11 @@
|
||||
<i class="mdi mdi-dots-horizontal mdi-48px" />
|
||||
</div>
|
||||
<p class="empty-title h5">
|
||||
{{ $t('message.thereAreNoParameters') }}
|
||||
{{ t('message.thereAreNoParameters') }}
|
||||
</p>
|
||||
<div class="empty-action">
|
||||
<button class="btn btn-primary" @click="addParameter">
|
||||
{{ $t('message.createNewParameter') }}
|
||||
{{ t('message.createNewParameter') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -164,113 +164,118 @@
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted, Ref, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceTabPropsFunctionParamsModal',
|
||||
components: {
|
||||
ConfirmModal,
|
||||
BaseSelect
|
||||
},
|
||||
props: {
|
||||
localParameters: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
func: String,
|
||||
workspace: Object
|
||||
},
|
||||
emits: ['hide', 'parameters-update'],
|
||||
data () {
|
||||
return {
|
||||
parametersProxy: [],
|
||||
isOptionsChanging: false,
|
||||
selectedParam: '',
|
||||
modalInnerHeight: 400,
|
||||
i: 1
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selectedParamObj () {
|
||||
return this.parametersProxy.find(param => param._antares_id === this.selectedParam);
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.localParameters) !== JSON.stringify(this.parametersProxy);
|
||||
},
|
||||
customizations () {
|
||||
return this.workspace.customizations;
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.parametersProxy = JSON.parse(JSON.stringify(this.localParameters));
|
||||
this.i = this.parametersProxy.length + 1;
|
||||
const { t } = useI18n();
|
||||
|
||||
if (this.parametersProxy.length)
|
||||
this.resetSelectedID();
|
||||
|
||||
this.getModalInnerHeight();
|
||||
window.addEventListener('resize', this.getModalInnerHeight);
|
||||
const props = defineProps({
|
||||
localParameters: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
unmounted () {
|
||||
window.removeEventListener('resize', this.getModalInnerHeight);
|
||||
},
|
||||
methods: {
|
||||
typeClass (type) {
|
||||
if (type)
|
||||
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
|
||||
return '';
|
||||
},
|
||||
confirmParametersChange () {
|
||||
this.$emit('parameters-update', this.parametersProxy);
|
||||
},
|
||||
selectParameter (event, uid) {
|
||||
if (this.selectedParam !== uid && !event.target.classList.contains('remove-field'))
|
||||
this.selectedParam = uid;
|
||||
},
|
||||
getModalInnerHeight () {
|
||||
const modalBody = document.querySelector('.modal-body');
|
||||
if (modalBody)
|
||||
this.modalInnerHeight = modalBody.clientHeight - (parseFloat(getComputedStyle(modalBody).paddingTop) + parseFloat(getComputedStyle(modalBody).paddingBottom));
|
||||
},
|
||||
addParameter () {
|
||||
const newUid = uidGen();
|
||||
this.parametersProxy = [...this.parametersProxy, {
|
||||
_antares_id: newUid,
|
||||
name: `param${this.i++}`,
|
||||
type: this.workspace.dataTypes[0].types[0].name,
|
||||
context: 'IN',
|
||||
length: ''
|
||||
}];
|
||||
func: String,
|
||||
workspace: Object
|
||||
});
|
||||
|
||||
if (this.parametersProxy.length === 1)
|
||||
this.resetSelectedID();
|
||||
const emit = defineEmits(['hide', 'parameters-update']);
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.parametersPanel.scrollTop = this.$refs.parametersPanel.scrollHeight + 60;
|
||||
this.selectedParam = newUid;
|
||||
}, 20);
|
||||
},
|
||||
removeParameter (uid) {
|
||||
this.parametersProxy = this.parametersProxy.filter(param => param._antares_id !== uid);
|
||||
const parametersPanel: Ref<HTMLDivElement> = ref(null);
|
||||
const parametersProxy = ref([]);
|
||||
const selectedParam = ref('');
|
||||
const modalInnerHeight = ref(400);
|
||||
const i = ref(1);
|
||||
|
||||
if (this.parametersProxy.length && this.selectedParam === uid)
|
||||
this.resetSelectedID();
|
||||
},
|
||||
clearChanges () {
|
||||
this.parametersProxy = JSON.parse(JSON.stringify(this.localParameters));
|
||||
this.i = this.parametersProxy.length + 1;
|
||||
const selectedParamObj = computed(() => {
|
||||
return parametersProxy.value.find(param => param._antares_id === selectedParam.value);
|
||||
});
|
||||
|
||||
if (!this.parametersProxy.some(param => param.name === this.selectedParam))
|
||||
this.resetSelectedID();
|
||||
},
|
||||
resetSelectedID () {
|
||||
this.selectedParam = this.parametersProxy.length ? this.parametersProxy[0]._antares_id : '';
|
||||
}
|
||||
}
|
||||
const isChanged = computed(() => {
|
||||
return JSON.stringify(props.localParameters) !== JSON.stringify(parametersProxy.value);
|
||||
});
|
||||
|
||||
const customizations = computed(() => {
|
||||
return props.workspace.customizations;
|
||||
});
|
||||
|
||||
const typeClass = (type: string) => {
|
||||
if (type)
|
||||
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
|
||||
return '';
|
||||
};
|
||||
|
||||
const confirmParametersChange = () => {
|
||||
emit('parameters-update', parametersProxy.value);
|
||||
};
|
||||
|
||||
const selectParameter = (event: MouseEvent, uid: string) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (selectedParam.value !== uid && !(event.target as any).classList.contains('remove-field'))
|
||||
selectedParam.value = uid;
|
||||
};
|
||||
|
||||
const getModalInnerHeight = () => {
|
||||
const modalBody = document.querySelector('.modal-body');
|
||||
if (modalBody)
|
||||
modalInnerHeight.value = modalBody.clientHeight - (parseFloat(getComputedStyle(modalBody).paddingTop) + parseFloat(getComputedStyle(modalBody).paddingBottom));
|
||||
};
|
||||
|
||||
const addParameter = () => {
|
||||
const newUid = uidGen();
|
||||
parametersProxy.value = [...parametersProxy.value, {
|
||||
_antares_id: newUid,
|
||||
name: `param${i.value++}`,
|
||||
type: props.workspace.dataTypes[0].types[0].name,
|
||||
context: 'IN',
|
||||
length: ''
|
||||
}];
|
||||
|
||||
if (parametersProxy.value.length === 1)
|
||||
resetSelectedID();
|
||||
|
||||
setTimeout(() => {
|
||||
parametersPanel.value.scrollTop = parametersPanel.value.scrollHeight + 60;
|
||||
selectedParam.value = newUid;
|
||||
}, 20);
|
||||
};
|
||||
|
||||
const removeParameter = (uid: string) => {
|
||||
parametersProxy.value = parametersProxy.value.filter(param => param._antares_id !== uid);
|
||||
|
||||
if (parametersProxy.value.length && selectedParam.value === uid)
|
||||
resetSelectedID();
|
||||
};
|
||||
|
||||
const clearChanges = () => {
|
||||
parametersProxy.value = JSON.parse(JSON.stringify(props.localParameters));
|
||||
i.value = parametersProxy.value.length + 1;
|
||||
|
||||
if (!parametersProxy.value.some(param => param.name === selectedParam.value))
|
||||
resetSelectedID();
|
||||
};
|
||||
|
||||
const resetSelectedID = () => {
|
||||
selectedParam.value = parametersProxy.value.length ? parametersProxy.value[0]._antares_id : '';
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
parametersProxy.value = JSON.parse(JSON.stringify(props.localParameters));
|
||||
i.value = parametersProxy.value.length + 1;
|
||||
|
||||
if (parametersProxy.value.length)
|
||||
resetSelectedID();
|
||||
|
||||
getModalInnerHeight();
|
||||
window.addEventListener('resize', getModalInnerHeight);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', getModalInnerHeight);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -11,16 +11,16 @@
|
||||
@click="saveChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
<span>{{ t('word.save') }}</span>
|
||||
</button>
|
||||
<button
|
||||
:disabled="!isChanged"
|
||||
class="btn btn-link btn-sm mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
:title="t('message.clearChanges')"
|
||||
@click="clearChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<span>{{ t('word.clear') }}</span>
|
||||
</button>
|
||||
|
||||
<div class="divider-vert py-3" />
|
||||
@ -31,15 +31,15 @@
|
||||
@click="runRoutineCheck"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-play mr-1" />
|
||||
<span>{{ $t('word.run') }}</span>
|
||||
<span>{{ t('word.run') }}</span>
|
||||
</button>
|
||||
<button class="btn btn-dark btn-sm" @click="showParamsModal">
|
||||
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
|
||||
<span>{{ $t('word.parameters') }}</span>
|
||||
<span>{{ t('word.parameters') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="workspace-query-info">
|
||||
<div class="d-flex" :title="$t('word.schema')">
|
||||
<div class="d-flex" :title="t('word.schema')">
|
||||
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||
</div>
|
||||
</div>
|
||||
@ -50,7 +50,7 @@
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.name') }}
|
||||
{{ t('word.name') }}
|
||||
</label>
|
||||
<input
|
||||
ref="firstInput"
|
||||
@ -64,7 +64,7 @@
|
||||
<div v-if="customizations.languages" class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.language') }}
|
||||
{{ t('word.language') }}
|
||||
</label>
|
||||
<BaseSelect
|
||||
v-model="localRoutine.language"
|
||||
@ -76,13 +76,13 @@
|
||||
<div v-if="customizations.definer" class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.definer') }}
|
||||
{{ t('word.definer') }}
|
||||
</label>
|
||||
<BaseSelect
|
||||
v-model="localRoutine.definer"
|
||||
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]"
|
||||
:option-label="(user) => user.value === '' ? user.name : `${user.name}@${user.host}`"
|
||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||
:options="[{value: '', name: t('message.currentUser')}, ...workspace.users]"
|
||||
:option-label="(user: any) => user.value === '' ? user.name : `${user.name}@${user.host}`"
|
||||
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||
class="form-select"
|
||||
/>
|
||||
</div>
|
||||
@ -90,7 +90,7 @@
|
||||
<div v-if="customizations.comment" class="column">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('word.comment') }}
|
||||
{{ t('word.comment') }}
|
||||
</label>
|
||||
<input
|
||||
v-model="localRoutine.comment"
|
||||
@ -102,7 +102,7 @@
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('message.sqlSecurity') }}
|
||||
{{ t('message.sqlSecurity') }}
|
||||
</label>
|
||||
<BaseSelect
|
||||
v-model="localRoutine.security"
|
||||
@ -114,7 +114,7 @@
|
||||
<div v-if="customizations.procedureDataAccess" class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{{ $t('message.dataAccess') }}
|
||||
{{ t('message.dataAccess') }}
|
||||
</label>
|
||||
<BaseSelect
|
||||
v-model="localRoutine.dataAccess"
|
||||
@ -127,7 +127,7 @@
|
||||
<div class="form-group">
|
||||
<label class="form-label d-invisible">.</label>
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="localRoutine.deterministic" type="checkbox"><i class="form-icon" /> {{ $t('word.deterministic') }}
|
||||
<input v-model="localRoutine.deterministic" type="checkbox"><i class="form-icon" /> {{ t('word.deterministic') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -135,7 +135,7 @@
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<label class="form-label ml-2">{{ $t('message.routineBody') }}</label>
|
||||
<label class="form-label ml-2">{{ t('message.routineBody') }}</label>
|
||||
<QueryEditor
|
||||
v-show="isSelected"
|
||||
ref="queryEditor"
|
||||
@ -163,286 +163,272 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { storeToRefs } from 'pinia';
|
||||
<script setup lang="ts">
|
||||
import { Component, computed, onUnmounted, onBeforeUnmount, onMounted, Ref, ref, watch } from 'vue';
|
||||
import { AlterRoutineParams, FunctionParam, RoutineInfos } from 'common/interfaces/antares';
|
||||
import { Ace } from 'ace-builds';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal';
|
||||
import ModalAskParameters from '@/components/ModalAskParameters';
|
||||
import Routines from '@/ipc-api/Routines';
|
||||
import QueryEditor from '@/components/QueryEditor.vue';
|
||||
import BaseLoader from '@/components/BaseLoader.vue';
|
||||
import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal.vue';
|
||||
import ModalAskParameters from '@/components/ModalAskParameters.vue';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceTabPropsRoutine',
|
||||
components: {
|
||||
QueryEditor,
|
||||
BaseLoader,
|
||||
WorkspaceTabPropsRoutineParamsModal,
|
||||
ModalAskParameters,
|
||||
BaseSelect
|
||||
},
|
||||
props: {
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
routine: String,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
},
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const props = defineProps({
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
routine: String,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
});
|
||||
|
||||
const {
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
renameTabs,
|
||||
newTab,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges
|
||||
} = workspacesStore;
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
renameTabs,
|
||||
newTab,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
isParamsModal: false,
|
||||
isAskingParameters: false,
|
||||
originalRoutine: null,
|
||||
localRoutine: { sql: '' },
|
||||
lastRoutine: null,
|
||||
sqlProxy: '',
|
||||
editorHeight: 300
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
customizations () {
|
||||
return this.workspace.customizations;
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.originalRoutine) !== JSON.stringify(this.localRoutine);
|
||||
},
|
||||
isDefinerInUsers () {
|
||||
return this.originalRoutine ? this.workspace.users.some(user => this.originalRoutine.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
||||
},
|
||||
isTableNameValid () {
|
||||
return this.localRoutine.name !== '';
|
||||
},
|
||||
schemaTables () {
|
||||
const schemaTables = this.workspace.structure
|
||||
.filter(schema => schema.name === this.schema)
|
||||
.map(schema => schema.tables);
|
||||
const {
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
renameTabs,
|
||||
newTab,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges
|
||||
} = workspacesStore;
|
||||
|
||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||
const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
|
||||
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const isParamsModal = ref(false);
|
||||
const isAskingParameters = ref(false);
|
||||
const originalRoutine: Ref<RoutineInfos> = ref(null);
|
||||
const localRoutine: Ref<RoutineInfos> = ref(null);
|
||||
const lastRoutine = ref(null);
|
||||
const sqlProxy = ref('');
|
||||
const editorHeight = ref(300);
|
||||
|
||||
const workspace = computed(() => {
|
||||
return getWorkspace(props.connection.uid);
|
||||
});
|
||||
|
||||
const customizations = computed(() => {
|
||||
return workspace.value.customizations;
|
||||
});
|
||||
|
||||
const isChanged = computed(() => {
|
||||
return JSON.stringify(originalRoutine.value) !== JSON.stringify(localRoutine.value);
|
||||
});
|
||||
|
||||
const isTableNameValid = computed(() => {
|
||||
return localRoutine.value.name !== '';
|
||||
});
|
||||
|
||||
const getRoutineData = async () => {
|
||||
if (!props.routine) return;
|
||||
|
||||
localRoutine.value = { name: '', sql: '', definer: null };
|
||||
isLoading.value = true;
|
||||
lastRoutine.value = props.routine;
|
||||
|
||||
const params = {
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
routine: props.routine
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Routines.getRoutineInformations(params);
|
||||
if (status === 'success') {
|
||||
originalRoutine.value = response;
|
||||
|
||||
originalRoutine.value.parameters = [...originalRoutine.value.parameters.map(param => {
|
||||
param._antares_id = uidGen();
|
||||
return param;
|
||||
})];
|
||||
|
||||
localRoutine.value = JSON.parse(JSON.stringify(originalRoutine.value));
|
||||
sqlProxy.value = localRoutine.value.sql;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async schema () {
|
||||
if (this.isSelected) {
|
||||
await this.getRoutineData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localRoutine.sql);
|
||||
this.lastRoutine = this.routine;
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
resizeQueryEditor();
|
||||
isLoading.value = false;
|
||||
};
|
||||
|
||||
const saveChanges = async () => {
|
||||
if (isSaving.value) return;
|
||||
isSaving.value = true;
|
||||
const params = {
|
||||
uid: props.connection.uid as string,
|
||||
routine: {
|
||||
...localRoutine.value,
|
||||
schema: props.schema,
|
||||
oldName: originalRoutine.value.name
|
||||
} as AlterRoutineParams
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Routines.alterRoutine(params);
|
||||
|
||||
if (status === 'success') {
|
||||
const oldName = originalRoutine.value.name;
|
||||
|
||||
await refreshStructure(props.connection.uid);
|
||||
|
||||
if (oldName !== localRoutine.value.name) {
|
||||
renameTabs({
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
elementName: oldName,
|
||||
elementNewName: localRoutine.value.name,
|
||||
elementType: 'procedure'
|
||||
});
|
||||
|
||||
changeBreadcrumbs({ schema: props.schema, routine: localRoutine.value.name });
|
||||
}
|
||||
},
|
||||
async routine () {
|
||||
if (this.isSelected) {
|
||||
await this.getRoutineData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localRoutine.sql);
|
||||
this.lastRoutine = this.routine;
|
||||
}
|
||||
},
|
||||
async isSelected (val) {
|
||||
if (val) {
|
||||
this.changeBreadcrumbs({ schema: this.schema, routine: this.routine });
|
||||
|
||||
setTimeout(() => {
|
||||
this.resizeQueryEditor();
|
||||
}, 200);
|
||||
|
||||
if (this.lastRoutine !== this.routine)
|
||||
this.getRoutineData();
|
||||
}
|
||||
},
|
||||
isChanged (val) {
|
||||
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
await this.getRoutineData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localRoutine.sql);
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
unmounted () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
async getRoutineData () {
|
||||
if (!this.routine) return;
|
||||
|
||||
this.localRoutine = { sql: '' };
|
||||
this.isLoading = true;
|
||||
this.lastRoutine = this.routine;
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
routine: this.routine
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Routines.getRoutineInformations(params);
|
||||
if (status === 'success') {
|
||||
this.originalRoutine = response;
|
||||
|
||||
this.originalRoutine.parameters = [...this.originalRoutine.parameters.map(param => {
|
||||
param._antares_id = uidGen();
|
||||
return param;
|
||||
})];
|
||||
|
||||
this.localRoutine = JSON.parse(JSON.stringify(this.originalRoutine));
|
||||
this.sqlProxy = this.localRoutine.sql;
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.resizeQueryEditor();
|
||||
this.isLoading = false;
|
||||
},
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
this.isSaving = true;
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
routine: {
|
||||
...this.localRoutine,
|
||||
schema: this.schema,
|
||||
oldName: this.originalRoutine.name
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Routines.alterRoutine(params);
|
||||
|
||||
if (status === 'success') {
|
||||
const oldName = this.originalRoutine.name;
|
||||
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
|
||||
if (oldName !== this.localRoutine.name) {
|
||||
this.renameTabs({
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
elementName: oldName,
|
||||
elementNewName: this.localRoutine.name,
|
||||
elementType: 'procedure'
|
||||
});
|
||||
|
||||
this.changeBreadcrumbs({ schema: this.schema, procedure: this.localRoutine.name });
|
||||
}
|
||||
else
|
||||
this.getRoutineData();
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isSaving = false;
|
||||
},
|
||||
clearChanges () {
|
||||
this.localRoutine = JSON.parse(JSON.stringify(this.originalRoutine));
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localRoutine.sql);
|
||||
},
|
||||
resizeQueryEditor () {
|
||||
if (this.$refs.queryEditor) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
this.editorHeight = size;
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
}
|
||||
},
|
||||
optionsUpdate (options) {
|
||||
this.localRoutine = options;
|
||||
},
|
||||
parametersUpdate (parameters) {
|
||||
this.localRoutine = { ...this.localRoutine, parameters };
|
||||
},
|
||||
runRoutineCheck () {
|
||||
if (this.localRoutine.parameters.length)
|
||||
this.showAskParamsModal();
|
||||
else
|
||||
this.runRoutine();
|
||||
},
|
||||
runRoutine (params) {
|
||||
if (!params) params = [];
|
||||
getRoutineData();
|
||||
}
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
let sql;
|
||||
switch (this.connection.client) { // TODO: move in a better place
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
case 'pg':
|
||||
sql = `CALL ${this.originalRoutine.name}(${params.join(',')})`;
|
||||
break;
|
||||
case 'mssql':
|
||||
sql = `EXEC ${this.originalRoutine.name} ${params.join(',')}`;
|
||||
break;
|
||||
default:
|
||||
sql = `CALL \`${this.originalRoutine.name}\`(${params.join(',')})`;
|
||||
}
|
||||
isSaving.value = false;
|
||||
};
|
||||
|
||||
this.newTab({ uid: this.connection.uid, content: sql, type: 'query', autorun: true });
|
||||
},
|
||||
showParamsModal () {
|
||||
this.isParamsModal = true;
|
||||
},
|
||||
hideParamsModal () {
|
||||
this.isParamsModal = false;
|
||||
},
|
||||
showAskParamsModal () {
|
||||
this.isAskingParameters = true;
|
||||
},
|
||||
hideAskParamsModal () {
|
||||
this.isAskingParameters = false;
|
||||
},
|
||||
onKey (e) {
|
||||
if (this.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
||||
if (this.isChanged)
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
const clearChanges = () => {
|
||||
localRoutine.value = JSON.parse(JSON.stringify(originalRoutine.value));
|
||||
queryEditor.value.editor.session.setValue(localRoutine.value.sql);
|
||||
};
|
||||
|
||||
const resizeQueryEditor = () => {
|
||||
if (queryEditor.value) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
editorHeight.value = size;
|
||||
queryEditor.value.editor.resize();
|
||||
}
|
||||
};
|
||||
|
||||
const parametersUpdate = (parameters: FunctionParam[]) => {
|
||||
localRoutine.value = { ...localRoutine.value, parameters };
|
||||
};
|
||||
|
||||
const runRoutineCheck = () => {
|
||||
if (localRoutine.value.parameters.length)
|
||||
showAskParamsModal();
|
||||
else
|
||||
runRoutine();
|
||||
};
|
||||
|
||||
const runRoutine = (params?: string[]) => {
|
||||
if (!params) params = [];
|
||||
|
||||
let sql;
|
||||
switch (props.connection.client) { // TODO: move in a better place
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
case 'pg':
|
||||
sql = `CALL ${originalRoutine.value.name}(${params.join(',')})`;
|
||||
break;
|
||||
case 'mssql':
|
||||
sql = `EXEC ${originalRoutine.value.name} ${params.join(',')}`;
|
||||
break;
|
||||
default:
|
||||
sql = `CALL \`${originalRoutine.value.name}\`(${params.join(',')})`;
|
||||
}
|
||||
|
||||
newTab({ uid: props.connection.uid, content: sql, type: 'query', autorun: true });
|
||||
};
|
||||
|
||||
const showParamsModal = () => {
|
||||
isParamsModal.value = true;
|
||||
};
|
||||
|
||||
const hideParamsModal = () => {
|
||||
isParamsModal.value = false;
|
||||
};
|
||||
|
||||
const showAskParamsModal = () => {
|
||||
isAskingParameters.value = true;
|
||||
};
|
||||
|
||||
const hideAskParamsModal = () => {
|
||||
isAskingParameters.value = false;
|
||||
};
|
||||
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (props.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.key === 's') { // CTRL + S
|
||||
if (isChanged.value)
|
||||
saveChanges();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.schema, async () => {
|
||||
if (props.isSelected) {
|
||||
await getRoutineData();
|
||||
queryEditor.value.editor.session.setValue(localRoutine.value.sql);
|
||||
lastRoutine.value = props.routine;
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => props.routine, async () => {
|
||||
if (props.isSelected) {
|
||||
await getRoutineData();
|
||||
queryEditor.value.editor.session.setValue(localRoutine.value.sql);
|
||||
lastRoutine.value = props.routine;
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => props.isSelected, async (val) => {
|
||||
if (val) {
|
||||
changeBreadcrumbs({ schema: props.schema, routine: props.routine });
|
||||
|
||||
setTimeout(() => {
|
||||
resizeQueryEditor();
|
||||
}, 200);
|
||||
|
||||
if (lastRoutine.value !== props.routine)
|
||||
getRoutineData();
|
||||
}
|
||||
});
|
||||
|
||||
watch(isChanged, (val) => {
|
||||
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
|
||||
});
|
||||
|
||||
(async () => {
|
||||
await getRoutineData();
|
||||
queryEditor.value.editor.session.setValue(localRoutine.value.sql);
|
||||
window.addEventListener('keydown', onKey);
|
||||
})();
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', resizeQueryEditor);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', resizeQueryEditor);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
:confirm-text="t('word.confirm')"
|
||||
size="medium"
|
||||
class="options-modal"
|
||||
@confirm="confirmParametersChange"
|
||||
@ -9,7 +9,7 @@
|
||||
<template #header>
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
|
||||
<span class="cut-text">{{ $t('word.parameters') }} "{{ routine }}"</span>
|
||||
<span class="cut-text">{{ t('word.parameters') }} "{{ routine }}"</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
@ -20,16 +20,16 @@
|
||||
<div class="d-flex">
|
||||
<button class="btn btn-dark btn-sm d-flex" @click="addParameter">
|
||||
<i class="mdi mdi-24px mdi-plus mr-1" />
|
||||
<span>{{ $t('word.add') }}</span>
|
||||
<span>{{ t('word.add') }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
:title="t('message.clearChanges')"
|
||||
:disabled="!isChanged"
|
||||
@click.prevent="clearChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<span>{{ t('word.clear') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -55,7 +55,7 @@
|
||||
<div class="tile-action">
|
||||
<button
|
||||
class="btn btn-link remove-field p-0 mr-2"
|
||||
:title="$t('word.delete')"
|
||||
:title="t('word.delete')"
|
||||
@click.prevent="removeParameter(param._antares_id)"
|
||||
>
|
||||
<i class="mdi mdi-close" />
|
||||
@ -74,7 +74,7 @@
|
||||
>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.name') }}
|
||||
{{ t('word.name') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
@ -86,7 +86,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.type') }}
|
||||
{{ t('word.type') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<BaseSelect
|
||||
@ -102,7 +102,7 @@
|
||||
</div>
|
||||
<div v-if="customizations.parametersLength" class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.length') }}
|
||||
{{ t('word.length') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
@ -115,7 +115,7 @@
|
||||
</div>
|
||||
<div v-if="customizations.procedureContext" class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.context') }}
|
||||
{{ t('word.context') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<label class="form-radio">
|
||||
@ -150,11 +150,11 @@
|
||||
<i class="mdi mdi-dots-horizontal mdi-48px" />
|
||||
</div>
|
||||
<p class="empty-title h5">
|
||||
{{ $t('message.thereAreNoParameters') }}
|
||||
{{ t('message.thereAreNoParameters') }}
|
||||
</p>
|
||||
<div class="empty-action">
|
||||
<button class="btn btn-primary" @click="addParameter">
|
||||
{{ $t('message.createNewParameter') }}
|
||||
{{ t('message.createNewParameter') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -164,113 +164,118 @@
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted, Ref, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceTabPropsRoutineParamsModal',
|
||||
components: {
|
||||
ConfirmModal,
|
||||
BaseSelect
|
||||
},
|
||||
props: {
|
||||
localParameters: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
routine: String,
|
||||
workspace: Object
|
||||
},
|
||||
emits: ['parameters-update', 'hide'],
|
||||
data () {
|
||||
return {
|
||||
parametersProxy: [],
|
||||
isOptionsChanging: false,
|
||||
selectedParam: '',
|
||||
modalInnerHeight: 400,
|
||||
i: 1
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selectedParamObj () {
|
||||
return this.parametersProxy.find(param => param._antares_id === this.selectedParam);
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.localParameters) !== JSON.stringify(this.parametersProxy);
|
||||
},
|
||||
customizations () {
|
||||
return this.workspace.customizations;
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.parametersProxy = JSON.parse(JSON.stringify(this.localParameters));
|
||||
this.i = this.parametersProxy.length + 1;
|
||||
const { t } = useI18n();
|
||||
|
||||
if (this.parametersProxy.length)
|
||||
this.resetSelectedID();
|
||||
|
||||
this.getModalInnerHeight();
|
||||
window.addEventListener('resize', this.getModalInnerHeight);
|
||||
const props = defineProps({
|
||||
localParameters: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
unmounted () {
|
||||
window.removeEventListener('resize', this.getModalInnerHeight);
|
||||
},
|
||||
methods: {
|
||||
typeClass (type) {
|
||||
if (type)
|
||||
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
|
||||
return '';
|
||||
},
|
||||
confirmParametersChange () {
|
||||
this.$emit('parameters-update', this.parametersProxy);
|
||||
},
|
||||
selectParameter (event, uid) {
|
||||
if (this.selectedParam !== uid && !event.target.classList.contains('remove-field'))
|
||||
this.selectedParam = uid;
|
||||
},
|
||||
getModalInnerHeight () {
|
||||
const modalBody = document.querySelector('.modal-body');
|
||||
if (modalBody)
|
||||
this.modalInnerHeight = modalBody.clientHeight - (parseFloat(getComputedStyle(modalBody).paddingTop) + parseFloat(getComputedStyle(modalBody).paddingBottom));
|
||||
},
|
||||
addParameter () {
|
||||
const newUid = uidGen();
|
||||
this.parametersProxy = [...this.parametersProxy, {
|
||||
_antares_id: newUid,
|
||||
name: `param${this.i++}`,
|
||||
type: this.workspace.dataTypes[0].types[0].name,
|
||||
context: 'IN',
|
||||
length: ''
|
||||
}];
|
||||
routine: String,
|
||||
workspace: Object
|
||||
});
|
||||
|
||||
if (this.parametersProxy.length === 1)
|
||||
this.resetSelectedID();
|
||||
const emit = defineEmits(['hide', 'parameters-update']);
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.parametersPanel.scrollTop = this.$refs.parametersPanel.scrollHeight + 60;
|
||||
this.selectedParam = newUid;
|
||||
}, 20);
|
||||
},
|
||||
removeParameter (uid) {
|
||||
this.parametersProxy = this.parametersProxy.filter(param => param._antares_id !== uid);
|
||||
const parametersPanel: Ref<HTMLDivElement> = ref(null);
|
||||
const parametersProxy = ref([]);
|
||||
const selectedParam = ref('');
|
||||
const modalInnerHeight = ref(400);
|
||||
const i = ref(1);
|
||||
|
||||
if (this.parametersProxy.length && this.selectedParam === uid)
|
||||
this.resetSelectedID();
|
||||
},
|
||||
clearChanges () {
|
||||
this.parametersProxy = JSON.parse(JSON.stringify(this.localParameters));
|
||||
this.i = this.parametersProxy.length + 1;
|
||||
const selectedParamObj = computed(() => {
|
||||
return parametersProxy.value.find(param => param._antares_id === selectedParam.value);
|
||||
});
|
||||
|
||||
if (!this.parametersProxy.some(param => param.name === this.selectedParam))
|
||||
this.resetSelectedID();
|
||||
},
|
||||
resetSelectedID () {
|
||||
this.selectedParam = this.parametersProxy.length ? this.parametersProxy[0]._antares_id : '';
|
||||
}
|
||||
}
|
||||
const isChanged = computed(() => {
|
||||
return JSON.stringify(props.localParameters) !== JSON.stringify(parametersProxy.value);
|
||||
});
|
||||
|
||||
const customizations = computed(() => {
|
||||
return props.workspace.customizations;
|
||||
});
|
||||
|
||||
const typeClass = (type: string) => {
|
||||
if (type)
|
||||
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
|
||||
return '';
|
||||
};
|
||||
|
||||
const confirmParametersChange = () => {
|
||||
emit('parameters-update', parametersProxy.value);
|
||||
};
|
||||
|
||||
const selectParameter = (event: MouseEvent, uid: string) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (selectedParam.value !== uid && !(event.target as any).classList.contains('remove-field'))
|
||||
selectedParam.value = uid;
|
||||
};
|
||||
|
||||
const getModalInnerHeight = () => {
|
||||
const modalBody = document.querySelector('.modal-body');
|
||||
if (modalBody)
|
||||
modalInnerHeight.value = modalBody.clientHeight - (parseFloat(getComputedStyle(modalBody).paddingTop) + parseFloat(getComputedStyle(modalBody).paddingBottom));
|
||||
};
|
||||
|
||||
const addParameter = () => {
|
||||
const newUid = uidGen();
|
||||
parametersProxy.value = [...parametersProxy.value, {
|
||||
_antares_id: newUid,
|
||||
name: `param${i.value++}`,
|
||||
type: props.workspace.dataTypes[0].types[0].name,
|
||||
context: 'IN',
|
||||
length: ''
|
||||
}];
|
||||
|
||||
if (parametersProxy.value.length === 1)
|
||||
resetSelectedID();
|
||||
|
||||
setTimeout(() => {
|
||||
parametersPanel.value.scrollTop = parametersPanel.value.scrollHeight + 60;
|
||||
selectedParam.value = newUid;
|
||||
}, 20);
|
||||
};
|
||||
|
||||
const removeParameter = (uid: string) => {
|
||||
parametersProxy.value = parametersProxy.value.filter(param => param._antares_id !== uid);
|
||||
|
||||
if (parametersProxy.value.length && selectedParam.value === uid)
|
||||
resetSelectedID();
|
||||
};
|
||||
|
||||
const clearChanges = () => {
|
||||
parametersProxy.value = JSON.parse(JSON.stringify(props.localParameters));
|
||||
i.value = parametersProxy.value.length + 1;
|
||||
|
||||
if (!parametersProxy.value.some(param => param.name === selectedParam.value))
|
||||
resetSelectedID();
|
||||
};
|
||||
|
||||
const resetSelectedID = () => {
|
||||
selectedParam.value = parametersProxy.value.length ? parametersProxy.value[0]._antares_id : '';
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
parametersProxy.value = JSON.parse(JSON.stringify(props.localParameters));
|
||||
i.value = parametersProxy.value.length + 1;
|
||||
|
||||
if (parametersProxy.value.length)
|
||||
resetSelectedID();
|
||||
|
||||
getModalInnerHeight();
|
||||
window.addEventListener('resize', getModalInnerHeight);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', getModalInnerHeight);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -11,26 +11,26 @@
|
||||
@click="saveChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
<span>{{ t('word.save') }}</span>
|
||||
</button>
|
||||
<button
|
||||
:disabled="!isChanged"
|
||||
class="btn btn-link btn-sm mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
:title="t('message.clearChanges')"
|
||||
@click="clearChanges"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<span>{{ t('word.clear') }}</span>
|
||||
</button>
|
||||
|
||||
<div class="divider-vert py-3" />
|
||||
<button class="btn btn-dark btn-sm" @click="showTimingModal">
|
||||
<i class="mdi mdi-24px mdi-timer mr-1" />
|
||||
<span>{{ $t('word.timing') }}</span>
|
||||
<span>{{ t('word.timing') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="workspace-query-info">
|
||||
<div class="d-flex" :title="$t('word.schema')">
|
||||
<div class="d-flex" :title="t('word.schema')">
|
||||
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||
</div>
|
||||
</div>
|
||||
@ -40,7 +40,7 @@
|
||||
<div class="columns">
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
<label class="form-label">{{ t('word.name') }}</label>
|
||||
<input
|
||||
v-model="localScheduler.name"
|
||||
class="form-input"
|
||||
@ -50,19 +50,19 @@
|
||||
</div>
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.definer') }}</label>
|
||||
<label class="form-label">{{ t('word.definer') }}</label>
|
||||
<BaseSelect
|
||||
v-model="localScheduler.definer"
|
||||
:options="users"
|
||||
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
|
||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||
:option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
|
||||
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||
class="form-select"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.comment') }}</label>
|
||||
<label class="form-label">{{ t('word.comment') }}</label>
|
||||
<input
|
||||
v-model="localScheduler.comment"
|
||||
class="form-input"
|
||||
@ -72,7 +72,7 @@
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="form-group">
|
||||
<label class="form-label mr-2">{{ $t('word.state') }}</label>
|
||||
<label class="form-label mr-2">{{ t('word.state') }}</label>
|
||||
<label class="form-radio form-inline">
|
||||
<input
|
||||
v-model="localScheduler.state"
|
||||
@ -103,7 +103,7 @@
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<label class="form-label ml-2">{{ $t('message.schedulerBody') }}</label>
|
||||
<label class="form-label ml-2">{{ t('message.schedulerBody') }}</label>
|
||||
<QueryEditor
|
||||
v-show="isSelected"
|
||||
ref="queryEditor"
|
||||
@ -122,245 +122,231 @@
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { storeToRefs } from 'pinia';
|
||||
<script setup lang="ts">
|
||||
import { AlterEventParams, EventInfos } from 'common/interfaces/antares';
|
||||
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
||||
import { Ace } from 'ace-builds';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal';
|
||||
import Schedulers from '@/ipc-api/Schedulers';
|
||||
import BaseLoader from '@/components/BaseLoader.vue';
|
||||
import QueryEditor from '@/components/QueryEditor.vue';
|
||||
import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal.vue';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
import Schedulers from '@/ipc-api/Schedulers';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceTabPropsScheduler',
|
||||
components: {
|
||||
BaseLoader,
|
||||
QueryEditor,
|
||||
WorkspaceTabPropsSchedulerTimingModal,
|
||||
BaseSelect
|
||||
},
|
||||
props: {
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
scheduler: String,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
},
|
||||
setup () {
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const props = defineProps({
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
scheduler: String,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
});
|
||||
|
||||
const {
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
renameTabs,
|
||||
newTab,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges
|
||||
} = workspacesStore;
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
return {
|
||||
addNotification,
|
||||
selectedWorkspace,
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
renameTabs,
|
||||
newTab,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
isTimingModal: false,
|
||||
originalScheduler: null,
|
||||
localScheduler: { sql: '' },
|
||||
lastScheduler: null,
|
||||
sqlProxy: '',
|
||||
editorHeight: 300
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.originalScheduler) !== JSON.stringify(this.localScheduler);
|
||||
},
|
||||
isDefinerInUsers () {
|
||||
return this.originalScheduler ? this.workspace.users.some(user => this.originalScheduler.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
||||
},
|
||||
schemaTables () {
|
||||
const schemaTables = this.workspace.structure
|
||||
.filter(schema => schema.name === this.schema)
|
||||
.map(schema => schema.tables);
|
||||
const {
|
||||
getWorkspace,
|
||||
refreshStructure,
|
||||
renameTabs,
|
||||
changeBreadcrumbs,
|
||||
setUnsavedChanges
|
||||
} = workspacesStore;
|
||||
|
||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||
},
|
||||
users () {
|
||||
const users = [{ value: '' }, ...this.workspace.users];
|
||||
if (!this.isDefinerInUsers) {
|
||||
const [name, host] = this.originalScheduler.definer.replaceAll('`', '').split('@');
|
||||
users.unshift({ name, host });
|
||||
}
|
||||
const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const isTimingModal = ref(false);
|
||||
const originalScheduler: Ref<EventInfos> = ref(null);
|
||||
const localScheduler: Ref<EventInfos> = ref({} as EventInfos);
|
||||
const lastScheduler = ref(null);
|
||||
const sqlProxy = ref('');
|
||||
const editorHeight = ref(300);
|
||||
|
||||
return users;
|
||||
const workspace = computed(() => {
|
||||
return getWorkspace(props.connection.uid);
|
||||
});
|
||||
|
||||
const isChanged = computed(() => {
|
||||
return JSON.stringify(originalScheduler.value) !== JSON.stringify(localScheduler.value);
|
||||
});
|
||||
|
||||
const isDefinerInUsers = computed(() => {
|
||||
return originalScheduler.value ? workspace.value.users.some(user => originalScheduler.value.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
||||
});
|
||||
|
||||
const users = computed(() => {
|
||||
const users = [{ value: '' }, ...workspace.value.users];
|
||||
if (!isDefinerInUsers.value) {
|
||||
const [name, host] = originalScheduler.value.definer.replaceAll('`', '').split('@');
|
||||
users.unshift({ name, host });
|
||||
}
|
||||
|
||||
return users;
|
||||
});
|
||||
|
||||
const getSchedulerData = async () => {
|
||||
if (!props.scheduler) return;
|
||||
|
||||
isLoading.value = true;
|
||||
lastScheduler.value = props.scheduler;
|
||||
|
||||
const params = {
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
scheduler: props.scheduler
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Schedulers.getSchedulerInformations(params);
|
||||
if (status === 'success') {
|
||||
originalScheduler.value = response;
|
||||
localScheduler.value = JSON.parse(JSON.stringify(originalScheduler.value));
|
||||
sqlProxy.value = localScheduler.value.sql;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async schema () {
|
||||
if (this.isSelected) {
|
||||
await this.getSchedulerData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localScheduler.sql);
|
||||
this.lastScheduler = this.scheduler;
|
||||
}
|
||||
},
|
||||
async scheduler () {
|
||||
if (this.isSelected) {
|
||||
await this.getSchedulerData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localScheduler.sql);
|
||||
this.lastScheduler = this.scheduler;
|
||||
}
|
||||
},
|
||||
async isSelected (val) {
|
||||
if (val) {
|
||||
this.changeBreadcrumbs({ schema: this.schema, scheduler: this.scheduler });
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.resizeQueryEditor();
|
||||
}, 200);
|
||||
resizeQueryEditor();
|
||||
isLoading.value = false;
|
||||
};
|
||||
|
||||
if (this.lastScheduler !== this.scheduler)
|
||||
this.getSchedulerData();
|
||||
const saveChanges = async () => {
|
||||
if (isSaving.value) return;
|
||||
isSaving.value = true;
|
||||
const params = {
|
||||
uid: props.connection.uid,
|
||||
scheduler: {
|
||||
...localScheduler.value,
|
||||
schema: props.schema,
|
||||
oldName: originalScheduler.value.name
|
||||
} as AlterEventParams
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Schedulers.alterScheduler(params);
|
||||
|
||||
if (status === 'success') {
|
||||
const oldName = originalScheduler.value.name;
|
||||
|
||||
await refreshStructure(props.connection.uid);
|
||||
|
||||
if (oldName !== localScheduler.value.name) {
|
||||
renameTabs({
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
elementName: oldName,
|
||||
elementNewName: localScheduler.value.name,
|
||||
elementType: 'scheduler'
|
||||
});
|
||||
|
||||
changeBreadcrumbs({ schema: props.schema, scheduler: localScheduler.value.name });
|
||||
}
|
||||
},
|
||||
isChanged (val) {
|
||||
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
|
||||
else
|
||||
getSchedulerData();
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
await this.getSchedulerData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localScheduler.sql);
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
unmounted () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
async getSchedulerData () {
|
||||
if (!this.scheduler) return;
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
this.lastScheduler = this.scheduler;
|
||||
isSaving.value = false;
|
||||
};
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
scheduler: this.scheduler
|
||||
};
|
||||
const clearChanges = () => {
|
||||
localScheduler.value = JSON.parse(JSON.stringify(originalScheduler.value));
|
||||
queryEditor.value.editor.session.setValue(localScheduler.value.sql);
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Schedulers.getSchedulerInformations(params);
|
||||
if (status === 'success') {
|
||||
this.originalScheduler = response;
|
||||
this.localScheduler = JSON.parse(JSON.stringify(this.originalScheduler));
|
||||
this.sqlProxy = this.localScheduler.sql;
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
const resizeQueryEditor = () => {
|
||||
if (queryEditor.value) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
editorHeight.value = size;
|
||||
queryEditor.value.editor.resize();
|
||||
}
|
||||
};
|
||||
|
||||
this.resizeQueryEditor();
|
||||
this.isLoading = false;
|
||||
},
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
this.isSaving = true;
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
scheduler: {
|
||||
...this.localScheduler,
|
||||
schema: this.schema,
|
||||
oldName: this.originalScheduler.name
|
||||
}
|
||||
};
|
||||
const showTimingModal = () => {
|
||||
isTimingModal.value = true;
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Schedulers.alterScheduler(params);
|
||||
const hideTimingModal = () => {
|
||||
isTimingModal.value = false;
|
||||
};
|
||||
|
||||
if (status === 'success') {
|
||||
const oldName = this.originalScheduler.name;
|
||||
const timingUpdate = (options: EventInfos) => {
|
||||
localScheduler.value = options;
|
||||
};
|
||||
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
|
||||
if (oldName !== this.localScheduler.name) {
|
||||
this.renameTabs({
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
elementName: oldName,
|
||||
elementNewName: this.localScheduler.name,
|
||||
elementType: 'scheduler'
|
||||
});
|
||||
|
||||
this.changeBreadcrumbs({ schema: this.schema, scheduler: this.localScheduler.name });
|
||||
}
|
||||
else
|
||||
this.getSchedulerData();
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isSaving = false;
|
||||
},
|
||||
clearChanges () {
|
||||
this.localScheduler = JSON.parse(JSON.stringify(this.originalScheduler));
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localScheduler.sql);
|
||||
},
|
||||
resizeQueryEditor () {
|
||||
if (this.$refs.queryEditor) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
this.editorHeight = size;
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
}
|
||||
},
|
||||
showTimingModal () {
|
||||
this.isTimingModal = true;
|
||||
},
|
||||
hideTimingModal () {
|
||||
this.isTimingModal = false;
|
||||
},
|
||||
timingUpdate (options) {
|
||||
this.localScheduler = options;
|
||||
},
|
||||
onKey (e) {
|
||||
if (this.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
||||
if (this.isChanged)
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (props.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.key === 's') { // CTRL + S
|
||||
if (isChanged.value)
|
||||
saveChanges();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.schema, async () => {
|
||||
if (props.isSelected) {
|
||||
await getSchedulerData();
|
||||
queryEditor.value.editor.session.setValue(localScheduler.value.sql);
|
||||
lastScheduler.value = props.scheduler;
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => props.scheduler, async () => {
|
||||
if (props.isSelected) {
|
||||
await getSchedulerData();
|
||||
queryEditor.value.editor.session.setValue(localScheduler.value.sql);
|
||||
lastScheduler.value = props.scheduler;
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => props.isSelected, async (val) => {
|
||||
if (val) {
|
||||
changeBreadcrumbs({ schema: props.schema, scheduler: props.scheduler });
|
||||
|
||||
setTimeout(() => {
|
||||
resizeQueryEditor();
|
||||
}, 200);
|
||||
|
||||
if (lastScheduler.value !== props.scheduler)
|
||||
getSchedulerData();
|
||||
}
|
||||
});
|
||||
|
||||
watch(isChanged, (val) => {
|
||||
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
|
||||
});
|
||||
|
||||
(async () => {
|
||||
await getSchedulerData();
|
||||
queryEditor.value.editor.session.setValue(localScheduler.value.sql);
|
||||
window.addEventListener('keydown', onKey);
|
||||
})();
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', resizeQueryEditor);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', resizeQueryEditor);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user