mirror of https://github.com/Fabio286/antares.git
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
|
node_modules
|
||||||
assets
|
assets
|
||||||
out
|
out
|
||||||
dist
|
dist
|
||||||
|
build
|
|
@ -7,5 +7,4 @@ node_modules
|
||||||
thumbs.db
|
thumbs.db
|
||||||
NOTES.md
|
NOTES.md
|
||||||
*.txt
|
*.txt
|
||||||
package-lock.json
|
|
||||||
*.heapsnapshot
|
*.heapsnapshot
|
File diff suppressed because it is too large
Load Diff
|
@ -22,8 +22,8 @@
|
||||||
"postinstall": "electron-builder install-app-deps && npm run devtools:install",
|
"postinstall": "electron-builder install-app-deps && npm run devtools:install",
|
||||||
"test:e2e": "npm run compile && npm run test:e2e-dry",
|
"test:e2e": "npm run compile && npm run test:e2e-dry",
|
||||||
"test:e2e-dry": "xvfb-maybe -- playwright test",
|
"test:e2e-dry": "xvfb-maybe -- playwright test",
|
||||||
"lint": "eslint . --ext .js,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
|
"lint": "eslint . --ext .js,.ts,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
|
||||||
"lint:fix": "eslint . --ext .js,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
|
"lint:fix": "eslint . --ext .js,.ts,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
|
||||||
"contributors:add": "all-contributors add",
|
"contributors:add": "all-contributors add",
|
||||||
"contributors:generate": "all-contributors generate"
|
"contributors:generate": "all-contributors generate"
|
||||||
},
|
},
|
||||||
|
@ -150,6 +150,8 @@
|
||||||
"@babel/preset-typescript": "~7.16.7",
|
"@babel/preset-typescript": "~7.16.7",
|
||||||
"@playwright/test": "~1.21.1",
|
"@playwright/test": "~1.21.1",
|
||||||
"@types/better-sqlite3": "~7.5.0",
|
"@types/better-sqlite3": "~7.5.0",
|
||||||
|
"@types/leaflet": "~1.7.9",
|
||||||
|
"@types/marked": "~4.0.3",
|
||||||
"@types/node": "~17.0.23",
|
"@types/node": "~17.0.23",
|
||||||
"@types/pg": "~8.6.5",
|
"@types/pg": "~8.6.5",
|
||||||
"@typescript-eslint/eslint-plugin": "~5.18.0",
|
"@typescript-eslint/eslint-plugin": "~5.18.0",
|
||||||
|
@ -189,7 +191,7 @@
|
||||||
"unzip-crx-3": "~0.2.0",
|
"unzip-crx-3": "~0.2.0",
|
||||||
"vue-eslint-parser": "~8.3.0",
|
"vue-eslint-parser": "~8.3.0",
|
||||||
"vue-loader": "~16.8.3",
|
"vue-loader": "~16.8.3",
|
||||||
"webpack": "~5.60.0",
|
"webpack": "~5.72.0",
|
||||||
"webpack-cli": "~4.9.1",
|
"webpack-cli": "~4.9.1",
|
||||||
"webpack-dev-server": "~4.4.0",
|
"webpack-dev-server": "~4.4.0",
|
||||||
"xvfb-maybe": "~0.2.1"
|
"xvfb-maybe": "~0.2.1"
|
||||||
|
|
|
@ -59,7 +59,7 @@ async function restartElectron () {
|
||||||
console.error(chalk.red(data.toString()));
|
console.error(chalk.red(data.toString()));
|
||||||
});
|
});
|
||||||
|
|
||||||
electronProcess.on('exit', (code, signal) => {
|
electronProcess.on('exit', () => {
|
||||||
if (!manualRestart) process.exit(0);
|
if (!manualRestart) process.exit(0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
module.exports = {
|
import { Customizations } from '../interfaces/customizations';
|
||||||
|
|
||||||
|
export const defaults: Customizations = {
|
||||||
// Defaults
|
// Defaults
|
||||||
defaultPort: null,
|
defaultPort: null,
|
||||||
defaultUser: null,
|
defaultUser: null,
|
||||||
|
@ -68,24 +70,24 @@ module.exports = {
|
||||||
viewUpdateOption: false,
|
viewUpdateOption: false,
|
||||||
procedureDeterministic: false,
|
procedureDeterministic: false,
|
||||||
procedureDataAccess: false,
|
procedureDataAccess: false,
|
||||||
procedureSql: false,
|
procedureSql: null,
|
||||||
procedureContext: false,
|
procedureContext: false,
|
||||||
procedureLanguage: false,
|
procedureLanguage: false,
|
||||||
functionDeterministic: false,
|
functionDeterministic: false,
|
||||||
functionDataAccess: false,
|
functionDataAccess: false,
|
||||||
functionSql: false,
|
functionSql: null,
|
||||||
functionContext: false,
|
functionContext: false,
|
||||||
functionLanguage: false,
|
functionLanguage: false,
|
||||||
triggerSql: false,
|
triggerSql: null,
|
||||||
triggerStatementInCreation: false,
|
triggerStatementInCreation: false,
|
||||||
triggerMultipleEvents: false,
|
triggerMultipleEvents: false,
|
||||||
triggerTableInName: false,
|
triggerTableInName: false,
|
||||||
triggerUpdateColumns: false,
|
triggerUpdateColumns: false,
|
||||||
triggerOnlyRename: false,
|
triggerOnlyRename: false,
|
||||||
triggerEnableDisable: false,
|
triggerEnableDisable: false,
|
||||||
triggerFunctionSql: false,
|
triggerFunctionSql: null,
|
||||||
triggerFunctionlanguages: false,
|
triggerFunctionlanguages: null,
|
||||||
parametersLength: false,
|
parametersLength: false,
|
||||||
languages: false,
|
languages: null,
|
||||||
readOnlyMode: false
|
readOnlyMode: false
|
||||||
};
|
};
|
|
@ -1,6 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
maria: require('./mysql'),
|
|
||||||
mysql: require('./mysql'),
|
|
||||||
pg: require('./postgresql'),
|
|
||||||
sqlite: require('./sqlite')
|
|
||||||
};
|
|
|
@ -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,
|
||||||
// Defaults
|
// Defaults
|
||||||
defaultPort: 3306,
|
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,
|
||||||
// Defaults
|
// Defaults
|
||||||
defaultPort: 5432,
|
defaultPort: 5432,
|
|
@ -1,4 +1,8 @@
|
||||||
module.exports = {
|
import { Customizations } from '../interfaces/customizations';
|
||||||
|
import { defaults } from './defaults';
|
||||||
|
|
||||||
|
export const customizations: Customizations = {
|
||||||
|
...defaults,
|
||||||
// Core
|
// Core
|
||||||
fileConnection: true,
|
fileConnection: true,
|
||||||
// Structure
|
// Structure
|
|
@ -1,4 +1,6 @@
|
||||||
module.exports = [
|
import { TypesGroup } from 'common/interfaces/antares';
|
||||||
|
|
||||||
|
export default [
|
||||||
{
|
{
|
||||||
group: 'integer',
|
group: 'integer',
|
||||||
types: [
|
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',
|
group: 'integer',
|
||||||
types: [
|
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',
|
group: 'integer',
|
||||||
types: [
|
types: [
|
||||||
|
@ -134,4 +136,4 @@ module.exports = [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
] as TypesGroup[];
|
|
@ -1,4 +1,4 @@
|
||||||
module.exports = [
|
export default [
|
||||||
'PRIMARY',
|
'PRIMARY',
|
||||||
'INDEX',
|
'INDEX',
|
||||||
'UNIQUE',
|
'UNIQUE',
|
|
@ -1,4 +1,4 @@
|
||||||
module.exports = [
|
export default [
|
||||||
'PRIMARY',
|
'PRIMARY',
|
||||||
'INDEX',
|
'INDEX',
|
||||||
'UNIQUE'
|
'UNIQUE'
|
|
@ -1,4 +1,4 @@
|
||||||
module.exports = [
|
export default [
|
||||||
'PRIMARY',
|
'PRIMARY',
|
||||||
'INDEX',
|
'INDEX',
|
||||||
'UNIQUE'
|
'UNIQUE'
|
|
@ -14,6 +14,12 @@ export type ClientCode = 'mysql' | 'maria' | 'pg' | 'sqlite'
|
||||||
export type Exporter = MysqlExporter | PostgreSQLExporter
|
export type Exporter = MysqlExporter | PostgreSQLExporter
|
||||||
export type Importer = MySQLImporter | PostgreSQLImporter
|
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
|
* Pasameters needed to create a new Antares connection to a database
|
||||||
*/
|
*/
|
||||||
|
@ -67,12 +73,34 @@ export interface TypeInformations {
|
||||||
zerofill: boolean;
|
zerofill: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TypesGroup {
|
||||||
|
group: string;
|
||||||
|
types: TypeInformations[];
|
||||||
|
}
|
||||||
|
|
||||||
// Tables
|
// 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;
|
name: string;
|
||||||
key: string;
|
|
||||||
type: string;
|
type: string;
|
||||||
schema: string;
|
schema: string;
|
||||||
|
table?: string;
|
||||||
numPrecision?: number;
|
numPrecision?: number;
|
||||||
numLength?: number;
|
numLength?: number;
|
||||||
datePrecision?: number;
|
datePrecision?: number;
|
||||||
|
@ -82,7 +110,8 @@ export interface TableField {
|
||||||
unsigned?: boolean;
|
unsigned?: boolean;
|
||||||
zerofill?: boolean;
|
zerofill?: boolean;
|
||||||
order?: number;
|
order?: number;
|
||||||
default?: number | string;
|
default?: string;
|
||||||
|
defaultType?: string;
|
||||||
enumValues?: string;
|
enumValues?: string;
|
||||||
charset?: string;
|
charset?: string;
|
||||||
collation?: string;
|
collation?: string;
|
||||||
|
@ -92,9 +121,16 @@ export interface TableField {
|
||||||
comment?: string;
|
comment?: string;
|
||||||
after?: string;
|
after?: string;
|
||||||
orgName?: string;
|
orgName?: string;
|
||||||
|
length?: number | false;
|
||||||
|
alias: string;
|
||||||
|
tableAlias: string;
|
||||||
|
orgTable: string;
|
||||||
|
key?: 'pri' | 'uni';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TableIndex {
|
export interface TableIndex {
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
_antares_id?: string;
|
||||||
name: string;
|
name: string;
|
||||||
fields: string[];
|
fields: string[];
|
||||||
type: string;
|
type: string;
|
||||||
|
@ -107,6 +143,8 @@ export interface TableIndex {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TableForeign {
|
export interface TableForeign {
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
_antares_id?: string;
|
||||||
constraintName: string;
|
constraintName: string;
|
||||||
refSchema: string;
|
refSchema: string;
|
||||||
table: string;
|
table: string;
|
||||||
|
@ -118,15 +156,6 @@ export interface TableForeign {
|
||||||
oldName?: string;
|
oldName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TableOptions {
|
|
||||||
name: string;
|
|
||||||
type?: 'table' | 'view';
|
|
||||||
engine?: string;
|
|
||||||
comment?: string;
|
|
||||||
collation?: string;
|
|
||||||
autoIncrement?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateTableParams {
|
export interface CreateTableParams {
|
||||||
/** Connection UID */
|
/** Connection UID */
|
||||||
uid?: string;
|
uid?: string;
|
||||||
|
@ -165,6 +194,7 @@ export interface AlterTableParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
|
export type ViewInfos = TableInfos
|
||||||
export interface CreateViewParams {
|
export interface CreateViewParams {
|
||||||
schema: string;
|
schema: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -180,6 +210,19 @@ export interface AlterViewParams extends CreateViewParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Triggers
|
// 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 {
|
export interface CreateTriggerParams {
|
||||||
definer?: string;
|
definer?: string;
|
||||||
schema: string;
|
schema: string;
|
||||||
|
@ -195,13 +238,38 @@ export interface AlterTriggerParams extends CreateTriggerParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routines & Functions
|
// Routines & Functions
|
||||||
|
|
||||||
export interface FunctionParam {
|
export interface FunctionParam {
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
_antares_id: string;
|
||||||
context: string;
|
context: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
length: number;
|
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 {
|
export interface CreateRoutineParams {
|
||||||
name: string;
|
name: string;
|
||||||
parameters?: FunctionParam[];
|
parameters?: FunctionParam[];
|
||||||
|
@ -239,7 +307,7 @@ export interface AlterFunctionParams extends CreateFunctionParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
export interface CreateEventParams {
|
export interface EventInfos {
|
||||||
definer?: string;
|
definer?: string;
|
||||||
schema: string;
|
schema: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -248,16 +316,39 @@ export interface CreateEventParams {
|
||||||
starts: string;
|
starts: string;
|
||||||
ends: string;
|
ends: string;
|
||||||
at: string;
|
at: string;
|
||||||
preserve: string;
|
preserve: boolean;
|
||||||
state: string;
|
state: string;
|
||||||
comment: string;
|
comment: string;
|
||||||
|
enabled?: boolean;
|
||||||
sql: string;
|
sql: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CreateEventParams = EventInfos;
|
||||||
|
|
||||||
export interface AlterEventParams extends CreateEventParams {
|
export interface AlterEventParams extends CreateEventParams {
|
||||||
oldName?: string;
|
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
|
// Query
|
||||||
export interface QueryBuilderObject {
|
export interface QueryBuilderObject {
|
||||||
schema: string;
|
schema: string;
|
||||||
|
@ -285,17 +376,10 @@ export interface QueryParams {
|
||||||
tabUid?: string;
|
tabUid?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryField {
|
/**
|
||||||
name: string;
|
* @deprecated Use TableFIeld
|
||||||
alias: string;
|
*/
|
||||||
orgName: string;
|
export type QueryField = TableField
|
||||||
schema: string;
|
|
||||||
table: string;
|
|
||||||
tableAlias: string;
|
|
||||||
orgTable: string;
|
|
||||||
type: string;
|
|
||||||
length: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface QueryForeign {
|
export interface QueryForeign {
|
||||||
schema: string;
|
schema: string;
|
||||||
|
|
|
@ -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';
|
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 {
|
export interface InsertRowsParams {
|
||||||
uid: string;
|
uid: string;
|
||||||
schema: string;
|
schema: string;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
'use strict';
|
export function bufferToBase64 (buf: Buffer) {
|
||||||
export function bufferToBase64 (buf) {
|
|
||||||
const binstr = Array.prototype.map.call(buf, ch => {
|
const binstr = Array.prototype.map.call(buf, ch => {
|
||||||
return String.fromCharCode(ch);
|
return String.fromCharCode(ch);
|
||||||
}).join('');
|
}).join('');
|
||||||
return btoa(binstr);
|
return Buffer.from(binstr, 'binary').toString('base64');
|
||||||
}
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
'use strict';
|
export function formatBytes (bytes: number, decimals = 2) {
|
||||||
export function formatBytes (bytes, decimals = 2) {
|
|
||||||
if (bytes === 0) return '0 Bytes';
|
if (bytes === 0) return '0 Bytes';
|
||||||
|
|
||||||
const k = 1024;
|
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;
|
|
||||||
}
|
|
|
@ -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 = {
|
const lookup = {
|
||||||
0: '0000',
|
0: '0000',
|
||||||
1: '0001',
|
1: '0001',
|
||||||
|
@ -23,15 +21,11 @@ const lookup = {
|
||||||
D: '1101',
|
D: '1101',
|
||||||
E: '1110',
|
E: '1110',
|
||||||
F: '1111'
|
F: '1111'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
/**
|
export type HexChar = keyof typeof lookup
|
||||||
* Converts hexadecimal string to binary string
|
|
||||||
*
|
export default function hexToBinary (hex: HexChar[]) {
|
||||||
* @param {string} hex Hexadecimal string
|
|
||||||
* @returns {string} Binary string
|
|
||||||
*/
|
|
||||||
export default function hexToBinary (hex) {
|
|
||||||
let binary = '';
|
let binary = '';
|
||||||
for (let i = 0; i < hex.length; i++)
|
for (let i = 0; i < hex.length; i++)
|
||||||
binary += lookup[hex[i]];
|
binary += lookup[hex[i]];
|
|
@ -1,5 +1,4 @@
|
||||||
'use strict';
|
export function mimeFromHex (hex: string) {
|
||||||
export function mimeFromHex (hex) {
|
|
||||||
switch (hex.substring(0, 4)) { // 2 bytes
|
switch (hex.substring(0, 4)) { // 2 bytes
|
||||||
case '424D':
|
case '424D':
|
||||||
return { ext: 'bmp', mime: 'image/bmp' };
|
return { ext: 'bmp', mime: 'image/bmp' };
|
||||||
|
@ -23,7 +22,7 @@ export function mimeFromHex (hex) {
|
||||||
case '425A68':
|
case '425A68':
|
||||||
return { ext: 'bz2', mime: 'application/x-bzip2' };
|
return { ext: 'bz2', mime: 'application/x-bzip2' };
|
||||||
default:
|
default:
|
||||||
switch (hex) { // 4 bytes
|
switch (hex) { // 4 bites
|
||||||
case '89504E47':
|
case '89504E47':
|
||||||
return { ext: 'png', mime: 'image/png' };
|
return { ext: 'png', mime: 'image/png' };
|
||||||
case '47494638':
|
case '47494638':
|
|
@ -3,13 +3,7 @@
|
||||||
const pattern = /[\0\x08\x09\x1a\n\r"'\\\%]/gm;
|
const pattern = /[\0\x08\x09\x1a\n\r"'\\\%]/gm;
|
||||||
const regex = new RegExp(pattern);
|
const regex = new RegExp(pattern);
|
||||||
|
|
||||||
/**
|
function sqlEscaper (string: string) {
|
||||||
* Escapes a string
|
|
||||||
*
|
|
||||||
* @param {String} string
|
|
||||||
* @returns {String}
|
|
||||||
*/
|
|
||||||
function sqlEscaper (string) {
|
|
||||||
return string.replace(regex, char => {
|
return string.replace(regex, char => {
|
||||||
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '\"', '\\', '\\\\', '%'];
|
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '\"', '\\', '\\\\', '%'];
|
||||||
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\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();
|
|
||||||
}
|
|
|
@ -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();
|
app.exit();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('get-key', async event => {
|
|
||||||
const key = false;
|
|
||||||
event.returnValue = key;
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('show-open-dialog', (event, options) => {
|
ipcMain.handle('show-open-dialog', (event, options) => {
|
||||||
return dialog.showOpenDialog(options);
|
return dialog.showOpenDialog(options);
|
||||||
});
|
});
|
||||||
|
|
|
@ -172,7 +172,10 @@ export default (connections: {[key: string]: antares.Client}) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('export', (event, { uid, type, tables, ...rest }) => {
|
ipcMain.handle('export', (event, { uid, type, tables, ...rest }) => {
|
||||||
if (exporter !== null) return;
|
if (exporter !== null) {
|
||||||
|
exporter.kill();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve/*, reject */) => {
|
return new Promise((resolve/*, reject */) => {
|
||||||
(async () => {
|
(async () => {
|
||||||
|
@ -265,7 +268,10 @@ export default (connections: {[key: string]: antares.Client}) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('import-sql', async (event, options) => {
|
ipcMain.handle('import-sql', async (event, options) => {
|
||||||
if (importer !== null) return;
|
if (importer !== null) {
|
||||||
|
importer.kill();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve/*, reject */) => {
|
return new Promise((resolve/*, reject */) => {
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import * as fs from 'fs';
|
||||||
import * as antares from 'common/interfaces/antares';
|
import * as antares from 'common/interfaces/antares';
|
||||||
import { InsertRowsParams } from 'common/interfaces/tableApis';
|
import { InsertRowsParams } from 'common/interfaces/tableApis';
|
||||||
import { ipcMain } from 'electron';
|
import { ipcMain } from 'electron';
|
||||||
|
@ -5,8 +6,7 @@ import { faker } from '@faker-js/faker';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { sqlEscaper } from 'common/libs/sqlEscaper';
|
import { sqlEscaper } from 'common/libs/sqlEscaper';
|
||||||
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes';
|
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes';
|
||||||
import * as customizations from 'common/customizations';
|
import customizations from 'common/customizations';
|
||||||
import fs from 'fs';
|
|
||||||
|
|
||||||
export default (connections: {[key: string]: antares.Client}) => {
|
export default (connections: {[key: string]: antares.Client}) => {
|
||||||
ipcMain.handle('get-table-columns', async (event, params) => {
|
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) => {
|
ipcMain.handle('insert-table-fake-rows', async (event, params: InsertRowsParams) => {
|
||||||
try { // TODO: move to client classes
|
try { // TODO: move to client classes
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
const rows: {[key: string]: string | number | boolean | Date | Buffer}[] = [];
|
||||||
const rows: {[key: string]: any}[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < +params.repeat; i++) {
|
for (let i = 0; i < +params.repeat; i++) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
const insertObj: {[key: string]: string | number | boolean | Date | Buffer} = {};
|
||||||
const insertObj: {[key: string]: any} = {};
|
|
||||||
|
|
||||||
for (const key in params.row) {
|
for (const key in params.row) {
|
||||||
const type = params.fields[key];
|
const type = params.fields[key];
|
||||||
|
@ -382,8 +310,7 @@ export default (connections: {[key: string]: antares.Client}) => {
|
||||||
insertObj[key] = escapedParam;
|
insertObj[key] = escapedParam;
|
||||||
}
|
}
|
||||||
else { // Faker value
|
else { // Faker value
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
const parsedParams: {[key: string]: string | number | boolean | Date | Buffer} = {};
|
||||||
const parsedParams: {[key: string]: any} = {};
|
|
||||||
let fakeValue;
|
let fakeValue;
|
||||||
|
|
||||||
if (params.locale)
|
if (params.locale)
|
||||||
|
@ -403,7 +330,7 @@ export default (connections: {[key: string]: antares.Client}) => {
|
||||||
|
|
||||||
if (typeof fakeValue === 'string') {
|
if (typeof fakeValue === 'string') {
|
||||||
if (params.row[key].length)
|
if (params.row[key].length)
|
||||||
fakeValue = fakeValue.substr(0, params.row[key].length);
|
fakeValue = fakeValue.substring(0, params.row[key].length);
|
||||||
fakeValue = `'${sqlEscaper(fakeValue)}'`;
|
fakeValue = `'${sqlEscaper(fakeValue)}'`;
|
||||||
}
|
}
|
||||||
else if ([...DATE, ...DATETIME].includes(type))
|
else if ([...DATE, ...DATETIME].includes(type))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ipcMain } from 'electron';
|
import { ipcMain } from 'electron';
|
||||||
import { autoUpdater } from 'electron-updater';
|
import { autoUpdater } from 'electron-updater';
|
||||||
import Store from 'electron-store';
|
import * as Store from 'electron-store';
|
||||||
const persistentStore = new Store({ name: 'settings' });
|
const persistentStore = new Store({ name: 'settings', clearInvalidConfig: true });
|
||||||
const isMacOS = process.platform === 'darwin';
|
const isMacOS = process.platform === 'darwin';
|
||||||
|
|
||||||
let mainWindow: Electron.IpcMainEvent;
|
let mainWindow: Electron.IpcMainEvent;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as antares from 'common/interfaces/antares';
|
import * as antares from 'common/interfaces/antares';
|
||||||
import * as mysql from 'mysql2/promise';
|
import * as mysql from 'mysql2/promise';
|
||||||
import { AntaresCore } from '../AntaresCore';
|
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 SSH2Promise = require('ssh2-promise');
|
||||||
import SSHConfig from 'ssh2-promise/lib/sshConfig';
|
import SSHConfig from 'ssh2-promise/lib/sshConfig';
|
||||||
|
|
||||||
|
@ -321,7 +321,7 @@ export class MySQLClient extends AntaresCore {
|
||||||
return filteredDatabases.map(db => {
|
return filteredDatabases.map(db => {
|
||||||
if (schemas.has(db.Database)) {
|
if (schemas.has(db.Database)) {
|
||||||
// TABLES
|
// 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;
|
let tableType;
|
||||||
switch (table.Comment) {
|
switch (table.Comment) {
|
||||||
case 'VIEW':
|
case 'VIEW':
|
||||||
|
@ -350,7 +350,7 @@ export class MySQLClient extends AntaresCore {
|
||||||
});
|
});
|
||||||
|
|
||||||
// PROCEDURES
|
// 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 {
|
return {
|
||||||
name: procedure.Name,
|
name: procedure.Name,
|
||||||
type: procedure.Type,
|
type: procedure.Type,
|
||||||
|
@ -364,7 +364,7 @@ export class MySQLClient extends AntaresCore {
|
||||||
});
|
});
|
||||||
|
|
||||||
// FUNCTIONS
|
// 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 {
|
return {
|
||||||
name: func.Name,
|
name: func.Name,
|
||||||
type: func.Type,
|
type: func.Type,
|
||||||
|
@ -378,33 +378,26 @@ export class MySQLClient extends AntaresCore {
|
||||||
});
|
});
|
||||||
|
|
||||||
// SCHEDULERS
|
// 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 {
|
return {
|
||||||
name: scheduler.EVENT_NAME,
|
name: scheduler.EVENT_NAME,
|
||||||
definition: scheduler.EVENT_DEFINITION,
|
schema: scheduler.Db,
|
||||||
type: scheduler.EVENT_TYPE,
|
sql: scheduler.EVENT_DEFINITION,
|
||||||
|
execution: scheduler.EVENT_TYPE === 'RECURRING' ? 'EVERY' : 'ONCE',
|
||||||
definer: scheduler.DEFINER,
|
definer: scheduler.DEFINER,
|
||||||
body: scheduler.EVENT_BODY,
|
|
||||||
starts: scheduler.STARTS,
|
starts: scheduler.STARTS,
|
||||||
ends: scheduler.ENDS,
|
ends: scheduler.ENDS,
|
||||||
|
state: scheduler.STATUS === 'ENABLED' ? 'ENABLE' : scheduler.STATE === 'DISABLED' ? 'DISABLE' : 'DISABLE ON SLAVE',
|
||||||
enabled: scheduler.STATUS === 'ENABLED',
|
enabled: scheduler.STATUS === 'ENABLED',
|
||||||
executeAt: scheduler.EXECUTE_AT,
|
at: scheduler.EXECUTE_AT,
|
||||||
intervalField: scheduler.INTERVAL_FIELD,
|
every: [scheduler.INTERVAL_FIELD, scheduler.INTERVAL_VALUE],
|
||||||
intervalValue: scheduler.INTERVAL_VALUE,
|
preserve: scheduler.ON_COMPLETION.includes('PRESERVE'),
|
||||||
onCompletion: scheduler.ON_COMPLETION,
|
comment: scheduler.EVENT_COMMENT
|
||||||
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
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// TRIGGERS
|
// 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 {
|
return {
|
||||||
name: trigger.Trigger,
|
name: trigger.Trigger,
|
||||||
statement: trigger.Statement,
|
statement: trigger.Statement,
|
||||||
|
@ -930,19 +923,22 @@ export class MySQLClient extends AntaresCore {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getViewInformations ({ schema, view }: { schema: string; view: string }) {
|
async getViewInformations ({ schema, view }: { schema: string; view: string }) {
|
||||||
const sql = `SHOW CREATE VIEW \`${schema}\`.\`${view}\``;
|
const { rows: algorithm } = await this.raw(`SHOW CREATE VIEW \`${schema}\`.\`${view}\``);
|
||||||
const results = await this.raw(sql);
|
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 {
|
||||||
return {
|
algorithm: algorithm[0]['Create View'].match(/(?<=CREATE ALGORITHM=).*?(?=\s)/gs)[0],
|
||||||
algorithm: row['Create View'].match(/(?<=CREATE ALGORITHM=).*?(?=\s)/gs)[0],
|
definer: viewInfo[0].DEFINER,
|
||||||
definer: row['Create View'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0],
|
security: viewInfo[0].SECURITY_TYPE,
|
||||||
security: row['Create View'].match(/(?<=SQL SECURITY ).*?(?=\s)/gs)[0],
|
updateOption: viewInfo[0].CHECK_OPTION === 'NONE' ? '' : viewInfo[0].CHECK_OPTION,
|
||||||
updateOption: row['Create View'].match(/(?<=WITH ).*?(?=\s)/gs) ? row['Create View'].match(/(?<=WITH ).*?(?=\s)/gs)[0] : '',
|
sql: viewInfo[0].VIEW_DEFINITION,
|
||||||
sql: row['Create View'].match(/(?<=AS ).*?$/gs)[0],
|
name: viewInfo[0].TABLE_NAME
|
||||||
name: row.View
|
};
|
||||||
};
|
|
||||||
})[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async dropView (params: { schema: string; view: string }) {
|
async dropView (params: { schema: string; view: string }) {
|
||||||
|
@ -955,7 +951,7 @@ export class MySQLClient extends AntaresCore {
|
||||||
USE \`${view.schema}\`;
|
USE \`${view.schema}\`;
|
||||||
ALTER ALGORITHM = ${view.algorithm}${view.definer ? ` DEFINER=${view.definer}` : ''}
|
ALTER ALGORITHM = ${view.algorithm}${view.definer ? ` DEFINER=${view.definer}` : ''}
|
||||||
SQL SECURITY ${view.security}
|
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)
|
if (view.name !== view.oldName)
|
||||||
|
@ -1381,6 +1377,14 @@ export class MySQLClient extends AntaresCore {
|
||||||
xa: row.XA,
|
xa: row.XA,
|
||||||
savepoints: row.Savepoints,
|
savepoints: row.Savepoints,
|
||||||
isDefault: row.Support.includes('DEFAULT')
|
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;
|
break;
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {}) as {
|
||||||
|
number: string;
|
||||||
|
name: string;
|
||||||
|
arch: string;
|
||||||
|
os: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProcesses () {
|
async getProcesses () {
|
||||||
|
@ -1423,6 +1432,15 @@ export class MySQLClient extends AntaresCore {
|
||||||
time: row.TIME,
|
time: row.TIME,
|
||||||
state: row.STATE,
|
state: row.STATE,
|
||||||
info: row.INFO
|
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 pg from 'pg';
|
||||||
import * as pgAst from 'pgsql-ast-parser';
|
import * as pgAst from 'pgsql-ast-parser';
|
||||||
import { AntaresCore } from '../AntaresCore';
|
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 SSH2Promise = require('ssh2-promise');
|
||||||
import SSHConfig from 'ssh2-promise/lib/sshConfig';
|
import SSHConfig from 'ssh2-promise/lib/sshConfig';
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as antares from 'common/interfaces/antares';
|
import * as antares from 'common/interfaces/antares';
|
||||||
import * as sqlite from 'better-sqlite3';
|
import * as sqlite from 'better-sqlite3';
|
||||||
import { AntaresCore } from '../AntaresCore';
|
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';
|
import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes';
|
||||||
|
|
||||||
export class SQLiteClient extends AntaresCore {
|
export class SQLiteClient extends AntaresCore {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as exporter from 'common/interfaces/exporter';
|
||||||
import * as mysql from 'mysql2/promise';
|
import * as mysql from 'mysql2/promise';
|
||||||
import { SqlExporter } from './SqlExporter';
|
import { SqlExporter } from './SqlExporter';
|
||||||
import { BLOB, BIT, DATE, DATETIME, FLOAT, SPATIAL, IS_MULTI_SPATIAL, NUMBER } from 'common/fieldTypes';
|
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 { getArrayDepth } from 'common/libs/getArrayDepth';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { lineString, point, polygon } from '@turf/helpers';
|
import { lineString, point, polygon } from '@turf/helpers';
|
||||||
|
@ -138,7 +138,7 @@ ${footer}
|
||||||
: this.escapeAndQuote(val);
|
: this.escapeAndQuote(val);
|
||||||
}
|
}
|
||||||
else if (BIT.includes(column.type))
|
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))
|
else if (BLOB.includes(column.type))
|
||||||
sqlInsertString += `X'${val.toString('hex').toUpperCase()}'`;
|
sqlInsertString += `X'${val.toString('hex').toUpperCase()}'`;
|
||||||
else if (NUMBER.includes(column.type))
|
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 * as exporter from 'common/interfaces/exporter';
|
||||||
import { SqlExporter } from './SqlExporter';
|
import { SqlExporter } from './SqlExporter';
|
||||||
import { BLOB, BIT, DATE, DATETIME, FLOAT, NUMBER, TEXT_SEARCH } from 'common/fieldTypes';
|
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';
|
import * as moment from 'moment';
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -249,7 +249,7 @@ SET row_security = off;\n\n\n`;
|
||||||
else if (TEXT_SEARCH.includes(column.type))
|
else if (TEXT_SEARCH.includes(column.type))
|
||||||
sqlInsertString += `'${val.replaceAll('\'', '\'\'')}'`;
|
sqlInsertString += `'${val.replaceAll('\'', '\'\'')}'`;
|
||||||
else if (BIT.includes(column.type))
|
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))
|
else if (BLOB.includes(column.type))
|
||||||
sqlInsertString += `decode('${val.toString('hex').toUpperCase()}', 'hex')`;
|
sqlInsertString += `decode('${val.toString('hex').toUpperCase()}', 'hex')`;
|
||||||
else if (NUMBER.includes(column.type))
|
else if (NUMBER.includes(column.type))
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as pg from 'pg';
|
import * as pg from 'pg';
|
||||||
import * as importer from 'common/interfaces/importer';
|
import * as importer from 'common/interfaces/importer';
|
||||||
import fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
import PostgreSQLParser from '../../parsers/PostgreSQLParser';
|
import PostgreSQLParser from '../../parsers/PostgreSQLParser';
|
||||||
import { BaseImporter } from '../BaseImporter';
|
import { BaseImporter } from '../BaseImporter';
|
||||||
|
|
||||||
|
|
|
@ -147,7 +147,7 @@ export default {
|
||||||
height: calc(100vh - #{$footer-height});
|
height: calc(100vh - #{$footer-height});
|
||||||
}
|
}
|
||||||
|
|
||||||
.connection-panel-wrapper{
|
.connection-panel-wrapper {
|
||||||
height: calc(100vh - #{$excluding-size});
|
height: calc(100vh - #{$excluding-size});
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-top: 15vh;
|
padding-top: 15vh;
|
||||||
|
@ -155,6 +155,6 @@ export default {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -46,65 +46,58 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
export default {
|
import { computed, onBeforeUnmount, PropType, useSlots } from 'vue';
|
||||||
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();
|
|
||||||
},
|
|
||||||
|
|
||||||
hideModal () {
|
const props = defineProps({
|
||||||
this.$emit('hide');
|
size: {
|
||||||
},
|
type: String as PropType<'small' | 'medium' | '400' | 'large'>,
|
||||||
onKey (e) {
|
validator: (prop: string) => ['small', 'medium', '400', 'large'].includes(prop),
|
||||||
e.stopPropagation();
|
default: 'small'
|
||||||
if (e.key === 'Escape')
|
},
|
||||||
this.hideModal();
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -15,67 +15,60 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
export default {
|
import { computed, onBeforeUnmount, onMounted, Ref, ref } from 'vue';
|
||||||
name: 'BaseContextMenu',
|
|
||||||
props: {
|
|
||||||
contextEvent: MouseEvent
|
|
||||||
},
|
|
||||||
emits: ['close-context'],
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
contextSize: null,
|
|
||||||
isBottom: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
position () {
|
|
||||||
let topCord = 0;
|
|
||||||
let leftCord = 0;
|
|
||||||
|
|
||||||
if (this.contextEvent) {
|
const contextContent: Ref<HTMLDivElement> = ref(null);
|
||||||
const { clientY, clientX } = this.contextEvent;
|
const contextSize: Ref<DOMRect> = ref(null);
|
||||||
topCord = `${clientY + 2}px`;
|
const isBottom: Ref<boolean> = ref(false);
|
||||||
leftCord = `${clientX + 5}px`;
|
const props = defineProps<{contextEvent: MouseEvent}>();
|
||||||
|
const emit = defineEmits(['close-context']);
|
||||||
|
|
||||||
if (this.contextSize) {
|
const position = computed(() => {
|
||||||
if (clientY + (this.contextSize.height < 200 ? 200 : this.contextSize.height) + 5 >= window.innerHeight) {
|
let topCord = '0px';
|
||||||
topCord = `${clientY + 3 - this.contextSize.height}px`;
|
let leftCord = '0px';
|
||||||
this.isBottom = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clientX + this.contextSize.width + 5 >= window.innerWidth)
|
if (props.contextEvent) {
|
||||||
leftCord = `${clientX - this.contextSize.width}px`;
|
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 {
|
if (clientX + contextSize.value.width + 5 >= window.innerWidth)
|
||||||
top: topCord,
|
leftCord = `${clientX - contextSize.value.width}px`;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -4,11 +4,6 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'BaseLoader'
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.empty {
|
.empty {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -1,95 +1,93 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="map" class="map" />
|
<div id="map" class="map" />
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
import L from 'leaflet';
|
<script setup lang="ts">
|
||||||
|
import { onMounted, PropType, Ref, ref } from 'vue';
|
||||||
|
import * as L from 'leaflet';
|
||||||
import {
|
import {
|
||||||
point,
|
point,
|
||||||
lineString,
|
lineString,
|
||||||
polygon
|
polygon
|
||||||
} from '@turf/helpers';
|
} from '@turf/helpers';
|
||||||
|
import { GeoJsonObject } from 'geojson';
|
||||||
import { getArrayDepth } from 'common/libs/getArrayDepth';
|
import { getArrayDepth } from 'common/libs/getArrayDepth';
|
||||||
|
|
||||||
export default {
|
interface Coordinates { x: number; y: number }
|
||||||
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);
|
|
||||||
|
|
||||||
if (!Array.isArray(this.points))
|
const props = defineProps({
|
||||||
this.center = [this.points.y, this.points.x];
|
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', {
|
const getMarkers = (points: Coordinates) => {
|
||||||
center: this.center || [0, 0],
|
if (Array.isArray(points)) {
|
||||||
zoom: 15,
|
if (getArrayDepth(points) === 1)
|
||||||
minZoom: 1,
|
return lineString(points.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []));
|
||||||
attributionControl: false
|
else
|
||||||
});
|
return polygon(points.map(arr => arr.reduce((acc: Coordinates[], curr: Coordinates) => [...acc, [curr.x, curr.y]], [])));
|
||||||
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -14,64 +14,58 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
export default {
|
import { computed, ref } from 'vue';
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { className, iconName };
|
const props = defineProps({
|
||||||
},
|
message: {
|
||||||
isExpandable () {
|
type: String,
|
||||||
return this.message.length > 80;
|
default: ''
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
status: {
|
||||||
hideToast () {
|
type: String,
|
||||||
this.$emit('close');
|
default: ''
|
||||||
},
|
|
||||||
toggleExpand () {
|
|
||||||
this.isExpanded = !this.isExpanded;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.toast {
|
.toast {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -143,7 +143,7 @@ export default defineComponent({
|
||||||
const hightlightedIndex = ref(0);
|
const hightlightedIndex = ref(0);
|
||||||
const isOpen = ref(false);
|
const isOpen = ref(false);
|
||||||
const isMouseDown = 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 el = ref(null);
|
||||||
const searchInput = ref(null);
|
const searchInput = ref(null);
|
||||||
const optionList = ref(null);
|
const optionList = ref(null);
|
||||||
|
@ -403,7 +403,8 @@ export default defineComponent({
|
||||||
optionList,
|
optionList,
|
||||||
optionRefs,
|
optionRefs,
|
||||||
handleBlurEvent,
|
handleBlurEvent,
|
||||||
handleMouseUpEvent
|
handleMouseUpEvent,
|
||||||
|
internalValue
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,121 +9,111 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { onMounted, watch } from 'vue';
|
||||||
import * as ace from 'ace-builds';
|
import * as ace from 'ace-builds';
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
import 'ace-builds/webpack-resolver';
|
import 'ace-builds/webpack-resolver';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
import { useSettingsStore } from '@/stores/settings';
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
import { uidGen } from 'common/libs/uidGen';
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'BaseTextEditor',
|
modelValue: String,
|
||||||
props: {
|
mode: { type: String, default: 'text' },
|
||||||
modelValue: String,
|
editorClass: { type: String, default: '' },
|
||||||
mode: { type: String, default: 'text' },
|
autoFocus: { type: Boolean, default: false },
|
||||||
editorClass: { type: String, default: '' },
|
readOnly: { type: Boolean, default: false },
|
||||||
autoFocus: { type: Boolean, default: false },
|
showLineNumbers: { type: Boolean, default: true },
|
||||||
readOnly: { type: Boolean, default: false },
|
height: { type: Number, default: 200 }
|
||||||
showLineNumbers: { type: Boolean, default: true },
|
});
|
||||||
height: { type: Number, default: 200 }
|
const emit = defineEmits(['update:modelValue']);
|
||||||
},
|
const settingsStore = useSettingsStore();
|
||||||
emits: ['update:modelValue'],
|
|
||||||
setup () {
|
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
editorTheme,
|
editorTheme,
|
||||||
editorFontSize,
|
editorFontSize,
|
||||||
autoComplete,
|
autoComplete,
|
||||||
lineWrap
|
lineWrap
|
||||||
} = storeToRefs(settingsStore);
|
} = storeToRefs(settingsStore);
|
||||||
|
|
||||||
return {
|
let editor: ace.Ace.Editor;
|
||||||
editorTheme,
|
const id = uidGen();
|
||||||
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'
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.editor) {
|
watch(() => props.mode, () => {
|
||||||
this.editor.setOptions({
|
if (editor)
|
||||||
fontSize: sizes[this.editorFontSize]
|
editor.session.setMode(`ace/mode/${props.mode}`);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
},
|
watch(editorTheme, () => {
|
||||||
autoComplete () {
|
if (editor)
|
||||||
if (this.editor) {
|
editor.setTheme(`ace/theme/${editorTheme.value}`);
|
||||||
this.editor.setOptions({
|
});
|
||||||
enableLiveAutocompletion: this.autoComplete
|
|
||||||
});
|
watch(editorFontSize, () => {
|
||||||
}
|
const sizes = {
|
||||||
},
|
small: 12,
|
||||||
lineWrap () {
|
medium: 14,
|
||||||
if (this.editor) {
|
large: 16
|
||||||
this.editor.setOptions({
|
};
|
||||||
wrap: this.lineWrap
|
|
||||||
});
|
if (editor) {
|
||||||
}
|
editor.setOptions({
|
||||||
}
|
fontSize: sizes[editorFontSize.value as undefined as 'small' | 'medium' | 'large']
|
||||||
},
|
|
||||||
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
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.editor.setOptions({
|
watch(autoComplete, () => {
|
||||||
enableBasicAutocompletion: false,
|
if (editor) {
|
||||||
wrap: this.lineWrap,
|
editor.setOptions({
|
||||||
enableSnippets: false,
|
enableLiveAutocompletion: autoComplete.value
|
||||||
enableLiveAutocompletion: false
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.editor.session.on('change', () => {
|
watch(lineWrap, () => {
|
||||||
const content = this.editor.getValue();
|
if (editor) {
|
||||||
this.$emit('update:modelValue', content);
|
editor.setOptions({
|
||||||
|
wrap: lineWrap.value
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (this.autoFocus) {
|
onMounted(() => {
|
||||||
setTimeout(() => {
|
editor = ace.edit(`editor-${id}`, {
|
||||||
this.editor.focus();
|
mode: `ace/mode/${props.mode}`,
|
||||||
this.editor.resize();
|
theme: `ace/theme/${editorTheme.value}`,
|
||||||
}, 20);
|
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(() => {
|
setTimeout(() => {
|
||||||
this.editor.resize();
|
editor.focus();
|
||||||
|
editor.resize();
|
||||||
}, 20);
|
}, 20);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
setTimeout(() => {
|
||||||
|
editor.resize();
|
||||||
|
}, 20);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -9,67 +9,63 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
export default {
|
import { computed, ref, watch } from 'vue';
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { className, iconName };
|
const props = defineProps({
|
||||||
}
|
message: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
},
|
},
|
||||||
watch: {
|
status: {
|
||||||
message: function () {
|
type: String,
|
||||||
if (this.message)
|
default: ''
|
||||||
this.isVisible = true;
|
|
||||||
else
|
|
||||||
this.isVisible = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
hideToast () {
|
|
||||||
this.isVisible = false;
|
|
||||||
this.$emit('close');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.toast {
|
.toast {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
{{ lastPart(modelValue) }}
|
{{ lastPart(modelValue) }}
|
||||||
</span>
|
</span>
|
||||||
<i
|
<i
|
||||||
v-if="modelValue.length"
|
v-if="modelValue"
|
||||||
class="file-uploader-reset mdi mdi-close"
|
class="file-uploader-reset mdi mdi-close"
|
||||||
@click.prevent="clear"
|
@click.prevent="clear"
|
||||||
/>
|
/>
|
||||||
|
@ -22,40 +22,35 @@
|
||||||
</label>
|
</label>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { uidGen } from 'common/libs/uidGen';
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
|
|
||||||
export default {
|
defineProps({
|
||||||
name: 'BaseUploadInput',
|
message: {
|
||||||
props: {
|
default: 'Browse',
|
||||||
message: {
|
type: String
|
||||||
default: 'Browse',
|
|
||||||
type: String
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
default: '',
|
|
||||||
type: String
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
emits: ['change', 'clear'],
|
modelValue: {
|
||||||
data () {
|
default: '',
|
||||||
return {
|
type: String
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="vscroll-holder">
|
<div ref="root" class="vscroll-holder">
|
||||||
<div
|
<div
|
||||||
class="vscroll-spacer"
|
class="vscroll-spacer"
|
||||||
:style="{
|
:style="{
|
||||||
|
@ -20,71 +20,76 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
export default {
|
import { onBeforeUnmount, onMounted, Ref, ref, watch } from 'vue';
|
||||||
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);
|
|
||||||
|
|
||||||
this.renderTimeout = setTimeout(() => {
|
const props = defineProps({
|
||||||
this.updateWindow(e);
|
items: Array,
|
||||||
}, 200);
|
itemHeight: Number,
|
||||||
},
|
visibleHeight: Number,
|
||||||
updateWindow () {
|
scrollElement: {
|
||||||
const visibleItemsCount = Math.ceil(this.visibleHeight / this.itemHeight);
|
type: HTMLDivElement,
|
||||||
const totalScrollHeight = this.items.length * this.itemHeight;
|
default: null
|
||||||
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 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>
|
</script>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
v-model="selectedGroup"
|
v-model="selectedGroup"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
:options="[{name: 'manual'}, ...fakerGroups]"
|
: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"
|
option-track-by="name"
|
||||||
:disabled="!isChecked"
|
:disabled="!isChecked"
|
||||||
style="flex-grow: 0;"
|
style="flex-grow: 0;"
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
v-if="selectedGroup !== 'manual'"
|
v-if="selectedGroup !== 'manual'"
|
||||||
v-model="selectedMethod"
|
v-model="selectedMethod"
|
||||||
:options="fakerMethods"
|
:options="fakerMethods"
|
||||||
:option-label="(opt) => $t(`faker.${opt.name}`)"
|
:option-label="(opt: any) => $t(`faker.${opt.name}`)"
|
||||||
option-track-by="name"
|
option-track-by="name"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
:disabled="!isChecked"
|
:disabled="!isChecked"
|
||||||
|
@ -85,153 +85,149 @@
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</template>
|
</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 { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
|
||||||
import BaseUploadInput from '@/components/BaseUploadInput';
|
import BaseUploadInput from '@/components/BaseUploadInput.vue';
|
||||||
import ForeignKeySelect from '@/components/ForeignKeySelect';
|
import ForeignKeySelect from '@/components/ForeignKeySelect.vue';
|
||||||
import FakerMethods from 'common/FakerMethods';
|
import FakerMethods from 'common/FakerMethods';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'FakerSelect',
|
type: String,
|
||||||
components: {
|
field: Object,
|
||||||
ForeignKeySelect,
|
isChecked: Boolean,
|
||||||
BaseUploadInput,
|
foreignKeys: Array,
|
||||||
BaseSelect
|
keyUsage: Array as PropType<{field: string}[]>,
|
||||||
},
|
fieldLength: Number,
|
||||||
props: {
|
fieldObj: Object
|
||||||
type: String,
|
});
|
||||||
field: Object,
|
const emit = defineEmits(['update:modelValue']);
|
||||||
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';
|
|
||||||
|
|
||||||
return FakerMethods.getGroupsByType(this.localType);
|
const localType: Ref<string> = ref(null);
|
||||||
},
|
const selectedGroup: Ref<string> = ref('manual');
|
||||||
fakerMethods () {
|
const selectedMethod: Ref<string> = ref('');
|
||||||
return FakerMethods.getMethods({ type: this.localType, group: this.selectedGroup });
|
const selectedValue: Ref<string> = ref('');
|
||||||
},
|
const debounceTimeout: Ref<NodeJS.Timeout> = ref(null);
|
||||||
methodData () {
|
const methodParams: Ref<{[key: string]: string}> = ref({});
|
||||||
return this.fakerMethods.find(method => method.name === this.selectedMethod);
|
const enumArray: Ref<string[]> = ref(null);
|
||||||
}
|
|
||||||
},
|
|
||||||
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 };
|
|
||||||
|
|
||||||
if ([...NUMBER, ...FLOAT].includes(this.type))
|
const fakerGroups = computed(() => {
|
||||||
return { type: 'number', mask: false };
|
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)) {
|
return FakerMethods.getGroupsByType(localType.value);
|
||||||
let timeMask = '##:##:##';
|
});
|
||||||
const precision = this.fieldLength;
|
|
||||||
|
|
||||||
for (let i = 0; i < precision; i++)
|
const fakerMethods = computed(() => {
|
||||||
timeMask += i === 0 ? '.#' : '#';
|
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))
|
const inputProps = () => {
|
||||||
return { type: 'text', mask: '####-##-##' };
|
if ([...TEXT, ...LONG_TEXT].includes(props.type))
|
||||||
|
return { type: 'text', mask: false };
|
||||||
|
|
||||||
if (DATETIME.includes(this.type)) {
|
if ([...NUMBER, ...FLOAT].includes(props.type))
|
||||||
let datetimeMask = '####-##-## ##:##:##';
|
return { type: 'number', mask: false };
|
||||||
const precision = this.fieldLength;
|
|
||||||
|
|
||||||
for (let i = 0; i < precision; i++)
|
if (TIME.includes(props.type)) {
|
||||||
datetimeMask += i === 0 ? '.#' : '#';
|
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: 'text', mask: timeMask };
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
|
@ -8,107 +8,100 @@
|
||||||
dropdown-class="select-sm"
|
dropdown-class="select-sm"
|
||||||
dropdown-container=".workspace-query-results > .vscroll"
|
dropdown-container=".workspace-query-results > .vscroll"
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
@blur="$emit('blur')"
|
@blur="emit('blur')"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { computed, Ref, ref } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import Tables from '@/ipc-api/Tables';
|
import Tables from '@/ipc-api/Tables';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import { TEXT, LONG_TEXT } from 'common/fieldTypes';
|
import { TEXT, LONG_TEXT } from 'common/fieldTypes';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
import { TableField } from 'common/interfaces/antares';
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'ForeignKeySelect',
|
modelValue: [String, Number],
|
||||||
components: { BaseSelect },
|
keyUsage: Object,
|
||||||
props: {
|
size: {
|
||||||
modelValue: [String, Number],
|
type: String,
|
||||||
keyUsage: Object,
|
default: ''
|
||||||
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 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>
|
</script>
|
||||||
|
|
|
@ -55,30 +55,25 @@
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
export default {
|
import { Ref, ref } from 'vue';
|
||||||
name: 'ModalAskCredentials',
|
|
||||||
emits: ['close-asking', 'credentials'],
|
const credentials = ref({
|
||||||
data () {
|
user: '',
|
||||||
return {
|
password: ''
|
||||||
credentials: {
|
});
|
||||||
user: '',
|
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||||
password: ''
|
const emit = defineEmits(['close-asking', 'credentials']);
|
||||||
}
|
|
||||||
};
|
const closeModal = () => {
|
||||||
},
|
emit('close-asking');
|
||||||
created () {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.$refs.firstInput.focus();
|
|
||||||
}, 20);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
closeModal () {
|
|
||||||
this.$emit('close-asking');
|
|
||||||
},
|
|
||||||
sendCredentials () {
|
|
||||||
this.$emit('credentials', this.credentials);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sendCredentials = () => {
|
||||||
|
emit('credentials', credentials.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
firstInput.value.focus();
|
||||||
|
}, 20);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -47,83 +47,75 @@
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { computed, PropType, Ref, ref } from 'vue';
|
||||||
import { NUMBER, FLOAT } from 'common/fieldTypes';
|
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 {
|
const props = defineProps({
|
||||||
name: 'ModalAskParameters',
|
localRoutine: Object as PropType<RoutineInfos | FunctionInfos>,
|
||||||
components: {
|
client: String
|
||||||
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);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
const emit = defineEmits(['confirm', 'close']);
|
||||||
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 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}`;
|
const inParameters = computed(() => {
|
||||||
acc.push(value);
|
return props.localRoutine.parameters.filter(param => param.context === 'IN');
|
||||||
return acc;
|
});
|
||||||
}, []);
|
|
||||||
this.$emit('confirm', valArr);
|
const typeClass = (type: string) => {
|
||||||
},
|
if (type)
|
||||||
closeModal () {
|
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
|
||||||
this.$emit('close');
|
return '';
|
||||||
},
|
|
||||||
onKey (e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (e.key === 'Escape')
|
|
||||||
this.closeModal();
|
|
||||||
},
|
|
||||||
wrapNumber (num) {
|
|
||||||
if (!num) return '';
|
|
||||||
return `(${num})`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
:confirm-text="$t('word.discard')"
|
:confirm-text="$t('word.discard')"
|
||||||
:cancel-text="$t('word.stay')"
|
:cancel-text="$t('word.stay')"
|
||||||
@confirm="$emit('confirm')"
|
@confirm="emit('confirm')"
|
||||||
@hide="$emit('close')"
|
@hide="emit('close')"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
|
@ -18,29 +18,23 @@
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||||
|
import { onBeforeUnmount } from 'vue';
|
||||||
|
|
||||||
export default {
|
const emit = defineEmits(['confirm', 'close']);
|
||||||
name: 'ModalDiscardChanges',
|
|
||||||
components: {
|
const onKey = (e: KeyboardEvent) => {
|
||||||
ConfirmModal
|
e.stopPropagation();
|
||||||
},
|
if (e.key === 'Escape')
|
||||||
emits: ['confirm', 'close'],
|
emit('close');
|
||||||
created () {
|
|
||||||
window.addEventListener('keydown', this.onKey);
|
|
||||||
},
|
|
||||||
beforeUnmount () {
|
|
||||||
window.removeEventListener('keydown', this.onKey);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onKey (e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (e.key === 'Escape')
|
|
||||||
this.closeModal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.addEventListener('keydown', onKey);
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('keydown', onKey);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -62,116 +62,99 @@
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { computed, onBeforeUnmount, Ref, ref } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import Schema from '@/ipc-api/Schema';
|
import Schema from '@/ipc-api/Schema';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'ModalEditSchema',
|
selectedSchema: String
|
||||||
components: {
|
});
|
||||||
BaseSelect
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
selectedSchema: String
|
|
||||||
},
|
|
||||||
emits: ['close'],
|
|
||||||
setup () {
|
|
||||||
const { addNotification } = useNotificationsStore();
|
|
||||||
const workspacesStore = useWorkspacesStore();
|
|
||||||
|
|
||||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const emit = defineEmits(['close']);
|
||||||
|
|
||||||
const { getWorkspace, getDatabaseVariable } = workspacesStore;
|
const { addNotification } = useNotificationsStore();
|
||||||
|
const workspacesStore = useWorkspacesStore();
|
||||||
|
|
||||||
return {
|
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||||
addNotification,
|
|
||||||
selectedWorkspace,
|
const { getWorkspace, getDatabaseVariable } = workspacesStore;
|
||||||
getWorkspace,
|
|
||||||
getDatabaseVariable
|
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||||
};
|
const database = ref({
|
||||||
},
|
name: '',
|
||||||
data () {
|
prevName: '',
|
||||||
return {
|
collation: '',
|
||||||
database: {
|
prevCollation: null
|
||||||
name: '',
|
});
|
||||||
prevName: '',
|
|
||||||
collation: ''
|
const collations = computed(() => getWorkspace(selectedWorkspace.value).collations);
|
||||||
}
|
const defaultCollation = computed(() => (getDatabaseVariable(selectedWorkspace.value, 'collation_server').value || ''));
|
||||||
};
|
|
||||||
},
|
const updateSchema = async () => {
|
||||||
computed: {
|
if (database.value.collation !== database.value.prevCollation) {
|
||||||
collations () {
|
|
||||||
return this.getWorkspace(this.selectedWorkspace).collations;
|
|
||||||
},
|
|
||||||
defaultCollation () {
|
|
||||||
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value || '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async created () {
|
|
||||||
let actualCollation;
|
|
||||||
try {
|
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')
|
if (status === 'success')
|
||||||
actualCollation = response;
|
closeModal();
|
||||||
|
|
||||||
else
|
else
|
||||||
this.addNotification({ status: 'error', message: response });
|
addNotification({ status: 'error', message: response });
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
this.addNotification({ status: 'error', message: err.stack });
|
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -146,7 +146,7 @@
|
||||||
<div class="tbody">
|
<div class="tbody">
|
||||||
<div
|
<div
|
||||||
v-for="item in tables"
|
v-for="item in tables"
|
||||||
:key="item.name"
|
:key="item.table"
|
||||||
class="tr"
|
class="tr"
|
||||||
>
|
>
|
||||||
<div class="td">
|
<div class="td">
|
||||||
|
@ -193,7 +193,7 @@
|
||||||
>
|
>
|
||||||
<input v-model="options.includes[key]" type="checkbox"><i class="form-icon" /> {{ $tc(`word.${key}`, 2) }}
|
<input v-model="options.includes[key]" type="checkbox"><i class="form-icon" /> {{ $tc(`word.${key}`, 2) }}
|
||||||
</label>
|
</label>
|
||||||
<div v-if="customizations.exportByChunks">
|
<div v-if="clientCustoms.exportByChunks">
|
||||||
<div class="h6 mt-4 mb-2">
|
<div class="h6 mt-4 mb-2">
|
||||||
{{ $t('message.newInserStmtEvery') }}:
|
{{ $t('message.newInserStmtEvery') }}:
|
||||||
</div>
|
</div>
|
||||||
|
@ -263,211 +263,204 @@
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import moment from 'moment';
|
import { computed, onBeforeUnmount, Ref, ref } from 'vue';
|
||||||
|
import * as moment from 'moment';
|
||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
import { storeToRefs } from 'pinia';
|
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 { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import customizations from 'common/customizations';
|
|
||||||
import Application from '@/ipc-api/Application';
|
import Application from '@/ipc-api/Application';
|
||||||
import Schema from '@/ipc-api/Schema';
|
import Schema from '@/ipc-api/Schema';
|
||||||
|
import { Customizations } from 'common/interfaces/customizations';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'ModalExportSchema',
|
selectedSchema: String
|
||||||
components: {
|
});
|
||||||
BaseSelect
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
selectedSchema: String
|
|
||||||
},
|
|
||||||
emits: ['close'],
|
|
||||||
setup () {
|
|
||||||
const { addNotification } = useNotificationsStore();
|
|
||||||
const workspacesStore = useWorkspacesStore();
|
|
||||||
|
|
||||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const emit = defineEmits(['close']);
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const {
|
const { addNotification } = useNotificationsStore();
|
||||||
getWorkspace,
|
const workspacesStore = useWorkspacesStore();
|
||||||
getDatabaseVariable,
|
|
||||||
refreshSchema
|
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||||
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');
|
|
||||||
|
|
||||||
return [];
|
const {
|
||||||
},
|
getWorkspace,
|
||||||
filename () {
|
refreshSchema
|
||||||
const date = moment().format('YYYY-MM-DD');
|
} = workspacesStore;
|
||||||
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();
|
|
||||||
|
|
||||||
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();
|
const currentWorkspace = computed(() => getWorkspace(selectedWorkspace.value));
|
||||||
this.tables = this.schemaItems.map(item => ({
|
const clientCustoms: Ref<Customizations> = computed(() => currentWorkspace.value.customizations);
|
||||||
table: item.name,
|
const schemaItems = computed(() => {
|
||||||
includeStructure: true,
|
const db: SchemaInfos = currentWorkspace.value.structure.find((db: SchemaInfos) => db.name === props.selectedSchema);
|
||||||
includeContent: true,
|
if (db)
|
||||||
includeDropStatement: true
|
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 startExport = async () => {
|
||||||
const val = customizations[this.currentWorkspace.client][feat];
|
isExporting.value = true;
|
||||||
if (val)
|
const { uid, client } = currentWorkspace.value;
|
||||||
this.options.includes[feat] = true;
|
const params = {
|
||||||
});
|
uid,
|
||||||
|
type: client,
|
||||||
|
schema: props.selectedSchema,
|
||||||
|
outputFile: dumpFilePath.value,
|
||||||
|
tables: [...tables.value],
|
||||||
|
...options.value
|
||||||
|
};
|
||||||
|
|
||||||
ipcRenderer.on('export-progress', this.updateProgress);
|
try {
|
||||||
},
|
const { status, response } = await Schema.export(params);
|
||||||
beforeUnmount () {
|
if (status === 'success')
|
||||||
window.removeEventListener('keydown', this.onKey);
|
progressStatus.value = response.cancelled ? t('word.aborted') : t('word.completed');
|
||||||
ipcRenderer.off('export-progress', this.updateProgress);
|
else {
|
||||||
},
|
progressStatus.value = response;
|
||||||
methods: {
|
addNotification({ status: 'error', message: response });
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -476,14 +469,15 @@ export default {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-query-results {
|
.workspace-query-results {
|
||||||
flex: 1 0 1px;
|
flex: 1 0 1px;
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
@ -499,25 +493,24 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
|
|
||||||
.modal-container {
|
.modal-container {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-body {
|
.modal-body {
|
||||||
max-height: 60vh;
|
max-height: 60vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-footer {
|
.modal-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-status {
|
.progress-status {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -97,251 +97,228 @@
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import moment from 'moment';
|
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 { storeToRefs } from 'pinia';
|
||||||
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
|
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import Tables from '@/ipc-api/Tables';
|
import Tables from '@/ipc-api/Tables';
|
||||||
import FakerSelect from '@/components/FakerSelect';
|
import FakerSelect from '@/components/FakerSelect.vue';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'ModalFakerRows',
|
tabUid: [String, Number],
|
||||||
components: {
|
fields: Array as Prop<TableField[]>,
|
||||||
FakerSelect,
|
keyUsage: Array as Prop<TableForeign[]>
|
||||||
BaseSelect
|
});
|
||||||
},
|
|
||||||
props: {
|
|
||||||
tabUid: [String, Number],
|
|
||||||
fields: Array,
|
|
||||||
keyUsage: Array
|
|
||||||
},
|
|
||||||
emits: ['reload', 'hide'],
|
|
||||||
setup () {
|
|
||||||
const { addNotification } = useNotificationsStore();
|
|
||||||
const workspacesStore = useWorkspacesStore();
|
|
||||||
|
|
||||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const emit = defineEmits(['reload', 'hide']);
|
||||||
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 { addNotification } = useNotificationsStore();
|
||||||
|
const workspacesStore = useWorkspacesStore();
|
||||||
|
|
||||||
return {
|
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||||
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 = {};
|
|
||||||
|
|
||||||
for (const field of this.fields) {
|
const { getWorkspace } = workspacesStore;
|
||||||
let fieldDefault;
|
|
||||||
|
|
||||||
if (field.default === 'NULL') fieldDefault = null;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
else {
|
const localRow: Ref<{[key: string]: any}> = ref({});
|
||||||
if ([...NUMBER, ...FLOAT].includes(field.type))
|
const fieldsToExclude = ref([]);
|
||||||
fieldDefault = !field.default || Number.isNaN(+field.default.replaceAll('\'', '')) ? null : +field.default.replaceAll('\'', '');
|
const nInserts = ref(1);
|
||||||
else if ([...TEXT, ...LONG_TEXT].includes(field.type)) {
|
const isInserting = ref(false);
|
||||||
fieldDefault = field.default
|
const fakerLocale = ref('en');
|
||||||
? 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 };
|
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
|
const locales = [
|
||||||
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
|
{ 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 => {
|
watch(nInserts, (val) => {
|
||||||
if (this.fieldsToExclude.includes(key))
|
if (!val || val < 1)
|
||||||
delete rowToInsert[key];
|
nInserts.value = 1;
|
||||||
|
else if (val > 1000)
|
||||||
|
nInserts.value = 1000;
|
||||||
|
});
|
||||||
|
|
||||||
if (typeof rowToInsert[key] === 'undefined')
|
const typeClass = (type: string) => {
|
||||||
delete rowToInsert[key];
|
if (type)
|
||||||
});
|
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
|
||||||
|
return '';
|
||||||
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 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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<div class="modal-title h6">
|
<div class="modal-title h6">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-history mr-1" />
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
v-model="searchTerm"
|
v-model="searchTerm"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
type="text"
|
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 v-if="!searchTerm" class="form-icon mdi mdi-magnify mdi-18px pr-4" />
|
||||||
<i
|
<i
|
||||||
|
@ -67,13 +67,13 @@
|
||||||
<small class="tile-subtitle">{{ query.schema }} · {{ formatDate(query.date) }}</small>
|
<small class="tile-subtitle">{{ query.schema }} · {{ formatDate(query.date) }}</small>
|
||||||
<div class="tile-history-buttons">
|
<div class="tile-history-buttons">
|
||||||
<button class="btn btn-link pl-1" @click.stop="$emit('select-query', query.sql)">
|
<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>
|
||||||
<button class="btn btn-link pl-1" @click="copyQuery(query.sql)">
|
<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>
|
||||||
<button class="btn btn-link pl-1" @click="deleteQuery(query)">
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -88,7 +88,7 @@
|
||||||
<i class="mdi mdi-history mdi-48px" />
|
<i class="mdi mdi-history mdi-48px" />
|
||||||
</div>
|
</div>
|
||||||
<p class="empty-title h5">
|
<p class="empty-title h5">
|
||||||
{{ $t('message.thereIsNoQueriesYet') }}
|
{{ t('message.thereIsNoQueriesYet') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -97,129 +97,111 @@
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import moment from 'moment';
|
import { Component, computed, ComputedRef, onBeforeUnmount, onMounted, onUpdated, Prop, Ref, ref, watch } from 'vue';
|
||||||
import { useHistoryStore } from '@/stores/history';
|
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 { useConnectionsStore } from '@/stores/connections';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue';
|
||||||
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
|
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
name: 'ModalHistory',
|
|
||||||
components: {
|
|
||||||
BaseVirtualScroll
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
connection: Object
|
|
||||||
},
|
|
||||||
emits: ['select-query', 'close'],
|
|
||||||
setup () {
|
|
||||||
const { getHistoryByWorkspace, deleteQueryFromHistory } = useHistoryStore();
|
|
||||||
const { getConnectionName } = useConnectionsStore();
|
|
||||||
const { addNotification } = useNotificationsStore();
|
|
||||||
|
|
||||||
return {
|
const { getHistoryByWorkspace, deleteQueryFromHistory } = useHistoryStore();
|
||||||
getHistoryByWorkspace,
|
const { getConnectionName } = useConnectionsStore();
|
||||||
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);
|
|
||||||
|
|
||||||
this.searchTermInterval = setTimeout(() => {
|
const props = defineProps({
|
||||||
this.localSearchTerm = this.searchTerm;
|
connection: Object as Prop<ConnectionParams>
|
||||||
}, 200);
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
window.addEventListener('keydown', this.onKey, { capture: true });
|
|
||||||
},
|
|
||||||
updated () {
|
|
||||||
if (this.$refs.table)
|
|
||||||
this.refreshScroller();
|
|
||||||
|
|
||||||
if (this.$refs.tableWrapper)
|
const emit = defineEmits(['select-query', 'close']);
|
||||||
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;
|
|
||||||
|
|
||||||
if (el)
|
const table: Ref<HTMLDivElement> = ref(null);
|
||||||
this.resultsSize = el.offsetHeight - this.$refs.searchForm.offsetHeight;
|
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();
|
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));
|
||||||
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('>', '>');
|
|
||||||
|
|
||||||
if (this.searchTerm) {
|
watch(searchTerm, () => {
|
||||||
const regexp = new RegExp(`(${this.searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
clearTimeout(searchTermInterval.value);
|
||||||
return string.replace(regexp, '<span class="text-primary text-bold">$1</span>');
|
|
||||||
}
|
searchTermInterval.value = setTimeout(() => {
|
||||||
else
|
localSearchTerm.value = searchTerm.value;
|
||||||
return string;
|
}, 200);
|
||||||
},
|
});
|
||||||
onKey (e) {
|
|
||||||
e.stopPropagation();
|
const copyQuery = (sql: string) => {
|
||||||
if (e.key === 'Escape')
|
navigator.clipboard.writeText(sql);
|
||||||
this.closeModal();
|
};
|
||||||
}
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -49,146 +49,140 @@
|
||||||
</teleport>
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { computed, onBeforeUnmount, Ref, ref } from 'vue';
|
||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import moment from 'moment';
|
|
||||||
import Schema from '@/ipc-api/Schema';
|
import Schema from '@/ipc-api/Schema';
|
||||||
import { storeToRefs } from 'pinia';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { ImportState } from 'common/interfaces/importer';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
name: 'ModalImportSchema',
|
|
||||||
|
|
||||||
props: {
|
const { addNotification } = useNotificationsStore();
|
||||||
selectedSchema: String
|
const workspacesStore = useWorkspacesStore();
|
||||||
},
|
|
||||||
emits: ['close'],
|
|
||||||
setup () {
|
|
||||||
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 {
|
const props = defineProps({
|
||||||
addNotification,
|
selectedSchema: String
|
||||||
selectedWorkspace,
|
});
|
||||||
getWorkspace,
|
|
||||||
refreshSchema
|
const emit = defineEmits(['close']);
|
||||||
};
|
|
||||||
},
|
const sqlFile = ref('');
|
||||||
data () {
|
const isImporting = ref(false);
|
||||||
return {
|
const progressPercentage = ref(0);
|
||||||
sqlFile: '',
|
const queryCount = ref(0);
|
||||||
isImporting: false,
|
const completed = ref(false);
|
||||||
progressPercentage: 0,
|
const progressStatus = ref('Reading');
|
||||||
queryCount: 0,
|
const queryErrors: Ref<{time: string; message: string}[]> = ref([]);
|
||||||
completed: false,
|
|
||||||
progressStatus: 'Reading',
|
const currentWorkspace = computed(() => getWorkspace(selectedWorkspace.value));
|
||||||
queryErrors: []
|
|
||||||
};
|
const formattedQueryErrors = computed(() => {
|
||||||
},
|
return queryErrors.value.map(err =>
|
||||||
computed: {
|
`Time: ${moment(err.time).format('HH:mm:ss.S')} (${err.time})\nError: ${err.message}`
|
||||||
currentWorkspace () {
|
).join('\n\n');
|
||||||
return this.getWorkspace(this.selectedWorkspace);
|
});
|
||||||
},
|
|
||||||
formattedQueryErrors () {
|
const startImport = async (file: string) => {
|
||||||
return this.queryErrors.map(err =>
|
isImporting.value = true;
|
||||||
`Time: ${moment(err.time).format('HH:mm:ss.S')} (${err.time})\nError: ${err.message}`
|
sqlFile.value = file;
|
||||||
).join('\n\n');
|
|
||||||
}
|
const { uid, client } = currentWorkspace.value;
|
||||||
},
|
const params = {
|
||||||
async created () {
|
uid,
|
||||||
window.addEventListener('keydown', this.onKey);
|
type: client,
|
||||||
|
schema: props.selectedSchema,
|
||||||
ipcRenderer.on('import-progress', this.updateProgress);
|
file: sqlFile.value
|
||||||
ipcRenderer.on('query-error', this.handleQueryError);
|
};
|
||||||
},
|
|
||||||
beforeUnmount () {
|
try {
|
||||||
window.removeEventListener('keydown', this.onKey);
|
completed.value = false;
|
||||||
ipcRenderer.off('import-progress', this.updateProgress);
|
const { status, response } = await Schema.import(params);
|
||||||
ipcRenderer.off('query-error', this.handleQueryError);
|
|
||||||
},
|
if (status === 'success')
|
||||||
methods: {
|
progressStatus.value = response.cancelled ? t('word.aborted') : t('word.completed');
|
||||||
async startImport (sqlFile) {
|
else {
|
||||||
this.isImporting = true;
|
progressStatus.value = response;
|
||||||
this.sqlFile = sqlFile;
|
addNotification({ status: 'error', message: response });
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.modal {
|
.modal {
|
||||||
|
.modal-container {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-container {
|
.modal-body {
|
||||||
max-width: 800px;
|
max-height: 60vh;
|
||||||
}
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-body {
|
.modal-footer {
|
||||||
max-height: 60vh;
|
display: flex;
|
||||||
display: flex;
|
}
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-footer {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-status {
|
.progress-status {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -65,93 +65,75 @@
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { computed, onBeforeUnmount, Ref, ref } from 'vue';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import Schema from '@/ipc-api/Schema';
|
import Schema from '@/ipc-api/Schema';
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
|
||||||
export default {
|
const { addNotification } = useNotificationsStore();
|
||||||
name: 'ModalNewSchema',
|
const workspacesStore = useWorkspacesStore();
|
||||||
components: { BaseSelect },
|
|
||||||
emits: ['reload', 'close'],
|
|
||||||
setup () {
|
|
||||||
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 {
|
const emit = defineEmits(['reload', 'close']);
|
||||||
addNotification,
|
|
||||||
selectedWorkspace,
|
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||||
getWorkspace,
|
const isLoading = ref(false);
|
||||||
getDatabaseVariable
|
const database = ref({
|
||||||
};
|
name: '',
|
||||||
},
|
collation: ''
|
||||||
data () {
|
});
|
||||||
return {
|
|
||||||
isLoading: false,
|
const collations = computed(() => getWorkspace(selectedWorkspace.value).collations);
|
||||||
database: {
|
const customizations = computed(() => getWorkspace(selectedWorkspace.value).customizations);
|
||||||
name: '',
|
const defaultCollation = computed(() => getDatabaseVariable(selectedWorkspace.value, 'collation_server') ? getDatabaseVariable(selectedWorkspace.value, 'collation_server').value : '');
|
||||||
collation: ''
|
|
||||||
}
|
database.value = { ...database.value, collation: defaultCollation.value };
|
||||||
};
|
|
||||||
},
|
const createSchema = async () => {
|
||||||
computed: {
|
isLoading.value = true;
|
||||||
collations () {
|
try {
|
||||||
return this.getWorkspace(this.selectedWorkspace).collations;
|
const { status, response } = await Schema.createSchema({
|
||||||
},
|
uid: selectedWorkspace.value,
|
||||||
customizations () {
|
...database.value
|
||||||
return this.getWorkspace(this.selectedWorkspace).customizations;
|
});
|
||||||
},
|
|
||||||
defaultCollation () {
|
if (status === 'success') {
|
||||||
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server') ? this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value : '';
|
closeModal();
|
||||||
}
|
emit('reload');
|
||||||
},
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<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>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import arrayToFile from '../libs/arrayToFile';
|
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 { useNotificationsStore } from '@/stores/notifications';
|
||||||
import Schema from '@/ipc-api/Schema';
|
import Schema from '@/ipc-api/Schema';
|
||||||
import { useConnectionsStore } from '@/stores/connections';
|
import { useConnectionsStore } from '@/stores/connections';
|
||||||
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
|
import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue';
|
||||||
import ModalProcessesListRow from '@/components/ModalProcessesListRow';
|
import ModalProcessesListRow from '@/components/ModalProcessesListRow.vue';
|
||||||
import ModalProcessesListContext from '@/components/ModalProcessesListContext';
|
import ModalProcessesListContext from '@/components/ModalProcessesListContext.vue';
|
||||||
|
|
||||||
export default {
|
const { addNotification } = useNotificationsStore();
|
||||||
name: 'ModalProcessesList',
|
const { getConnectionName } = useConnectionsStore();
|
||||||
components: {
|
|
||||||
BaseVirtualScroll,
|
|
||||||
ModalProcessesListRow,
|
|
||||||
ModalProcessesListContext
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
connection: Object
|
|
||||||
},
|
|
||||||
emits: ['close'],
|
|
||||||
setup () {
|
|
||||||
const { addNotification } = useNotificationsStore();
|
|
||||||
const { getConnectionName } = useConnectionsStore();
|
|
||||||
|
|
||||||
return { addNotification, getConnectionName };
|
const props = defineProps({
|
||||||
},
|
connection: Object as Prop<ConnectionParams>
|
||||||
data () {
|
});
|
||||||
return {
|
|
||||||
resultsSize: 1000,
|
const emit = defineEmits(['close']);
|
||||||
isQuering: false,
|
|
||||||
isContext: false,
|
const tableWrapper: Ref<HTMLDivElement> = ref(null);
|
||||||
autorefreshTimer: 0,
|
const table: Ref<HTMLDivElement> = ref(null);
|
||||||
refreshInterval: null,
|
const resultTable: Ref<Component & {updateWindow: () => void}> = ref(null);
|
||||||
contextEvent: null,
|
const resultsSize = ref(1000);
|
||||||
selectedCell: null,
|
const isQuering = ref(false);
|
||||||
selectedRow: null,
|
const isContext = ref(false);
|
||||||
results: [],
|
const autorefreshTimer = ref(0);
|
||||||
fields: [],
|
const refreshInterval: Ref<NodeJS.Timeout> = ref(null);
|
||||||
currentSort: '',
|
const contextEvent = ref(null);
|
||||||
currentSortDir: 'asc',
|
const selectedCell = ref(null);
|
||||||
scrollElement: null
|
const selectedRow: Ref<number> = ref(null);
|
||||||
};
|
const results = ref([]);
|
||||||
},
|
const fields = ref([]);
|
||||||
computed: {
|
const currentSort = ref('');
|
||||||
connectionName () {
|
const currentSortDir = ref('asc');
|
||||||
return this.getConnectionName(this.connection.uid);
|
const scrollElement = ref(null);
|
||||||
},
|
|
||||||
sortedResults () {
|
const connectionName = computed(() => getConnectionName(props.connection.uid));
|
||||||
if (this.currentSort) {
|
|
||||||
return [...this.results].sort((a, b) => {
|
const sortedResults = computed(() => {
|
||||||
let modifier = 1;
|
if (currentSort.value) {
|
||||||
const valA = typeof a[this.currentSort] === 'string' ? a[this.currentSort].toLowerCase() : a[this.currentSort];
|
return [...results.value].sort((a, b) => {
|
||||||
const valB = typeof b[this.currentSort] === 'string' ? b[this.currentSort].toLowerCase() : b[this.currentSort];
|
let modifier = 1;
|
||||||
if (this.currentSortDir === 'desc') modifier = -1;
|
const valA = typeof a[currentSort.value] === 'string' ? a[currentSort.value].toLowerCase() : a[currentSort.value];
|
||||||
if (valA < valB) return -1 * modifier;
|
const valB = typeof b[currentSort.value] === 'string' ? b[currentSort.value].toLowerCase() : b[currentSort.value];
|
||||||
if (valA > valB) return 1 * modifier;
|
if (currentSortDir.value === 'desc') modifier = -1;
|
||||||
return 0;
|
if (valA < valB) return -1 * modifier;
|
||||||
});
|
if (valA > valB) return 1 * modifier;
|
||||||
}
|
return 0;
|
||||||
else
|
});
|
||||||
return this.results;
|
}
|
||||||
|
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]) : [];
|
||||||
}
|
}
|
||||||
},
|
else
|
||||||
created () {
|
addNotification({ status: 'error', message: response });
|
||||||
window.addEventListener('keydown', this.onKey, { capture: true });
|
}
|
||||||
},
|
catch (err) {
|
||||||
updated () {
|
addNotification({ status: 'error', message: err.stack });
|
||||||
if (this.$refs.table)
|
}
|
||||||
this.refreshScroller();
|
|
||||||
|
|
||||||
if (this.$refs.tableWrapper)
|
isQuering.value = false;
|
||||||
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;
|
|
||||||
|
|
||||||
// if table changes clear cached values
|
const setRefreshInterval = () => {
|
||||||
if (this.lastTable !== this.table)
|
clearRefresh();
|
||||||
this.results = [];
|
|
||||||
|
|
||||||
try { // Table data
|
if (+autorefreshTimer.value) {
|
||||||
const { status, response } = await Schema.getProcesses(this.connection.uid);
|
refreshInterval.value = setInterval(() => {
|
||||||
|
if (!isQuering.value)
|
||||||
if (status === 'success') {
|
getProcessesList();
|
||||||
this.results = response;
|
}, autorefreshTimer.value * 1000);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<BaseContextMenu
|
<BaseContextMenu
|
||||||
:context-event="contextEvent"
|
:context-event="props.contextEvent"
|
||||||
@close-context="closeContext"
|
@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>
|
<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" />
|
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
|
||||||
<div class="context-submenu">
|
<div class="context-submenu">
|
||||||
<div
|
<div
|
||||||
v-if="selectedRow"
|
v-if="props.selectedRow"
|
||||||
class="context-element"
|
class="context-element"
|
||||||
@click="copyCell"
|
@click="copyCell"
|
||||||
>
|
>
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="selectedRow"
|
v-if="props.selectedRow"
|
||||||
class="context-element"
|
class="context-element"
|
||||||
@click="copyRow"
|
@click="copyRow"
|
||||||
>
|
>
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="selectedRow"
|
v-if="props.selectedRow"
|
||||||
class="context-element"
|
class="context-element"
|
||||||
@click="killProcess"
|
@click="killProcess"
|
||||||
>
|
>
|
||||||
|
@ -39,38 +39,33 @@
|
||||||
</BaseContextMenu>
|
</BaseContextMenu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'ModalProcessesListContext',
|
contextEvent: MouseEvent,
|
||||||
components: {
|
selectedRow: Number,
|
||||||
BaseContextMenu
|
selectedCell: Object
|
||||||
},
|
});
|
||||||
props: {
|
|
||||||
contextEvent: MouseEvent,
|
const emit = defineEmits(['close-context', 'copy-cell', 'copy-row', 'kill-process']);
|
||||||
selectedRow: Number,
|
|
||||||
selectedCell: Object
|
const closeContext = () => {
|
||||||
},
|
emit('close-context');
|
||||||
emits: ['close-context', 'copy-cell', 'copy-row', 'kill-process'],
|
};
|
||||||
computed: {
|
|
||||||
},
|
const copyCell = () => {
|
||||||
methods: {
|
emit('copy-cell');
|
||||||
closeContext () {
|
closeContext();
|
||||||
this.$emit('close-context');
|
};
|
||||||
},
|
|
||||||
copyCell () {
|
const copyRow = () => {
|
||||||
this.$emit('copy-cell');
|
emit('copy-row');
|
||||||
this.closeContext();
|
closeContext();
|
||||||
},
|
};
|
||||||
copyRow () {
|
|
||||||
this.$emit('copy-row');
|
const killProcess = () => {
|
||||||
this.closeContext();
|
emit('kill-process');
|
||||||
},
|
closeContext();
|
||||||
killProcess () {
|
|
||||||
this.$emit('kill-process');
|
|
||||||
this.closeContext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -31,11 +31,12 @@
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<TextEditor
|
<TextEditor
|
||||||
:value="row.info || ''"
|
:model-value="props.row.info || ''"
|
||||||
editor-class="textarea-editor"
|
editor-class="textarea-editor"
|
||||||
:mode="editorMode"
|
:mode="editorMode"
|
||||||
:read-only="true"
|
:read-only="true"
|
||||||
/>
|
/>
|
||||||
|
<div class="mb-4" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -43,60 +44,46 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
import { Ref, ref } from 'vue';
|
||||||
import TextEditor from '@/components/BaseTextEditor';
|
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||||
|
import TextEditor from '@/components/BaseTextEditor.vue';
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'ModalProcessesListRow',
|
row: Object
|
||||||
components: {
|
});
|
||||||
ConfirmModal,
|
|
||||||
TextEditor
|
const emit = defineEmits(['select-row', 'contextmenu', 'stop-refresh']);
|
||||||
},
|
|
||||||
props: {
|
const isInlineEditor: Ref<{[key: string]: boolean}> = ref({});
|
||||||
row: Object
|
const isInfoModal = ref(false);
|
||||||
},
|
const editorMode = ref('sql');
|
||||||
emits: ['select-row', 'contextmenu', 'stop-refresh'],
|
|
||||||
data () {
|
const isNull = (value: string | number) => value === null ? ' is-null' : '';
|
||||||
return {
|
|
||||||
isInlineEditor: {},
|
const selectRow = () => {
|
||||||
isInfoModal: false,
|
emit('select-row');
|
||||||
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 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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<div class="modal-title h6">
|
<div class="modal-title h6">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-cog mr-1" />
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<a class="btn btn-clear c-hand" @click="closeModal" />
|
<a class="btn btn-clear c-hand" @click="closeModal" />
|
||||||
|
@ -21,14 +21,14 @@
|
||||||
:class="{'active': selectedTab === 'general'}"
|
:class="{'active': selectedTab === 'general'}"
|
||||||
@click="selectTab('general')"
|
@click="selectTab('general')"
|
||||||
>
|
>
|
||||||
<a class="tab-link">{{ $t('word.general') }}</a>
|
<a class="tab-link">{{ t('word.general') }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class="tab-item c-hand"
|
class="tab-item c-hand"
|
||||||
:class="{'active': selectedTab === 'themes'}"
|
:class="{'active': selectedTab === 'themes'}"
|
||||||
@click="selectTab('themes')"
|
@click="selectTab('themes')"
|
||||||
>
|
>
|
||||||
<a class="tab-link">{{ $t('word.themes') }}</a>
|
<a class="tab-link">{{ t('word.themes') }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="updateStatus !== 'disabled'"
|
v-if="updateStatus !== 'disabled'"
|
||||||
|
@ -36,21 +36,21 @@
|
||||||
:class="{'active': selectedTab === 'update'}"
|
:class="{'active': selectedTab === 'update'}"
|
||||||
@click="selectTab('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>
|
||||||
<li
|
<li
|
||||||
class="tab-item c-hand"
|
class="tab-item c-hand"
|
||||||
:class="{'active': selectedTab === 'changelog'}"
|
:class="{'active': selectedTab === 'changelog'}"
|
||||||
@click="selectTab('changelog')"
|
@click="selectTab('changelog')"
|
||||||
>
|
>
|
||||||
<a class="tab-link">{{ $t('word.changelog') }}</a>
|
<a class="tab-link">{{ t('word.changelog') }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class="tab-item c-hand"
|
class="tab-item c-hand"
|
||||||
:class="{'active': selectedTab === 'about'}"
|
:class="{'active': selectedTab === 'about'}"
|
||||||
@click="selectTab('about')"
|
@click="selectTab('about')"
|
||||||
>
|
>
|
||||||
<a class="tab-link">{{ $t('word.about') }}</a>
|
<a class="tab-link">{{ t('word.about') }}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -58,14 +58,14 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<form class="form-horizontal columns">
|
<form class="form-horizontal columns">
|
||||||
<div class="column col-12 h6 text-uppercase mb-1">
|
<div class="column col-12 h6 text-uppercase mb-1">
|
||||||
{{ $t('word.application') }}
|
{{ t('word.application') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-12 col-sm-12 mb-2 columns">
|
<div class="column col-12 col-sm-12 mb-2 columns">
|
||||||
<div class="form-group column col-12">
|
<div class="form-group column col-12">
|
||||||
<div class="col-5 col-sm-12">
|
<div class="col-5 col-sm-12">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
<i class="mdi mdi-18px mdi-translate mr-1" />
|
<i class="mdi mdi-18px mdi-translate mr-1" />
|
||||||
{{ $t('word.language') }}
|
{{ t('word.language') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3 col-sm-12">
|
<div class="col-3 col-sm-12">
|
||||||
|
@ -79,16 +79,16 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4 col-sm-12 px-2 p-vcentered">
|
<div class="col-4 col-sm-12 px-2 p-vcentered">
|
||||||
<small class="d-block" style="line-height:1.1; font-size:70%;">
|
<small class="d-block" style="line-height: 1.1; font-size: 70%;">
|
||||||
{{ $t('message.missingOrIncompleteTranslation') }}<br>
|
{{ 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>
|
<a class="text-bold c-hand" @click="openOutside('https://github.com/antares-sql/antares/wiki/Translate-Antares')">{{ t('message.findOutHowToContribute') }}</a>
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group column col-12">
|
<div class="form-group column col-12">
|
||||||
<div class="col-5 col-sm-12">
|
<div class="col-5 col-sm-12">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('message.dataTabPageSize') }}
|
{{ t('message.dataTabPageSize') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3 col-sm-12">
|
<div class="col-3 col-sm-12">
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
<div class="form-group column col-12 mb-0">
|
<div class="form-group column col-12 mb-0">
|
||||||
<div class="col-5 col-sm-12">
|
<div class="col-5 col-sm-12">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('message.restorePreviourSession') }}
|
{{ t('message.restorePreviourSession') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3 col-sm-12">
|
<div class="col-3 col-sm-12">
|
||||||
|
@ -116,7 +116,7 @@
|
||||||
<div class="form-group column col-12 mb-0">
|
<div class="form-group column col-12 mb-0">
|
||||||
<div class="col-5 col-sm-12">
|
<div class="col-5 col-sm-12">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('message.disableBlur') }}
|
{{ t('message.disableBlur') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3 col-sm-12">
|
<div class="col-3 col-sm-12">
|
||||||
|
@ -129,7 +129,7 @@
|
||||||
<div class="form-group column col-12">
|
<div class="form-group column col-12">
|
||||||
<div class="col-5 col-sm-12">
|
<div class="col-5 col-sm-12">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('message.notificationsTimeout') }}
|
{{ t('message.notificationsTimeout') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3 col-sm-12">
|
<div class="col-3 col-sm-12">
|
||||||
|
@ -141,19 +141,19 @@
|
||||||
min="1"
|
min="1"
|
||||||
@focusout="checkNotificationsTimeout"
|
@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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-12 h6 mt-4 text-uppercase mb-1">
|
<div class="column col-12 h6 mt-4 text-uppercase mb-1">
|
||||||
{{ $t('word.editor') }}
|
{{ t('word.editor') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-12 col-sm-12 columns">
|
<div class="column col-12 col-sm-12 columns">
|
||||||
<div class="form-group column col-12 mb-0">
|
<div class="form-group column col-12 mb-0">
|
||||||
<div class="col-5 col-sm-12">
|
<div class="col-5 col-sm-12">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.autoCompletion') }}
|
{{ t('word.autoCompletion') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3 col-sm-12">
|
<div class="col-3 col-sm-12">
|
||||||
|
@ -166,7 +166,7 @@
|
||||||
<div class="form-group column col-12 mb-0">
|
<div class="form-group column col-12 mb-0">
|
||||||
<div class="col-5 col-sm-12">
|
<div class="col-5 col-sm-12">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('message.wrapLongLines') }}
|
{{ t('message.wrapLongLines') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3 col-sm-12">
|
<div class="col-3 col-sm-12">
|
||||||
|
@ -185,18 +185,18 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column col-12 h6 text-uppercase mb-2">
|
<div class="column col-12 h6 text-uppercase mb-2">
|
||||||
{{ $t('message.applicationTheme') }}
|
{{ t('message.applicationTheme') }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="column col-6 c-hand theme-block"
|
class="column col-6 c-hand theme-block"
|
||||||
:class="{'selected': applicationTheme === 'dark'}"
|
:class="{'selected': applicationTheme === 'dark'}"
|
||||||
@click="changeApplicationTheme('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">
|
<div class="theme-name text-light">
|
||||||
<i class="mdi mdi-moon-waning-crescent mdi-48px" />
|
<i class="mdi mdi-moon-waning-crescent mdi-48px" />
|
||||||
<div class="h6 mt-4">
|
<div class="h6 mt-4">
|
||||||
{{ $t('word.dark') }}
|
{{ t('word.dark') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -205,11 +205,11 @@
|
||||||
:class="{'selected': applicationTheme === 'light'}"
|
:class="{'selected': applicationTheme === 'light'}"
|
||||||
@click="changeApplicationTheme('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">
|
<div class="theme-name text-dark">
|
||||||
<i class="mdi mdi-white-balance-sunny mdi-48px" />
|
<i class="mdi mdi-white-balance-sunny mdi-48px" />
|
||||||
<div class="h6 mt-4">
|
<div class="h6 mt-4">
|
||||||
{{ $t('word.light') }}
|
{{ t('word.light') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -217,7 +217,7 @@
|
||||||
|
|
||||||
<div class="columns mt-4">
|
<div class="columns mt-4">
|
||||||
<div class="column col-12 h6 text-uppercase mb-2 mt-4">
|
<div class="column col-12 h6 text-uppercase mb-2 mt-4">
|
||||||
{{ $t('message.editorTheme') }}
|
{{ t('message.editorTheme') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-6 h5 mb-4">
|
<div class="column col-6 h5 mb-4">
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
|
@ -238,21 +238,21 @@
|
||||||
:class="{'active': editorFontSize === 'small'}"
|
:class="{'active': editorFontSize === 'small'}"
|
||||||
@click="changeEditorFontSize('small')"
|
@click="changeEditorFontSize('small')"
|
||||||
>
|
>
|
||||||
{{ $t('word.small') }}
|
{{ t('word.small') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn-dark cut-text"
|
class="btn btn-dark cut-text"
|
||||||
:class="{'active': editorFontSize === 'medium'}"
|
:class="{'active': editorFontSize === 'medium'}"
|
||||||
@click="changeEditorFontSize('medium')"
|
@click="changeEditorFontSize('medium')"
|
||||||
>
|
>
|
||||||
{{ $t('word.medium') }}
|
{{ t('word.medium') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn-dark cut-text"
|
class="btn btn-dark cut-text"
|
||||||
:class="{'active': editorFontSize === 'large'}"
|
:class="{'active': editorFontSize === 'large'}"
|
||||||
@click="changeEditorFontSize('large')"
|
@click="changeEditorFontSize('large')"
|
||||||
>
|
>
|
||||||
{{ $t('word.large') }}
|
{{ t('word.large') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -278,19 +278,19 @@
|
||||||
|
|
||||||
<div v-show="selectedTab === 'about'" class="panel-body py-4">
|
<div v-show="selectedTab === 'about'" class="panel-body py-4">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<img src="../images/logo.svg" width="128">
|
<img :src="appLogo" width="128">
|
||||||
<h4>{{ appName }}</h4>
|
<h4>{{ appName }}</h4>
|
||||||
<p class="mb-2">
|
<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>
|
<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>
|
</p>
|
||||||
<div class="mb-2">
|
<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">
|
<div class="d-block py-1">
|
||||||
<small v-for="(contributor, i) in otherContributors" :key="i">{{ i !== 0 ? ', ' : '' }}{{ contributor }}</small>
|
<small v-for="(contributor, i) in otherContributors" :key="i">{{ i !== 0 ? ', ' : '' }}{{ contributor }}</small>
|
||||||
</div>
|
</div>
|
||||||
<small>{{ $t('message.madeWithJS') }}</small>
|
<small>{{ t('message.madeWithJS') }}</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -301,176 +301,121 @@
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { onBeforeUnmount, Ref, ref } from 'vue';
|
||||||
import { shell } from 'electron';
|
import { shell } from 'electron';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useApplicationStore } from '@/stores/application';
|
import { useApplicationStore } from '@/stores/application';
|
||||||
import { useSettingsStore } from '@/stores/settings';
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import localesNames from '@/i18n/supported-locales';
|
import { localesNames } from '@/i18n/supported-locales';
|
||||||
import ModalSettingsUpdate from '@/components/ModalSettingsUpdate';
|
import ModalSettingsUpdate from '@/components/ModalSettingsUpdate.vue';
|
||||||
import ModalSettingsChangelog from '@/components/ModalSettingsChangelog';
|
import ModalSettingsChangelog from '@/components/ModalSettingsChangelog.vue';
|
||||||
import BaseTextEditor from '@/components/BaseTextEditor';
|
import BaseTextEditor from '@/components/BaseTextEditor.vue';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
import { computed } from '@vue/reactivity';
|
||||||
|
|
||||||
export default {
|
const { t, availableLocales } = useI18n();
|
||||||
name: 'ModalSettings',
|
|
||||||
components: {
|
const applicationStore = useApplicationStore();
|
||||||
ModalSettingsUpdate,
|
const settingsStore = useSettingsStore();
|
||||||
ModalSettingsChangelog,
|
const workspacesStore = useWorkspacesStore();
|
||||||
BaseTextEditor,
|
|
||||||
BaseSelect
|
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();
|
group: t('word.dark'),
|
||||||
const settingsStore = useSettingsStore();
|
themes: [
|
||||||
const workspacesStore = useWorkspacesStore();
|
{ code: 'ambiance', name: 'Ambiance' },
|
||||||
|
{ code: 'chaos', name: 'Chaos' },
|
||||||
const {
|
{ code: 'clouds_midnight', name: 'Clouds Midnight' },
|
||||||
selectedSettingTab,
|
{ code: 'dracula', name: 'Dracula' },
|
||||||
updateStatus
|
{ code: 'cobalt', name: 'Cobalt' },
|
||||||
} = storeToRefs(applicationStore);
|
{ code: 'gruvbox', name: 'Gruvbox' },
|
||||||
const {
|
{ code: 'gob', name: 'Green on Black' },
|
||||||
locale: selectedLocale,
|
{ code: 'idle_fingers', name: 'Idle Fingers' },
|
||||||
dataTabLimit: pageSize,
|
{ code: 'kr_theme', name: 'krTheme' },
|
||||||
autoComplete: selectedAutoComplete,
|
{ code: 'merbivore', name: 'Merbivore' },
|
||||||
lineWrap: selectedLineWrap,
|
{ code: 'mono_industrial', name: 'Mono Industrial' },
|
||||||
notificationsTimeout,
|
{ code: 'monokai', name: 'Monokai' },
|
||||||
restoreTabs,
|
{ code: 'nord_dark', name: 'Nord Dark' },
|
||||||
disableBlur,
|
{ code: 'pastel_on_dark', name: 'Pastel on Dark' },
|
||||||
applicationTheme,
|
{ code: 'solarized_dark', name: 'Solarized Dark' },
|
||||||
editorTheme,
|
{ code: 'terminal', name: 'Terminal' },
|
||||||
editorFontSize
|
{ code: 'tomorrow_night', name: 'Tomorrow Night' },
|
||||||
} = storeToRefs(settingsStore);
|
{ code: 'tomorrow_night_blue', name: 'Tomorrow Night Blue' },
|
||||||
|
{ code: 'tomorrow_night_bright', name: 'Tomorrow Night Bright' },
|
||||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
{ code: 'tomorrow_night_eighties', name: 'Tomorrow Night 80s' },
|
||||||
|
{ code: 'twilight', name: 'Twilight' },
|
||||||
const {
|
{ code: 'vibrant_ink', name: 'Vibrant Ink' }
|
||||||
changeLocale,
|
]
|
||||||
changePageSize,
|
}
|
||||||
changeRestoreTabs,
|
];
|
||||||
changeDisableBlur,
|
const exampleQuery = `-- This is an example
|
||||||
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
|
|
||||||
SELECT
|
SELECT
|
||||||
employee.id,
|
employee.id,
|
||||||
employee.first_name,
|
employee.first_name,
|
||||||
|
@ -485,57 +430,81 @@ GROUP BY
|
||||||
ORDER BY
|
ORDER BY
|
||||||
employee.id ASC;
|
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);
|
const localLocale: Ref<string> = ref(null);
|
||||||
},
|
const localPageSize: Ref<number> = ref(null);
|
||||||
onKey (e) {
|
const localTimeout: Ref<number> = ref(null);
|
||||||
e.stopPropagation();
|
const localEditorTheme: Ref<string> = ref(null);
|
||||||
if (e.key === 'Escape')
|
const selectedTab: Ref<string> = ref('general');
|
||||||
this.closeModal();
|
|
||||||
},
|
const locales = computed(() => {
|
||||||
toggleRestoreSession () {
|
const locales = [];
|
||||||
this.changeRestoreTabs(!this.restoreTabs);
|
for (const locale of availableLocales)
|
||||||
},
|
locales.push({ code: locale, name: localesNames[locale] });
|
||||||
toggleDisableBlur () {
|
|
||||||
this.changeDisableBlur(!this.disableBlur);
|
return locales;
|
||||||
},
|
});
|
||||||
toggleAutoComplete () {
|
|
||||||
this.changeAutoComplete(!this.selectedAutoComplete);
|
const hasUpdates = computed(() => ['available', 'downloading', 'downloaded', 'link'].includes(updateStatus.value));
|
||||||
},
|
|
||||||
toggleLineWrap () {
|
const workspace = computed(() => {
|
||||||
this.changeLineWrap(!this.selectedLineWrap);
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -13,66 +13,53 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
<script>
|
|
||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
import BaseLoader from '@/components/BaseLoader';
|
import BaseLoader from '@/components/BaseLoader.vue';
|
||||||
import { useApplicationStore } from '@/stores/application';
|
import { useApplicationStore } from '@/stores/application';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
export default {
|
const { appVersion } = useApplicationStore();
|
||||||
name: 'ModalSettingsChangelog',
|
|
||||||
components: {
|
const changelog = ref('');
|
||||||
BaseLoader
|
const isLoading = ref(true);
|
||||||
},
|
const error = ref('');
|
||||||
setup () {
|
const isError = ref(false);
|
||||||
const { appVersion } = useApplicationStore();
|
|
||||||
return { appVersion };
|
const getChangelog = async () => {
|
||||||
},
|
try {
|
||||||
data () {
|
const apiRes = await fetch(`https://api.github.com/repos/antares-sql/antares/releases/tags/v${appVersion}`, {
|
||||||
return {
|
method: 'GET'
|
||||||
changelog: '',
|
});
|
||||||
isLoading: true,
|
|
||||||
error: '',
|
const { body } = await apiRes.json();
|
||||||
isError: false
|
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();
|
marked.use({ renderer });
|
||||||
const cutOffset = body.indexOf('### Download');
|
|
||||||
const markdown = cutOffset >= 0
|
|
||||||
? body.substr(0, cutOffset)
|
|
||||||
: body;
|
|
||||||
|
|
||||||
const renderer = {
|
changelog.value = marked(markdown);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (err) {
|
||||||
|
error.value = err.message;
|
||||||
|
isError.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getChangelog();
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
#changelog {
|
#changelog {
|
||||||
|
|
|
@ -52,68 +52,61 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import { ipcRenderer, shell } from 'electron';
|
import { ipcRenderer, shell } from 'electron';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useApplicationStore } from '@/stores/application';
|
import { useApplicationStore } from '@/stores/application';
|
||||||
import { useSettingsStore } from '@/stores/settings';
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
name: 'ModalSettingsUpdate',
|
|
||||||
setup () {
|
|
||||||
const applicationStore = useApplicationStore();
|
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
|
|
||||||
const {
|
const applicationStore = useApplicationStore();
|
||||||
updateStatus,
|
const settingsStore = useSettingsStore();
|
||||||
getDownloadProgress
|
|
||||||
} = storeToRefs(applicationStore);
|
|
||||||
const { allowPrerelease } = storeToRefs(settingsStore);
|
|
||||||
|
|
||||||
const { changeAllowPrerelease } = settingsStore;
|
const {
|
||||||
|
updateStatus,
|
||||||
|
getDownloadProgress: downloadPercentage
|
||||||
|
} = storeToRefs(applicationStore);
|
||||||
|
const { allowPrerelease } = storeToRefs(settingsStore);
|
||||||
|
|
||||||
return {
|
const { changeAllowPrerelease } = settingsStore;
|
||||||
updateStatus,
|
|
||||||
downloadPercentage: getDownloadProgress,
|
const updateMessage = computed(() => {
|
||||||
allowPrerelease,
|
switch (updateStatus.value) {
|
||||||
changeAllowPrerelease
|
case 'noupdate':
|
||||||
};
|
return t('message.noUpdatesAvailable');
|
||||||
},
|
case 'checking':
|
||||||
computed: {
|
return t('message.checkingForUpdate');
|
||||||
updateMessage () {
|
case 'nocheck':
|
||||||
switch (this.updateStatus) {
|
return t('message.checkFailure');
|
||||||
case 'noupdate':
|
case 'available':
|
||||||
return this.$t('message.noUpdatesAvailable');
|
return t('message.updateAvailable');
|
||||||
case 'checking':
|
case 'downloading':
|
||||||
return this.$t('message.checkingForUpdate');
|
return t('message.downloadingUpdate');
|
||||||
case 'nocheck':
|
case 'downloaded':
|
||||||
return this.$t('message.checkFailure');
|
return t('message.updateDownloaded');
|
||||||
case 'available':
|
case 'link':
|
||||||
return this.$t('message.updateAvailable');
|
return t('message.updateAvailable');
|
||||||
case 'downloading':
|
default:
|
||||||
return this.$t('message.downloadingUpdate');
|
return updateStatus.value;
|
||||||
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 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>
|
</script>
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, Prop, Ref, ref, toRef, watch } from 'vue';
|
||||||
import * as ace from 'ace-builds';
|
import * as ace from 'ace-builds';
|
||||||
import 'ace-builds/webpack-resolver';
|
import 'ace-builds/webpack-resolver';
|
||||||
import '../libs/ext-language_tools';
|
import '../libs/ext-language_tools';
|
||||||
|
@ -16,329 +17,330 @@ import { storeToRefs } from 'pinia';
|
||||||
import { uidGen } from 'common/libs/uidGen';
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
import { useApplicationStore } from '@/stores/application';
|
import { useApplicationStore } from '@/stores/application';
|
||||||
import { useSettingsStore } from '@/stores/settings';
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
|
import { Workspace } from '@/stores/workspaces';
|
||||||
import Tables from '@/ipc-api/Tables';
|
import Tables from '@/ipc-api/Tables';
|
||||||
|
|
||||||
export default {
|
const editor: Ref<ace.Ace.Editor> = ref(null);
|
||||||
name: 'QueryEditor',
|
const applicationStore = useApplicationStore();
|
||||||
props: {
|
const settingsStore = useSettingsStore();
|
||||||
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 { setBaseCompleters } = applicationStore;
|
const { setBaseCompleters } = applicationStore;
|
||||||
|
|
||||||
const { baseCompleter } = storeToRefs(applicationStore);
|
const { baseCompleter } = storeToRefs(applicationStore);
|
||||||
const {
|
const {
|
||||||
editorTheme,
|
editorTheme,
|
||||||
editorFontSize,
|
editorFontSize,
|
||||||
autoComplete,
|
autoComplete,
|
||||||
lineWrap
|
lineWrap
|
||||||
} = storeToRefs(settingsStore);
|
} = storeToRefs(settingsStore);
|
||||||
|
|
||||||
return {
|
const props = defineProps({
|
||||||
editor,
|
modelValue: String,
|
||||||
baseCompleter,
|
workspace: Object as Prop<Workspace>,
|
||||||
setBaseCompleters,
|
isSelected: Boolean,
|
||||||
editorTheme,
|
schema: { type: String, default: '' },
|
||||||
editorFontSize,
|
autoFocus: { type: Boolean, default: false },
|
||||||
autoComplete,
|
readOnly: { type: Boolean, default: false },
|
||||||
lineWrap
|
height: { type: Number, default: 200 }
|
||||||
};
|
});
|
||||||
},
|
|
||||||
data () {
|
const emit = defineEmits(['update:modelValue']);
|
||||||
return {
|
|
||||||
cursorPosition: 0,
|
const cursorPosition = ref(0);
|
||||||
fields: [],
|
const fields = ref([]);
|
||||||
customCompleter: [],
|
const customCompleter = ref([]);
|
||||||
id: uidGen(),
|
const id = ref(uidGen());
|
||||||
lastSchema: null
|
const lastSchema: Ref<string> = ref(null);
|
||||||
};
|
|
||||||
},
|
const tables = computed(() => {
|
||||||
computed: {
|
return props.workspace
|
||||||
tables () {
|
? props.workspace.structure.filter(schema => schema.name === props.schema)
|
||||||
return this.workspace
|
.reduce((acc, curr) => {
|
||||||
? this.workspace.structure.filter(schema => schema.name === this.schema)
|
acc.push(...curr.tables);
|
||||||
.reduce((acc, curr) => {
|
return acc;
|
||||||
acc.push(...curr.tables);
|
}, []).map(table => {
|
||||||
return acc;
|
return {
|
||||||
}, []).map(table => {
|
name: table.name as string,
|
||||||
return {
|
type: table.type as string,
|
||||||
name: table.name,
|
fields: []
|
||||||
type: table.type,
|
};
|
||||||
fields: []
|
})
|
||||||
};
|
: [];
|
||||||
})
|
});
|
||||||
: [];
|
|
||||||
},
|
const triggers = computed(() => {
|
||||||
triggers () {
|
return props.workspace
|
||||||
return this.workspace
|
? props.workspace.structure.filter(schema => schema.name === props.schema)
|
||||||
? this.workspace.structure.filter(schema => schema.name === this.schema)
|
.reduce((acc, curr) => {
|
||||||
.reduce((acc, curr) => {
|
acc.push(...curr.triggers);
|
||||||
acc.push(...curr.triggers);
|
return acc;
|
||||||
return acc;
|
}, []).map(trigger => {
|
||||||
}, []).map(trigger => {
|
return {
|
||||||
return {
|
name: trigger.name as string,
|
||||||
name: trigger.name,
|
type: 'trigger'
|
||||||
type: 'trigger'
|
};
|
||||||
};
|
})
|
||||||
})
|
: [];
|
||||||
: [];
|
});
|
||||||
},
|
|
||||||
procedures () {
|
const procedures = computed(() => {
|
||||||
return this.workspace
|
return props.workspace
|
||||||
? this.workspace.structure.filter(schema => schema.name === this.schema)
|
? props.workspace.structure.filter(schema => schema.name === props.schema)
|
||||||
.reduce((acc, curr) => {
|
.reduce((acc, curr) => {
|
||||||
acc.push(...curr.procedures);
|
acc.push(...curr.procedures);
|
||||||
return acc;
|
return acc;
|
||||||
}, []).map(procedure => {
|
}, []).map(procedure => {
|
||||||
return {
|
return {
|
||||||
name: `${procedure.name}()`,
|
name: `${procedure.name}()`,
|
||||||
type: 'routine'
|
type: 'routine'
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
},
|
});
|
||||||
functions () {
|
|
||||||
return this.workspace
|
const functions = computed(() => {
|
||||||
? this.workspace.structure.filter(schema => schema.name === this.schema)
|
return props.workspace
|
||||||
.reduce((acc, curr) => {
|
? props.workspace.structure.filter(schema => schema.name === props.schema)
|
||||||
acc.push(...curr.functions);
|
.reduce((acc, curr) => {
|
||||||
return acc;
|
acc.push(...curr.functions);
|
||||||
}, []).map(func => {
|
return acc;
|
||||||
return {
|
}, []).map(func => {
|
||||||
name: `${func.name}()`,
|
return {
|
||||||
type: 'function'
|
name: `${func.name}()`,
|
||||||
};
|
type: 'function'
|
||||||
})
|
};
|
||||||
: [];
|
})
|
||||||
},
|
: [];
|
||||||
schedulers () {
|
});
|
||||||
return this.workspace
|
|
||||||
? this.workspace.structure.filter(schema => schema.name === this.schema)
|
const schedulers = computed(() => {
|
||||||
.reduce((acc, curr) => {
|
return props.workspace
|
||||||
acc.push(...curr.schedulers);
|
? props.workspace.structure.filter(schema => schema.name === props.schema)
|
||||||
return acc;
|
.reduce((acc, curr) => {
|
||||||
}, []).map(scheduler => {
|
acc.push(...curr.schedulers);
|
||||||
return {
|
return acc;
|
||||||
name: scheduler.name,
|
}, []).map(scheduler => {
|
||||||
type: 'scheduler'
|
return {
|
||||||
};
|
name: scheduler.name as string,
|
||||||
})
|
type: 'scheduler'
|
||||||
: [];
|
};
|
||||||
},
|
})
|
||||||
mode () {
|
: [];
|
||||||
switch (this.workspace.client) {
|
});
|
||||||
case 'mysql':
|
|
||||||
case 'maria':
|
const mode = computed(() => {
|
||||||
return 'mysql';
|
switch (props.workspace.client) {
|
||||||
case 'mssql':
|
case 'mysql':
|
||||||
return 'sqlserver';
|
case 'maria':
|
||||||
case 'pg':
|
return 'mysql';
|
||||||
return 'pgsql';
|
// case 'mssql':
|
||||||
default:
|
// return 'sqlserver';
|
||||||
return 'sql';
|
case 'pg':
|
||||||
}
|
return 'pgsql';
|
||||||
},
|
default:
|
||||||
lastWord () {
|
return 'sql';
|
||||||
const charsBefore = this.modelValue.slice(0, this.cursorPosition);
|
}
|
||||||
const words = charsBefore.replaceAll('\n', ' ').split(' ').filter(Boolean);
|
});
|
||||||
return words.pop();
|
|
||||||
},
|
const lastWord = computed(() => {
|
||||||
isLastWordATable () {
|
const charsBefore = props.modelValue.slice(0, cursorPosition.value);
|
||||||
return /\w+\.\w*/gm.test(this.lastWord);
|
const words = charsBefore.replaceAll('\n', ' ').split(' ').filter(Boolean);
|
||||||
},
|
return words.pop();
|
||||||
fieldsCompleter () {
|
});
|
||||||
return {
|
|
||||||
getCompletions: (editor, session, pos, prefix, callback) => {
|
const isLastWordATable = computed(() => /\w+\.\w*/gm.test(lastWord.value));
|
||||||
const completions = [];
|
|
||||||
this.fields.forEach(field => {
|
const fieldsCompleter = computed(() => {
|
||||||
completions.push({
|
return {
|
||||||
value: field,
|
getCompletions: (editor: never, session: never, pos: never, prefix: never, callback: (err: null, response: ace.Ace.Completion[]) => void) => {
|
||||||
meta: 'column',
|
const completions: ace.Ace.Completion[] = [];
|
||||||
score: 1000
|
fields.value.forEach(field => {
|
||||||
});
|
completions.push({
|
||||||
});
|
value: field,
|
||||||
callback(null, completions);
|
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) {
|
const setCustomCompleter = () => {
|
||||||
this.editor.setOptions({
|
editor.value.completers.push({
|
||||||
fontSize: sizes[this.editorFontSize]
|
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
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
},
|
callback(null, completions);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
created () {
|
|
||||||
this.lastSchema = this.schema;
|
customCompleter.value = editor.value.completers;
|
||||||
},
|
};
|
||||||
mounted () {
|
|
||||||
this.editor = ace.edit(`editor-${this.id}`, {
|
watch(() => props.modelValue, () => {
|
||||||
mode: `ace/mode/${this.mode}`,
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
theme: `ace/theme/${this.editorTheme}`,
|
cursorPosition.value = (editor.value.session as any).doc.positionToIndex(editor.value.getCursorPosition());
|
||||||
value: this.modelValue,
|
});
|
||||||
fontSize: '14px',
|
|
||||||
printMargin: false,
|
watch(editorTheme, () => {
|
||||||
readOnly: this.readOnly
|
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({
|
watch(autoComplete, () => {
|
||||||
enableBasicAutocompletion: true,
|
if (editor.value) {
|
||||||
wrap: this.lineWrap,
|
editor.value.setOptions({
|
||||||
enableSnippets: true,
|
enableLiveAutocompletion: autoComplete.value
|
||||||
enableLiveAutocompletion: this.autoComplete
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (!this.baseCompleter.length)
|
watch(lineWrap, () => {
|
||||||
this.setBaseCompleters(this.editor.completers.map(el => Object.assign({}, el)));
|
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 => {
|
watch(() => props.height, () => {
|
||||||
if (['insertstring', 'backspace', 'del'].includes(e.command.name)) {
|
setTimeout(() => {
|
||||||
if (this.isLastWordATable || e.args === '.') {
|
editor.value.resize();
|
||||||
if (e.args !== ' ') {
|
}, 20);
|
||||||
const table = this.tables.find(t => t.name === this.lastWord.split('.').pop().trim());
|
});
|
||||||
|
|
||||||
if (table) {
|
watch(lastSchema, () => {
|
||||||
const params = {
|
if (editor.value) {
|
||||||
uid: this.workspace.uid,
|
editor.value.completers = baseCompleter.value.map(el => Object.assign({}, el));
|
||||||
schema: this.schema,
|
setCustomCompleter();
|
||||||
table: table.name
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
Tables.getTableColumns(params).then(res => {
|
lastSchema.value = toRef(props, 'schema').value;
|
||||||
if (res.response.length)
|
|
||||||
this.fields = res.response.map(field => field.name);
|
onMounted(() => {
|
||||||
this.editor.completers = [this.fieldsCompleter];
|
editor.value = ace.edit(`editor-${id.value}`, {
|
||||||
this.editor.execCommand('startAutocomplete');
|
mode: `ace/mode/${mode.value}`,
|
||||||
}).catch(console.log);
|
theme: `ace/theme/${editorTheme.value}`,
|
||||||
}
|
value: props.modelValue,
|
||||||
else
|
fontSize: 14,
|
||||||
this.editor.completers = this.customCompleter;
|
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
|
else
|
||||||
this.editor.completers = this.customCompleter;
|
editor.value.completers = customCompleter.value;
|
||||||
}
|
}
|
||||||
else
|
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
|
else
|
||||||
e.editor.session.clearBreakpoint(row);
|
editor.value.completers = customCompleter.value;
|
||||||
e.stop();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.autoFocus) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.editor.focus();
|
|
||||||
this.editor.resize();
|
|
||||||
}, 20);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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(() => {
|
setTimeout(() => {
|
||||||
this.editor.resize();
|
editor.value.focus();
|
||||||
|
editor.value.resize();
|
||||||
}, 20);
|
}, 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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -29,83 +29,66 @@
|
||||||
</BaseContextMenu>
|
</BaseContextMenu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { computed, Prop, ref } from 'vue';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
import { uidGen } from 'common/libs/uidGen';
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
import { useConnectionsStore } from '@/stores/connections';
|
import { useConnectionsStore } from '@/stores/connections';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
||||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { ConnectionParams } from 'common/interfaces/antares';
|
||||||
|
|
||||||
export default {
|
const {
|
||||||
name: 'SettingBarContext',
|
getConnectionName,
|
||||||
components: {
|
addConnection,
|
||||||
BaseContextMenu,
|
deleteConnection
|
||||||
ConfirmModal
|
} = useConnectionsStore();
|
||||||
},
|
const workspacesStore = useWorkspacesStore();
|
||||||
props: {
|
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||||
contextEvent: MouseEvent,
|
|
||||||
contextConnection: Object
|
|
||||||
},
|
|
||||||
emits: ['close-context'],
|
|
||||||
setup () {
|
|
||||||
const {
|
|
||||||
getConnectionName,
|
|
||||||
addConnection,
|
|
||||||
deleteConnection
|
|
||||||
} = useConnectionsStore();
|
|
||||||
const workspacesStore = useWorkspacesStore();
|
|
||||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
|
||||||
|
|
||||||
const { selectWorkspace } = workspacesStore;
|
const { selectWorkspace } = workspacesStore;
|
||||||
|
|
||||||
return {
|
const props = defineProps({
|
||||||
getConnectionName,
|
contextEvent: MouseEvent,
|
||||||
addConnection,
|
contextConnection: Object as Prop<ConnectionParams>
|
||||||
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` : ''
|
|
||||||
};
|
|
||||||
|
|
||||||
this.addConnection(connectionCopy);
|
const emit = defineEmits(['close-context']);
|
||||||
this.closeContext();
|
|
||||||
},
|
const isConfirmModal = ref(false);
|
||||||
showConfirmModal () {
|
|
||||||
this.isConfirmModal = true;
|
const connectionName = computed(() => getConnectionName(props.contextConnection.uid));
|
||||||
},
|
|
||||||
hideConfirmModal () {
|
const confirmDeleteConnection = () => {
|
||||||
this.isConfirmModal = false;
|
if (selectedWorkspace.value === props.contextConnection.uid)
|
||||||
this.closeContext();
|
selectWorkspace(null);
|
||||||
},
|
deleteConnection(props.contextConnection);
|
||||||
closeContext () {
|
closeContext();
|
||||||
this.$emit('close-context');
|
};
|
||||||
}
|
|
||||||
}
|
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>
|
</script>
|
||||||
|
|
|
@ -26,46 +26,39 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { shell } from 'electron';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
import { useApplicationStore } from '@/stores/application';
|
import { useApplicationStore } from '@/stores/application';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import { storeToRefs } from 'pinia';
|
import { computed, ComputedRef } from 'vue';
|
||||||
const { shell } = require('electron');
|
|
||||||
|
|
||||||
export default {
|
interface DatabaseInfos {// TODO: temp
|
||||||
name: 'TheFooter',
|
name: string;
|
||||||
setup () {
|
number: string;
|
||||||
const applicationStore = useApplicationStore();
|
arch: string;
|
||||||
const workspacesStore = useWorkspacesStore();
|
os: string;
|
||||||
|
}
|
||||||
|
|
||||||
const { getSelected: workspace } = storeToRefs(workspacesStore);
|
const applicationStore = useApplicationStore();
|
||||||
|
const workspacesStore = useWorkspacesStore();
|
||||||
|
|
||||||
const { appVersion, showSettingModal } = applicationStore;
|
const { getSelected: workspace } = storeToRefs(workspacesStore);
|
||||||
const { getWorkspace } = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const { showSettingModal } = applicationStore;
|
||||||
appVersion,
|
const { getWorkspace } = workspacesStore;
|
||||||
showSettingModal,
|
|
||||||
workspace,
|
const version: ComputedRef<DatabaseInfos> = computed(() => {
|
||||||
getWorkspace
|
return getWorkspace(workspace.value) ? getWorkspace(workspace.value).version : null;
|
||||||
};
|
});
|
||||||
},
|
|
||||||
computed: {
|
const versionString = computed(() => {
|
||||||
version () {
|
if (version.value)
|
||||||
return this.getWorkspace(this.workspace) ? this.getWorkspace(this.workspace).version : null;
|
return `${version.value.name} ${version.value.number} (${version.value.arch} ${version.value.os})`;
|
||||||
},
|
return '';
|
||||||
versionString () {
|
});
|
||||||
if (this.version)
|
|
||||||
return `${this.version.name} ${this.version.number} (${this.version.arch} ${this.version.os})`;
|
const openOutside = (link: string) => shell.openExternal(link);
|
||||||
return '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
openOutside (link) {
|
|
||||||
shell.openExternal(link);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -16,71 +16,51 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { computed, Ref, ref, watch } from 'vue';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useSettingsStore } from '@/stores/settings';
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
import BaseNotification from '@/components/BaseNotification';
|
import BaseNotification from '@/components/BaseNotification.vue';
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
|
|
||||||
export default {
|
const notificationsStore = useNotificationsStore();
|
||||||
name: 'TheNotificationsBoard',
|
const settingsStore = useSettingsStore();
|
||||||
components: {
|
|
||||||
BaseNotification
|
|
||||||
},
|
|
||||||
setup () {
|
|
||||||
const notificationsStore = useNotificationsStore();
|
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
|
|
||||||
const { removeNotification } = notificationsStore;
|
const { removeNotification } = notificationsStore;
|
||||||
|
|
||||||
const { notifications } = storeToRefs(notificationsStore);
|
const { notifications } = storeToRefs(notificationsStore);
|
||||||
const { notificationsTimeout } = storeToRefs(settingsStore);
|
const { notificationsTimeout } = storeToRefs(settingsStore);
|
||||||
|
|
||||||
return {
|
const timeouts: Ref<{[key: string]: NodeJS.Timeout}> = ref({});
|
||||||
removeNotification,
|
|
||||||
notifications,
|
const latestNotifications = computed(() => notifications.value.slice(0, 10));
|
||||||
notificationsTimeout
|
|
||||||
};
|
watch(() => notifications.value.length, (val) => {
|
||||||
},
|
if (val > 0) {
|
||||||
data () {
|
const nUid: string = notifications.value[0].uid;
|
||||||
return {
|
timeouts.value[nUid] = setTimeout(() => {
|
||||||
timeouts: {}
|
removeNotification(nUid);
|
||||||
};
|
delete timeouts.value[nUid];
|
||||||
},
|
}, notificationsTimeout.value * 1000);
|
||||||
computed: {
|
}
|
||||||
latestNotifications () {
|
});
|
||||||
return this.notifications.slice(0, 10);
|
|
||||||
}
|
const clearTimeouts = () => {
|
||||||
},
|
for (const uid in timeouts.value) {
|
||||||
watch: {
|
clearTimeout(timeouts.value[uid]);
|
||||||
'notifications.length': function (val) {
|
delete timeouts.value[uid];
|
||||||
if (val > 0) {
|
}
|
||||||
const nUid = this.notifications[0].uid;
|
};
|
||||||
this.timeouts[nUid] = setTimeout(() => {
|
|
||||||
this.removeNotification(nUid);
|
const rearmTimeouts = () => {
|
||||||
delete this.timeouts[nUid];
|
const delay = 50;
|
||||||
}, this.notificationsTimeout * 1000);
|
let i = notifications.value.length * delay;
|
||||||
}
|
for (const notification of notifications.value) {
|
||||||
}
|
timeouts.value[notification.uid] = setTimeout(() => {
|
||||||
},
|
removeNotification(notification.uid);
|
||||||
methods: {
|
delete timeouts.value[notification.uid];
|
||||||
clearTimeouts () {
|
}, (notificationsTimeout.value * 1000) + i);
|
||||||
for (const uid in this.timeouts) {
|
i = i > delay ? i - delay : 0;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -28,55 +28,30 @@
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { ref, Ref, watch } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useApplicationStore } from '@/stores/application';
|
import { useApplicationStore } from '@/stores/application';
|
||||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
|
||||||
import TextEditor from '@/components/BaseTextEditor';
|
|
||||||
import { useScratchpadStore } from '@/stores/scratchpad';
|
import { useScratchpadStore } from '@/stores/scratchpad';
|
||||||
|
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||||
|
import TextEditor from '@/components/BaseTextEditor.vue';
|
||||||
|
|
||||||
export default {
|
const applicationStore = useApplicationStore();
|
||||||
name: 'TheScratchpad',
|
const scratchpadStore = useScratchpadStore();
|
||||||
components: {
|
|
||||||
ConfirmModal,
|
|
||||||
TextEditor
|
|
||||||
},
|
|
||||||
emits: ['hide'],
|
|
||||||
setup () {
|
|
||||||
const applicationStore = useApplicationStore();
|
|
||||||
const scratchpadStore = useScratchpadStore();
|
|
||||||
|
|
||||||
const { notes } = storeToRefs(scratchpadStore);
|
const { notes } = storeToRefs(scratchpadStore);
|
||||||
const { changeNotes } = scratchpadStore;
|
const { changeNotes } = scratchpadStore;
|
||||||
|
const { hideScratchpad } = applicationStore;
|
||||||
|
|
||||||
return {
|
const localNotes = ref(notes.value);
|
||||||
notes,
|
const debounceTimeout: Ref<NodeJS.Timeout> = ref(null);
|
||||||
hideScratchpad: applicationStore.hideScratchpad,
|
|
||||||
changeNotes
|
watch(localNotes, () => {
|
||||||
};
|
clearTimeout(debounceTimeout.value);
|
||||||
},
|
|
||||||
data () {
|
debounceTimeout.value = setTimeout(() => {
|
||||||
return {
|
changeNotes(localNotes.value);
|
||||||
localNotes: '',
|
}, 200);
|
||||||
debounceTimeout: null
|
});
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
localNotes () {
|
|
||||||
clearTimeout(this.debounceTimeout);
|
|
||||||
|
|
||||||
this.debounceTimeout = setTimeout(() => {
|
|
||||||
this.changeNotes(this.localNotes);
|
|
||||||
}, 200);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.localNotes = this.notes;
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
hideModal () {
|
|
||||||
this.$emit('hide');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -55,111 +55,84 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { ref, Ref, computed } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useApplicationStore } from '@/stores/application';
|
import { useApplicationStore } from '@/stores/application';
|
||||||
import { useConnectionsStore } from '@/stores/connections';
|
import { useConnectionsStore } from '@/stores/connections';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import Draggable from 'vuedraggable';
|
import * as Draggable from 'vuedraggable';
|
||||||
import SettingBarContext from '@/components/SettingBarContext';
|
import SettingBarContext from '@/components/SettingBarContext.vue';
|
||||||
|
import { ConnectionParams } from 'common/interfaces/antares';
|
||||||
|
|
||||||
export default {
|
const applicationStore = useApplicationStore();
|
||||||
name: 'TheSettingBar',
|
const connectionsStore = useConnectionsStore();
|
||||||
components: {
|
const workspacesStore = useWorkspacesStore();
|
||||||
Draggable,
|
|
||||||
SettingBarContext
|
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 () {
|
set (value: ConnectionParams[]) {
|
||||||
const applicationStore = useApplicationStore();
|
updateConnections(value);
|
||||||
const connectionsStore = useConnectionsStore();
|
}
|
||||||
const workspacesStore = useWorkspacesStore();
|
});
|
||||||
|
|
||||||
const { updateStatus } = storeToRefs(applicationStore);
|
const hasUpdates = computed(() => ['available', 'downloading', 'downloaded', 'link'].includes(updateStatus.value));
|
||||||
const { connections: getConnections } = storeToRefs(connectionsStore);
|
|
||||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
|
||||||
|
|
||||||
const { showSettingModal, showScratchpad } = applicationStore;
|
const contextMenu = (event: MouseEvent, connection: ConnectionParams) => {
|
||||||
const { getConnectionName, updateConnections } = connectionsStore;
|
contextEvent.value = event;
|
||||||
const { getWorkspace, selectWorkspace } = workspacesStore;
|
contextConnection.value = connection;
|
||||||
|
isContext.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
const tooltipPosition = (e: Event) => {
|
||||||
applicationStore,
|
const el = e.target ? e.target : e;
|
||||||
updateStatus,
|
const fromTop = isLinux
|
||||||
showSettingModal,
|
? window.scrollY + (el as HTMLElement).getBoundingClientRect().top + ((el as HTMLElement).offsetHeight / 4)
|
||||||
showScratchpad,
|
: window.scrollY + (el as HTMLElement).getBoundingClientRect().top - ((el as HTMLElement).offsetHeight / 4);
|
||||||
getConnections,
|
(el as HTMLElement).querySelector<HTMLElement>('.ex-tooltip-content').style.top = `${fromTop}px`;
|
||||||
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;
|
|
||||||
|
|
||||||
switch (status) {
|
const getStatusBadge = (uid: string) => {
|
||||||
case 'connected':
|
if (getWorkspace(uid)) {
|
||||||
return 'badge badge-connected';
|
const status = getWorkspace(uid).connectionStatus;
|
||||||
case 'connecting':
|
|
||||||
return 'badge badge-connecting';
|
|
||||||
case 'failed':
|
|
||||||
return 'badge badge-failed';
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dragStop (e) {
|
|
||||||
this.isDragging = false;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
switch (status) {
|
||||||
this.tooltipPosition(e.originalEvent.target.parentNode);
|
case 'connected':
|
||||||
}, 200);
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<img
|
<img
|
||||||
v-if="!isMacOS"
|
v-if="!isMacOS"
|
||||||
class="titlebar-logo"
|
class="titlebar-logo"
|
||||||
src="@/images/logo.svg"
|
:src="appIcon"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="titlebar-elements titlebar-title">
|
<div class="titlebar-elements titlebar-title">
|
||||||
|
@ -31,112 +31,70 @@
|
||||||
<i class="mdi mdi-24px mdi-refresh" />
|
<i class="mdi mdi-24px mdi-refresh" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isWindows" style="width: 140px;" />
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { ipcRenderer } from 'electron';
|
import { computed, onUnmounted, ref } from 'vue';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
import { getCurrentWindow } from '@electron/remote';
|
import { getCurrentWindow } from '@electron/remote';
|
||||||
import { useConnectionsStore } from '@/stores/connections';
|
import { useConnectionsStore } from '@/stores/connections';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import { storeToRefs } from 'pinia';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
name: 'TheTitleBar',
|
|
||||||
setup () {
|
|
||||||
const { getConnectionName } = useConnectionsStore();
|
|
||||||
const workspacesStore = useWorkspacesStore();
|
|
||||||
|
|
||||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const { getConnectionName } = useConnectionsStore();
|
||||||
|
const workspacesStore = useWorkspacesStore();
|
||||||
|
|
||||||
const { getWorkspace } = workspacesStore;
|
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||||
|
|
||||||
return {
|
const { getWorkspace } = workspacesStore;
|
||||||
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 connectionName = this.getConnectionName(this.selectedWorkspace);
|
const appIcon = require('@/images/logo.svg');
|
||||||
const workspace = this.getWorkspace(this.selectedWorkspace);
|
const w = ref(getCurrentWindow());
|
||||||
const breadcrumbs = Object.values(workspace.breadcrumbs).filter(breadcrumb => breadcrumb) || [workspace.client];
|
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(' • ');
|
const windowTitle = computed(() => {
|
||||||
}
|
if (!selectedWorkspace.value) return '';
|
||||||
},
|
if (selectedWorkspace.value === 'NEW') return t('message.createNewConnection');
|
||||||
watch: {
|
|
||||||
windowTitle: function (val) {
|
const connectionName = getConnectionName(selectedWorkspace.value);
|
||||||
ipcRenderer.send('change-window-title', val);
|
const workspace = getWorkspace(selectedWorkspace.value);
|
||||||
}
|
const breadcrumbs = Object.values(workspace.breadcrumbs).filter(breadcrumb => breadcrumb) || [workspace.client];
|
||||||
},
|
|
||||||
created () {
|
return [connectionName, ...breadcrumbs].join(' • ');
|
||||||
window.addEventListener('resize', this.onResize);
|
});
|
||||||
},
|
|
||||||
unmounted () {
|
const toggleFullScreen = () => {
|
||||||
window.removeEventListener('resize', this.onResize);
|
if (isMaximized.value)
|
||||||
},
|
w.value.unmaximize();
|
||||||
methods: {
|
else
|
||||||
closeApp () {
|
w.value.maximize();
|
||||||
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 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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -469,266 +469,196 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { computed, onBeforeUnmount, Prop, ref, watch } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import Draggable from 'vuedraggable';
|
import * as Draggable from 'vuedraggable';
|
||||||
import Connection from '@/ipc-api/Connection';
|
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 WorkspaceEmptyState from '@/components/WorkspaceEmptyState.vue';
|
||||||
import WorkspaceExploreBar from '@/components/WorkspaceExploreBar';
|
import WorkspaceExploreBar from '@/components/WorkspaceExploreBar.vue';
|
||||||
import WorkspaceEditConnectionPanel from '@/components/WorkspaceEditConnectionPanel';
|
import WorkspaceEditConnectionPanel from '@/components/WorkspaceEditConnectionPanel.vue';
|
||||||
import WorkspaceTabQuery from '@/components/WorkspaceTabQuery';
|
import WorkspaceTabQuery from '@/components/WorkspaceTabQuery.vue';
|
||||||
import WorkspaceTabTable from '@/components/WorkspaceTabTable';
|
import WorkspaceTabTable from '@/components/WorkspaceTabTable.vue';
|
||||||
|
|
||||||
import WorkspaceTabNewTable from '@/components/WorkspaceTabNewTable';
|
import WorkspaceTabNewTable from '@/components/WorkspaceTabNewTable.vue';
|
||||||
import WorkspaceTabNewView from '@/components/WorkspaceTabNewView';
|
import WorkspaceTabNewView from '@/components/WorkspaceTabNewView.vue';
|
||||||
import WorkspaceTabNewTrigger from '@/components/WorkspaceTabNewTrigger';
|
import WorkspaceTabNewTrigger from '@/components/WorkspaceTabNewTrigger.vue';
|
||||||
import WorkspaceTabNewRoutine from '@/components/WorkspaceTabNewRoutine';
|
import WorkspaceTabNewRoutine from '@/components/WorkspaceTabNewRoutine.vue';
|
||||||
import WorkspaceTabNewFunction from '@/components/WorkspaceTabNewFunction';
|
import WorkspaceTabNewFunction from '@/components/WorkspaceTabNewFunction.vue';
|
||||||
import WorkspaceTabNewScheduler from '@/components/WorkspaceTabNewScheduler';
|
import WorkspaceTabNewScheduler from '@/components/WorkspaceTabNewScheduler.vue';
|
||||||
import WorkspaceTabNewTriggerFunction from '@/components/WorkspaceTabNewTriggerFunction';
|
import WorkspaceTabNewTriggerFunction from '@/components/WorkspaceTabNewTriggerFunction.vue';
|
||||||
|
|
||||||
import WorkspaceTabPropsTable from '@/components/WorkspaceTabPropsTable';
|
import WorkspaceTabPropsTable from '@/components/WorkspaceTabPropsTable.vue';
|
||||||
import WorkspaceTabPropsView from '@/components/WorkspaceTabPropsView';
|
import WorkspaceTabPropsView from '@/components/WorkspaceTabPropsView.vue';
|
||||||
import WorkspaceTabPropsTrigger from '@/components/WorkspaceTabPropsTrigger';
|
import WorkspaceTabPropsTrigger from '@/components/WorkspaceTabPropsTrigger.vue';
|
||||||
import WorkspaceTabPropsTriggerFunction from '@/components/WorkspaceTabPropsTriggerFunction';
|
import WorkspaceTabPropsTriggerFunction from '@/components/WorkspaceTabPropsTriggerFunction.vue';
|
||||||
import WorkspaceTabPropsRoutine from '@/components/WorkspaceTabPropsRoutine';
|
import WorkspaceTabPropsRoutine from '@/components/WorkspaceTabPropsRoutine.vue';
|
||||||
import WorkspaceTabPropsFunction from '@/components/WorkspaceTabPropsFunction';
|
import WorkspaceTabPropsFunction from '@/components/WorkspaceTabPropsFunction.vue';
|
||||||
import WorkspaceTabPropsScheduler from '@/components/WorkspaceTabPropsScheduler';
|
import WorkspaceTabPropsScheduler from '@/components/WorkspaceTabPropsScheduler.vue';
|
||||||
import ModalProcessesList from '@/components/ModalProcessesList';
|
import ModalProcessesList from '@/components/ModalProcessesList.vue';
|
||||||
import ModalDiscardChanges from '@/components/ModalDiscardChanges';
|
import ModalDiscardChanges from '@/components/ModalDiscardChanges.vue';
|
||||||
|
|
||||||
export default {
|
const workspacesStore = useWorkspacesStore();
|
||||||
name: 'Workspace',
|
|
||||||
components: {
|
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||||
Draggable,
|
|
||||||
WorkspaceEmptyState,
|
const {
|
||||||
WorkspaceExploreBar,
|
getWorkspace,
|
||||||
WorkspaceEditConnectionPanel,
|
addWorkspace,
|
||||||
WorkspaceTabQuery,
|
connectWorkspace,
|
||||||
WorkspaceTabTable,
|
selectTab,
|
||||||
WorkspaceTabNewTable,
|
newTab,
|
||||||
WorkspaceTabPropsTable,
|
removeTab,
|
||||||
WorkspaceTabNewView,
|
updateTabs
|
||||||
WorkspaceTabPropsView,
|
} = workspacesStore;
|
||||||
WorkspaceTabNewTrigger,
|
|
||||||
WorkspaceTabPropsTrigger,
|
const props = defineProps({
|
||||||
WorkspaceTabNewTriggerFunction,
|
connection: Object as Prop<ConnectionParams>
|
||||||
WorkspaceTabPropsTriggerFunction,
|
});
|
||||||
WorkspaceTabNewRoutine,
|
|
||||||
WorkspaceTabNewFunction,
|
const hasWheelEvent = ref(false);
|
||||||
WorkspaceTabPropsRoutine,
|
const isProcessesModal = ref(false);
|
||||||
WorkspaceTabPropsFunction,
|
const unsavedTab = ref(null);
|
||||||
WorkspaceTabNewScheduler,
|
const tabWrap = ref(null);
|
||||||
WorkspaceTabPropsScheduler,
|
|
||||||
ModalProcessesList,
|
const workspace = computed(() => getWorkspace(props.connection.uid));
|
||||||
ModalDiscardChanges
|
|
||||||
|
const draggableTabs = computed<WorkspaceTab[]>({
|
||||||
|
get () {
|
||||||
|
return workspace.value.tabs;
|
||||||
},
|
},
|
||||||
props: {
|
set (val) {
|
||||||
connection: Object
|
updateTabs({ uid: props.connection.uid, tabs: val });
|
||||||
},
|
}
|
||||||
setup () {
|
});
|
||||||
const workspacesStore = useWorkspacesStore();
|
|
||||||
|
|
||||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const isSelected = computed(() => {
|
||||||
|
return selectedWorkspace.value === props.connection.uid;
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const selectedTab = computed(() => {
|
||||||
getWorkspace,
|
return workspace.value ? workspace.value.selectedTab : null;
|
||||||
addWorkspace,
|
});
|
||||||
connectWorkspace,
|
|
||||||
removeConnected,
|
|
||||||
selectTab,
|
|
||||||
newTab,
|
|
||||||
removeTab,
|
|
||||||
updateTabs,
|
|
||||||
selectNextTab,
|
|
||||||
selectPrevTab
|
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const queryTabs = computed(() => {
|
||||||
selectedWorkspace,
|
return workspace.value ? workspace.value.tabs.filter(tab => tab.type === 'query') : [];
|
||||||
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();
|
|
||||||
|
|
||||||
if (!this.isSelected)
|
const hasTools = computed(() => {
|
||||||
return;
|
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
|
watch(queryTabs, (newVal, oldVal) => {
|
||||||
this.addQueryTab();
|
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 addQueryTab = () => {
|
||||||
const currentTab = this.getSelectedTab();
|
newTab({ uid: props.connection.uid, type: 'query' });
|
||||||
if (currentTab)
|
};
|
||||||
this.closeTab(currentTab);
|
|
||||||
}
|
|
||||||
|
|
||||||
// select next tab
|
const getSelectedTab = () => {
|
||||||
if (e.altKey && (e.ctrlKey || e.metaKey) && e.key === 'ArrowRight')
|
return workspace.value.tabs.find(tab => tab.uid === selectedTab.value);
|
||||||
this.selectNextTab({ uid: this.connection.uid });
|
};
|
||||||
|
|
||||||
// select prev tab
|
const onKey = (e: KeyboardEvent) => {
|
||||||
if (e.altKey && (e.ctrlKey || e.metaKey) && e.key === 'ArrowLeft')
|
e.stopPropagation();
|
||||||
this.selectPrevTab({ uid: this.connection.uid });
|
|
||||||
|
|
||||||
// select tab by index (range 1-9). CTRL|CMD number
|
if (!isSelected.value)
|
||||||
if ((e.ctrlKey || e.metaKey) && !e.altKey && e.keyCode >= 49 && e.keyCode <= 57) {
|
return;
|
||||||
const newIndex = parseInt(e.key) - 1;
|
|
||||||
|
|
||||||
if (this.workspace.tabs[newIndex])
|
if ((e.ctrlKey || e.metaKey) && e.key === 't' && !e.altKey) { // CTRL|Command + t
|
||||||
this.selectTab({ uid: this.connection.uid, tab: this.workspace.tabs[newIndex].uid });
|
addQueryTab();
|
||||||
}
|
}
|
||||||
},
|
|
||||||
openAsPermanentTab (tab) {
|
|
||||||
const permanentTabs = {
|
|
||||||
table: 'data',
|
|
||||||
view: 'data',
|
|
||||||
trigger: 'trigger-props',
|
|
||||||
triggerFunction: 'trigger-function-props',
|
|
||||||
function: 'function-props',
|
|
||||||
routine: 'routine-props',
|
|
||||||
scheduler: 'scheduler-props'
|
|
||||||
};
|
|
||||||
|
|
||||||
this.newTab({
|
if ((e.ctrlKey || e.metaKey) && e.key === 'w' && !e.altKey) { // CTRL|Command + w
|
||||||
uid: this.connection.uid,
|
const currentTab = getSelectedTab();
|
||||||
schema: tab.schema,
|
if (currentTab)
|
||||||
elementName: tab.elementName,
|
closeTab(currentTab);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -8,23 +8,23 @@
|
||||||
:class="{'active': selectedTab === 'general'}"
|
:class="{'active': selectedTab === 'general'}"
|
||||||
@click="selectTab('general')"
|
@click="selectTab('general')"
|
||||||
>
|
>
|
||||||
<a class="tab-link">{{ $t('word.general') }}</a>
|
<a class="tab-link">{{ t('word.general') }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="customizations.sslConnection"
|
v-if="clientCustomizations.sslConnection"
|
||||||
class="tab-item c-hand"
|
class="tab-item c-hand"
|
||||||
:class="{'active': selectedTab === 'ssl'}"
|
:class="{'active': selectedTab === 'ssl'}"
|
||||||
@click="selectTab('ssl')"
|
@click="selectTab('ssl')"
|
||||||
>
|
>
|
||||||
<a class="tab-link">{{ $t('word.ssl') }}</a>
|
<a class="tab-link">{{ t('word.ssl') }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="customizations.sshConnection"
|
v-if="clientCustomizations.sshConnection"
|
||||||
class="tab-item c-hand"
|
class="tab-item c-hand"
|
||||||
:class="{'active': selectedTab === 'ssh'}"
|
:class="{'active': selectedTab === 'ssh'}"
|
||||||
@click="selectTab('ssh')"
|
@click="selectTab('ssh')"
|
||||||
>
|
>
|
||||||
<a class="tab-link">{{ $t('word.sshTunnel') }}</a>
|
<a class="tab-link">{{ t('word.sshTunnel') }}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
<fieldset class="m-0" :disabled="isBusy">
|
<fieldset class="m-0" :disabled="isBusy">
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-if="connection.client === 'pg'" class="form-group columns">
|
<div v-if="connection.client === 'pg'" class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -72,9 +72,9 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</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-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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -84,22 +84,22 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</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-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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
:model-value="connection.databasePath"
|
:model-value="connection.databasePath"
|
||||||
:message="$t('word.browse')"
|
:message="t('word.browse')"
|
||||||
@clear="pathClear('databasePath')"
|
@clear="pathClear('databasePath')"
|
||||||
@change="pathSelection($event, 'databasePath')"
|
@change="pathSelection($event, 'databasePath')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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-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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -111,9 +111,9 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -123,9 +123,9 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</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-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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -136,9 +136,9 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</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-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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -149,32 +149,32 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
v-model="connection.schema"
|
v-model="connection.schema"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
type="text"
|
type="text"
|
||||||
:placeholder="$t('word.all')"
|
:placeholder="t('word.all')"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</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-4 col-sm-12" />
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<label class="form-checkbox form-inline">
|
<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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</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-4 col-sm-12" />
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<label class="form-checkbox form-inline">
|
<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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -188,7 +188,7 @@
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label cut-text">
|
<label class="form-label cut-text">
|
||||||
{{ $t('message.enableSsl') }}
|
{{ t('message.enableSsl') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
|
@ -201,12 +201,12 @@
|
||||||
<fieldset class="m-0" :disabled="isBusy || !connection.ssl">
|
<fieldset class="m-0" :disabled="isBusy || !connection.ssl">
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
:model-value="connection.key"
|
:model-value="connection.key"
|
||||||
:message="$t('word.browse')"
|
:message="t('word.browse')"
|
||||||
@clear="pathClear('key')"
|
@clear="pathClear('key')"
|
||||||
@change="pathSelection($event, 'key')"
|
@change="pathSelection($event, 'key')"
|
||||||
/>
|
/>
|
||||||
|
@ -214,12 +214,12 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
:model-value="connection.cert"
|
:model-value="connection.cert"
|
||||||
:message="$t('word.browse')"
|
:message="t('word.browse')"
|
||||||
@clear="pathClear('cert')"
|
@clear="pathClear('cert')"
|
||||||
@change="pathSelection($event, 'cert')"
|
@change="pathSelection($event, 'cert')"
|
||||||
/>
|
/>
|
||||||
|
@ -227,12 +227,12 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
:model-value="connection.ca"
|
:model-value="connection.ca"
|
||||||
:message="$t('word.browse')"
|
:message="t('word.browse')"
|
||||||
@clear="pathClear('ca')"
|
@clear="pathClear('ca')"
|
||||||
@change="pathSelection($event, 'ca')"
|
@change="pathSelection($event, 'ca')"
|
||||||
/>
|
/>
|
||||||
|
@ -240,7 +240,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -255,7 +255,7 @@
|
||||||
<div class="column col-4 col-sm-12" />
|
<div class="column col-4 col-sm-12" />
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<label class="form-checkbox form-inline">
|
<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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -269,7 +269,7 @@
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label cut-text">
|
<label class="form-label cut-text">
|
||||||
{{ $t('message.enableSsh') }}
|
{{ t('message.enableSsh') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
|
@ -282,7 +282,7 @@
|
||||||
<fieldset class="m-0" :disabled="isBusy || !connection.ssh">
|
<fieldset class="m-0" :disabled="isBusy || !connection.ssh">
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -294,7 +294,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -306,7 +306,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -318,7 +318,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -332,12 +332,12 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
:model-value="connection.sshKey"
|
:model-value="connection.sshKey"
|
||||||
:message="$t('word.browse')"
|
:message="t('word.browse')"
|
||||||
@clear="pathClear('sshKey')"
|
@clear="pathClear('sshKey')"
|
||||||
@change="pathSelection($event, 'sshKey')"
|
@change="pathSelection($event, 'sshKey')"
|
||||||
/>
|
/>
|
||||||
|
@ -345,7 +345,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -368,7 +368,7 @@
|
||||||
@click="startTest"
|
@click="startTest"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-lightning-bolt mr-1" />
|
<i class="mdi mdi-24px mdi-lightning-bolt mr-1" />
|
||||||
{{ $t('message.testConnection') }}
|
{{ t('message.testConnection') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="connection-save"
|
id="connection-save"
|
||||||
|
@ -377,7 +377,7 @@
|
||||||
@click="saveConnection"
|
@click="saveConnection"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||||
{{ $t('word.save') }}
|
{{ t('word.save') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -389,188 +389,171 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { computed, Ref, ref, watch } from 'vue';
|
||||||
import customizations from 'common/customizations';
|
import customizations from 'common/customizations';
|
||||||
import Connection from '@/ipc-api/Connection';
|
import Connection from '@/ipc-api/Connection';
|
||||||
import { uidGen } from 'common/libs/uidGen';
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
import { useConnectionsStore } from '@/stores/connections';
|
import { useConnectionsStore } from '@/stores/connections';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import ModalAskCredentials from '@/components/ModalAskCredentials';
|
import ModalAskCredentials from '@/components/ModalAskCredentials.vue';
|
||||||
import BaseUploadInput from '@/components/BaseUploadInput';
|
import BaseUploadInput from '@/components/BaseUploadInput.vue';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
import { ConnectionParams } from 'common/interfaces/antares';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
name: 'WorkspaceAddConnectionPanel',
|
|
||||||
components: {
|
|
||||||
ModalAskCredentials,
|
|
||||||
BaseUploadInput,
|
|
||||||
BaseSelect
|
|
||||||
},
|
|
||||||
setup () {
|
|
||||||
const { addConnection } = useConnectionsStore();
|
|
||||||
const { addNotification } = useNotificationsStore();
|
|
||||||
const workspacesStore = useWorkspacesStore();
|
|
||||||
|
|
||||||
const { connectWorkspace, selectWorkspace } = workspacesStore;
|
const { addConnection } = useConnectionsStore();
|
||||||
|
const { addNotification } = useNotificationsStore();
|
||||||
|
const workspacesStore = useWorkspacesStore();
|
||||||
|
|
||||||
return {
|
const { connectWorkspace, selectWorkspace } = workspacesStore;
|
||||||
addConnection,
|
|
||||||
addNotification,
|
const clients = ref([
|
||||||
connectWorkspace,
|
{ name: 'MySQL', slug: 'mysql' },
|
||||||
selectWorkspace
|
{ name: 'MariaDB', slug: 'maria' },
|
||||||
};
|
{ name: 'PostgreSQL', slug: 'pg' },
|
||||||
},
|
{ name: 'SQLite', slug: 'sqlite' }
|
||||||
data () {
|
]);
|
||||||
return {
|
|
||||||
clients: [
|
const connection = ref({
|
||||||
{ name: 'MySQL', slug: 'mysql' },
|
name: '',
|
||||||
{ name: 'MariaDB', slug: 'maria' },
|
client: 'mysql',
|
||||||
{ name: 'PostgreSQL', slug: 'pg' },
|
host: '127.0.0.1',
|
||||||
{ name: 'SQLite', slug: 'sqlite' }
|
database: null,
|
||||||
],
|
databasePath: '',
|
||||||
connection: {
|
port: null,
|
||||||
name: '',
|
user: null,
|
||||||
client: 'mysql',
|
password: '',
|
||||||
host: '127.0.0.1',
|
ask: false,
|
||||||
database: null,
|
readonly: false,
|
||||||
databasePath: '',
|
uid: uidGen('C'),
|
||||||
port: null,
|
ssl: false,
|
||||||
user: null,
|
cert: '',
|
||||||
password: '',
|
key: '',
|
||||||
ask: false,
|
ca: '',
|
||||||
readonly: false,
|
ciphers: '',
|
||||||
uid: uidGen('C'),
|
untrustedConnection: false,
|
||||||
ssl: false,
|
ssh: false,
|
||||||
cert: '',
|
sshHost: '',
|
||||||
key: '',
|
sshUser: '',
|
||||||
ca: '',
|
sshPass: '',
|
||||||
ciphers: '',
|
sshKey: '',
|
||||||
untrustedConnection: false,
|
sshPort: 22,
|
||||||
ssh: false,
|
pgConnString: ''
|
||||||
sshHost: '',
|
}) as Ref<ConnectionParams & { pgConnString: string }>;
|
||||||
sshUser: '',
|
|
||||||
sshPass: '',
|
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||||
sshKey: '',
|
const isConnecting = ref(false);
|
||||||
sshPort: 22,
|
const isTesting = ref(false);
|
||||||
pgConnString: ''
|
const isAsking = ref(false);
|
||||||
},
|
const selectedTab = ref('general');
|
||||||
isConnecting: false,
|
|
||||||
isTesting: false,
|
const clientCustomizations = computed(() => {
|
||||||
isAsking: false,
|
return customizations[connection.value.client];
|
||||||
selectedTab: 'general'
|
});
|
||||||
};
|
|
||||||
},
|
const isBusy = computed(() => {
|
||||||
computed: {
|
return isConnecting.value || isTesting.value;
|
||||||
customizations () {
|
});
|
||||||
return customizations[this.connection.client];
|
|
||||||
},
|
watch(() => connection.value.client, () => {
|
||||||
isBusy () {
|
connection.value.user = clientCustomizations.value.defaultUser;
|
||||||
return this.isConnecting || this.isTesting;
|
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') });
|
||||||
}
|
}
|
||||||
},
|
catch (err) {
|
||||||
watch: {
|
addNotification({ status: 'error', message: err.stack });
|
||||||
'connection.client' () {
|
|
||||||
this.connection.user = this.customizations.defaultUser;
|
|
||||||
this.connection.port = this.customizations.defaultPort;
|
|
||||||
this.connection.database = this.customizations.defaultDatabase;
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.setDefaults();
|
|
||||||
|
|
||||||
setTimeout(() => {
|
isTesting.value = false;
|
||||||
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] = '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -8,23 +8,23 @@
|
||||||
:class="{'active': selectedTab === 'general'}"
|
:class="{'active': selectedTab === 'general'}"
|
||||||
@click="selectTab('general')"
|
@click="selectTab('general')"
|
||||||
>
|
>
|
||||||
<a class="tab-link">{{ $t('word.general') }}</a>
|
<a class="tab-link">{{ t('word.general') }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="customizations.sslConnection"
|
v-if="clientCustomizations.sslConnection"
|
||||||
class="tab-item c-hand"
|
class="tab-item c-hand"
|
||||||
:class="{'active': selectedTab === 'ssl'}"
|
:class="{'active': selectedTab === 'ssl'}"
|
||||||
@click="selectTab('ssl')"
|
@click="selectTab('ssl')"
|
||||||
>
|
>
|
||||||
<a class="tab-link">{{ $t('word.ssl') }}</a>
|
<a class="tab-link">{{ t('word.ssl') }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="customizations.sshConnection"
|
v-if="clientCustomizations.sshConnection"
|
||||||
class="tab-item c-hand"
|
class="tab-item c-hand"
|
||||||
:class="{'active': selectedTab === 'ssh'}"
|
:class="{'active': selectedTab === 'ssh'}"
|
||||||
@click="selectTab('ssh')"
|
@click="selectTab('ssh')"
|
||||||
>
|
>
|
||||||
<a class="tab-link">{{ $t('word.sshTunnel') }}</a>
|
<a class="tab-link">{{ t('word.sshTunnel') }}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
<fieldset class="m-0" :disabled="isBusy">
|
<fieldset class="m-0" :disabled="isBusy">
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
|
@ -63,7 +63,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-if="connection.client === 'pg'" class="form-group columns">
|
<div v-if="connection.client === 'pg'" class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -74,9 +74,9 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</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-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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -86,22 +86,22 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</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-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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
:model-value="localConnection.databasePath"
|
:model-value="localConnection.databasePath"
|
||||||
:message="$t('word.browse')"
|
:message="t('word.browse')"
|
||||||
@clear="pathClear('databasePath')"
|
@clear="pathClear('databasePath')"
|
||||||
@change="pathSelection($event, 'databasePath')"
|
@change="pathSelection($event, 'databasePath')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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-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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -113,9 +113,9 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -125,9 +125,9 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</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-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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -138,9 +138,9 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</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-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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -151,32 +151,32 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
v-model="localConnection.schema"
|
v-model="localConnection.schema"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
type="text"
|
type="text"
|
||||||
:placeholder="$t('word.all')"
|
:placeholder="t('word.all')"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</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-4 col-sm-12" />
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<label class="form-checkbox form-inline">
|
<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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</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-4 col-sm-12" />
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<label class="form-checkbox form-inline">
|
<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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -190,7 +190,7 @@
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label cut-text">
|
<label class="form-label cut-text">
|
||||||
{{ $t('message.enableSsl') }}
|
{{ t('message.enableSsl') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
|
@ -203,12 +203,12 @@
|
||||||
<fieldset class="m-0" :disabled="isBusy || !localConnection.ssl">
|
<fieldset class="m-0" :disabled="isBusy || !localConnection.ssl">
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
:model-value="localConnection.key"
|
:model-value="localConnection.key"
|
||||||
:message="$t('word.browse')"
|
:message="t('word.browse')"
|
||||||
@clear="pathClear('key')"
|
@clear="pathClear('key')"
|
||||||
@change="pathSelection($event, 'key')"
|
@change="pathSelection($event, 'key')"
|
||||||
/>
|
/>
|
||||||
|
@ -216,12 +216,12 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
:model-value="localConnection.cert"
|
:model-value="localConnection.cert"
|
||||||
:message="$t('word.browse')"
|
:message="t('word.browse')"
|
||||||
@clear="pathClear('cert')"
|
@clear="pathClear('cert')"
|
||||||
@change="pathSelection($event, 'cert')"
|
@change="pathSelection($event, 'cert')"
|
||||||
/>
|
/>
|
||||||
|
@ -229,12 +229,12 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
:model-value="localConnection.ca"
|
:model-value="localConnection.ca"
|
||||||
:message="$t('word.browse')"
|
:message="t('word.browse')"
|
||||||
@clear="pathClear('ca')"
|
@clear="pathClear('ca')"
|
||||||
@change="pathSelection($event, 'ca')"
|
@change="pathSelection($event, 'ca')"
|
||||||
/>
|
/>
|
||||||
|
@ -242,7 +242,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -263,7 +263,7 @@
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label cut-text">
|
<label class="form-label cut-text">
|
||||||
{{ $t('message.enableSsh') }}
|
{{ t('message.enableSsh') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
|
@ -276,7 +276,7 @@
|
||||||
<fieldset class="m-0" :disabled="isBusy || !localConnection.ssh">
|
<fieldset class="m-0" :disabled="isBusy || !localConnection.ssh">
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -288,7 +288,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -300,7 +300,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -312,7 +312,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -326,12 +326,12 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
:model-value="localConnection.sshKey"
|
:model-value="localConnection.sshKey"
|
||||||
:message="$t('word.browse')"
|
:message="t('word.browse')"
|
||||||
@clear="pathClear('sshKey')"
|
@clear="pathClear('sshKey')"
|
||||||
@change="pathSelection($event, 'sshKey')"
|
@change="pathSelection($event, 'sshKey')"
|
||||||
/>
|
/>
|
||||||
|
@ -339,7 +339,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<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>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
@ -362,7 +362,7 @@
|
||||||
@click="startTest"
|
@click="startTest"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-lightning-bolt mr-1" />
|
<i class="mdi mdi-24px mdi-lightning-bolt mr-1" />
|
||||||
{{ $t('message.testConnection') }}
|
{{ t('message.testConnection') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="connection-save"
|
id="connection-save"
|
||||||
|
@ -371,7 +371,7 @@
|
||||||
@click="saveConnection"
|
@click="saveConnection"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||||
{{ $t('word.save') }}
|
{{ t('word.save') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="connection-connect"
|
id="connection-connect"
|
||||||
|
@ -381,7 +381,7 @@
|
||||||
@click="startConnection"
|
@click="startConnection"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-connection mr-1" />
|
<i class="mdi mdi-24px mdi-connection mr-1" />
|
||||||
{{ $t('word.connect') }}
|
{{ t('word.connect') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -393,154 +393,150 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { computed, Prop, Ref, ref, watch } from 'vue';
|
||||||
import customizations from 'common/customizations';
|
import customizations from 'common/customizations';
|
||||||
import { useConnectionsStore } from '@/stores/connections';
|
import { useConnectionsStore } from '@/stores/connections';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import Connection from '@/ipc-api/Connection';
|
import Connection from '@/ipc-api/Connection';
|
||||||
import ModalAskCredentials from '@/components/ModalAskCredentials';
|
import ModalAskCredentials from '@/components/ModalAskCredentials.vue';
|
||||||
import BaseUploadInput from '@/components/BaseUploadInput';
|
import BaseUploadInput from '@/components/BaseUploadInput.vue';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
import { ConnectionParams } from 'common/interfaces/antares';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
name: 'WorkspaceEditConnectionPanel',
|
|
||||||
components: {
|
|
||||||
ModalAskCredentials,
|
|
||||||
BaseUploadInput,
|
|
||||||
BaseSelect
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
connection: Object
|
|
||||||
},
|
|
||||||
setup () {
|
|
||||||
const { editConnection } = useConnectionsStore();
|
|
||||||
const { addNotification } = useNotificationsStore();
|
|
||||||
const { connectWorkspace } = useWorkspacesStore();
|
|
||||||
|
|
||||||
return {
|
const props = defineProps({
|
||||||
editConnection,
|
connection: Object as Prop<ConnectionParams>
|
||||||
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;
|
|
||||||
|
|
||||||
if (this.localConnection.ask)
|
const { editConnection } = useConnectionsStore();
|
||||||
this.isAsking = true;
|
const { addNotification } = useNotificationsStore();
|
||||||
else {
|
const { connectWorkspace } = useWorkspacesStore();
|
||||||
await this.connectWorkspace(this.localConnection);
|
|
||||||
this.isConnecting = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async startTest () {
|
|
||||||
this.isTesting = true;
|
|
||||||
|
|
||||||
if (this.localConnection.ask)
|
const clients = ref([
|
||||||
this.isAsking = true;
|
{ name: 'MySQL', slug: 'mysql' },
|
||||||
else {
|
{ name: 'MariaDB', slug: 'maria' },
|
||||||
try {
|
{ name: 'PostgreSQL', slug: 'pg' },
|
||||||
const res = await Connection.makeTest(this.localConnection);
|
{ name: 'SQLite', slug: 'sqlite' }
|
||||||
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;
|
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||||
}
|
const localConnection: Ref<ConnectionParams & { pgConnString: string }> = ref(null);
|
||||||
},
|
const isConnecting = ref(false);
|
||||||
async continueTest (credentials) { // if "Ask for credentials" is true
|
const isTesting = ref(false);
|
||||||
this.isAsking = false;
|
const isAsking = ref(false);
|
||||||
const params = Object.assign({}, this.localConnection, credentials);
|
const selectedTab = ref('general');
|
||||||
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;
|
const clientCustomizations = computed(() => {
|
||||||
},
|
return customizations[localConnection.value.client];
|
||||||
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;
|
|
||||||
|
|
||||||
this.localConnection[name] = files[0].path;
|
const isBusy = computed(() => {
|
||||||
},
|
return isConnecting.value || isTesting.value;
|
||||||
pathClear (name) {
|
});
|
||||||
this.localConnection[name] = '';
|
|
||||||
}
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -1,61 +1,48 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="column col-12 empty">
|
<div class="column col-12 empty">
|
||||||
<div class="empty-icon">
|
<div class="empty-icon">
|
||||||
<img
|
<img :src="logos[applicationTheme]" width="200">
|
||||||
v-if="applicationTheme === 'dark'"
|
|
||||||
src="../images/logo-dark.svg"
|
|
||||||
width="200"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
v-if="applicationTheme === 'light'"
|
|
||||||
src="../images/logo-light.svg"
|
|
||||||
width="200"
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="h6 empty-subtitle">
|
<p class="h6 empty-subtitle">
|
||||||
{{ $t('message.noOpenTabs') }}
|
{{ t('message.noOpenTabs') }}
|
||||||
</p>
|
</p>
|
||||||
<div class="empty-action">
|
<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" />
|
<i class="mdi mdi-24px mdi-tab-plus mr-2" />
|
||||||
{{ $t('message.openNewTab') }}
|
{{ t('message.openNewTab') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
<script>
|
import { computed } from 'vue';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
import { useSettingsStore } from '@/stores/settings';
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import { storeToRefs } from 'pinia';
|
import { useI18n } from 'vue-i18n';
|
||||||
export default {
|
|
||||||
name: 'WorkspaceEmptyState',
|
|
||||||
emits: ['new-tab'],
|
|
||||||
setup () {
|
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
const workspacesStore = useWorkspacesStore();
|
|
||||||
|
|
||||||
const { applicationTheme } = storeToRefs(settingsStore);
|
const { t } = useI18n();
|
||||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
|
||||||
|
|
||||||
const { getWorkspace, changeBreadcrumbs } = workspacesStore;
|
const emit = defineEmits(['new-tab']);
|
||||||
|
|
||||||
return {
|
const logos = {
|
||||||
applicationTheme,
|
light: require('../images/logo-light.svg') as string,
|
||||||
selectedWorkspace,
|
dark: require('../images/logo-dark.svg') as string
|
||||||
getWorkspace,
|
|
||||||
changeBreadcrumbs
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
workspace () {
|
|
||||||
return this.getWorkspace(this.selectedWorkspace);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.changeBreadcrumbs({ schema: this.workspace.breadcrumbs.schema });
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-explorebar-body" @click="$refs.explorebar.focus()">
|
<div class="workspace-explorebar-body" @click="explorebar.focus()">
|
||||||
<WorkspaceExploreBarSchema
|
<WorkspaceExploreBarSchema
|
||||||
v-for="db of workspace.structure"
|
v-for="db of workspace.structure"
|
||||||
:key="db.name"
|
:key="db.name"
|
||||||
|
@ -115,7 +115,8 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { Component, computed, onMounted, Ref, ref, watch } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
|
|
||||||
import { useConnectionsStore } from '@/stores/connections';
|
import { useConnectionsStore } from '@/stores/connections';
|
||||||
|
@ -125,428 +126,290 @@ import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
|
|
||||||
import Tables from '@/ipc-api/Tables';
|
import Tables from '@/ipc-api/Tables';
|
||||||
import Views from '@/ipc-api/Views';
|
import Views from '@/ipc-api/Views';
|
||||||
import Functions from '@/ipc-api/Functions';
|
|
||||||
import Schedulers from '@/ipc-api/Schedulers';
|
|
||||||
|
|
||||||
import WorkspaceExploreBarSchema from '@/components/WorkspaceExploreBarSchema';
|
import WorkspaceExploreBarSchema from '@/components/WorkspaceExploreBarSchema.vue';
|
||||||
import DatabaseContext from '@/components/WorkspaceExploreBarSchemaContext';
|
import DatabaseContext from '@/components/WorkspaceExploreBarSchemaContext.vue';
|
||||||
import TableContext from '@/components/WorkspaceExploreBarTableContext';
|
import TableContext from '@/components/WorkspaceExploreBarTableContext.vue';
|
||||||
import MiscContext from '@/components/WorkspaceExploreBarMiscContext';
|
import MiscContext from '@/components/WorkspaceExploreBarMiscContext.vue';
|
||||||
import MiscFolderContext from '@/components/WorkspaceExploreBarMiscFolderContext';
|
import MiscFolderContext from '@/components/WorkspaceExploreBarMiscFolderContext.vue';
|
||||||
import ModalNewSchema from '@/components/ModalNewSchema';
|
import ModalNewSchema from '@/components/ModalNewSchema.vue';
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'WorkspaceExploreBar',
|
connection: Object,
|
||||||
components: {
|
isSelected: Boolean
|
||||||
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 { explorebarSize } = storeToRefs(settingsStore);
|
const { getConnectionName } = useConnectionsStore();
|
||||||
|
const { addNotification } = useNotificationsStore();
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
const workspacesStore = useWorkspacesStore();
|
||||||
|
|
||||||
const { changeExplorebarSize } = settingsStore;
|
const { explorebarSize } = storeToRefs(settingsStore);
|
||||||
const {
|
|
||||||
getWorkspace,
|
|
||||||
removeConnected: disconnectWorkspace,
|
|
||||||
refreshStructure,
|
|
||||||
changeBreadcrumbs,
|
|
||||||
selectTab,
|
|
||||||
newTab,
|
|
||||||
removeTabs,
|
|
||||||
setSearchTerm,
|
|
||||||
addLoadingElement,
|
|
||||||
removeLoadingElement
|
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const { changeExplorebarSize } = settingsStore;
|
||||||
getConnectionName,
|
const {
|
||||||
addNotification,
|
getWorkspace,
|
||||||
explorebarSize,
|
removeConnected: disconnectWorkspace,
|
||||||
changeExplorebarSize,
|
refreshStructure,
|
||||||
getWorkspace,
|
newTab,
|
||||||
disconnectWorkspace,
|
removeTabs,
|
||||||
refreshStructure,
|
setSearchTerm,
|
||||||
changeBreadcrumbs,
|
addLoadingElement,
|
||||||
selectTab,
|
removeLoadingElement
|
||||||
newTab,
|
} = workspacesStore;
|
||||||
removeTabs,
|
|
||||||
setSearchTerm,
|
|
||||||
addLoadingElement,
|
|
||||||
removeLoadingElement
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isRefreshing: false,
|
|
||||||
|
|
||||||
isNewDBModal: false,
|
const searchInput: Ref<HTMLInputElement> = ref(null);
|
||||||
isNewViewModal: false,
|
const explorebar: Ref<HTMLInputElement> = ref(null);
|
||||||
isNewTriggerModal: false,
|
const resizer: Ref<HTMLInputElement> = ref(null);
|
||||||
isNewRoutineModal: false,
|
const schema: Ref<Component & { selectSchema: (name: string) => void; $refs: {schemaAccordion: HTMLDetailsElement} }[]> = ref(null);
|
||||||
isNewFunctionModal: false,
|
const isRefreshing = ref(false);
|
||||||
isNewTriggerFunctionModal: false,
|
const isNewDBModal = ref(false);
|
||||||
isNewSchedulerModal: 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,
|
const workspace = computed(() => {
|
||||||
explorebarWidthInterval: null,
|
return getWorkspace(props.connection.uid);
|
||||||
searchTermInterval: null,
|
});
|
||||||
isDatabaseContext: false,
|
|
||||||
isTableContext: false,
|
|
||||||
isMiscContext: false,
|
|
||||||
isMiscFolderContext: false,
|
|
||||||
|
|
||||||
databaseContextEvent: null,
|
const connectionName = computed(() => {
|
||||||
tableContextEvent: null,
|
return getConnectionName(props.connection.uid);
|
||||||
miscContextEvent: null,
|
});
|
||||||
|
|
||||||
selectedSchema: '',
|
const customizations = computed(() => {
|
||||||
selectedTable: null,
|
return workspace.value.customizations;
|
||||||
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);
|
|
||||||
|
|
||||||
this.explorebarWidthInterval = setTimeout(() => {
|
watch(localWidth, (val: number) => {
|
||||||
this.changeExplorebarSize(val);
|
clearTimeout(explorebarWidthInterval.value);
|
||||||
}, 500);
|
|
||||||
},
|
|
||||||
isSelected (val) {
|
|
||||||
if (val) this.localWidth = this.explorebarSize;
|
|
||||||
},
|
|
||||||
searchTerm () {
|
|
||||||
clearTimeout(this.searchTermInterval);
|
|
||||||
|
|
||||||
this.searchTermInterval = setTimeout(() => {
|
explorebarWidthInterval.value = setTimeout(() => {
|
||||||
this.setSearchTerm(this.searchTerm);
|
changeExplorebarSize(val);
|
||||||
}, 200);
|
}, 500);
|
||||||
}
|
});
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.localWidth = this.explorebarSize;
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
const resizer = this.$refs.resizer;
|
|
||||||
|
|
||||||
resizer.addEventListener('mousedown', e => {
|
watch(() => props.isSelected, (val: boolean) => {
|
||||||
e.preventDefault();
|
if (val) localWidth.value = explorebarSize.value;
|
||||||
|
});
|
||||||
|
|
||||||
window.addEventListener('mousemove', this.resize);
|
watch(searchTerm, () => {
|
||||||
window.addEventListener('mouseup', this.stopResize);
|
clearTimeout(searchTermInterval.value);
|
||||||
});
|
|
||||||
|
|
||||||
if (this.workspace.structure.length === 1) { // Auto-open if juust one schema
|
searchTermInterval.value = setTimeout(() => {
|
||||||
this.$refs.schema[0].selectSchema(this.workspace.structure[0].name);
|
setSearchTerm(searchTerm.value);
|
||||||
this.$refs.schema[0].$refs.schemaAccordion.open = true;
|
}, 200);
|
||||||
}
|
});
|
||||||
},
|
|
||||||
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;
|
|
||||||
|
|
||||||
this.$refs.searchInput.focus();
|
localWidth.value = explorebarSize.value;
|
||||||
},
|
|
||||||
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();
|
|
||||||
|
|
||||||
this.newTab({
|
onMounted(() => {
|
||||||
uid: this.workspace.uid,
|
resizer.value.addEventListener('mousedown', (e: MouseEvent) => {
|
||||||
schema: this.selectedSchema,
|
e.preventDefault();
|
||||||
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();
|
|
||||||
|
|
||||||
this.addLoadingElement({
|
window.addEventListener('mousemove', resize);
|
||||||
name: payload.table.name,
|
window.addEventListener('mouseup', stopResize);
|
||||||
schema: payload.schema,
|
});
|
||||||
type: 'table'
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
if (workspace.value.structure.length === 1) { // Auto-open if juust one schema
|
||||||
let res;
|
schema.value[0].selectSchema(workspace.value.structure[0].name);
|
||||||
|
schema.value[0].$refs.schemaAccordion.open = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (payload.table.type === 'table') {
|
const refresh = async () => {
|
||||||
res = await Tables.dropTable({
|
if (!isRefreshing.value) {
|
||||||
uid: this.connection.uid,
|
isRefreshing.value = true;
|
||||||
table: payload.table.name,
|
await refreshStructure(props.connection.uid);
|
||||||
schema: payload.schema
|
isRefreshing.value = false;
|
||||||
});
|
|
||||||
}
|
|
||||||
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 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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
@close-context="closeContext"
|
@close-context="closeContext"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="['procedure', 'function'].includes(selectedMisc.type)"
|
v-if="['procedure', 'routine', 'function'].includes(selectedMisc.type)"
|
||||||
class="context-element"
|
class="context-element"
|
||||||
@click="runElementCheck"
|
@click="runElementCheck"
|
||||||
>
|
>
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
<ModalAskParameters
|
<ModalAskParameters
|
||||||
v-if="isAskingParameters"
|
v-if="isAskingParameters"
|
||||||
:local-routine="localElement"
|
:local-routine="(localElement as any)"
|
||||||
:client="workspace.client"
|
:client="workspace.client"
|
||||||
@confirm="runElement"
|
@confirm="runElement"
|
||||||
@close="hideAskParamsModal"
|
@close="hideAskParamsModal"
|
||||||
|
@ -64,324 +64,333 @@
|
||||||
</BaseContextMenu>
|
</BaseContextMenu>
|
||||||
</template>
|
</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 { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
||||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||||
import ModalAskParameters from '@/components/ModalAskParameters';
|
import ModalAskParameters from '@/components/ModalAskParameters.vue';
|
||||||
import Triggers from '@/ipc-api/Triggers';
|
import Triggers from '@/ipc-api/Triggers';
|
||||||
import Routines from '@/ipc-api/Routines';
|
import Routines from '@/ipc-api/Routines';
|
||||||
import Functions from '@/ipc-api/Functions';
|
import Functions from '@/ipc-api/Functions';
|
||||||
import Schedulers from '@/ipc-api/Schedulers';
|
import Schedulers from '@/ipc-api/Schedulers';
|
||||||
import { storeToRefs } from 'pinia';
|
import { EventInfos, FunctionInfos, IpcResponse, RoutineInfos, TriggerInfos } from 'common/interfaces/antares';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
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 { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const props = defineProps({
|
||||||
|
contextEvent: MouseEvent,
|
||||||
|
selectedMisc: Object as Prop<{ name:string; type:string; enabled?: boolean }>,
|
||||||
|
selectedSchema: String
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const emit = defineEmits(['close-context', 'reload']);
|
||||||
getWorkspace,
|
|
||||||
changeBreadcrumbs,
|
|
||||||
addLoadingElement,
|
|
||||||
removeLoadingElement,
|
|
||||||
removeTabs,
|
|
||||||
newTab
|
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const { addNotification } = useNotificationsStore();
|
||||||
addNotification,
|
const workspacesStore = useWorkspacesStore();
|
||||||
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;
|
|
||||||
|
|
||||||
switch (this.selectedMisc.type) {
|
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||||
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 { status, response } = res;
|
const {
|
||||||
|
getWorkspace,
|
||||||
|
addLoadingElement,
|
||||||
|
removeLoadingElement,
|
||||||
|
removeTabs,
|
||||||
|
newTab
|
||||||
|
} = workspacesStore;
|
||||||
|
|
||||||
if (status === 'success') {
|
const isDeleteModal = ref(false);
|
||||||
this.removeTabs({
|
const isAskingParameters = ref(false);
|
||||||
uid: this.selectedWorkspace,
|
const localElement: Ref<TriggerInfos | RoutineInfos | FunctionInfos | EventInfos> = ref(null);
|
||||||
elementName: this.selectedMisc.name,
|
|
||||||
elementType: this.selectedMisc.type,
|
|
||||||
schema: this.selectedSchema
|
|
||||||
});
|
|
||||||
|
|
||||||
this.closeContext();
|
const workspace = computed(() => {
|
||||||
this.$emit('reload');
|
return getWorkspace(selectedWorkspace.value);
|
||||||
}
|
});
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
const customizations = computed(() => {
|
||||||
const { status, response } = await Routines.getRoutineInformations(params);
|
return getWorkspace(selectedWorkspace.value).customizations;
|
||||||
if (status === 'success')
|
});
|
||||||
this.localElement = response;
|
|
||||||
|
|
||||||
else
|
const deleteMessage = computed(() => {
|
||||||
this.addNotification({ status: 'error', message: response });
|
switch (props.selectedMisc.type) {
|
||||||
}
|
case 'trigger':
|
||||||
catch (err) {
|
return t('message.deleteTrigger');
|
||||||
this.addNotification({ status: 'error', message: err.stack });
|
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)
|
const showDeleteModal = () => {
|
||||||
this.showAskParamsModal();
|
isDeleteModal.value = true;
|
||||||
else
|
};
|
||||||
this.runRoutine();
|
|
||||||
},
|
|
||||||
runRoutine (params) {
|
|
||||||
if (!params) params = [];
|
|
||||||
|
|
||||||
let sql;
|
const hideDeleteModal = () => {
|
||||||
switch (this.workspace.client) { // TODO: move in a better place
|
isDeleteModal.value = false;
|
||||||
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(',')})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.newTab({ uid: this.workspace.uid, content: sql, type: 'query', autorun: true });
|
const showAskParamsModal = () => {
|
||||||
this.closeContext();
|
isAskingParameters.value = true;
|
||||||
},
|
};
|
||||||
async runFunctionCheck () {
|
|
||||||
const params = {
|
|
||||||
uid: this.selectedWorkspace,
|
|
||||||
schema: this.selectedSchema,
|
|
||||||
func: this.selectedMisc.name
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
const hideAskParamsModal = () => {
|
||||||
const { status, response } = await Functions.getFunctionInformations(params);
|
isAskingParameters.value = false;
|
||||||
if (status === 'success')
|
closeContext();
|
||||||
this.localElement = response;
|
};
|
||||||
else
|
|
||||||
this.addNotification({ status: 'error', message: response });
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
this.addNotification({ status: 'error', message: err.stack });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.localElement.parameters.length)
|
const closeContext = () => {
|
||||||
this.showAskParamsModal();
|
emit('close-context');
|
||||||
else
|
};
|
||||||
this.runFunction();
|
|
||||||
},
|
|
||||||
runFunction (params) {
|
|
||||||
if (!params) params = [];
|
|
||||||
|
|
||||||
let sql;
|
const deleteMisc = async () => {
|
||||||
switch (this.workspace.client) { // TODO: move in a better place
|
try {
|
||||||
case 'maria':
|
let res: IpcResponse;
|
||||||
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(',')})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.newTab({ uid: this.workspace.uid, content: sql, type: 'query', autorun: true });
|
switch (props.selectedMisc.type) {
|
||||||
this.closeContext();
|
case 'trigger':
|
||||||
},
|
res = await Triggers.dropTrigger({
|
||||||
async toggleTrigger () {
|
uid: selectedWorkspace.value,
|
||||||
this.addLoadingElement({
|
schema: props.selectedSchema,
|
||||||
name: this.selectedMisc.name,
|
trigger: props.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
|
|
||||||
});
|
});
|
||||||
|
break;
|
||||||
if (status !== 'success')
|
case 'routine':
|
||||||
this.addNotification({ status: 'error', message: response });
|
case 'procedure':
|
||||||
}
|
res = await Routines.dropRoutine({
|
||||||
catch (err) {
|
uid: selectedWorkspace.value,
|
||||||
this.addNotification({ status: 'error', message: err.stack });
|
schema: props.selectedSchema,
|
||||||
}
|
routine: props.selectedMisc.name
|
||||||
|
|
||||||
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 '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')
|
console.log(res);
|
||||||
this.addNotification({ status: 'error', message: response });
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
this.addNotification({ status: 'error', message: err.stack });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.removeLoadingElement({
|
const { status, response } = res;
|
||||||
name: this.selectedMisc.name,
|
|
||||||
schema: this.selectedSchema,
|
if (status === 'success') {
|
||||||
type: 'scheduler'
|
removeTabs({
|
||||||
|
uid: selectedWorkspace.value,
|
||||||
|
elementName: props.selectedMisc.name,
|
||||||
|
elementType: props.selectedMisc.type,
|
||||||
|
schema: props.selectedSchema
|
||||||
});
|
});
|
||||||
|
|
||||||
this.closeContext();
|
closeContext();
|
||||||
this.$emit('reload');
|
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>
|
</script>
|
||||||
|
|
|
@ -1,112 +1,68 @@
|
||||||
<template>
|
<template>
|
||||||
<BaseContextMenu
|
<BaseContextMenu
|
||||||
:context-event="contextEvent"
|
:context-event="props.contextEvent"
|
||||||
@close-context="closeContext"
|
@close-context="closeContext"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="selectedMisc === 'trigger'"
|
v-if="props.selectedMisc === 'trigger'"
|
||||||
class="context-element"
|
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>
|
||||||
<div
|
<div
|
||||||
v-if="selectedMisc === 'procedure'"
|
v-if="['procedure', 'routine'].includes(props.selectedMisc)"
|
||||||
class="context-element"
|
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>
|
||||||
<div
|
<div
|
||||||
v-if="selectedMisc === 'function'"
|
v-if="props.selectedMisc === 'function'"
|
||||||
class="context-element"
|
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>
|
||||||
<div
|
<div
|
||||||
v-if="selectedMisc === 'triggerFunction'"
|
v-if="props.selectedMisc === 'triggerFunction'"
|
||||||
class="context-element"
|
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>
|
||||||
<div
|
<div
|
||||||
v-if="selectedMisc === 'scheduler'"
|
v-if="props.selectedMisc === 'scheduler'"
|
||||||
class="context-element"
|
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>
|
</div>
|
||||||
</BaseContextMenu>
|
</BaseContextMenu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
||||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
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 { 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 {
|
const closeContext = () => {
|
||||||
addNotification,
|
emit('close-context');
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<ul class="menu menu-nav pt-0">
|
<ul class="menu menu-nav pt-0">
|
||||||
<li
|
<li
|
||||||
v-for="table of filteredTables"
|
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"
|
:key="table.name"
|
||||||
class="menu-item"
|
class="menu-item"
|
||||||
:class="{'selected': breadcrumbs.schema === database.name && [breadcrumbs.table, breadcrumbs.view].includes(table.name)}"
|
:class="{'selected': breadcrumbs.schema === database.name && [breadcrumbs.table, breadcrumbs.view].includes(table.name)}"
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
@contextmenu.prevent="showMiscFolderContext($event, 'trigger')"
|
@contextmenu.prevent="showMiscFolderContext($event, 'trigger')"
|
||||||
>
|
>
|
||||||
<i class="misc-icon mdi mdi-18px mdi-folder-cog mr-1" />
|
<i class="misc-icon mdi mdi-18px mdi-folder-cog mr-1" />
|
||||||
{{ $tc('word.trigger', 2) }}
|
{{ t('word.trigger', 2) }}
|
||||||
</summary>
|
</summary>
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
<div>
|
<div>
|
||||||
|
@ -69,7 +69,7 @@
|
||||||
<li
|
<li
|
||||||
v-for="trigger of filteredTriggers"
|
v-for="trigger of filteredTriggers"
|
||||||
:key="trigger.name"
|
: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="menu-item"
|
||||||
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.trigger === trigger.name}"
|
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.trigger === trigger.name}"
|
||||||
@mousedown.left="selectMisc({schema: database.name, misc: trigger, type: 'trigger'})"
|
@mousedown.left="selectMisc({schema: database.name, misc: trigger, type: 'trigger'})"
|
||||||
|
@ -84,7 +84,7 @@
|
||||||
<div
|
<div
|
||||||
v-if="trigger.enabled === false"
|
v-if="trigger.enabled === false"
|
||||||
class="tooltip tooltip-left disabled-indicator"
|
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" />
|
<i class="table-icon mdi mdi-pause mdi-18px mr-1" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -100,27 +100,27 @@
|
||||||
<summary
|
<summary
|
||||||
class="accordion-header misc-name"
|
class="accordion-header misc-name"
|
||||||
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.routine}"
|
: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" />
|
<i class="misc-icon mdi mdi-18px mdi-folder-sync mr-1" />
|
||||||
{{ $tc('word.storedRoutine', 2) }}
|
{{ t('word.storedRoutine', 2) }}
|
||||||
</summary>
|
</summary>
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
<div>
|
<div>
|
||||||
<ul class="menu menu-nav pt-0">
|
<ul class="menu menu-nav pt-0">
|
||||||
<li
|
<li
|
||||||
v-for="(procedure, i) of filteredProcedures"
|
v-for="(routine, i) of filteredProcedures"
|
||||||
:key="`${procedure.name}-${i}`"
|
:key="`${routine.name}-${i}`"
|
||||||
:ref="breadcrumbs.schema === database.name && breadcrumbs.routine === procedure.name ? 'explorebar-selected' : ''"
|
:ref="breadcrumbs.schema === database.name && breadcrumbs.routine === routine.name ? 'explorebarSelected' : ''"
|
||||||
class="menu-item"
|
class="menu-item"
|
||||||
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.routine === procedure.name}"
|
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.routine === routine.name}"
|
||||||
@mousedown.left="selectMisc({schema: database.name, misc: procedure, type: 'routine'})"
|
@mousedown.left="selectMisc({schema: database.name, misc: routine, type: 'routine'})"
|
||||||
@dblclick="openMiscPermanentTab({schema: database.name, misc: procedure, type: 'routine'})"
|
@dblclick="openMiscPermanentTab({schema: database.name, misc: routine, type: 'routine'})"
|
||||||
@contextmenu.prevent="showMiscContext($event, {...procedure, type: 'procedure'})"
|
@contextmenu.prevent="showMiscContext($event, {...routine, type: 'routine'})"
|
||||||
>
|
>
|
||||||
<a class="table-name">
|
<a class="table-name">
|
||||||
<i class="table-icon mdi mdi-sync-circle mdi-18px mr-1" />
|
<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>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -137,7 +137,7 @@
|
||||||
@contextmenu.prevent="showMiscFolderContext($event, 'triggerFunction')"
|
@contextmenu.prevent="showMiscFolderContext($event, 'triggerFunction')"
|
||||||
>
|
>
|
||||||
<i class="misc-icon mdi mdi-18px mdi-folder-refresh mr-1" />
|
<i class="misc-icon mdi mdi-18px mdi-folder-refresh mr-1" />
|
||||||
{{ $tc('word.triggerFunction', 2) }}
|
{{ t('word.triggerFunction', 2) }}
|
||||||
</summary>
|
</summary>
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
<div>
|
<div>
|
||||||
|
@ -145,7 +145,7 @@
|
||||||
<li
|
<li
|
||||||
v-for="(func, i) of filteredTriggerFunctions"
|
v-for="(func, i) of filteredTriggerFunctions"
|
||||||
:key="`${func.name}-${i}`"
|
: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="menu-item"
|
||||||
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.triggerFunction === func.name}"
|
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.triggerFunction === func.name}"
|
||||||
@mousedown.left="selectMisc({schema: database.name, misc: func, type: 'triggerFunction'})"
|
@mousedown.left="selectMisc({schema: database.name, misc: func, type: 'triggerFunction'})"
|
||||||
|
@ -171,7 +171,7 @@
|
||||||
@contextmenu.prevent="showMiscFolderContext($event, 'function')"
|
@contextmenu.prevent="showMiscFolderContext($event, 'function')"
|
||||||
>
|
>
|
||||||
<i class="misc-icon mdi mdi-18px mdi-folder-move mr-1" />
|
<i class="misc-icon mdi mdi-18px mdi-folder-move mr-1" />
|
||||||
{{ $tc('word.function', 2) }}
|
{{ t('word.function', 2) }}
|
||||||
</summary>
|
</summary>
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
<div>
|
<div>
|
||||||
|
@ -179,7 +179,7 @@
|
||||||
<li
|
<li
|
||||||
v-for="(func, i) of filteredFunctions"
|
v-for="(func, i) of filteredFunctions"
|
||||||
:key="`${func.name}-${i}`"
|
: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="menu-item"
|
||||||
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.function === func.name}"
|
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.function === func.name}"
|
||||||
@mousedown.left="selectMisc({schema: database.name, misc: func, type: 'function'})"
|
@mousedown.left="selectMisc({schema: database.name, misc: func, type: 'function'})"
|
||||||
|
@ -205,7 +205,7 @@
|
||||||
@contextmenu.prevent="showMiscFolderContext($event, 'scheduler')"
|
@contextmenu.prevent="showMiscFolderContext($event, 'scheduler')"
|
||||||
>
|
>
|
||||||
<i class="misc-icon mdi mdi-18px mdi-folder-clock mr-1" />
|
<i class="misc-icon mdi mdi-18px mdi-folder-clock mr-1" />
|
||||||
{{ $tc('word.scheduler', 2) }}
|
{{ t('word.scheduler', 2) }}
|
||||||
</summary>
|
</summary>
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
<div>
|
<div>
|
||||||
|
@ -213,7 +213,7 @@
|
||||||
<li
|
<li
|
||||||
v-for="scheduler of filteredSchedulers"
|
v-for="scheduler of filteredSchedulers"
|
||||||
:key="scheduler.name"
|
: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="menu-item"
|
||||||
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.scheduler === scheduler.name}"
|
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.scheduler === scheduler.name}"
|
||||||
@mousedown.left="selectMisc({schema: database.name, misc: scheduler, type: 'scheduler'})"
|
@mousedown.left="selectMisc({schema: database.name, misc: scheduler, type: 'scheduler'})"
|
||||||
|
@ -228,7 +228,7 @@
|
||||||
<div
|
<div
|
||||||
v-if="scheduler.enabled === false"
|
v-if="scheduler.enabled === false"
|
||||||
class="tooltip tooltip-left disabled-indicator"
|
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" />
|
<i class="table-icon mdi mdi-pause mdi-18px mr-1" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -242,226 +242,235 @@
|
||||||
</details>
|
</details>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { useSettingsStore } from '@/stores/settings';
|
import { computed, Prop, Ref, ref, watch } from 'vue';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
|
||||||
import { formatBytes } from 'common/libs/formatBytes';
|
|
||||||
import { storeToRefs } from 'pinia';
|
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 {
|
const { t } = useI18n();
|
||||||
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 { applicationTheme } = storeToRefs(settingsStore);
|
const props = defineProps({
|
||||||
|
database: Object as Prop<WorkspaceStructure>,
|
||||||
|
connection: Object
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const emit = defineEmits([
|
||||||
getLoadedSchemas,
|
'show-schema-context',
|
||||||
getWorkspace,
|
'show-table-context',
|
||||||
getSearchTerm,
|
'show-misc-context',
|
||||||
changeBreadcrumbs,
|
'show-misc-folder-context'
|
||||||
addLoadedSchema,
|
]);
|
||||||
newTab,
|
|
||||||
refreshSchema
|
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const settingsStore = useSettingsStore();
|
||||||
applicationTheme,
|
const workspacesStore = useWorkspacesStore();
|
||||||
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);
|
|
||||||
|
|
||||||
if (!isVisible) {
|
const { applicationTheme } = storeToRefs(settingsStore);
|
||||||
element.setAttribute('tabindex', '-1');
|
|
||||||
element.focus();
|
const {
|
||||||
element.removeAttribute('tabindex');
|
getLoadedSchemas,
|
||||||
}
|
getWorkspace,
|
||||||
}
|
getSearchTerm,
|
||||||
}, 50);
|
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}, 50);
|
||||||
},
|
}
|
||||||
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
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setBreadcrumbs({ schema, [table.type]: table.name });
|
const selectSchema = async (schema: string) => {
|
||||||
},
|
if (!loadedSchemas.value.has(schema) && !isLoading.value) {
|
||||||
selectMisc ({ schema, misc, type }) {
|
isLoading.value = true;
|
||||||
const miscTempTabs = {
|
setBreadcrumbs({ schema });
|
||||||
trigger: 'temp-trigger-props',
|
await refreshSchema({ uid: props.connection.uid, schema });
|
||||||
triggerFunction: 'temp-trigger-function-props',
|
addLoadedSchema(schema);
|
||||||
function: 'temp-function-props',
|
isLoading.value = false;
|
||||||
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 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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -123,159 +123,154 @@
|
||||||
</BaseContextMenu>
|
</BaseContextMenu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { Component, computed, nextTick, Ref, ref } from 'vue';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
||||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||||
import ModalEditSchema from '@/components/ModalEditSchema';
|
import ModalEditSchema from '@/components/ModalEditSchema.vue';
|
||||||
import ModalExportSchema from '@/components/ModalExportSchema';
|
import ModalExportSchema from '@/components/ModalExportSchema.vue';
|
||||||
import ModalImportSchema from '@/components/ModalImportSchema';
|
import ModalImportSchema from '@/components/ModalImportSchema.vue';
|
||||||
import Schema from '@/ipc-api/Schema';
|
import Schema from '@/ipc-api/Schema';
|
||||||
import Application from '@/ipc-api/Application';
|
import Application from '@/ipc-api/Application';
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'WorkspaceExploreBarSchemaContext',
|
contextEvent: MouseEvent,
|
||||||
components: {
|
selectedSchema: String
|
||||||
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 { 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 {
|
const { addNotification } = useNotificationsStore();
|
||||||
getWorkspace,
|
const workspacesStore = useWorkspacesStore();
|
||||||
changeBreadcrumbs
|
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||||
addNotification,
|
|
||||||
selectedWorkspace,
|
const {
|
||||||
getWorkspace,
|
getWorkspace,
|
||||||
changeBreadcrumbs
|
changeBreadcrumbs
|
||||||
};
|
} = workspacesStore;
|
||||||
},
|
|
||||||
data () {
|
const importModalRef: Ref<Component & {startImport: (file: string) => void}> = ref(null);
|
||||||
return {
|
const isDeleteModal = ref(false);
|
||||||
isDeleteModal: false,
|
const isEditModal = ref(false);
|
||||||
isEditModal: false,
|
const isExportSchemaModal = ref(false);
|
||||||
isExportSchemaModal: false,
|
const isImportSchemaModal = ref(false);
|
||||||
isImportSchemaModal: false
|
|
||||||
};
|
const workspace = computed(() => getWorkspace(selectedWorkspace.value));
|
||||||
},
|
|
||||||
computed: {
|
const openCreateTableTab = () => {
|
||||||
workspace () {
|
emit('open-create-table-tab');
|
||||||
return this.getWorkspace(this.selectedWorkspace);
|
};
|
||||||
}
|
|
||||||
},
|
const openCreateViewTab = () => {
|
||||||
methods: {
|
emit('open-create-view-tab');
|
||||||
openCreateTableTab () {
|
};
|
||||||
this.$emit('open-create-table-tab');
|
|
||||||
},
|
const openCreateTriggerTab = () => {
|
||||||
openCreateViewTab () {
|
emit('open-create-trigger-tab');
|
||||||
this.$emit('open-create-view-tab');
|
};
|
||||||
},
|
|
||||||
openCreateTriggerTab () {
|
const openCreateRoutineTab = () => {
|
||||||
this.$emit('open-create-trigger-tab');
|
emit('open-create-routine-tab');
|
||||||
},
|
};
|
||||||
openCreateRoutineTab () {
|
|
||||||
this.$emit('open-create-routine-tab');
|
const openCreateFunctionTab = () => {
|
||||||
},
|
emit('open-create-function-tab');
|
||||||
openCreateFunctionTab () {
|
};
|
||||||
this.$emit('open-create-function-tab');
|
|
||||||
},
|
const openCreateTriggerFunctionTab = () => {
|
||||||
openCreateTriggerFunctionTab () {
|
emit('open-create-trigger-function-tab');
|
||||||
this.$emit('open-create-trigger-function-tab');
|
};
|
||||||
},
|
|
||||||
openCreateSchedulerTab () {
|
const openCreateSchedulerTab = () => {
|
||||||
this.$emit('open-create-scheduler-tab');
|
emit('open-create-scheduler-tab');
|
||||||
},
|
};
|
||||||
showDeleteModal () {
|
|
||||||
this.isDeleteModal = true;
|
const showDeleteModal = () => {
|
||||||
},
|
isDeleteModal.value = true;
|
||||||
hideDeleteModal () {
|
};
|
||||||
this.isDeleteModal = false;
|
|
||||||
},
|
const hideDeleteModal = () => {
|
||||||
showEditModal () {
|
isDeleteModal.value = false;
|
||||||
this.isEditModal = true;
|
};
|
||||||
},
|
|
||||||
hideEditModal () {
|
const showEditModal = () => {
|
||||||
this.isEditModal = false;
|
isEditModal.value = true;
|
||||||
this.closeContext();
|
};
|
||||||
},
|
|
||||||
showExportSchemaModal () {
|
const hideEditModal = () => {
|
||||||
this.isExportSchemaModal = true;
|
isEditModal.value = false;
|
||||||
},
|
closeContext();
|
||||||
hideExportSchemaModal () {
|
};
|
||||||
this.isExportSchemaModal = false;
|
|
||||||
this.closeContext();
|
const showExportSchemaModal = () => {
|
||||||
},
|
isExportSchemaModal.value = true;
|
||||||
showImportSchemaModal () {
|
};
|
||||||
this.isImportSchemaModal = true;
|
|
||||||
},
|
const hideExportSchemaModal = () => {
|
||||||
hideImportSchemaModal () {
|
isExportSchemaModal.value = false;
|
||||||
this.isImportSchemaModal = false;
|
closeContext();
|
||||||
this.closeContext();
|
};
|
||||||
},
|
|
||||||
async initImport () {
|
const showImportSchemaModal = () => {
|
||||||
const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: 'SQL', extensions: ['sql'] }] });
|
isImportSchemaModal.value = true;
|
||||||
if (result && !result.canceled) {
|
};
|
||||||
const file = result.filePaths[0];
|
|
||||||
this.showImportSchemaModal();
|
const hideImportSchemaModal = () => {
|
||||||
this.$nextTick(() => {
|
isImportSchemaModal.value = false;
|
||||||
this.$refs.importModalRef.startImport(file);
|
closeContext();
|
||||||
});
|
};
|
||||||
}
|
|
||||||
},
|
const initImport = async () => {
|
||||||
closeContext () {
|
const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: 'SQL', extensions: ['sql'] }] });
|
||||||
this.$emit('close-context');
|
if (result && !result.canceled) {
|
||||||
},
|
const file = result.filePaths[0];
|
||||||
async deleteSchema () {
|
showImportSchemaModal();
|
||||||
try {
|
await nextTick();
|
||||||
const { status, response } = await Schema.deleteSchema({
|
importModalRef.value.startImport(file);
|
||||||
uid: this.selectedWorkspace,
|
}
|
||||||
database: this.selectedSchema
|
};
|
||||||
});
|
|
||||||
|
const closeContext = () => {
|
||||||
if (status === 'success') {
|
emit('close-context');
|
||||||
if (this.selectedSchema === this.workspace.breadcrumbs.schema)
|
};
|
||||||
this.changeBreadcrumbs({ schema: null });
|
|
||||||
|
const deleteSchema = async () => {
|
||||||
this.closeContext();
|
try {
|
||||||
this.$emit('reload');
|
const { status, response } = await Schema.deleteSchema({
|
||||||
}
|
uid: selectedWorkspace.value,
|
||||||
else
|
database: props.selectedSchema
|
||||||
this.addNotification({ status: 'error', message: response });
|
});
|
||||||
}
|
|
||||||
catch (err) {
|
if (status === 'success') {
|
||||||
this.addNotification({ status: 'error', message: err.stack });
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.context-submenu {
|
.context-submenu {
|
||||||
min-width: 150px !important;
|
min-width: 150px !important;
|
||||||
|
|
|
@ -71,151 +71,133 @@
|
||||||
</BaseContextMenu>
|
</BaseContextMenu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
||||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||||
import Tables from '@/ipc-api/Tables';
|
import Tables from '@/ipc-api/Tables';
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'WorkspaceExploreBarTableContext',
|
contextEvent: MouseEvent,
|
||||||
components: {
|
selectedTable: Object,
|
||||||
BaseContextMenu,
|
selectedSchema: String
|
||||||
ConfirmModal
|
});
|
||||||
},
|
|
||||||
props: {
|
|
||||||
contextEvent: MouseEvent,
|
|
||||||
selectedTable: Object,
|
|
||||||
selectedSchema: String
|
|
||||||
},
|
|
||||||
emits: ['close-context', 'duplicate-table', 'reload', 'delete-table'],
|
|
||||||
setup () {
|
|
||||||
const { addNotification } = useNotificationsStore();
|
|
||||||
const workspacesStore = useWorkspacesStore();
|
|
||||||
|
|
||||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const emit = defineEmits(['close-context', 'duplicate-table', 'reload', 'delete-table']);
|
||||||
|
|
||||||
const {
|
const { addNotification } = useNotificationsStore();
|
||||||
getWorkspace,
|
const workspacesStore = useWorkspacesStore();
|
||||||
newTab,
|
|
||||||
removeTabs,
|
|
||||||
addLoadingElement,
|
|
||||||
removeLoadingElement,
|
|
||||||
changeBreadcrumbs
|
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||||
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'
|
|
||||||
});
|
|
||||||
|
|
||||||
this.changeBreadcrumbs({
|
const {
|
||||||
schema: this.selectedSchema,
|
getWorkspace,
|
||||||
table: this.selectedTable.name
|
newTab,
|
||||||
});
|
addLoadingElement,
|
||||||
|
removeLoadingElement,
|
||||||
|
changeBreadcrumbs
|
||||||
|
} = workspacesStore;
|
||||||
|
|
||||||
this.closeContext();
|
const isDeleteModal = ref(false);
|
||||||
},
|
const isEmptyModal = ref(false);
|
||||||
openViewSettingTab () {
|
|
||||||
this.newTab({
|
|
||||||
uid: this.selectedWorkspace,
|
|
||||||
elementType: 'table',
|
|
||||||
elementName: this.selectedTable.name,
|
|
||||||
schema: this.selectedSchema,
|
|
||||||
type: 'view-props'
|
|
||||||
});
|
|
||||||
|
|
||||||
this.changeBreadcrumbs({
|
const workspace = computed(() => getWorkspace(selectedWorkspace.value));
|
||||||
schema: this.selectedSchema,
|
const customizations = computed(() => workspace.value && workspace.value.customizations ? workspace.value.customizations : null);
|
||||||
view: this.selectedTable.name
|
|
||||||
});
|
|
||||||
|
|
||||||
this.closeContext();
|
const showDeleteModal = () => {
|
||||||
},
|
isDeleteModal.value = true;
|
||||||
duplicateTable () {
|
};
|
||||||
this.$emit('duplicate-table', { schema: this.selectedSchema, table: this.selectedTable });
|
|
||||||
},
|
|
||||||
async emptyTable () {
|
|
||||||
this.closeContext();
|
|
||||||
|
|
||||||
this.addLoadingElement({
|
const hideDeleteModal = () => {
|
||||||
name: this.selectedTable.name,
|
isDeleteModal.value = false;
|
||||||
schema: this.selectedSchema,
|
};
|
||||||
type: 'table'
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
const showEmptyModal = () => {
|
||||||
const { status, response } = await Tables.truncateTable({
|
isEmptyModal.value = true;
|
||||||
uid: this.selectedWorkspace,
|
};
|
||||||
table: this.selectedTable.name,
|
|
||||||
schema: this.selectedSchema
|
|
||||||
});
|
|
||||||
|
|
||||||
if (status === 'success')
|
const hideEmptyModal = () => {
|
||||||
this.$emit('reload');
|
isEmptyModal.value = false;
|
||||||
else
|
};
|
||||||
this.addNotification({ status: 'error', message: response });
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
this.addNotification({ status: 'error', message: err.stack });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.removeLoadingElement({
|
const closeContext = () => {
|
||||||
name: this.selectedTable.name,
|
emit('close-context');
|
||||||
schema: this.selectedSchema,
|
};
|
||||||
type: 'table'
|
|
||||||
});
|
const openTableSettingTab = () => {
|
||||||
},
|
newTab({
|
||||||
deleteTable () {
|
uid: selectedWorkspace.value,
|
||||||
this.$emit('delete-table', { schema: this.selectedSchema, table: this.selectedTable });
|
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>
|
</script>
|
||||||
|
|
|
@ -11,27 +11,27 @@
|
||||||
@click="saveChanges"
|
@click="saveChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||||
<span>{{ $t('word.save') }}</span>
|
<span>{{ t('word.save') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:disabled="!isChanged"
|
:disabled="!isChanged"
|
||||||
class="btn btn-link btn-sm mr-0"
|
class="btn btn-link btn-sm mr-0"
|
||||||
:title="$t('message.clearChanges')"
|
:title="t('message.clearChanges')"
|
||||||
@click="clearChanges"
|
@click="clearChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||||
<span>{{ $t('word.clear') }}</span>
|
<span>{{ t('word.clear') }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="divider-vert py-3" />
|
<div class="divider-vert py-3" />
|
||||||
|
|
||||||
<button class="btn btn-dark btn-sm" @click="showParamsModal">
|
<button class="btn btn-dark btn-sm" @click="showParamsModal">
|
||||||
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
|
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
|
||||||
<span>{{ $t('word.parameters') }}</span>
|
<span>{{ t('word.parameters') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-info">
|
<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>
|
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,7 +42,7 @@
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.name') }}
|
{{ t('word.name') }}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
ref="firstInput"
|
ref="firstInput"
|
||||||
|
@ -55,7 +55,7 @@
|
||||||
<div v-if="customizations.languages" class="column col-auto">
|
<div v-if="customizations.languages" class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.language') }}
|
{{ t('word.language') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localFunction.language"
|
v-model="localFunction.language"
|
||||||
|
@ -67,13 +67,13 @@
|
||||||
<div v-if="customizations.definer" class="column col-auto">
|
<div v-if="customizations.definer" class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.definer') }}
|
{{ t('word.definer') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localFunction.definer"
|
v-model="localFunction.definer"
|
||||||
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]"
|
:options="[{value: '', name:t('message.currentUser')}, ...workspace.users]"
|
||||||
:option-label="(user) => user.value === '' ? user.name : `${user.name}@${user.host}`"
|
:option-label="(user: any) => user.value === '' ? user.name : `${user.name}@${user.host}`"
|
||||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -81,13 +81,13 @@
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.returns') }}
|
{{ t('word.returns') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localFunction.returns"
|
v-model="localFunction.returns"
|
||||||
class="form-select text-uppercase"
|
class="form-select text-uppercase"
|
||||||
:options="[{ name: 'VOID' }, ...workspace.dataTypes]"
|
:options="[{ name: 'VOID' }, ...(workspace.dataTypes as any)]"
|
||||||
group-label="group"
|
group-label="group"
|
||||||
group-values="types"
|
group-values="types"
|
||||||
option-label="name"
|
option-label="name"
|
||||||
|
@ -101,7 +101,7 @@
|
||||||
class="form-input"
|
class="form-input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
:placeholder="$t('word.length')"
|
:placeholder="t('word.length')"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -109,7 +109,7 @@
|
||||||
<div v-if="customizations.comment" class="column">
|
<div v-if="customizations.comment" class="column">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.comment') }}
|
{{ t('word.comment') }}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
v-model="localFunction.comment"
|
v-model="localFunction.comment"
|
||||||
|
@ -121,7 +121,7 @@
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('message.sqlSecurity') }}
|
{{ t('message.sqlSecurity') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localFunction.security"
|
v-model="localFunction.security"
|
||||||
|
@ -133,7 +133,7 @@
|
||||||
<div v-if="customizations.functionDataAccess" class="column col-auto">
|
<div v-if="customizations.functionDataAccess" class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('message.dataAccess') }}
|
{{ t('message.dataAccess') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localFunction.dataAccess"
|
v-model="localFunction.dataAccess"
|
||||||
|
@ -146,7 +146,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label d-invisible">.</label>
|
<label class="form-label d-invisible">.</label>
|
||||||
<label class="form-checkbox form-inline">
|
<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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -154,7 +154,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||||
<BaseLoader v-if="isLoading" />
|
<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
|
<QueryEditor
|
||||||
v-show="isSelected"
|
v-show="isSelected"
|
||||||
ref="queryEditor"
|
ref="queryEditor"
|
||||||
|
@ -175,213 +175,177 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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 { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import BaseLoader from '@/components/BaseLoader';
|
import BaseLoader from '@/components/BaseLoader.vue';
|
||||||
import QueryEditor from '@/components/QueryEditor';
|
import QueryEditor from '@/components/QueryEditor.vue';
|
||||||
import WorkspaceTabPropsFunctionParamsModal from '@/components/WorkspaceTabPropsFunctionParamsModal';
|
import WorkspaceTabPropsFunctionParamsModal from '@/components/WorkspaceTabPropsFunctionParamsModal.vue';
|
||||||
import Functions from '@/ipc-api/Functions';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
import { FunctionInfos, FunctionParam } from 'common/interfaces/antares';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
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 { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const props = defineProps({
|
||||||
|
tabUid: String,
|
||||||
|
connection: Object,
|
||||||
|
tab: Object,
|
||||||
|
isSelected: Boolean,
|
||||||
|
schema: String
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const { addNotification } = useNotificationsStore();
|
||||||
getWorkspace,
|
const workspacesStore = useWorkspacesStore();
|
||||||
refreshStructure,
|
|
||||||
changeBreadcrumbs,
|
|
||||||
setUnsavedChanges,
|
|
||||||
newTab,
|
|
||||||
removeTab,
|
|
||||||
renameTabs
|
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const {
|
||||||
addNotification,
|
getWorkspace,
|
||||||
selectedWorkspace,
|
refreshStructure,
|
||||||
getWorkspace,
|
changeBreadcrumbs,
|
||||||
refreshStructure,
|
setUnsavedChanges,
|
||||||
changeBreadcrumbs,
|
newTab,
|
||||||
setUnsavedChanges,
|
removeTab
|
||||||
newTab,
|
} = workspacesStore;
|
||||||
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);
|
|
||||||
|
|
||||||
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 });
|
||||||
}
|
}
|
||||||
},
|
else
|
||||||
watch: {
|
addNotification({ status: 'error', message: response });
|
||||||
isSelected (val) {
|
}
|
||||||
if (val)
|
catch (err) {
|
||||||
this.changeBreadcrumbs({ schema: this.schema });
|
addNotification({ status: 'error', message: err.stack });
|
||||||
},
|
}
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction));
|
isSaving.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
setTimeout(() => {
|
const clearChanges = () => {
|
||||||
this.resizeQueryEditor();
|
localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
|
||||||
}, 50);
|
queryEditor.value.editor.session.setValue(localFunction.value.sql);
|
||||||
|
};
|
||||||
|
|
||||||
window.addEventListener('keydown', this.onKey);
|
const resizeQueryEditor = () => {
|
||||||
},
|
if (queryEditor.value) {
|
||||||
mounted () {
|
const footer = document.getElementById('footer');
|
||||||
if (this.isSelected)
|
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||||
this.changeBreadcrumbs({ schema: this.schema });
|
editorHeight.value = size;
|
||||||
|
queryEditor.value.editor.resize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
setTimeout(() => {
|
const parametersUpdate = (parameters: FunctionParam[]) => {
|
||||||
this.$refs.firstInput.focus();
|
localFunction.value = { ...localFunction.value, parameters };
|
||||||
}, 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
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
const showParamsModal = () => {
|
||||||
const { status, response } = await Functions.createFunction(params);
|
isParamsModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
if (status === 'success') {
|
const hideParamsModal = () => {
|
||||||
await this.refreshStructure(this.connection.uid);
|
isParamsModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
this.newTab({
|
const onKey = (e: KeyboardEvent) => {
|
||||||
uid: this.connection.uid,
|
if (props.isSelected) {
|
||||||
schema: this.schema,
|
e.stopPropagation();
|
||||||
elementName: this.localFunction.name,
|
if (e.ctrlKey && e.key === 's') { // CTRL + S
|
||||||
elementType: 'function',
|
if (isChanged.value)
|
||||||
type: 'function-props'
|
saveChanges();
|
||||||
});
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
|
@ -72,8 +72,8 @@
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localRoutine.definer"
|
v-model="localRoutine.definer"
|
||||||
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]"
|
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]"
|
||||||
:option-label="(user) => user.value === '' ? user.name : `${user.name}@${user.host}`"
|
:option-label="(user: any) => user.value === '' ? user.name : `${user.name}@${user.host}`"
|
||||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -129,7 +129,6 @@
|
||||||
<label class="form-label ml-2">{{ $t('message.routineBody') }}</label>
|
<label class="form-label ml-2">{{ $t('message.routineBody') }}</label>
|
||||||
<QueryEditor
|
<QueryEditor
|
||||||
v-show="isSelected"
|
v-show="isSelected"
|
||||||
:key="`new-${_uid}`"
|
|
||||||
ref="queryEditor"
|
ref="queryEditor"
|
||||||
v-model="localRoutine.sql"
|
v-model="localRoutine.sql"
|
||||||
:workspace="workspace"
|
:workspace="workspace"
|
||||||
|
@ -148,209 +147,174 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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 { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import QueryEditor from '@/components/QueryEditor';
|
import QueryEditor from '@/components/QueryEditor.vue';
|
||||||
import BaseLoader from '@/components/BaseLoader';
|
import BaseLoader from '@/components/BaseLoader.vue';
|
||||||
import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal';
|
import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal.vue';
|
||||||
import Routines from '@/ipc-api/Routines';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
import { FunctionParam } from 'common/interfaces/antares';
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'WorkspaceTabNewRoutine',
|
tabUid: String,
|
||||||
components: {
|
connection: Object,
|
||||||
QueryEditor,
|
tab: Object,
|
||||||
BaseLoader,
|
isSelected: Boolean,
|
||||||
WorkspaceTabPropsRoutineParamsModal,
|
schema: String
|
||||||
BaseSelect
|
});
|
||||||
},
|
|
||||||
props: {
|
|
||||||
tabUid: String,
|
|
||||||
connection: Object,
|
|
||||||
tab: Object,
|
|
||||||
isSelected: Boolean,
|
|
||||||
schema: String
|
|
||||||
},
|
|
||||||
setup () {
|
|
||||||
const { addNotification } = useNotificationsStore();
|
|
||||||
const workspacesStore = useWorkspacesStore();
|
|
||||||
|
|
||||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const { addNotification } = useNotificationsStore();
|
||||||
|
const workspacesStore = useWorkspacesStore();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getWorkspace,
|
getWorkspace,
|
||||||
refreshStructure,
|
refreshStructure,
|
||||||
changeBreadcrumbs,
|
changeBreadcrumbs,
|
||||||
setUnsavedChanges,
|
setUnsavedChanges,
|
||||||
newTab,
|
newTab,
|
||||||
removeTab,
|
removeTab
|
||||||
renameTabs
|
} = workspacesStore;
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
|
||||||
addNotification,
|
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||||
selectedWorkspace,
|
const isLoading = ref(false);
|
||||||
getWorkspace,
|
const isSaving = ref(false);
|
||||||
refreshStructure,
|
const isParamsModal = ref(false);
|
||||||
changeBreadcrumbs,
|
const originalRoutine = ref(null);
|
||||||
setUnsavedChanges,
|
const localRoutine = ref(null);
|
||||||
newTab,
|
const editorHeight = ref(300);
|
||||||
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);
|
|
||||||
|
|
||||||
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 });
|
||||||
}
|
}
|
||||||
},
|
else
|
||||||
watch: {
|
addNotification({ status: 'error', message: response });
|
||||||
isSelected (val) {
|
}
|
||||||
if (val)
|
catch (err) {
|
||||||
this.changeBreadcrumbs({ schema: this.schema });
|
addNotification({ status: 'error', message: err.stack });
|
||||||
},
|
}
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
this.localRoutine = JSON.parse(JSON.stringify(this.originalRoutine));
|
isSaving.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
setTimeout(() => {
|
const clearChanges = () => {
|
||||||
this.resizeQueryEditor();
|
localRoutine.value = JSON.parse(JSON.stringify(originalRoutine.value));
|
||||||
}, 50);
|
queryEditor.value.editor.session.setValue(localRoutine.value.sql);
|
||||||
|
};
|
||||||
|
|
||||||
window.addEventListener('keydown', this.onKey);
|
const resizeQueryEditor = () => {
|
||||||
},
|
if (queryEditor.value) {
|
||||||
mounted () {
|
const footer = document.getElementById('footer');
|
||||||
if (this.isSelected)
|
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||||
this.changeBreadcrumbs({ schema: this.schema });
|
editorHeight.value = size;
|
||||||
|
queryEditor.value.editor.resize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
setTimeout(() => {
|
const parametersUpdate = (parameters: FunctionParam[]) => {
|
||||||
this.$refs.firstInput.focus();
|
localRoutine.value = { ...localRoutine.value, parameters };
|
||||||
}, 100);
|
};
|
||||||
|
|
||||||
window.addEventListener('resize', this.resizeQueryEditor);
|
const showParamsModal = () => {
|
||||||
},
|
isParamsModal.value = true;
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
const hideParamsModal = () => {
|
||||||
const { status, response } = await Routines.createRoutine(params);
|
isParamsModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
if (status === 'success') {
|
const onKey = (e: KeyboardEvent) => {
|
||||||
await this.refreshStructure(this.connection.uid);
|
if (props.isSelected) {
|
||||||
|
e.stopPropagation();
|
||||||
this.newTab({
|
if (e.ctrlKey && e.key === 's') { // CTRL + S
|
||||||
uid: this.connection.uid,
|
if (isChanged.value)
|
||||||
schema: this.schema,
|
saveChanges();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
|
@ -11,26 +11,26 @@
|
||||||
@click="saveChanges"
|
@click="saveChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||||
<span>{{ $t('word.save') }}</span>
|
<span>{{ t('word.save') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:disabled="!isChanged"
|
:disabled="!isChanged"
|
||||||
class="btn btn-link btn-sm mr-0"
|
class="btn btn-link btn-sm mr-0"
|
||||||
:title="$t('message.clearChanges')"
|
:title="t('message.clearChanges')"
|
||||||
@click="clearChanges"
|
@click="clearChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||||
<span>{{ $t('word.clear') }}</span>
|
<span>{{ t('word.clear') }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="divider-vert py-3" />
|
<div class="divider-vert py-3" />
|
||||||
<button class="btn btn-dark btn-sm" @click="showTimingModal">
|
<button class="btn btn-dark btn-sm" @click="showTimingModal">
|
||||||
<i class="mdi mdi-24px mdi-timer mr-1" />
|
<i class="mdi mdi-24px mdi-timer mr-1" />
|
||||||
<span>{{ $t('word.timing') }}</span>
|
<span>{{ t('word.timing') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-info">
|
<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>
|
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.name') }}</label>
|
<label class="form-label">{{ t('word.name') }}</label>
|
||||||
<input
|
<input
|
||||||
ref="firstInput"
|
ref="firstInput"
|
||||||
v-model="localScheduler.name"
|
v-model="localScheduler.name"
|
||||||
|
@ -51,19 +51,19 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.definer') }}</label>
|
<label class="form-label">{{ t('word.definer') }}</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localScheduler.definer"
|
v-model="localScheduler.definer"
|
||||||
:options="users"
|
:options="users"
|
||||||
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
|
:option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
|
||||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.comment') }}</label>
|
<label class="form-label">{{ t('word.comment') }}</label>
|
||||||
<input
|
<input
|
||||||
v-model="localScheduler.comment"
|
v-model="localScheduler.comment"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<div class="form-group">
|
<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">
|
<label class="form-radio form-inline">
|
||||||
<input
|
<input
|
||||||
v-model="localScheduler.state"
|
v-model="localScheduler.state"
|
||||||
|
@ -104,7 +104,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||||
<BaseLoader v-if="isLoading" />
|
<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
|
<QueryEditor
|
||||||
v-show="isSelected"
|
v-show="isSelected"
|
||||||
ref="queryEditor"
|
ref="queryEditor"
|
||||||
|
@ -124,209 +124,186 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { storeToRefs } from 'pinia';
|
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 { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import BaseLoader from '@/components/BaseLoader';
|
import BaseLoader from '@/components/BaseLoader.vue';
|
||||||
import QueryEditor from '@/components/QueryEditor';
|
import QueryEditor from '@/components/QueryEditor.vue';
|
||||||
import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal';
|
import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal.vue';
|
||||||
import Schedulers from '@/ipc-api/Schedulers';
|
import Schedulers from '@/ipc-api/Schedulers';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
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 { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const props = defineProps({
|
||||||
|
tabUid: String,
|
||||||
|
connection: Object,
|
||||||
|
tab: Object,
|
||||||
|
isSelected: Boolean,
|
||||||
|
schema: String
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const { addNotification } = useNotificationsStore();
|
||||||
getWorkspace,
|
const workspacesStore = useWorkspacesStore();
|
||||||
refreshStructure,
|
|
||||||
changeBreadcrumbs,
|
|
||||||
setUnsavedChanges,
|
|
||||||
newTab,
|
|
||||||
removeTab,
|
|
||||||
renameTabs
|
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const {
|
||||||
addNotification,
|
getWorkspace,
|
||||||
selectedWorkspace,
|
refreshStructure,
|
||||||
getWorkspace,
|
changeBreadcrumbs,
|
||||||
refreshStructure,
|
setUnsavedChanges,
|
||||||
changeBreadcrumbs,
|
newTab,
|
||||||
setUnsavedChanges,
|
removeTab
|
||||||
newTab,
|
} = workspacesStore;
|
||||||
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);
|
|
||||||
|
|
||||||
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);
|
||||||
users () {
|
const isLoading = ref(false);
|
||||||
const users = [{ value: '' }, ...this.workspace.users];
|
const isSaving = ref(false);
|
||||||
if (!this.isDefinerInUsers) {
|
const isTimingModal = ref(false);
|
||||||
const [name, host] = this.originalScheduler.definer.replaceAll('`', '').split('@');
|
const originalScheduler = ref(null);
|
||||||
users.unshift({ name, host });
|
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 });
|
||||||
}
|
}
|
||||||
},
|
else
|
||||||
watch: {
|
addNotification({ status: 'error', message: response });
|
||||||
isSelected (val) {
|
}
|
||||||
if (val)
|
catch (err) {
|
||||||
this.changeBreadcrumbs({ schema: this.schema });
|
addNotification({ status: 'error', message: err.stack });
|
||||||
},
|
}
|
||||||
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'
|
|
||||||
};
|
|
||||||
|
|
||||||
this.localScheduler = JSON.parse(JSON.stringify(this.originalScheduler));
|
isSaving.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
setTimeout(() => {
|
const clearChanges = () => {
|
||||||
this.resizeQueryEditor();
|
localScheduler.value = JSON.parse(JSON.stringify(originalScheduler.value));
|
||||||
}, 50);
|
queryEditor.value.editor.session.setValue(localScheduler.value.sql);
|
||||||
|
};
|
||||||
|
|
||||||
window.addEventListener('keydown', this.onKey);
|
const resizeQueryEditor = () => {
|
||||||
},
|
if (queryEditor.value) {
|
||||||
mounted () {
|
const footer = document.getElementById('footer');
|
||||||
if (this.isSelected)
|
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||||
this.changeBreadcrumbs({ schema: this.schema });
|
editorHeight.value = size;
|
||||||
|
queryEditor.value.editor.resize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
setTimeout(() => {
|
const showTimingModal = () => {
|
||||||
this.$refs.firstInput.focus();
|
isTimingModal.value = true;
|
||||||
}, 100);
|
};
|
||||||
|
|
||||||
window.addEventListener('resize', this.resizeQueryEditor);
|
const hideTimingModal = () => {
|
||||||
},
|
isTimingModal.value = false;
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
const timingUpdate = (options: EventInfos) => {
|
||||||
const { status, response } = await Schedulers.createScheduler(params);
|
localScheduler.value = options;
|
||||||
|
};
|
||||||
|
|
||||||
if (status === 'success') {
|
const onKey = (e: KeyboardEvent) => {
|
||||||
await this.refreshStructure(this.connection.uid);
|
if (props.isSelected) {
|
||||||
|
e.stopPropagation();
|
||||||
this.newTab({
|
if (e.ctrlKey && e.key === 's') { // CTRL + S
|
||||||
uid: this.connection.uid,
|
if (isChanged.value)
|
||||||
schema: this.schema,
|
saveChanges();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
|
@ -11,16 +11,16 @@
|
||||||
@click="saveChanges"
|
@click="saveChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||||
<span>{{ $t('word.save') }}</span>
|
<span>{{ t('word.save') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:disabled="!isChanged || isSaving"
|
:disabled="!isChanged || isSaving"
|
||||||
class="btn btn-link btn-sm mr-0"
|
class="btn btn-link btn-sm mr-0"
|
||||||
:title="$t('message.clearChanges')"
|
:title="t('message.clearChanges')"
|
||||||
@click="clearChanges"
|
@click="clearChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||||
<span>{{ $t('word.clear') }}</span>
|
<span>{{ t('word.clear') }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="divider-vert py-3" />
|
<div class="divider-vert py-3" />
|
||||||
|
@ -28,20 +28,20 @@
|
||||||
<button
|
<button
|
||||||
:disabled="isSaving"
|
:disabled="isSaving"
|
||||||
class="btn btn-dark btn-sm"
|
class="btn btn-dark btn-sm"
|
||||||
:title="$t('message.addNewField')"
|
:title="t('message.addNewField')"
|
||||||
@click="addField"
|
@click="addField"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
|
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
|
||||||
<span>{{ $t('word.add') }}</span>
|
<span>{{ t('word.add') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:disabled="isSaving || !localFields.length"
|
:disabled="isSaving || !localFields.length"
|
||||||
class="btn btn-dark btn-sm"
|
class="btn btn-dark btn-sm"
|
||||||
:title="$t('message.manageIndexes')"
|
:title="t('message.manageIndexes')"
|
||||||
@click="showIntdexesModal"
|
@click="showIntdexesModal"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" />
|
<i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" />
|
||||||
<span>{{ $t('word.indexes') }}</span>
|
<span>{{ t('word.indexes') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn-dark btn-sm"
|
class="btn btn-dark btn-sm"
|
||||||
|
@ -49,11 +49,11 @@
|
||||||
@click="showForeignModal"
|
@click="showForeignModal"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-key-link mr-1" />
|
<i class="mdi mdi-24px mdi-key-link mr-1" />
|
||||||
<span>{{ $t('word.foreignKeys') }}</span>
|
<span>{{ t('word.foreignKeys') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-info">
|
<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>
|
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -63,7 +63,7 @@
|
||||||
<div class="columns mb-4">
|
<div class="columns mb-4">
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.name') }}</label>
|
<label class="form-label">{{ t('word.name') }}</label>
|
||||||
<input
|
<input
|
||||||
ref="firstInput"
|
ref="firstInput"
|
||||||
v-model="localOptions.name"
|
v-model="localOptions.name"
|
||||||
|
@ -74,7 +74,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-if="workspace.customizations.comment" class="column">
|
<div v-if="workspace.customizations.comment" class="column">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.comment') }}</label>
|
<label class="form-label">{{ t('word.comment') }}</label>
|
||||||
<input
|
<input
|
||||||
v-model="localOptions.comment"
|
v-model="localOptions.comment"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
|
@ -86,7 +86,7 @@
|
||||||
<div v-if="workspace.customizations.collations" class="column col-auto">
|
<div v-if="workspace.customizations.collations" class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.collation') }}
|
{{ t('word.collation') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localOptions.collation"
|
v-model="localOptions.collation"
|
||||||
|
@ -100,7 +100,7 @@
|
||||||
<div v-if="workspace.customizations.engines" class="column col-auto">
|
<div v-if="workspace.customizations.engines" class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.engine') }}
|
{{ t('word.engine') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localOptions.engine"
|
v-model="localOptions.engine"
|
||||||
|
@ -160,310 +160,302 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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 { storeToRefs } from 'pinia';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import { uidGen } from 'common/libs/uidGen';
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
import Tables from '@/ipc-api/Tables';
|
import Tables from '@/ipc-api/Tables';
|
||||||
import BaseLoader from '@/components/BaseLoader';
|
import BaseLoader from '@/components/BaseLoader.vue';
|
||||||
import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields';
|
import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields.vue';
|
||||||
import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal';
|
import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal.vue';
|
||||||
import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal';
|
import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal.vue';
|
||||||
import WorkspaceTabNewTableEmptyState from '@/components/WorkspaceTabNewTableEmptyState';
|
import WorkspaceTabNewTableEmptyState from '@/components/WorkspaceTabNewTableEmptyState.vue';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
import { ConnectionParams, TableField, TableForeign, TableIndex, TableOptions } from 'common/interfaces/antares';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
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 { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const props = defineProps({
|
||||||
|
tabUid: String,
|
||||||
|
connection: Object as Prop<ConnectionParams>,
|
||||||
|
tab: Object,
|
||||||
|
isSelected: Boolean,
|
||||||
|
schema: String
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const { addNotification } = useNotificationsStore();
|
||||||
getWorkspace,
|
const workspacesStore = useWorkspacesStore();
|
||||||
getDatabaseVariable,
|
|
||||||
refreshStructure,
|
|
||||||
setUnsavedChanges,
|
|
||||||
newTab,
|
|
||||||
renameTabs,
|
|
||||||
removeTab,
|
|
||||||
changeBreadcrumbs
|
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||||
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);
|
|
||||||
|
|
||||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
const {
|
||||||
},
|
getWorkspace,
|
||||||
isChanged () {
|
getDatabaseVariable,
|
||||||
return JSON.stringify(this.originalFields) !== JSON.stringify(this.localFields) ||
|
refreshStructure,
|
||||||
JSON.stringify(this.originalKeyUsage) !== JSON.stringify(this.localKeyUsage) ||
|
setUnsavedChanges,
|
||||||
JSON.stringify(this.originalIndexes) !== JSON.stringify(this.localIndexes) ||
|
newTab,
|
||||||
JSON.stringify(this.tableOptions) !== JSON.stringify(this.localOptions);
|
removeTab,
|
||||||
},
|
changeBreadcrumbs
|
||||||
isValid () {
|
} = workspacesStore;
|
||||||
return !!this.localFields.length && !!this.localOptions.name.trim().length;
|
|
||||||
|
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 });
|
||||||
}
|
}
|
||||||
},
|
else
|
||||||
watch: {
|
addNotification({ status: 'error', message: response });
|
||||||
isSelected (val) {
|
}
|
||||||
if (val)
|
catch (err) {
|
||||||
this.changeBreadcrumbs({ schema: this.schema });
|
addNotification({ status: 'error', message: err.stack });
|
||||||
},
|
}
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions));
|
isSaving.value = false;
|
||||||
window.addEventListener('keydown', this.onKey);
|
newFieldsCounter.value = 0;
|
||||||
},
|
};
|
||||||
mounted () {
|
|
||||||
if (this.isSelected)
|
|
||||||
this.changeBreadcrumbs({ schema: this.schema });
|
|
||||||
|
|
||||||
setTimeout(() => {
|
const clearChanges = () => {
|
||||||
this.$refs.firstInput.focus();
|
localFields.value = JSON.parse(JSON.stringify(originalFields.value));
|
||||||
}, 100);
|
localIndexes.value = JSON.parse(JSON.stringify(originalIndexes.value));
|
||||||
},
|
localKeyUsage.value = JSON.parse(JSON.stringify(originalKeyUsage.value));
|
||||||
beforeUnmount () {
|
|
||||||
window.removeEventListener('keydown', this.onKey);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async saveChanges () {
|
|
||||||
if (this.isSaving || !this.isValid) return;
|
|
||||||
this.isSaving = true;
|
|
||||||
|
|
||||||
const params = {
|
tableOptions.value = {
|
||||||
uid: this.connection.uid,
|
name: '',
|
||||||
schema: this.schema,
|
type: 'table',
|
||||||
fields: this.localFields,
|
engine: defaultEngine.value,
|
||||||
foreigns: this.localKeyUsage,
|
comment: '',
|
||||||
indexes: this.localIndexes,
|
collation: defaultCollation.value
|
||||||
options: this.localOptions
|
};
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
localOptions.value = JSON.parse(JSON.stringify(tableOptions.value));
|
||||||
const { status, response } = await Tables.createTable(params);
|
newFieldsCounter.value = 0;
|
||||||
|
};
|
||||||
|
|
||||||
if (status === 'success') {
|
const addField = () => {
|
||||||
await this.refreshStructure(this.connection.uid);
|
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({
|
setTimeout(() => {
|
||||||
uid: this.connection.uid,
|
const scrollable = indexTable.value.tableWrapper;
|
||||||
schema: this.schema,
|
scrollable.scrollTop = scrollable.scrollHeight + 30;
|
||||||
elementName: this.localOptions.name,
|
}, 20);
|
||||||
elementType: 'table',
|
};
|
||||||
type: 'table-props'
|
|
||||||
});
|
|
||||||
|
|
||||||
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
|
const renameField = (payload: {index: string; new: string; old: string}) => {
|
||||||
this.changeBreadcrumbs({ schema: this.schema, table: this.localOptions.name });
|
localIndexes.value = localIndexes.value.map(index => {
|
||||||
}
|
const fi = index.fields.findIndex(field => field === payload.old);
|
||||||
else
|
if (fi !== -1)
|
||||||
this.addNotification({ status: 'error', message: response });
|
index.fields[fi] = payload.new;
|
||||||
}
|
return index;
|
||||||
catch (err) {
|
});
|
||||||
this.addNotification({ status: 'error', message: err.stack });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isSaving = false;
|
localKeyUsage.value = localKeyUsage.value.map(key => {
|
||||||
this.newFieldsCounter = 0;
|
if (key.field === payload.old)
|
||||||
},
|
key.field = payload.new;
|
||||||
clearChanges () {
|
return key;
|
||||||
this.localFields = JSON.parse(JSON.stringify(this.originalFields));
|
});
|
||||||
this.localIndexes = JSON.parse(JSON.stringify(this.originalIndexes));
|
};
|
||||||
this.localKeyUsage = JSON.parse(JSON.stringify(this.originalKeyUsage));
|
|
||||||
|
|
||||||
this.tableOptions = {
|
const duplicateField = (uid: string) => {
|
||||||
name: '',
|
const fieldToClone = Object.assign({}, localFields.value.find(field => field._antares_id === uid));
|
||||||
type: 'table',
|
fieldToClone._antares_id = uidGen();
|
||||||
engine: this.defaultEngine,
|
fieldToClone.name = `${fieldToClone.name}_copy`;
|
||||||
comment: '',
|
fieldToClone.order = localFields.value.length + 1;
|
||||||
collation: this.defaultCollation
|
localFields.value = [...localFields.value, fieldToClone];
|
||||||
};
|
|
||||||
|
|
||||||
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions));
|
setTimeout(() => {
|
||||||
this.newFieldsCounter = 0;
|
const scrollable = indexTable.value.tableWrapper;
|
||||||
},
|
scrollable.scrollTop = scrollable.scrollHeight + 30;
|
||||||
addField () {
|
}, 20);
|
||||||
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 removeField = (uid: string) => {
|
||||||
const scrollable = this.$refs.indexTable.$refs.tableWrapper;
|
localFields.value = localFields.value.filter(field => field._antares_id !== uid);
|
||||||
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;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.localKeyUsage = this.localKeyUsage.map(key => {
|
const addNewIndex = (payload: { index: string; field: string }) => {
|
||||||
if (key.field === payload.old)
|
localIndexes.value = [...localIndexes.value, {
|
||||||
key.field = payload.new;
|
_antares_id: uidGen(),
|
||||||
return key;
|
name: payload.index === 'PRIMARY' ? 'PRIMARY' : payload.field,
|
||||||
});
|
fields: [payload.field],
|
||||||
},
|
type: payload.index,
|
||||||
duplicateField (uid) {
|
comment: '',
|
||||||
const fieldToClone = Object.assign({}, this.localFields.find(field => field._antares_id === uid));
|
indexType: 'BTREE',
|
||||||
fieldToClone._antares_id = uidGen();
|
indexComment: '',
|
||||||
fieldToClone.name = `${fieldToClone.name}_copy`;
|
cardinality: 0
|
||||||
fieldToClone.order = this.localFields.length + 1;
|
}];
|
||||||
this.localFields = [...this.localFields, fieldToClone];
|
};
|
||||||
|
|
||||||
setTimeout(() => {
|
const addToIndex = (payload: { index: string; field: string }) => {
|
||||||
const scrollable = this.$refs.indexTable.$refs.tableWrapper;
|
localIndexes.value = localIndexes.value.map(index => {
|
||||||
scrollable.scrollTop = scrollable.scrollHeight + 30;
|
if (index._antares_id === payload.index) index.fields.push(payload.field);
|
||||||
}, 20);
|
return index;
|
||||||
},
|
});
|
||||||
removeField (uid) {
|
};
|
||||||
this.localFields = this.localFields.filter(field => field._antares_id !== uid);
|
|
||||||
},
|
const showIntdexesModal = () => {
|
||||||
addNewIndex (payload) {
|
isIndexesModal.value = true;
|
||||||
this.localIndexes = [...this.localIndexes, {
|
};
|
||||||
_antares_id: uidGen(),
|
|
||||||
name: payload.index === 'PRIMARY' ? 'PRIMARY' : payload.field,
|
const hideIndexesModal = () => {
|
||||||
fields: [payload.field],
|
isIndexesModal.value = false;
|
||||||
type: payload.index,
|
};
|
||||||
comment: '',
|
|
||||||
indexType: 'BTREE',
|
const indexesUpdate = (indexes: TableIndex[]) => {
|
||||||
indexComment: '',
|
localIndexes.value = indexes;
|
||||||
cardinality: 0
|
};
|
||||||
}];
|
|
||||||
},
|
const showForeignModal = () => {
|
||||||
addToIndex (payload) {
|
isForeignModal.value = true;
|
||||||
this.localIndexes = this.localIndexes.map(index => {
|
};
|
||||||
if (index._antares_id === payload.index) index.fields.push(payload.field);
|
|
||||||
return index;
|
const hideForeignModal = () => {
|
||||||
});
|
isForeignModal.value = false;
|
||||||
},
|
};
|
||||||
optionsUpdate (options) {
|
|
||||||
this.localOptions = options;
|
const foreignsUpdate = (foreigns: TableForeign[]) => {
|
||||||
},
|
localKeyUsage.value = foreigns;
|
||||||
showIntdexesModal () {
|
};
|
||||||
this.isIndexesModal = true;
|
|
||||||
},
|
const onKey = (e: KeyboardEvent) => {
|
||||||
hideIndexesModal () {
|
if (props.isSelected) {
|
||||||
this.isIndexesModal = false;
|
e.stopPropagation();
|
||||||
},
|
if (e.ctrlKey && e.key === 's') { // CTRL + S
|
||||||
indexesUpdate (indexes) {
|
if (isChanged.value)
|
||||||
this.localIndexes = indexes;
|
saveChanges();
|
||||||
},
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="column col-12 empty">
|
<div class="column col-12 empty">
|
||||||
<p class="h6 empty-subtitle">
|
<p class="h6 empty-subtitle">
|
||||||
{{ $t('message.thereAreNoTableFields') }}
|
{{ t('message.thereAreNoTableFields') }}
|
||||||
</p>
|
</p>
|
||||||
<div class="empty-action">
|
<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" />
|
<i class="mdi mdi-24px mdi-playlist-plus mr-2" />
|
||||||
{{ $t('message.addNewField') }}
|
{{ t('message.addNewField') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
export default {
|
import { useI18n } from 'vue-i18n';
|
||||||
name: 'WorkspaceTabNewTableEmptyState',
|
|
||||||
emits: ['new-field']
|
const { t } = useI18n();
|
||||||
};
|
const emit = defineEmits(['new-field']);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -11,20 +11,20 @@
|
||||||
@click="saveChanges"
|
@click="saveChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||||
<span>{{ $t('word.save') }}</span>
|
<span>{{ t('word.save') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:disabled="!isChanged"
|
:disabled="!isChanged"
|
||||||
class="btn btn-link btn-sm mr-0"
|
class="btn btn-link btn-sm mr-0"
|
||||||
:title="$t('message.clearChanges')"
|
:title="t('message.clearChanges')"
|
||||||
@click="clearChanges"
|
@click="clearChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||||
<span>{{ $t('word.clear') }}</span>
|
<span>{{ t('word.clear') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-info">
|
<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>
|
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.name') }}</label>
|
<label class="form-label">{{ t('word.name') }}</label>
|
||||||
<input
|
<input
|
||||||
ref="firstInput"
|
ref="firstInput"
|
||||||
v-model="localTrigger.name"
|
v-model="localTrigger.name"
|
||||||
|
@ -45,12 +45,12 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-if="customizations.definer" class="column col-auto">
|
<div v-if="customizations.definer" class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.definer') }}</label>
|
<label class="form-label">{{ t('word.definer') }}</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localTrigger.definer"
|
v-model="localTrigger.definer"
|
||||||
:options="users"
|
:options="users"
|
||||||
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
|
:option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
|
||||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -58,7 +58,7 @@
|
||||||
<fieldset class="column columns mb-0" :disabled="customizations.triggerOnlyRename">
|
<fieldset class="column columns mb-0" :disabled="customizations.triggerOnlyRename">
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.table') }}</label>
|
<label class="form-label">{{ t('word.table') }}</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localTrigger.table"
|
v-model="localTrigger.table"
|
||||||
:options="schemaTables"
|
:options="schemaTables"
|
||||||
|
@ -70,7 +70,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.event') }}</label>
|
<label class="form-label">{{ t('word.event') }}</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localTrigger.activation"
|
v-model="localTrigger.activation"
|
||||||
|
@ -85,7 +85,7 @@
|
||||||
/>
|
/>
|
||||||
<div v-if="customizations.triggerMultipleEvents" class="px-4">
|
<div v-if="customizations.triggerMultipleEvents" class="px-4">
|
||||||
<label
|
<label
|
||||||
v-for="event in Object.keys(localEvents)"
|
v-for="event in Object.keys(localEvents) as ('INSERT' | 'UPDATE' | 'DELETE')[]"
|
||||||
:key="event"
|
:key="event"
|
||||||
class="form-checkbox form-inline"
|
class="form-checkbox form-inline"
|
||||||
@change.prevent="changeEvents(event)"
|
@change.prevent="changeEvents(event)"
|
||||||
|
@ -101,7 +101,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||||
<BaseLoader v-if="isLoading" />
|
<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
|
<QueryEditor
|
||||||
v-show="isSelected"
|
v-show="isSelected"
|
||||||
ref="queryEditor"
|
ref="queryEditor"
|
||||||
|
@ -114,219 +114,204 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { storeToRefs } from 'pinia';
|
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 { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import QueryEditor from '@/components/QueryEditor';
|
import QueryEditor from '@/components/QueryEditor.vue';
|
||||||
import BaseLoader from '@/components/BaseLoader';
|
import BaseLoader from '@/components/BaseLoader.vue';
|
||||||
import Triggers from '@/ipc-api/Triggers';
|
import Triggers from '@/ipc-api/Triggers';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
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 { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const props = defineProps({
|
||||||
|
tabUid: String,
|
||||||
|
connection: Object,
|
||||||
|
tab: Object,
|
||||||
|
isSelected: Boolean,
|
||||||
|
schema: String
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const { addNotification } = useNotificationsStore();
|
||||||
getWorkspace,
|
const workspacesStore = useWorkspacesStore();
|
||||||
refreshStructure,
|
|
||||||
changeBreadcrumbs,
|
|
||||||
setUnsavedChanges,
|
|
||||||
newTab,
|
|
||||||
removeTab,
|
|
||||||
renameTabs
|
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const {
|
||||||
addNotification,
|
getWorkspace,
|
||||||
selectedWorkspace,
|
refreshStructure,
|
||||||
getWorkspace,
|
changeBreadcrumbs,
|
||||||
refreshStructure,
|
setUnsavedChanges,
|
||||||
changeBreadcrumbs,
|
newTab,
|
||||||
setUnsavedChanges,
|
removeTab
|
||||||
newTab,
|
} = workspacesStore;
|
||||||
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);
|
|
||||||
|
|
||||||
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);
|
||||||
users () {
|
const isLoading = ref(false);
|
||||||
const users = [{ value: '' }, ...this.workspace.users];
|
const isSaving = ref(false);
|
||||||
if (!this.isDefinerInUsers) {
|
const originalTrigger = ref(null);
|
||||||
const [name, host] = this.originalTrigger.definer.replaceAll('`', '').split('@');
|
const localTrigger = ref(null);
|
||||||
users.unshift({ name, host });
|
const editorHeight = ref(300);
|
||||||
}
|
const localEvents = ref({ INSERT: false, UPDATE: false, DELETE: false });
|
||||||
|
|
||||||
return users;
|
const workspace = computed(() => {
|
||||||
}
|
return getWorkspace(props.connection.uid);
|
||||||
},
|
});
|
||||||
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: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
this.localTrigger = JSON.parse(JSON.stringify(this.originalTrigger));
|
const customizations = computed(() => {
|
||||||
|
return workspace.value.customizations;
|
||||||
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
const isChanged = computed(() => {
|
||||||
this.resizeQueryEditor();
|
return JSON.stringify(originalTrigger.value) !== JSON.stringify(localTrigger.value);
|
||||||
}, 50);
|
});
|
||||||
|
|
||||||
window.addEventListener('keydown', this.onKey);
|
const isDefinerInUsers = computed(() => {
|
||||||
},
|
return originalTrigger.value ? workspace.value.users.some(user => originalTrigger.value.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
||||||
mounted () {
|
});
|
||||||
if (this.isSelected)
|
|
||||||
this.changeBreadcrumbs({ schema: this.schema });
|
|
||||||
|
|
||||||
setTimeout(() => {
|
const schemaTables = computed(() => {
|
||||||
this.$refs.firstInput.focus();
|
const schemaTables = workspace.value.structure
|
||||||
}, 100);
|
.filter(schema => schema.name === props.schema)
|
||||||
|
.map(schema => schema.tables);
|
||||||
|
|
||||||
window.addEventListener('resize', this.resizeQueryEditor);
|
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||||
},
|
});
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
const users = computed(() => {
|
||||||
const { status, response } = await Triggers.createTrigger(params);
|
const users = [{ value: '' }, ...workspace.value.users];
|
||||||
|
if (!isDefinerInUsers.value) {
|
||||||
|
const [name, host] = originalTrigger.value.definer.replaceAll('`', '').split('@');
|
||||||
|
users.unshift({ name, host });
|
||||||
|
}
|
||||||
|
|
||||||
if (status === 'success') {
|
return users;
|
||||||
await this.refreshStructure(this.connection.uid);
|
});
|
||||||
|
|
||||||
this.newTab({
|
const changeEvents = (event: 'INSERT' | 'UPDATE' | 'DELETE') => {
|
||||||
uid: this.connection.uid,
|
if (customizations.value.triggerMultipleEvents) {
|
||||||
schema: this.schema,
|
localEvents.value[event] = !localEvents.value[event];
|
||||||
elementName: this.localTrigger.name,
|
localTrigger.value.event = [];
|
||||||
elementType: 'trigger',
|
for (const key in localEvents.value) {
|
||||||
type: 'trigger-props'
|
if (localEvents.value[key as 'INSERT' | 'UPDATE' | 'DELETE'])
|
||||||
});
|
localTrigger.value.event.push(key);
|
||||||
|
|
||||||
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 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>
|
</script>
|
||||||
|
|
|
@ -11,16 +11,16 @@
|
||||||
@click="saveChanges"
|
@click="saveChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||||
<span>{{ $t('word.save') }}</span>
|
<span>{{ t('word.save') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:disabled="!isChanged"
|
:disabled="!isChanged"
|
||||||
class="btn btn-link btn-sm mr-0"
|
class="btn btn-link btn-sm mr-0"
|
||||||
:title="$t('message.clearChanges')"
|
:title="t('message.clearChanges')"
|
||||||
@click="clearChanges"
|
@click="clearChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||||
<span>{{ $t('word.clear') }}</span>
|
<span>{{ t('word.clear') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.name') }}
|
{{ t('word.name') }}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
ref="firstInput"
|
ref="firstInput"
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
<div v-if="customizations.triggerFunctionlanguages" class="column col-auto">
|
<div v-if="customizations.triggerFunctionlanguages" class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.language') }}
|
{{ t('word.language') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localFunction.language"
|
v-model="localFunction.language"
|
||||||
|
@ -55,20 +55,20 @@
|
||||||
<div v-if="customizations.definer" class="column col-auto">
|
<div v-if="customizations.definer" class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.definer') }}
|
{{ t('word.definer') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localFunction.definer"
|
v-model="localFunction.definer"
|
||||||
:options="workspace.users"
|
:options="workspace.users"
|
||||||
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
|
:option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
|
||||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="customizations.comment" class="form-group">
|
<div v-if="customizations.comment" class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.comment') }}
|
{{ t('word.comment') }}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
v-model="localFunction.comment"
|
v-model="localFunction.comment"
|
||||||
|
@ -80,7 +80,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||||
<BaseLoader v-if="isLoading" />
|
<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
|
<QueryEditor
|
||||||
v-show="isSelected"
|
v-show="isSelected"
|
||||||
ref="queryEditor"
|
ref="queryEditor"
|
||||||
|
@ -93,190 +93,157 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { storeToRefs } from 'pinia';
|
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 { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import BaseLoader from '@/components/BaseLoader';
|
import BaseLoader from '@/components/BaseLoader.vue';
|
||||||
import QueryEditor from '@/components/QueryEditor';
|
import QueryEditor from '@/components/QueryEditor.vue';
|
||||||
import Functions from '@/ipc-api/Functions';
|
import Functions from '@/ipc-api/Functions';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
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 { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const props = defineProps({
|
||||||
|
tabUid: String,
|
||||||
|
connection: Object,
|
||||||
|
tab: Object,
|
||||||
|
isSelected: Boolean,
|
||||||
|
schema: String
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const { addNotification } = useNotificationsStore();
|
||||||
getWorkspace,
|
const workspacesStore = useWorkspacesStore();
|
||||||
refreshStructure,
|
|
||||||
changeBreadcrumbs,
|
|
||||||
setUnsavedChanges,
|
|
||||||
newTab,
|
|
||||||
removeTab,
|
|
||||||
renameTabs
|
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const {
|
||||||
addNotification,
|
getWorkspace,
|
||||||
selectedWorkspace,
|
refreshStructure,
|
||||||
getWorkspace,
|
changeBreadcrumbs,
|
||||||
refreshStructure,
|
setUnsavedChanges,
|
||||||
changeBreadcrumbs,
|
newTab,
|
||||||
setUnsavedChanges,
|
removeTab
|
||||||
newTab,
|
} = workspacesStore;
|
||||||
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);
|
|
||||||
|
|
||||||
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 });
|
||||||
}
|
}
|
||||||
},
|
else
|
||||||
watch: {
|
addNotification({ status: 'error', message: response });
|
||||||
isSelected (val) {
|
}
|
||||||
if (val)
|
catch (err) {
|
||||||
this.changeBreadcrumbs({ schema: this.schema });
|
addNotification({ status: 'error', message: err.stack });
|
||||||
},
|
}
|
||||||
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: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction));
|
isSaving.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
setTimeout(() => {
|
const clearChanges = () => {
|
||||||
this.resizeQueryEditor();
|
localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
|
||||||
}, 50);
|
queryEditor.value.editor.session.setValue(localFunction.value.sql);
|
||||||
|
};
|
||||||
|
|
||||||
window.addEventListener('keydown', this.onKey);
|
const resizeQueryEditor = () => {
|
||||||
},
|
if (queryEditor.value) {
|
||||||
mounted () {
|
const footer = document.getElementById('footer');
|
||||||
if (this.isSelected)
|
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||||
this.changeBreadcrumbs({ schema: this.schema });
|
editorHeight.value = size;
|
||||||
|
queryEditor.value.editor.resize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
setTimeout(() => {
|
const onKey = (e: KeyboardEvent) => {
|
||||||
this.$refs.firstInput.focus();
|
if (props.isSelected) {
|
||||||
}, 100);
|
e.stopPropagation();
|
||||||
|
if (e.ctrlKey && e.key === 's') { // CTRL + S
|
||||||
window.addEventListener('resize', this.resizeQueryEditor);
|
if (isChanged.value)
|
||||||
},
|
saveChanges();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
|
@ -11,20 +11,20 @@
|
||||||
@click="saveChanges"
|
@click="saveChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||||
<span>{{ $t('word.save') }}</span>
|
<span>{{ t('word.save') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:disabled="!isChanged"
|
:disabled="!isChanged"
|
||||||
class="btn btn-link btn-sm mr-0"
|
class="btn btn-link btn-sm mr-0"
|
||||||
:title="$t('message.clearChanges')"
|
:title="t('message.clearChanges')"
|
||||||
@click="clearChanges"
|
@click="clearChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||||
<span>{{ $t('word.clear') }}</span>
|
<span>{{ t('word.clear') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-info">
|
<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>
|
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.name') }}</label>
|
<label class="form-label">{{ t('word.name') }}</label>
|
||||||
<input
|
<input
|
||||||
ref="firstInput"
|
ref="firstInput"
|
||||||
v-model="localView.name"
|
v-model="localView.name"
|
||||||
|
@ -45,19 +45,19 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div v-if="workspace.customizations.definer" class="form-group">
|
<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
|
<BaseSelect
|
||||||
v-model="localView.definer"
|
v-model="localView.definer"
|
||||||
:options="users"
|
:options="users"
|
||||||
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
|
:option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
|
||||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-auto mr-2">
|
<div class="column col-auto mr-2">
|
||||||
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
|
<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
|
<BaseSelect
|
||||||
v-model="localView.security"
|
v-model="localView.security"
|
||||||
:options="['DEFINER', 'INVOKER']"
|
:options="['DEFINER', 'INVOKER']"
|
||||||
|
@ -67,7 +67,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-auto mr-2">
|
<div class="column col-auto mr-2">
|
||||||
<div v-if="workspace.customizations.viewAlgorithm" class="form-group">
|
<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
|
<BaseSelect
|
||||||
v-model="localView.algorithm"
|
v-model="localView.algorithm"
|
||||||
:options="['UNDEFINED', 'MERGE', 'TEMPTABLE']"
|
:options="['UNDEFINED', 'MERGE', 'TEMPTABLE']"
|
||||||
|
@ -77,10 +77,10 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2">
|
<div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('message.updateOption') }}</label>
|
<label class="form-label">{{ t('message.updateOption') }}</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localView.updateOption"
|
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'}]"
|
:options="[{label: 'None', value: ''}, {label: 'CASCADED', value: 'CASCADED'}, {label: 'LOCAL', value: 'LOCAL'}]"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
/>
|
/>
|
||||||
|
@ -90,7 +90,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||||
<BaseLoader v-if="isLoading" />
|
<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
|
<QueryEditor
|
||||||
v-show="isSelected"
|
v-show="isSelected"
|
||||||
ref="queryEditor"
|
ref="queryEditor"
|
||||||
|
@ -103,194 +103,170 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { storeToRefs } from 'pinia';
|
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 { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import BaseLoader from '@/components/BaseLoader';
|
import BaseLoader from '@/components/BaseLoader.vue';
|
||||||
import QueryEditor from '@/components/QueryEditor';
|
import QueryEditor from '@/components/QueryEditor.vue';
|
||||||
import Views from '@/ipc-api/Views';
|
import Views from '@/ipc-api/Views';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
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 { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const props = defineProps({
|
||||||
|
tabUid: String,
|
||||||
|
connection: Object,
|
||||||
|
tab: Object,
|
||||||
|
isSelected: Boolean,
|
||||||
|
schema: String
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const { addNotification } = useNotificationsStore();
|
||||||
getWorkspace,
|
const workspacesStore = useWorkspacesStore();
|
||||||
refreshStructure,
|
|
||||||
setUnsavedChanges,
|
|
||||||
changeBreadcrumbs,
|
|
||||||
newTab,
|
|
||||||
removeTab,
|
|
||||||
renameTabs
|
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const {
|
||||||
addNotification,
|
getWorkspace,
|
||||||
selectedWorkspace,
|
refreshStructure,
|
||||||
getWorkspace,
|
setUnsavedChanges,
|
||||||
refreshStructure,
|
changeBreadcrumbs,
|
||||||
setUnsavedChanges,
|
newTab,
|
||||||
changeBreadcrumbs,
|
removeTab
|
||||||
newTab,
|
} = workspacesStore;
|
||||||
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 });
|
|
||||||
}
|
|
||||||
|
|
||||||
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 });
|
||||||
}
|
}
|
||||||
},
|
else
|
||||||
watch: {
|
addNotification({ status: 'error', message: response });
|
||||||
isSelected (val) {
|
}
|
||||||
if (val) {
|
catch (err) {
|
||||||
this.changeBreadcrumbs({ schema: this.schema, view: this.view });
|
addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
isSaving.value = false;
|
||||||
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: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
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(() => {
|
const resizeQueryEditor = () => {
|
||||||
this.resizeQueryEditor();
|
if (queryEditor.value) {
|
||||||
}, 50);
|
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);
|
const onKey = (e: KeyboardEvent) => {
|
||||||
},
|
if (props.isSelected) {
|
||||||
mounted () {
|
e.stopPropagation();
|
||||||
if (this.isSelected)
|
if (e.ctrlKey && e.key === 's') { // CTRL + S
|
||||||
this.changeBreadcrumbs({ schema: this.schema });
|
if (isChanged.value)
|
||||||
|
saveChanges();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
|
@ -11,16 +11,16 @@
|
||||||
@click="saveChanges"
|
@click="saveChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||||
<span>{{ $t('word.save') }}</span>
|
<span>{{ t('word.save') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:disabled="!isChanged"
|
:disabled="!isChanged"
|
||||||
class="btn btn-link btn-sm mr-0"
|
class="btn btn-link btn-sm mr-0"
|
||||||
:title="$t('message.clearChanges')"
|
:title="t('message.clearChanges')"
|
||||||
@click="clearChanges"
|
@click="clearChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||||
<span>{{ $t('word.clear') }}</span>
|
<span>{{ t('word.clear') }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="divider-vert py-3" />
|
<div class="divider-vert py-3" />
|
||||||
|
@ -31,15 +31,15 @@
|
||||||
@click="runFunctionCheck"
|
@click="runFunctionCheck"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-play mr-1" />
|
<i class="mdi mdi-24px mdi-play mr-1" />
|
||||||
<span>{{ $t('word.run') }}</span>
|
<span>{{ t('word.run') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-dark btn-sm" @click="showParamsModal">
|
<button class="btn btn-dark btn-sm" @click="showParamsModal">
|
||||||
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
|
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
|
||||||
<span>{{ $t('word.parameters') }}</span>
|
<span>{{ t('word.parameters') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-info">
|
<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>
|
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.name') }}
|
{{ t('word.name') }}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
ref="firstInput"
|
ref="firstInput"
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
<div v-if="customizations.languages" class="column col-auto">
|
<div v-if="customizations.languages" class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.language') }}
|
{{ t('word.language') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localFunction.language"
|
v-model="localFunction.language"
|
||||||
|
@ -76,13 +76,13 @@
|
||||||
<div v-if="customizations.definer" class="column col-auto">
|
<div v-if="customizations.definer" class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.definer') }}
|
{{ t('word.definer') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localFunction.definer"
|
v-model="localFunction.definer"
|
||||||
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]"
|
:options="[{value: '', name:t('message.currentUser')}, ...workspace.users]"
|
||||||
:option-label="(user) => user.value === '' ? user.name : `${user.name}@${user.host}`"
|
:option-label="(user: any) => user.value === '' ? user.name : `${user.name}@${user.host}`"
|
||||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -90,13 +90,13 @@
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.returns') }}
|
{{ t('word.returns') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localFunction.returns"
|
v-model="localFunction.returns"
|
||||||
class="form-select text-uppercase"
|
class="form-select text-uppercase"
|
||||||
:options="[{ name: 'VOID' }, ...workspace.dataTypes]"
|
:options="[{ name: 'VOID' }, ...(workspace.dataTypes as any)]"
|
||||||
group-label="group"
|
group-label="group"
|
||||||
group-values="types"
|
group-values="types"
|
||||||
option-label="name"
|
option-label="name"
|
||||||
|
@ -110,7 +110,7 @@
|
||||||
class="form-input"
|
class="form-input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
:placeholder="$t('word.length')"
|
:placeholder="t('word.length')"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -118,7 +118,7 @@
|
||||||
<div v-if="customizations.comment" class="column">
|
<div v-if="customizations.comment" class="column">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.comment') }}
|
{{ t('word.comment') }}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
v-model="localFunction.comment"
|
v-model="localFunction.comment"
|
||||||
|
@ -130,7 +130,7 @@
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('message.sqlSecurity') }}
|
{{ t('message.sqlSecurity') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localFunction.security"
|
v-model="localFunction.security"
|
||||||
|
@ -142,7 +142,7 @@
|
||||||
<div v-if="customizations.functionDataAccess" class="column col-auto">
|
<div v-if="customizations.functionDataAccess" class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('message.dataAccess') }}
|
{{ t('message.dataAccess') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localFunction.dataAccess"
|
v-model="localFunction.dataAccess"
|
||||||
|
@ -155,7 +155,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label d-invisible">.</label>
|
<label class="form-label d-invisible">.</label>
|
||||||
<label class="form-checkbox form-inline">
|
<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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -163,7 +163,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||||
<BaseLoader v-if="isLoading" />
|
<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
|
<QueryEditor
|
||||||
v-show="isSelected"
|
v-show="isSelected"
|
||||||
ref="queryEditor"
|
ref="queryEditor"
|
||||||
|
@ -191,300 +191,273 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { storeToRefs } from 'pinia';
|
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
||||||
|
import { Ace } from 'ace-builds';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import { uidGen } from 'common/libs/uidGen';
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
import BaseLoader from '@/components/BaseLoader';
|
import BaseLoader from '@/components/BaseLoader.vue';
|
||||||
import QueryEditor from '@/components/QueryEditor';
|
import QueryEditor from '@/components/QueryEditor.vue';
|
||||||
import WorkspaceTabPropsFunctionParamsModal from '@/components/WorkspaceTabPropsFunctionParamsModal';
|
import WorkspaceTabPropsFunctionParamsModal from '@/components/WorkspaceTabPropsFunctionParamsModal.vue';
|
||||||
import ModalAskParameters from '@/components/ModalAskParameters';
|
import ModalAskParameters from '@/components/ModalAskParameters.vue';
|
||||||
import Functions from '@/ipc-api/Functions';
|
import Functions from '@/ipc-api/Functions';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { AlterFunctionParams, FunctionInfos, FunctionParam } from 'common/interfaces/antares';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
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 { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const props = defineProps({
|
||||||
|
tabUid: String,
|
||||||
|
connection: Object,
|
||||||
|
function: String,
|
||||||
|
isSelected: Boolean,
|
||||||
|
schema: String
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const { addNotification } = useNotificationsStore();
|
||||||
getWorkspace,
|
const workspacesStore = useWorkspacesStore();
|
||||||
refreshStructure,
|
|
||||||
renameTabs,
|
|
||||||
newTab,
|
|
||||||
changeBreadcrumbs,
|
|
||||||
setUnsavedChanges
|
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const {
|
||||||
addNotification,
|
getWorkspace,
|
||||||
selectedWorkspace,
|
refreshStructure,
|
||||||
getWorkspace,
|
renameTabs,
|
||||||
refreshStructure,
|
newTab,
|
||||||
renameTabs,
|
changeBreadcrumbs,
|
||||||
newTab,
|
setUnsavedChanges
|
||||||
changeBreadcrumbs,
|
} = workspacesStore;
|
||||||
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);
|
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
},
|
else
|
||||||
watch: {
|
addNotification({ status: 'error', message: response });
|
||||||
async schema () {
|
}
|
||||||
if (this.isSelected) {
|
catch (err) {
|
||||||
await this.getFunctionData();
|
addNotification({ status: 'error', message: err.stack });
|
||||||
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
|
}
|
||||||
this.lastFunction = this.function;
|
|
||||||
|
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
|
else
|
||||||
this.runFunction();
|
getFunctionData();
|
||||||
},
|
}
|
||||||
runFunction (params) {
|
else
|
||||||
if (!params) params = [];
|
addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
let sql;
|
isSaving.value = false;
|
||||||
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(',')})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.newTab({ uid: this.connection.uid, content: sql, type: 'query', autorun: true });
|
const clearChanges = () => {
|
||||||
},
|
localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
|
||||||
showParamsModal () {
|
queryEditor.value.editor.session.setValue(localFunction.value.sql);
|
||||||
this.isParamsModal = true;
|
};
|
||||||
},
|
|
||||||
hideParamsModal () {
|
const resizeQueryEditor = () => {
|
||||||
this.isParamsModal = false;
|
if (queryEditor.value) {
|
||||||
},
|
const footer = document.getElementById('footer');
|
||||||
showAskParamsModal () {
|
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||||
this.isAskingParameters = true;
|
editorHeight.value = size;
|
||||||
},
|
queryEditor.value.editor.resize();
|
||||||
hideAskParamsModal () {
|
}
|
||||||
this.isAskingParameters = false;
|
};
|
||||||
},
|
|
||||||
onKey (e) {
|
const parametersUpdate = (parameters: FunctionParam[]) => {
|
||||||
if (this.isSelected) {
|
localFunction.value = { ...localFunction.value, parameters };
|
||||||
e.stopPropagation();
|
};
|
||||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
|
||||||
if (this.isChanged)
|
const runFunctionCheck = () => {
|
||||||
this.saveChanges();
|
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>
|
</script>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
:confirm-text="$t('word.confirm')"
|
:confirm-text="t('word.confirm')"
|
||||||
size="medium"
|
size="medium"
|
||||||
class="options-modal"
|
class="options-modal"
|
||||||
@confirm="confirmParametersChange"
|
@confirm="confirmParametersChange"
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #body>
|
<template #body>
|
||||||
|
@ -20,16 +20,16 @@
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<button class="btn btn-dark btn-sm d-flex" @click="addParameter">
|
<button class="btn btn-dark btn-sm d-flex" @click="addParameter">
|
||||||
<i class="mdi mdi-24px mdi-plus mr-1" />
|
<i class="mdi mdi-24px mdi-plus mr-1" />
|
||||||
<span>{{ $t('word.add') }}</span>
|
<span>{{ t('word.add') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
|
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
|
||||||
:title="$t('message.clearChanges')"
|
:title="t('message.clearChanges')"
|
||||||
:disabled="!isChanged"
|
:disabled="!isChanged"
|
||||||
@click.prevent="clearChanges"
|
@click.prevent="clearChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||||
<span>{{ $t('word.clear') }}</span>
|
<span>{{ t('word.clear') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -55,7 +55,7 @@
|
||||||
<div class="tile-action">
|
<div class="tile-action">
|
||||||
<button
|
<button
|
||||||
class="btn btn-link remove-field p-0 mr-2"
|
class="btn btn-link remove-field p-0 mr-2"
|
||||||
:title="$t('word.delete')"
|
:title="t('word.delete')"
|
||||||
@click.prevent="removeParameter(param._antares_id)"
|
@click.prevent="removeParameter(param._antares_id)"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-close" />
|
<i class="mdi mdi-close" />
|
||||||
|
@ -74,7 +74,7 @@
|
||||||
>
|
>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label col-3">
|
<label class="form-label col-3">
|
||||||
{{ $t('word.name') }}
|
{{ t('word.name') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<input
|
<input
|
||||||
|
@ -86,7 +86,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label col-3">
|
<label class="form-label col-3">
|
||||||
{{ $t('word.type') }}
|
{{ t('word.type') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
|
@ -102,7 +102,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-if="customizations.parametersLength" class="form-group">
|
<div v-if="customizations.parametersLength" class="form-group">
|
||||||
<label class="form-label col-3">
|
<label class="form-label col-3">
|
||||||
{{ $t('word.length') }}
|
{{ t('word.length') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<input
|
<input
|
||||||
|
@ -115,7 +115,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-if="customizations.functionContext" class="form-group">
|
<div v-if="customizations.functionContext" class="form-group">
|
||||||
<label class="form-label col-3">
|
<label class="form-label col-3">
|
||||||
{{ $t('word.context') }}
|
{{ t('word.context') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<label class="form-radio">
|
<label class="form-radio">
|
||||||
|
@ -150,11 +150,11 @@
|
||||||
<i class="mdi mdi-dots-horizontal mdi-48px" />
|
<i class="mdi mdi-dots-horizontal mdi-48px" />
|
||||||
</div>
|
</div>
|
||||||
<p class="empty-title h5">
|
<p class="empty-title h5">
|
||||||
{{ $t('message.thereAreNoParameters') }}
|
{{ t('message.thereAreNoParameters') }}
|
||||||
</p>
|
</p>
|
||||||
<div class="empty-action">
|
<div class="empty-action">
|
||||||
<button class="btn btn-primary" @click="addParameter">
|
<button class="btn btn-primary" @click="addParameter">
|
||||||
{{ $t('message.createNewParameter') }}
|
{{ t('message.createNewParameter') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -164,113 +164,118 @@
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</template>
|
</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 { uidGen } from 'common/libs/uidGen';
|
||||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
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;
|
|
||||||
|
|
||||||
if (this.parametersProxy.length)
|
const props = defineProps({
|
||||||
this.resetSelectedID();
|
localParameters: {
|
||||||
|
type: Array,
|
||||||
this.getModalInnerHeight();
|
default: () => []
|
||||||
window.addEventListener('resize', this.getModalInnerHeight);
|
|
||||||
},
|
},
|
||||||
unmounted () {
|
func: String,
|
||||||
window.removeEventListener('resize', this.getModalInnerHeight);
|
workspace: Object
|
||||||
},
|
});
|
||||||
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: ''
|
|
||||||
}];
|
|
||||||
|
|
||||||
if (this.parametersProxy.length === 1)
|
const emit = defineEmits(['hide', 'parameters-update']);
|
||||||
this.resetSelectedID();
|
|
||||||
|
|
||||||
setTimeout(() => {
|
const parametersPanel: Ref<HTMLDivElement> = ref(null);
|
||||||
this.$refs.parametersPanel.scrollTop = this.$refs.parametersPanel.scrollHeight + 60;
|
const parametersProxy = ref([]);
|
||||||
this.selectedParam = newUid;
|
const selectedParam = ref('');
|
||||||
}, 20);
|
const modalInnerHeight = ref(400);
|
||||||
},
|
const i = ref(1);
|
||||||
removeParameter (uid) {
|
|
||||||
this.parametersProxy = this.parametersProxy.filter(param => param._antares_id !== uid);
|
|
||||||
|
|
||||||
if (this.parametersProxy.length && this.selectedParam === uid)
|
const selectedParamObj = computed(() => {
|
||||||
this.resetSelectedID();
|
return parametersProxy.value.find(param => param._antares_id === selectedParam.value);
|
||||||
},
|
});
|
||||||
clearChanges () {
|
|
||||||
this.parametersProxy = JSON.parse(JSON.stringify(this.localParameters));
|
|
||||||
this.i = this.parametersProxy.length + 1;
|
|
||||||
|
|
||||||
if (!this.parametersProxy.some(param => param.name === this.selectedParam))
|
const isChanged = computed(() => {
|
||||||
this.resetSelectedID();
|
return JSON.stringify(props.localParameters) !== JSON.stringify(parametersProxy.value);
|
||||||
},
|
});
|
||||||
resetSelectedID () {
|
|
||||||
this.selectedParam = this.parametersProxy.length ? this.parametersProxy[0]._antares_id : '';
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -11,16 +11,16 @@
|
||||||
@click="saveChanges"
|
@click="saveChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||||
<span>{{ $t('word.save') }}</span>
|
<span>{{ t('word.save') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:disabled="!isChanged"
|
:disabled="!isChanged"
|
||||||
class="btn btn-link btn-sm mr-0"
|
class="btn btn-link btn-sm mr-0"
|
||||||
:title="$t('message.clearChanges')"
|
:title="t('message.clearChanges')"
|
||||||
@click="clearChanges"
|
@click="clearChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||||
<span>{{ $t('word.clear') }}</span>
|
<span>{{ t('word.clear') }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="divider-vert py-3" />
|
<div class="divider-vert py-3" />
|
||||||
|
@ -31,15 +31,15 @@
|
||||||
@click="runRoutineCheck"
|
@click="runRoutineCheck"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-play mr-1" />
|
<i class="mdi mdi-24px mdi-play mr-1" />
|
||||||
<span>{{ $t('word.run') }}</span>
|
<span>{{ t('word.run') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-dark btn-sm" @click="showParamsModal">
|
<button class="btn btn-dark btn-sm" @click="showParamsModal">
|
||||||
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
|
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
|
||||||
<span>{{ $t('word.parameters') }}</span>
|
<span>{{ t('word.parameters') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-info">
|
<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>
|
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.name') }}
|
{{ t('word.name') }}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
ref="firstInput"
|
ref="firstInput"
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
<div v-if="customizations.languages" class="column col-auto">
|
<div v-if="customizations.languages" class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.language') }}
|
{{ t('word.language') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localRoutine.language"
|
v-model="localRoutine.language"
|
||||||
|
@ -76,13 +76,13 @@
|
||||||
<div v-if="customizations.definer" class="column col-auto">
|
<div v-if="customizations.definer" class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.definer') }}
|
{{ t('word.definer') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localRoutine.definer"
|
v-model="localRoutine.definer"
|
||||||
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]"
|
:options="[{value: '', name: t('message.currentUser')}, ...workspace.users]"
|
||||||
:option-label="(user) => user.value === '' ? user.name : `${user.name}@${user.host}`"
|
:option-label="(user: any) => user.value === '' ? user.name : `${user.name}@${user.host}`"
|
||||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -90,7 +90,7 @@
|
||||||
<div v-if="customizations.comment" class="column">
|
<div v-if="customizations.comment" class="column">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.comment') }}
|
{{ t('word.comment') }}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
v-model="localRoutine.comment"
|
v-model="localRoutine.comment"
|
||||||
|
@ -102,7 +102,7 @@
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('message.sqlSecurity') }}
|
{{ t('message.sqlSecurity') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localRoutine.security"
|
v-model="localRoutine.security"
|
||||||
|
@ -114,7 +114,7 @@
|
||||||
<div v-if="customizations.procedureDataAccess" class="column col-auto">
|
<div v-if="customizations.procedureDataAccess" class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('message.dataAccess') }}
|
{{ t('message.dataAccess') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localRoutine.dataAccess"
|
v-model="localRoutine.dataAccess"
|
||||||
|
@ -127,7 +127,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label d-invisible">.</label>
|
<label class="form-label d-invisible">.</label>
|
||||||
<label class="form-checkbox form-inline">
|
<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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -135,7 +135,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||||
<BaseLoader v-if="isLoading" />
|
<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
|
<QueryEditor
|
||||||
v-show="isSelected"
|
v-show="isSelected"
|
||||||
ref="queryEditor"
|
ref="queryEditor"
|
||||||
|
@ -163,286 +163,272 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { storeToRefs } from 'pinia';
|
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 { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import { uidGen } from 'common/libs/uidGen';
|
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 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';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
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 { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const props = defineProps({
|
||||||
|
tabUid: String,
|
||||||
|
connection: Object,
|
||||||
|
routine: String,
|
||||||
|
isSelected: Boolean,
|
||||||
|
schema: String
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const { addNotification } = useNotificationsStore();
|
||||||
getWorkspace,
|
const workspacesStore = useWorkspacesStore();
|
||||||
refreshStructure,
|
|
||||||
renameTabs,
|
|
||||||
newTab,
|
|
||||||
changeBreadcrumbs,
|
|
||||||
setUnsavedChanges
|
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const {
|
||||||
addNotification,
|
getWorkspace,
|
||||||
selectedWorkspace,
|
refreshStructure,
|
||||||
getWorkspace,
|
renameTabs,
|
||||||
refreshStructure,
|
newTab,
|
||||||
renameTabs,
|
changeBreadcrumbs,
|
||||||
newTab,
|
setUnsavedChanges
|
||||||
changeBreadcrumbs,
|
} = workspacesStore;
|
||||||
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);
|
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
},
|
else
|
||||||
watch: {
|
addNotification({ status: 'error', message: response });
|
||||||
async schema () {
|
}
|
||||||
if (this.isSelected) {
|
catch (err) {
|
||||||
await this.getRoutineData();
|
addNotification({ status: 'error', message: err.stack });
|
||||||
this.$refs.queryEditor.editor.session.setValue(this.localRoutine.sql);
|
}
|
||||||
this.lastRoutine = this.routine;
|
|
||||||
|
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
|
else
|
||||||
this.runRoutine();
|
getRoutineData();
|
||||||
},
|
}
|
||||||
runRoutine (params) {
|
else
|
||||||
if (!params) params = [];
|
addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
let sql;
|
isSaving.value = false;
|
||||||
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(',')})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.newTab({ uid: this.connection.uid, content: sql, type: 'query', autorun: true });
|
const clearChanges = () => {
|
||||||
},
|
localRoutine.value = JSON.parse(JSON.stringify(originalRoutine.value));
|
||||||
showParamsModal () {
|
queryEditor.value.editor.session.setValue(localRoutine.value.sql);
|
||||||
this.isParamsModal = true;
|
};
|
||||||
},
|
|
||||||
hideParamsModal () {
|
const resizeQueryEditor = () => {
|
||||||
this.isParamsModal = false;
|
if (queryEditor.value) {
|
||||||
},
|
const footer = document.getElementById('footer');
|
||||||
showAskParamsModal () {
|
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||||
this.isAskingParameters = true;
|
editorHeight.value = size;
|
||||||
},
|
queryEditor.value.editor.resize();
|
||||||
hideAskParamsModal () {
|
}
|
||||||
this.isAskingParameters = false;
|
};
|
||||||
},
|
|
||||||
onKey (e) {
|
const parametersUpdate = (parameters: FunctionParam[]) => {
|
||||||
if (this.isSelected) {
|
localRoutine.value = { ...localRoutine.value, parameters };
|
||||||
e.stopPropagation();
|
};
|
||||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
|
||||||
if (this.isChanged)
|
const runRoutineCheck = () => {
|
||||||
this.saveChanges();
|
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>
|
</script>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
:confirm-text="$t('word.confirm')"
|
:confirm-text="t('word.confirm')"
|
||||||
size="medium"
|
size="medium"
|
||||||
class="options-modal"
|
class="options-modal"
|
||||||
@confirm="confirmParametersChange"
|
@confirm="confirmParametersChange"
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #body>
|
<template #body>
|
||||||
|
@ -20,16 +20,16 @@
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<button class="btn btn-dark btn-sm d-flex" @click="addParameter">
|
<button class="btn btn-dark btn-sm d-flex" @click="addParameter">
|
||||||
<i class="mdi mdi-24px mdi-plus mr-1" />
|
<i class="mdi mdi-24px mdi-plus mr-1" />
|
||||||
<span>{{ $t('word.add') }}</span>
|
<span>{{ t('word.add') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
|
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
|
||||||
:title="$t('message.clearChanges')"
|
:title="t('message.clearChanges')"
|
||||||
:disabled="!isChanged"
|
:disabled="!isChanged"
|
||||||
@click.prevent="clearChanges"
|
@click.prevent="clearChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||||
<span>{{ $t('word.clear') }}</span>
|
<span>{{ t('word.clear') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -55,7 +55,7 @@
|
||||||
<div class="tile-action">
|
<div class="tile-action">
|
||||||
<button
|
<button
|
||||||
class="btn btn-link remove-field p-0 mr-2"
|
class="btn btn-link remove-field p-0 mr-2"
|
||||||
:title="$t('word.delete')"
|
:title="t('word.delete')"
|
||||||
@click.prevent="removeParameter(param._antares_id)"
|
@click.prevent="removeParameter(param._antares_id)"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-close" />
|
<i class="mdi mdi-close" />
|
||||||
|
@ -74,7 +74,7 @@
|
||||||
>
|
>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label col-3">
|
<label class="form-label col-3">
|
||||||
{{ $t('word.name') }}
|
{{ t('word.name') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<input
|
<input
|
||||||
|
@ -86,7 +86,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label col-3">
|
<label class="form-label col-3">
|
||||||
{{ $t('word.type') }}
|
{{ t('word.type') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
|
@ -102,7 +102,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-if="customizations.parametersLength" class="form-group">
|
<div v-if="customizations.parametersLength" class="form-group">
|
||||||
<label class="form-label col-3">
|
<label class="form-label col-3">
|
||||||
{{ $t('word.length') }}
|
{{ t('word.length') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<input
|
<input
|
||||||
|
@ -115,7 +115,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-if="customizations.procedureContext" class="form-group">
|
<div v-if="customizations.procedureContext" class="form-group">
|
||||||
<label class="form-label col-3">
|
<label class="form-label col-3">
|
||||||
{{ $t('word.context') }}
|
{{ t('word.context') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<label class="form-radio">
|
<label class="form-radio">
|
||||||
|
@ -150,11 +150,11 @@
|
||||||
<i class="mdi mdi-dots-horizontal mdi-48px" />
|
<i class="mdi mdi-dots-horizontal mdi-48px" />
|
||||||
</div>
|
</div>
|
||||||
<p class="empty-title h5">
|
<p class="empty-title h5">
|
||||||
{{ $t('message.thereAreNoParameters') }}
|
{{ t('message.thereAreNoParameters') }}
|
||||||
</p>
|
</p>
|
||||||
<div class="empty-action">
|
<div class="empty-action">
|
||||||
<button class="btn btn-primary" @click="addParameter">
|
<button class="btn btn-primary" @click="addParameter">
|
||||||
{{ $t('message.createNewParameter') }}
|
{{ t('message.createNewParameter') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -164,113 +164,118 @@
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</template>
|
</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 { uidGen } from 'common/libs/uidGen';
|
||||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
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;
|
|
||||||
|
|
||||||
if (this.parametersProxy.length)
|
const props = defineProps({
|
||||||
this.resetSelectedID();
|
localParameters: {
|
||||||
|
type: Array,
|
||||||
this.getModalInnerHeight();
|
default: () => []
|
||||||
window.addEventListener('resize', this.getModalInnerHeight);
|
|
||||||
},
|
},
|
||||||
unmounted () {
|
routine: String,
|
||||||
window.removeEventListener('resize', this.getModalInnerHeight);
|
workspace: Object
|
||||||
},
|
});
|
||||||
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: ''
|
|
||||||
}];
|
|
||||||
|
|
||||||
if (this.parametersProxy.length === 1)
|
const emit = defineEmits(['hide', 'parameters-update']);
|
||||||
this.resetSelectedID();
|
|
||||||
|
|
||||||
setTimeout(() => {
|
const parametersPanel: Ref<HTMLDivElement> = ref(null);
|
||||||
this.$refs.parametersPanel.scrollTop = this.$refs.parametersPanel.scrollHeight + 60;
|
const parametersProxy = ref([]);
|
||||||
this.selectedParam = newUid;
|
const selectedParam = ref('');
|
||||||
}, 20);
|
const modalInnerHeight = ref(400);
|
||||||
},
|
const i = ref(1);
|
||||||
removeParameter (uid) {
|
|
||||||
this.parametersProxy = this.parametersProxy.filter(param => param._antares_id !== uid);
|
|
||||||
|
|
||||||
if (this.parametersProxy.length && this.selectedParam === uid)
|
const selectedParamObj = computed(() => {
|
||||||
this.resetSelectedID();
|
return parametersProxy.value.find(param => param._antares_id === selectedParam.value);
|
||||||
},
|
});
|
||||||
clearChanges () {
|
|
||||||
this.parametersProxy = JSON.parse(JSON.stringify(this.localParameters));
|
|
||||||
this.i = this.parametersProxy.length + 1;
|
|
||||||
|
|
||||||
if (!this.parametersProxy.some(param => param.name === this.selectedParam))
|
const isChanged = computed(() => {
|
||||||
this.resetSelectedID();
|
return JSON.stringify(props.localParameters) !== JSON.stringify(parametersProxy.value);
|
||||||
},
|
});
|
||||||
resetSelectedID () {
|
|
||||||
this.selectedParam = this.parametersProxy.length ? this.parametersProxy[0]._antares_id : '';
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -11,26 +11,26 @@
|
||||||
@click="saveChanges"
|
@click="saveChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||||
<span>{{ $t('word.save') }}</span>
|
<span>{{ t('word.save') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:disabled="!isChanged"
|
:disabled="!isChanged"
|
||||||
class="btn btn-link btn-sm mr-0"
|
class="btn btn-link btn-sm mr-0"
|
||||||
:title="$t('message.clearChanges')"
|
:title="t('message.clearChanges')"
|
||||||
@click="clearChanges"
|
@click="clearChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||||
<span>{{ $t('word.clear') }}</span>
|
<span>{{ t('word.clear') }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="divider-vert py-3" />
|
<div class="divider-vert py-3" />
|
||||||
<button class="btn btn-dark btn-sm" @click="showTimingModal">
|
<button class="btn btn-dark btn-sm" @click="showTimingModal">
|
||||||
<i class="mdi mdi-24px mdi-timer mr-1" />
|
<i class="mdi mdi-24px mdi-timer mr-1" />
|
||||||
<span>{{ $t('word.timing') }}</span>
|
<span>{{ t('word.timing') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-info">
|
<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>
|
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.name') }}</label>
|
<label class="form-label">{{ t('word.name') }}</label>
|
||||||
<input
|
<input
|
||||||
v-model="localScheduler.name"
|
v-model="localScheduler.name"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
|
@ -50,19 +50,19 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.definer') }}</label>
|
<label class="form-label">{{ t('word.definer') }}</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localScheduler.definer"
|
v-model="localScheduler.definer"
|
||||||
:options="users"
|
:options="users"
|
||||||
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
|
:option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
|
||||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.comment') }}</label>
|
<label class="form-label">{{ t('word.comment') }}</label>
|
||||||
<input
|
<input
|
||||||
v-model="localScheduler.comment"
|
v-model="localScheduler.comment"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
|
@ -72,7 +72,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<div class="form-group">
|
<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">
|
<label class="form-radio form-inline">
|
||||||
<input
|
<input
|
||||||
v-model="localScheduler.state"
|
v-model="localScheduler.state"
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||||
<BaseLoader v-if="isLoading" />
|
<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
|
<QueryEditor
|
||||||
v-show="isSelected"
|
v-show="isSelected"
|
||||||
ref="queryEditor"
|
ref="queryEditor"
|
||||||
|
@ -122,245 +122,231 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
<script>
|
import { AlterEventParams, EventInfos } from 'common/interfaces/antares';
|
||||||
import { storeToRefs } from 'pinia';
|
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 { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import BaseLoader from '@/components/BaseLoader';
|
import BaseLoader from '@/components/BaseLoader.vue';
|
||||||
import QueryEditor from '@/components/QueryEditor';
|
import QueryEditor from '@/components/QueryEditor.vue';
|
||||||
import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal';
|
import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal.vue';
|
||||||
import Schedulers from '@/ipc-api/Schedulers';
|
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
import Schedulers from '@/ipc-api/Schedulers';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
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 { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const props = defineProps({
|
||||||
|
tabUid: String,
|
||||||
|
connection: Object,
|
||||||
|
scheduler: String,
|
||||||
|
isSelected: Boolean,
|
||||||
|
schema: String
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const { addNotification } = useNotificationsStore();
|
||||||
getWorkspace,
|
const workspacesStore = useWorkspacesStore();
|
||||||
refreshStructure,
|
|
||||||
renameTabs,
|
|
||||||
newTab,
|
|
||||||
changeBreadcrumbs,
|
|
||||||
setUnsavedChanges
|
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const {
|
||||||
addNotification,
|
getWorkspace,
|
||||||
selectedWorkspace,
|
refreshStructure,
|
||||||
getWorkspace,
|
renameTabs,
|
||||||
refreshStructure,
|
changeBreadcrumbs,
|
||||||
renameTabs,
|
setUnsavedChanges
|
||||||
newTab,
|
} = workspacesStore;
|
||||||
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);
|
|
||||||
|
|
||||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
|
||||||
},
|
const isLoading = ref(false);
|
||||||
users () {
|
const isSaving = ref(false);
|
||||||
const users = [{ value: '' }, ...this.workspace.users];
|
const isTimingModal = ref(false);
|
||||||
if (!this.isDefinerInUsers) {
|
const originalScheduler: Ref<EventInfos> = ref(null);
|
||||||
const [name, host] = this.originalScheduler.definer.replaceAll('`', '').split('@');
|
const localScheduler: Ref<EventInfos> = ref({} as EventInfos);
|
||||||
users.unshift({ name, host });
|
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;
|
||||||
}
|
}
|
||||||
},
|
else
|
||||||
watch: {
|
addNotification({ status: 'error', message: response });
|
||||||
async schema () {
|
}
|
||||||
if (this.isSelected) {
|
catch (err) {
|
||||||
await this.getSchedulerData();
|
addNotification({ status: 'error', message: err.stack });
|
||||||
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 });
|
|
||||||
|
|
||||||
setTimeout(() => {
|
resizeQueryEditor();
|
||||||
this.resizeQueryEditor();
|
isLoading.value = false;
|
||||||
}, 200);
|
};
|
||||||
|
|
||||||
if (this.lastScheduler !== this.scheduler)
|
const saveChanges = async () => {
|
||||||
this.getSchedulerData();
|
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 });
|
||||||
}
|
}
|
||||||
},
|
else
|
||||||
isChanged (val) {
|
getSchedulerData();
|
||||||
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
|
|
||||||
}
|
}
|
||||||
},
|
else
|
||||||
async created () {
|
addNotification({ status: 'error', message: response });
|
||||||
await this.getSchedulerData();
|
}
|
||||||
this.$refs.queryEditor.editor.session.setValue(this.localScheduler.sql);
|
catch (err) {
|
||||||
window.addEventListener('keydown', this.onKey);
|
addNotification({ status: 'error', message: err.stack });
|
||||||
},
|
}
|
||||||
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;
|
|
||||||
|
|
||||||
this.isLoading = true;
|
isSaving.value = false;
|
||||||
this.lastScheduler = this.scheduler;
|
};
|
||||||
|
|
||||||
const params = {
|
const clearChanges = () => {
|
||||||
uid: this.connection.uid,
|
localScheduler.value = JSON.parse(JSON.stringify(originalScheduler.value));
|
||||||
schema: this.schema,
|
queryEditor.value.editor.session.setValue(localScheduler.value.sql);
|
||||||
scheduler: this.scheduler
|
};
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
const resizeQueryEditor = () => {
|
||||||
const { status, response } = await Schedulers.getSchedulerInformations(params);
|
if (queryEditor.value) {
|
||||||
if (status === 'success') {
|
const footer = document.getElementById('footer');
|
||||||
this.originalScheduler = response;
|
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||||
this.localScheduler = JSON.parse(JSON.stringify(this.originalScheduler));
|
editorHeight.value = size;
|
||||||
this.sqlProxy = this.localScheduler.sql;
|
queryEditor.value.editor.resize();
|
||||||
}
|
}
|
||||||
else
|
};
|
||||||
this.addNotification({ status: 'error', message: response });
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
this.addNotification({ status: 'error', message: err.stack });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.resizeQueryEditor();
|
const showTimingModal = () => {
|
||||||
this.isLoading = false;
|
isTimingModal.value = true;
|
||||||
},
|
};
|
||||||
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
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
const hideTimingModal = () => {
|
||||||
const { status, response } = await Schedulers.alterScheduler(params);
|
isTimingModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
if (status === 'success') {
|
const timingUpdate = (options: EventInfos) => {
|
||||||
const oldName = this.originalScheduler.name;
|
localScheduler.value = options;
|
||||||
|
};
|
||||||
|
|
||||||
await this.refreshStructure(this.connection.uid);
|
const onKey = (e: KeyboardEvent) => {
|
||||||
|
if (props.isSelected) {
|
||||||
if (oldName !== this.localScheduler.name) {
|
e.stopPropagation();
|
||||||
this.renameTabs({
|
if (e.ctrlKey && e.key === 's') { // CTRL + S
|
||||||
uid: this.connection.uid,
|
if (isChanged.value)
|
||||||
schema: this.schema,
|
saveChanges();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue