refactor: ts and composition api on more elements

This commit is contained in:
Fabio Di Stasio 2022-05-17 19:11:31 +02:00
parent 5a50ba88e8
commit 84826ff4c0
25 changed files with 1179 additions and 1680 deletions

View File

@ -73,6 +73,19 @@ export interface TypesGroup {
} }
// Tables // Tables
export interface TableInfos {
name: string;
type: string;
rows: number;
created: Date;
updated: Date;
engine: string;
comment: string;
size: number;
autoIncrement: number;
collation: string;
}
export interface TableField { export interface TableField {
name: string; name: string;
key: string; key: string;
@ -87,7 +100,7 @@ export interface TableField {
unsigned?: boolean; unsigned?: boolean;
zerofill?: boolean; zerofill?: boolean;
order?: number; order?: number;
default?: number | string; default?: string;
enumValues?: string; enumValues?: string;
charset?: string; charset?: string;
collation?: string; collation?: string;
@ -97,6 +110,7 @@ export interface TableField {
comment?: string; comment?: string;
after?: string; after?: string;
orgName?: string; orgName?: string;
length?: number;
} }
export interface TableIndex { export interface TableIndex {
@ -170,6 +184,7 @@ export interface AlterTableParams {
} }
// Views // Views
export type ViewInfos = TableInfos
export interface CreateViewParams { export interface CreateViewParams {
schema: string; schema: string;
name: string; name: string;
@ -185,6 +200,18 @@ 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;
}
export interface CreateTriggerParams { export interface CreateTriggerParams {
definer?: string; definer?: string;
schema: string; schema: string;
@ -200,6 +227,19 @@ export interface AlterTriggerParams extends CreateTriggerParams {
} }
// Routines & Functions // Routines & Functions
export interface RoutineInfos {
name: string;
type: string;
definer: string;
created: string;
updated: string;
comment?: string;
charset?: string;
security?: string;
}
export type FunctionInfos = RoutineInfos
export interface FunctionParam { export interface FunctionParam {
context: string; context: string;
name: string; name: string;
@ -244,6 +284,29 @@ export interface AlterFunctionParams extends CreateFunctionParams {
} }
// Events // Events
export interface EventInfos {
name: string;
definition: string;
type: string;
definer: string;
body: string;
starts: string;
ends: string;
enabled: boolean;
executeAt: string;
intervalField: string;
intervalValue: string;
onCompletion: string;
originator: string;
sqlMode: string;
created: string;
updated: string;
lastExecuted: string;
comment: string;
charset: string;
timezone: string;
}
export interface CreateEventParams { export interface CreateEventParams {
definer?: string; definer?: string;
schema: string; schema: string;
@ -263,6 +326,17 @@ export interface AlterEventParams extends CreateEventParams {
oldName?: string; oldName?: string;
} }
// Schema
export interface SchemaInfos {
name: string;
size: number;
tables: TableInfos[];
functions: RoutineInfos[];
procedures: RoutineInfos[];
triggers: TriggerInfos[];
schedulers: EventInfos[];
}
// Query // Query
export interface QueryBuilderObject { export interface QueryBuilderObject {
schema: string; schema: string;

View File

@ -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 () => {

View File

@ -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,7 +378,7 @@ 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, definition: scheduler.EVENT_DEFINITION,
@ -404,7 +404,7 @@ export class MySQLClient extends AntaresCore {
}); });
// 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,

View File

@ -10,7 +10,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref, watch } from 'vue'; import { onMounted, 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 { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
@ -28,7 +28,6 @@ const props = defineProps({
}); });
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const mode = ref(props.mode);
const { const {
editorTheme, editorTheme,
@ -40,7 +39,7 @@ const {
let editor: ace.Ace.Editor; let editor: ace.Ace.Editor;
const id = uidGen(); const id = uidGen();
watch(mode, () => { watch(() => props.mode, () => {
if (editor) if (editor)
editor.session.setMode(`ace/mode/${props.mode}`); editor.session.setMode(`ace/mode/${props.mode}`);
}); });
@ -82,7 +81,7 @@ watch(lineWrap, () => {
onMounted(() => { onMounted(() => {
editor = ace.edit(`editor-${id}`, { editor = ace.edit(`editor-${id}`, {
mode: `ace/mode/${mode.value}`, mode: `ace/mode/${props.mode}`,
theme: `ace/theme/${editorTheme.value}`, theme: `ace/theme/${editorTheme.value}`,
value: props.modelValue || '', value: props.modelValue || '',
fontSize: 14, fontSize: 14,

View File

@ -10,8 +10,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from '@vue/reactivity'; import { computed, ref, watch } from 'vue';
import { ref, watch } from 'vue';
const props = defineProps({ const props = defineProps({
message: { message: {

View File

@ -4,7 +4,7 @@
class="form-select pl-1 pr-4" class="form-select pl-1 pr-4"
:class="{'small-select': size === 'small'}" :class="{'small-select': size === 'small'}"
@change="onChange" @change="onChange"
@blur="$emit('blur')" @blur="emit('blur')"
> >
<option v-if="!isValidDefault" :value="modelValue"> <option v-if="!isValidDefault" :value="modelValue">
{{ modelValue === null ? 'NULL' : modelValue }} {{ modelValue === null ? 'NULL' : modelValue }}
@ -20,88 +20,84 @@
</select> </select>
</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';
export default {
name: 'ForeignKeySelect', const props = defineProps({
props: {
modelValue: [String, Number], modelValue: [String, Number],
keyUsage: Object, keyUsage: Object,
size: { size: {
type: String, type: String,
default: '' default: ''
} }
}, });
emits: ['update:modelValue', 'blur'],
setup () { const emit = defineEmits(['update:modelValue', 'blur']);
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
return { addNotification, selectedWorkspace }; const editField: Ref<HTMLSelectElement> = ref(null);
}, const foreignList = ref([]);
data () {
return { const isValidDefault = computed(() => {
foreignList: [] if (!foreignList.value.length) return true;
}; if (props.modelValue === null) return false;
}, return foreignList.value.some(foreign => foreign.foreign_column.toString() === props.modelValue.toString());
computed: { });
isValidDefault () {
if (!this.foreignList.length) return true; const onChange = () => {
if (this.modelValue === null) return false; emit('update:modelValue', editField.value.value);
return this.foreignList.some(foreign => foreign.foreign_column.toString() === this.modelValue.toString());
}
},
async created () {
let foreignDesc;
const params = {
uid: this.selectedWorkspace,
schema: this.keyUsage.refSchema,
table: this.keyUsage.refTable
}; };
const cutText = (val: string) => {
if (typeof val !== 'string') return val;
return val.length > 15 ? `${val.substring(0, 15)}...` : val;
};
let foreignDesc;
const params = {
uid: selectedWorkspace.value,
schema: props.keyUsage.refSchema,
table: props.keyUsage.refTable
};
(async () => {
try { // Field data try { // Field data
const { status, response } = await Tables.getTableColumns(params); const { status, response } = await Tables.getTableColumns(params);
if (status === 'success') { if (status === 'success') {
const textField = response.find(field => [...TEXT, ...LONG_TEXT].includes(field.type) && field.name !== this.keyUsage.refField); const textField = response.find((field: {type: string; name: string}) => [...TEXT, ...LONG_TEXT].includes(field.type) && field.name !== props.keyUsage.refField);
foreignDesc = textField ? textField.name : false; foreignDesc = textField ? textField.name : false;
} }
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 });
} }
try { // Foregn list try { // Foregn list
const { status, response } = await Tables.getForeignList({ const { status, response } = await Tables.getForeignList({
...params, ...params,
column: this.keyUsage.refField, column: props.keyUsage.refField,
description: foreignDesc description: foreignDesc
}); });
if (status === 'success') if (status === 'success')
this.foreignList = response.rows; foreignList.value = response.rows;
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 });
} }
}, })();
methods: {
onChange () {
this.$emit('update:modelValue', this.$refs.editField.value);
},
cutText (val) {
if (typeof val !== 'string') return val;
return val.length > 15 ? `${val.substring(0, 15)}...` : val;
}
}
};
</script> </script>

View File

@ -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 () {
return {
credentials: {
user: '', user: '',
password: '' password: ''
} });
const firstInput: Ref<HTMLInputElement> = ref(null);
const emit = defineEmits(['close-asking', 'credentials']);
const closeModal = () => {
emit('close-asking');
}; };
},
created () { const sendCredentials = () => {
emit('credentials', credentials.value);
};
setTimeout(() => { setTimeout(() => {
this.$refs.firstInput.focus(); firstInput.value.focus();
}, 20); }, 20);
},
methods: {
closeModal () {
this.$emit('close-asking');
},
sendCredentials () {
this.$emit('credentials', this.credentials);
}
}
};
</script> </script>

View File

@ -47,50 +47,39 @@
</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 { FunctionParam } from 'common/interfaces/antares';
import ConfirmModal from '@/components/BaseConfirmModal.vue';
export default { // eslint-disable-next-line camelcase
name: 'ModalAskParameters', type LocalRoutineParams = FunctionParam & {_antares_id: string};
components: {
ConfirmModal const props = defineProps({
}, localRoutine: Object as PropType<{name: string; parameters: LocalRoutineParams[]}>,
props: {
localRoutine: Object,
client: String 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); const firstInput: Ref<HTMLInputElement[]> = ref(null);
}, const values: Ref<{[key: string]: string}> = ref({});
beforeUnmount () {
window.removeEventListener('keydown', this.onKey); const inParameters = computed(() => {
}, return props.localRoutine.parameters.filter(param => param.context === 'IN');
methods: { });
typeClass (type) {
const typeClass = (type: string) => {
if (type) if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`; return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return ''; return '';
}, };
runRoutine () {
const valArr = Object.keys(this.values).reduce((acc, curr, i) => { const runRoutine = () => {
const valArr = Object.keys(values.value).reduce((acc, curr, i) => {
let qc; let qc;
switch (this.client) { switch (props.client) {
case 'maria': case 'maria':
case 'mysql': case 'mysql':
qc = '"'; qc = '"';
@ -102,28 +91,34 @@ export default {
qc = '"'; qc = '"';
} }
const param = this.localRoutine.parameters.find(param => `${i}-${param.name}` === curr); const param = props.localRoutine.parameters.find(param => `${i}-${param.name}` === curr);
const value = [...NUMBER, ...FLOAT].includes(param.type) ? this.values[curr] : `${qc}${this.values[curr]}${qc}`; const value = [...NUMBER, ...FLOAT].includes(param.type) ? values.value[curr] : `${qc}${values.value[curr]}${qc}`;
acc.push(value); acc.push(value);
return acc; return acc;
}, []); }, []);
this.$emit('confirm', valArr);
}, emit('confirm', valArr);
closeModal () { };
this.$emit('close');
}, const closeModal = () => emit('close');
onKey (e) {
const onKey = (e: KeyboardEvent) => {
e.stopPropagation(); e.stopPropagation();
if (e.key === 'Escape') if (e.key === 'Escape')
this.closeModal(); closeModal();
}, };
wrapNumber (num) {
const wrapNumber = (num: number) => {
if (!num) return ''; if (!num) return '';
return `(${num})`; return `(${num})`;
}
}
}; };
window.addEventListener('keydown', onKey);
setTimeout(() => {
firstInput.value[0].focus();
}, 20);
</script> </script>
<style scoped> <style scoped>

View File

@ -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
},
emits: ['confirm', 'close'],
created () {
window.addEventListener('keydown', this.onKey);
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
onKey (e) {
e.stopPropagation(); e.stopPropagation();
if (e.key === 'Escape') if (e.key === 'Escape')
this.closeModal(); emit('close');
}
}
}; };
window.addEventListener('keydown', onKey);
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script> </script>
<style scoped> <style scoped>

View File

@ -67,19 +67,19 @@
</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';
export default { const props = defineProps({
name: 'ModalEditSchema',
props: {
selectedSchema: String selectedSchema: String
}, });
emits: ['close'],
setup () { const emit = defineEmits(['close']);
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
@ -87,92 +87,78 @@ export default {
const { getWorkspace, getDatabaseVariable } = workspacesStore; const { getWorkspace, getDatabaseVariable } = workspacesStore;
return { const firstInput: Ref<HTMLInputElement> = ref(null);
addNotification, const database = ref({
selectedWorkspace,
getWorkspace,
getDatabaseVariable
};
},
data () {
return {
database: {
name: '', name: '',
prevName: '', prevName: '',
collation: '' collation: '',
prevCollation: null
});
const collations = computed(() => getWorkspace(selectedWorkspace.value).collations);
const defaultCollation = computed(() => (getDatabaseVariable(selectedWorkspace.value, 'collation_server').value || ''));
const updateSchema = async () => {
if (database.value.collation !== database.value.prevCollation) {
try {
const { status, response } = await Schema.updateSchema({
uid: selectedWorkspace.value,
...database.value
});
if (status === 'success')
closeModal();
else
addNotification({ status: 'error', message: response });
} }
catch (err) {
addNotification({ status: 'error', message: err.stack });
}
}
else closeModal();
}; };
},
computed: { const closeModal = () => emit('close');
collations () {
return this.getWorkspace(this.selectedWorkspace).collations; const onKey =(e: KeyboardEvent) => {
}, e.stopPropagation();
defaultCollation () { if (e.key === 'Escape')
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value || ''; closeModal();
} };
},
async created () { (async () => {
let actualCollation; let actualCollation;
try { try {
const { status, response } = await Schema.getDatabaseCollation({ uid: this.selectedWorkspace, database: this.selectedSchema }); const { status, response } = await Schema.getDatabaseCollation({ uid: selectedWorkspace.value, database: props.selectedSchema });
if (status === 'success') if (status === 'success')
actualCollation = response; actualCollation = response;
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 = { database.value = {
name: this.selectedSchema, name: props.selectedSchema,
prevName: this.selectedSchema, prevName: props.selectedSchema,
collation: actualCollation || this.defaultCollation, collation: actualCollation || defaultCollation.value,
prevCollation: actualCollation || this.defaultCollation prevCollation: actualCollation || defaultCollation.value
}; };
window.addEventListener('keydown', this.onKey); window.addEventListener('keydown', onKey);
setTimeout(() => { setTimeout(() => {
this.$refs.firstInput.focus(); firstInput.value.focus();
}, 20); }, 20);
}, })();
beforeUnmount () {
window.removeEventListener('keydown', this.onKey); onBeforeUnmount(() => {
}, window.removeEventListener('keydown', 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();
}
}
};
</script> </script>
<style scoped> <style scoped>

View File

@ -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>
@ -269,23 +269,27 @@
</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';
export default { const props = defineProps({
name: 'ModalExportSchema',
props: {
selectedSchema: String selectedSchema: String
}, });
emits: ['close'],
setup () { const emit = defineEmits(['close']);
const { t } = useI18n();
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
@ -293,78 +297,153 @@ export default {
const { const {
getWorkspace, getWorkspace,
getDatabaseVariable,
refreshSchema refreshSchema
} = workspacesStore; } = workspacesStore;
return { const isExporting = ref(false);
addNotification, const isRefreshing = ref(false);
selectedWorkspace, const progressPercentage = ref(0);
getWorkspace, const progressStatus = ref('');
getDatabaseVariable, const tables: Ref<TableParams[]> = ref([]);
refreshSchema const options = ref({
}; includes: {} as {[key: string]: boolean},
},
data () {
return {
isExporting: false,
isRefreshing: false,
progressPercentage: 0,
progressStatus: '',
tables: [],
options: {
includes: {},
outputFormat: 'sql', outputFormat: 'sql',
sqlInsertAfter: 250, sqlInsertAfter: 250,
sqlInsertDivider: 'bytes' sqlInsertDivider: 'bytes'
}, });
basePath: '' const basePath = ref('');
};
}, const currentWorkspace = computed(() => getWorkspace(selectedWorkspace.value));
computed: { const clientCustoms: Ref<Customizations> = computed(() => currentWorkspace.value.customizations);
currentWorkspace () { const schemaItems = computed(() => {
return this.getWorkspace(this.selectedWorkspace); const db: SchemaInfos = currentWorkspace.value.structure.find((db: SchemaInfos) => db.name === props.selectedSchema);
},
customizations () {
return this.currentWorkspace.customizations;
},
schemaItems () {
const db = this.currentWorkspace.structure.find(db => db.name === this.selectedSchema);
if (db) if (db)
return db.tables.filter(table => table.type === 'table'); return db.tables.filter(table => table.type === 'table');
return []; return [];
}, });
filename () { const filename = computed(() => {
const date = moment().format('YYYY-MM-DD'); const date = moment().format('YYYY-MM-DD');
return `${this.selectedSchema}_${date}.${this.options.outputFormat}`; return `${props.selectedSchema}_${date}.${options.value.outputFormat}`;
}, });
dumpFilePath () { const dumpFilePath = computed(() => `${basePath.value}/${filename.value}`);
return `${this.basePath}/${this.filename}`; const includeStructureStatus = computed(() => {
}, if (tables.value.every(item => item.includeStructure)) return 1;
includeStructureStatus () { else if (tables.value.some(item => item.includeStructure)) return 2;
if (this.tables.every(item => item.includeStructure)) return 1;
else if (this.tables.some(item => item.includeStructure)) return 2;
else return 0; else return 0;
}, });
includeContentStatus () { const includeContentStatus = computed(() => {
if (this.tables.every(item => item.includeContent)) return 1; if (tables.value.every(item => item.includeContent)) return 1;
else if (this.tables.some(item => item.includeContent)) return 2; else if (tables.value.some(item => item.includeContent)) return 2;
else return 0; else return 0;
}, });
includeDropStatementStatus () { const includeDropStatementStatus = computed(() => {
if (this.tables.every(item => item.includeDropStatement)) return 1; if (tables.value.every(item => item.includeDropStatement)) return 1;
else if (this.tables.some(item => item.includeDropStatement)) return 2; else if (tables.value.some(item => item.includeDropStatement)) return 2;
else return 0; else return 0;
});
const startExport = async () => {
isExporting.value = true;
const { uid, client } = currentWorkspace.value;
const params = {
uid,
type: client,
schema: props.selectedSchema,
outputFile: dumpFilePath.value,
tables: [...tables.value],
...options.value
};
try {
const { status, response } = await Schema.export(params);
if (status === 'success')
progressStatus.value = response.cancelled ? t('word.aborted') : t('word.completed');
else {
progressStatus.value = response;
addNotification({ status: 'error', message: response });
}
}
catch (err) {
addNotification({ status: 'error', message: err.stack });
} }
},
async created () {
if (!this.schemaItems.length) await this.refresh();
window.addEventListener('keydown', this.onKey); isExporting.value = false;
};
this.basePath = await Application.getDownloadPathDirectory(); const updateProgress = (event: Event, state: ExportState) => {
this.tables = this.schemaItems.map(item => ({ 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, table: item.name,
includeStructure: true, includeStructure: true,
includeContent: true, includeContent: true,
@ -373,103 +452,20 @@ export default {
const structure = ['functions', 'views', 'triggers', 'routines', 'schedulers']; const structure = ['functions', 'views', 'triggers', 'routines', 'schedulers'];
structure.forEach(feat => { structure.forEach((feat: keyof Customizations) => {
const val = customizations[this.currentWorkspace.client][feat]; const val = clientCustoms.value[feat];
if (val) if (val)
this.options.includes[feat] = true; options.value.includes[feat] = true;
}); });
ipcRenderer.on('export-progress', this.updateProgress); ipcRenderer.on('export-progress', updateProgress);
}, })();
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
ipcRenderer.off('export-progress', this.updateProgress);
},
methods: {
async startExport () {
this.isExporting = true;
const { uid, client } = this.currentWorkspace;
const params = {
uid,
type: client,
schema: this.selectedSchema,
outputFile: this.dumpFilePath,
tables: [...this.tables],
...this.options
};
try { onBeforeUnmount(() => {
const { status, response } = await Schema.export(params); window.removeEventListener('keydown', onKey);
if (status === 'success') ipcRenderer.off('export-progress', updateProgress);
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];
}
}
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -185,76 +185,133 @@
</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';
export default { const props = defineProps({
name: 'ModalFakerRows',
components: {
FakerSelect
},
props: {
tabUid: [String, Number], tabUid: [String, Number],
fields: Array, fields: Array as Prop<TableField[]>,
keyUsage: Array keyUsage: Array as Prop<TableForeign[]>
}, });
emits: ['reload', 'hide'],
setup () { const emit = defineEmits(['reload', 'hide']);
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { getWorkspace, getWorkspaceTab } = workspacesStore; const { getWorkspace } = workspacesStore;
return { // eslint-disable-next-line @typescript-eslint/no-explicit-any
addNotification, const localRow: Ref<{[key: string]: any}> = ref({});
selectedWorkspace, const fieldsToExclude = ref([]);
getWorkspace, const nInserts = ref(1);
getWorkspaceTab const isInserting = ref(false);
}; const fakerLocale = ref('en');
},
data () { const workspace = computed(() => getWorkspace(selectedWorkspace.value));
return { const foreignKeys = computed(() => props.keyUsage.map(key => key.field));
localRow: {}, const hasFakes = computed(() => Object.keys(localRow.value).some(field => 'group' in localRow.value[field] && localRow.value[field].group !== 'manual'));
fieldsToExclude: [],
nInserts: 1, watch(nInserts, (val) => {
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) if (!val || val < 1)
this.nInserts = 1; nInserts.value = 1;
else if (val > 1000) else if (val > 1000)
this.nInserts = 1000; nInserts.value = 1000;
} });
},
created () {
window.addEventListener('keydown', this.onKey);
},
mounted () {
const rowObj = {};
for (const field of this.fields) { const typeClass = (type: string) => {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return '';
};
const insertRows = async () => {
isInserting.value = true;
const rowToInsert = localRow.value;
Object.keys(rowToInsert).forEach(key => {
if (fieldsToExclude.value.includes(key))
delete rowToInsert[key];
if (typeof rowToInsert[key] === 'undefined')
delete rowToInsert[key];
});
const fieldTypes: {[key: string]: string} = {};
props.fields.forEach(field => {
fieldTypes[field.name] = field.type;
});
try {
const { status, response } = await Tables.insertTableFakeRows({
uid: selectedWorkspace.value,
schema: workspace.value.breadcrumbs.schema,
table: workspace.value.breadcrumbs.table,
row: rowToInsert,
repeat: nInserts.value,
fields: fieldTypes,
locale: fakerLocale.value
});
if (status === 'success') {
closeModal();
emit('reload');
}
else
addNotification({ status: 'error', message: response });
}
catch (err) {
addNotification({ status: 'error', message: err.stack });
}
isInserting.value = false;
};
const closeModal = () => {
emit('hide');
};
const fieldLength = (field: TableField) => {
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
else if (TEXT.includes(field.type)) return Number(field.charLength);
return Number(field.length);
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const toggleFields = (event: any, field: TableField) => {
if (event.target.checked)
fieldsToExclude.value = fieldsToExclude.value.filter(f => f !== field.name);
else
fieldsToExclude.value = [...fieldsToExclude.value, field.name];
};
const onKey = (e: KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape')
closeModal();
};
const wrapNumber = (num: number) => {
if (!num) return '';
return `(${num})`;
};
window.addEventListener('keydown', onKey);
onMounted(() => {
const rowObj: {[key: string]: unknown} = {};
for (const field of props.fields) {
let fieldDefault; let fieldDefault;
if (field.default === 'NULL') fieldDefault = null; if (field.default === 'NULL') fieldDefault = null;
@ -291,95 +348,15 @@ export default {
rowObj[field.name] = { value: fieldDefault }; rowObj[field.name] = { value: fieldDefault };
if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields
this.fieldsToExclude = [...this.fieldsToExclude, field.name]; fieldsToExclude.value = [...fieldsToExclude.value, field.name];
} }
this.localRow = { ...rowObj }; localRow.value = { ...rowObj };
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
typeClass (type) {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return '';
},
async insertRows () {
this.isInserting = true;
const rowToInsert = this.localRow;
Object.keys(rowToInsert).forEach(key => {
if (this.fieldsToExclude.includes(key))
delete rowToInsert[key];
if (typeof rowToInsert[key] === 'undefined')
delete rowToInsert[key];
}); });
const fieldTypes = {}; onBeforeMount(() => {
this.fields.forEach(field => { window.removeEventListener('keydown', onKey);
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})`;
}
}
};
</script> </script>
<style scoped> <style scoped>

View File

@ -97,129 +97,115 @@
</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 * as moment from 'moment';
import { ConnectionParams } from 'common/interfaces/antares';
import { useHistoryStore } from '@/stores/history'; import { 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';
interface HistoryRow {
uid:string;
sql: string;
schema: string;
date: string;
}
export default {
name: 'ModalHistory',
components: {
BaseVirtualScroll
},
props: {
connection: Object
},
emits: ['select-query', 'close'],
setup () {
const { getHistoryByWorkspace, deleteQueryFromHistory } = useHistoryStore(); const { getHistoryByWorkspace, deleteQueryFromHistory } = useHistoryStore();
const { getConnectionName } = useConnectionsStore(); const { getConnectionName } = useConnectionsStore();
const { addNotification } = useNotificationsStore();
return { const props = defineProps({
getHistoryByWorkspace, connection: Object as Prop<ConnectionParams>
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 emit = defineEmits(['select-query', 'close']);
this.localSearchTerm = this.searchTerm;
const table: Ref<HTMLDivElement> = ref(null);
const resultTable: Ref<Component & { updateWindow: () => void }> = ref(null);
const tableWrapper: Ref<HTMLDivElement> = ref(null);
const searchForm: Ref<HTMLInputElement> = ref(null);
const resultsSize = ref(1000);
const scrollElement: Ref<HTMLDivElement> = ref(null);
const searchTermInterval: Ref<NodeJS.Timeout> = ref(null);
const searchTerm = ref('');
const localSearchTerm = ref('');
const connectionName = computed(() => getConnectionName(props.connection.uid));
const history: ComputedRef<HistoryRow[]> = computed(() => (getHistoryByWorkspace(props.connection.uid) || []));
const filteredHistory = computed(() => history.value.filter(q => q.sql.toLowerCase().search(searchTerm.value.toLowerCase()) >= 0));
watch(searchTerm, () => {
clearTimeout(searchTermInterval.value);
searchTermInterval.value = setTimeout(() => {
localSearchTerm.value = searchTerm.value;
}, 200); }, 200);
} });
},
created () {
window.addEventListener('keydown', this.onKey, { capture: true });
},
updated () {
if (this.$refs.table)
this.refreshScroller();
if (this.$refs.tableWrapper) const copyQuery = (sql: string) => {
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); navigator.clipboard.writeText(sql);
}, };
deleteQuery (query) {
this.deleteQueryFromHistory({ const deleteQuery = (query: HistoryRow[]) => {
workspace: this.connection.uid, deleteQueryFromHistory({
workspace: props.connection.uid,
...query ...query
}); });
}, };
resizeResults () {
if (this.$refs.resultTable) { const resizeResults = () => {
const el = this.$refs.tableWrapper.parentElement; if (resultTable.value) {
const el = tableWrapper.value.parentElement;
if (el) if (el)
this.resultsSize = el.offsetHeight - this.$refs.searchForm.offsetHeight; resultsSize.value = el.offsetHeight - searchForm.value.offsetHeight;
this.$refs.resultTable.updateWindow(); resultTable.value.updateWindow();
} }
}, };
formatDate (date) {
return moment(date).isValid() ? moment(date).format('HH:mm:ss - YYYY/MM/DD') : date; const formatDate = (date: Date) => moment(date).isValid() ? moment(date).format('HH:mm:ss - YYYY/MM/DD') : date;
}, const refreshScroller = () => resizeResults();
refreshScroller () { const closeModal = () => emit('close');
this.resizeResults();
}, const highlightWord = (string: string) => {
closeModal () {
this.$emit('close');
},
highlightWord (string) {
string = string.replaceAll('<', '&lt;').replaceAll('>', '&gt;'); string = string.replaceAll('<', '&lt;').replaceAll('>', '&gt;');
if (this.searchTerm) { if (searchTerm.value) {
const regexp = new RegExp(`(${this.searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'); const regexp = new RegExp(`(${searchTerm.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
return string.replace(regexp, '<span class="text-primary text-bold">$1</span>'); return string.replace(regexp, '<span class="text-primary text-bold">$1</span>');
} }
else else
return string; return string;
}, };
onKey (e) {
const onKey = (e: KeyboardEvent) => {
e.stopPropagation(); e.stopPropagation();
if (e.key === 'Escape') if (e.key === 'Escape')
this.closeModal(); 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>

View File

@ -49,22 +49,19 @@
</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: {
selectedSchema: String
},
emits: ['close'],
setup () {
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
@ -72,101 +69,99 @@ export default {
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: {
currentWorkspace () {
return this.getWorkspace(this.selectedWorkspace);
},
formattedQueryErrors () {
return this.queryErrors.map(err =>
`Time: ${moment(err.time).format('HH:mm:ss.S')} (${err.time})\nError: ${err.message}` `Time: ${moment(err.time).format('HH:mm:ss.S')} (${err.time})\nError: ${err.message}`
).join('\n\n'); ).join('\n\n');
} });
},
async created () {
window.addEventListener('keydown', this.onKey);
ipcRenderer.on('import-progress', this.updateProgress); const startImport = async (file: string) => {
ipcRenderer.on('query-error', this.handleQueryError); isImporting.value = true;
}, sqlFile.value = file;
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
ipcRenderer.off('import-progress', this.updateProgress);
ipcRenderer.off('query-error', this.handleQueryError);
},
methods: {
async startImport (sqlFile) {
this.isImporting = true;
this.sqlFile = sqlFile;
const { uid, client } = this.currentWorkspace; const { uid, client } = currentWorkspace.value;
const params = { const params = {
uid, uid,
type: client, type: client,
schema: this.selectedSchema, schema: props.selectedSchema,
file: sqlFile file: sqlFile.value
}; };
try { try {
this.completed = false; completed.value = false;
const { status, response } = await Schema.import(params); const { status, response } = await Schema.import(params);
if (status === 'success') if (status === 'success')
this.progressStatus = response.cancelled ? this.$t('word.aborted') : this.$t('word.completed'); progressStatus.value = response.cancelled ? t('word.aborted') : t('word.completed');
else { else {
this.progressStatus = response; progressStatus.value = response;
this.addNotification({ status: 'error', message: response }); addNotification({ status: 'error', message: response });
} }
this.refreshSchema({ uid, schema: this.selectedSchema }); refreshSchema({ uid, schema: props.selectedSchema });
this.completed = true; completed.value = true;
} }
catch (err) { catch (err) {
this.addNotification({ status: 'error', message: err.stack }); addNotification({ status: 'error', message: err.stack });
} }
this.isImporting = false; isImporting.value = false;
}, };
updateProgress (event, state) {
this.progressPercentage = Number(state.percentage).toFixed(1); const updateProgress = (event: Event, state: ImportState) => {
this.queryCount = Number(state.queryCount); progressPercentage.value = parseFloat(Number(state.percentage).toFixed(1));
}, queryCount.value = Number(state.queryCount);
handleQueryError (event, err) { };
this.queryErrors.push(err);
}, const handleQueryError = (event: Event, err: { time: string; message: string }) => {
async closeModal () { queryErrors.value.push(err);
};
const closeModal = async () => {
let willClose = true; let willClose = true;
if (this.isImporting) { if (isImporting.value) {
willClose = false; willClose = false;
const { response } = await Schema.abortImport(); const { response } = await Schema.abortImport();
willClose = response.willAbort; willClose = response.willAbort;
} }
if (willClose) if (willClose)
this.$emit('close'); emit('close');
}, };
onKey (e) {
const onKey = (e: KeyboardEvent) => {
e.stopPropagation(); e.stopPropagation();
if (e.key === 'Escape') if (e.key === 'Escape')
this.closeModal(); 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>

View File

@ -67,16 +67,13 @@
</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';
export default {
name: 'ModalNewSchema',
emits: ['reload', 'close'],
setup () {
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
@ -84,74 +81,60 @@ export default {
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({
};
},
data () {
return {
isLoading: false,
database: {
name: '', name: '',
collation: '' collation: ''
} });
};
}, const collations = computed(() => getWorkspace(selectedWorkspace.value).collations);
computed: { const customizations = computed(() => getWorkspace(selectedWorkspace.value).customizations);
collations () { const defaultCollation = computed(() => getDatabaseVariable(selectedWorkspace.value, 'collation_server') ? getDatabaseVariable(selectedWorkspace.value, 'collation_server').value : '');
return this.getWorkspace(this.selectedWorkspace).collations;
}, database.value = { ...database.value, collation: defaultCollation.value };
customizations () {
return this.getWorkspace(this.selectedWorkspace).customizations; const createSchema = async () => {
}, isLoading.value = true;
defaultCollation () {
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server') ? this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value : '';
}
},
created () {
this.database = { ...this.database, collation: this.defaultCollation };
window.addEventListener('keydown', this.onKey);
setTimeout(() => {
this.$refs.firstInput.focus();
}, 20);
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
async createSchema () {
this.isLoading = true;
try { try {
const { status, response } = await Schema.createSchema({ const { status, response } = await Schema.createSchema({
uid: this.selectedWorkspace, uid: selectedWorkspace.value,
...this.database ...database.value
}); });
if (status === 'success') { if (status === 'success') {
this.closeModal(); closeModal();
this.$emit('reload'); emit('reload');
} }
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.isLoading = false; isLoading.value = false;
}, };
closeModal () {
this.$emit('close'); const closeModal = () => {
}, emit('close');
onKey (e) { };
const onKey = (e: KeyboardEvent) => {
e.stopPropagation(); e.stopPropagation();
if (e.key === 'Escape') if (e.key === 'Escape')
this.closeModal(); closeModal();
}
}
}; };
window.addEventListener('keydown', onKey);
setTimeout(() => {
firstInput.value.focus();
}, 20);
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script> </script>
<style scoped> <style scoped>

View File

@ -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>

View File

@ -133,218 +133,218 @@
</Teleport> </Teleport>
</template> </template>
<script> <script setup lang="ts">
import { Component, computed, onBeforeUnmount, onMounted, onUpdated, Prop, Ref, ref } from 'vue';
import { ConnectionParams } from 'common/interfaces/antares';
import { arrayToFile } from '../libs/arrayToFile'; import { 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 {
name: 'ModalProcessesList',
components: {
BaseVirtualScroll,
ModalProcessesListRow,
ModalProcessesListContext
},
props: {
connection: Object
},
emits: ['close'],
setup () {
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const { getConnectionName } = useConnectionsStore(); 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(() => {
if (currentSort.value) {
return [...results.value].sort((a, b) => {
let modifier = 1; let modifier = 1;
const valA = typeof a[this.currentSort] === 'string' ? a[this.currentSort].toLowerCase() : a[this.currentSort]; const valA = typeof a[currentSort.value] === 'string' ? a[currentSort.value].toLowerCase() : a[currentSort.value];
const valB = typeof b[this.currentSort] === 'string' ? b[this.currentSort].toLowerCase() : b[this.currentSort]; const valB = typeof b[currentSort.value] === 'string' ? b[currentSort.value].toLowerCase() : b[currentSort.value];
if (this.currentSortDir === 'desc') modifier = -1; if (currentSortDir.value === 'desc') modifier = -1;
if (valA < valB) return -1 * modifier; if (valA < valB) return -1 * modifier;
if (valA > valB) return 1 * modifier; if (valA > valB) return 1 * modifier;
return 0; return 0;
}); });
} }
else else
return this.results; return results.value;
} });
},
created () {
window.addEventListener('keydown', this.onKey, { capture: true });
},
updated () {
if (this.$refs.table)
this.refreshScroller();
if (this.$refs.tableWrapper) const getProcessesList = async () => {
this.scrollElement = this.$refs.tableWrapper; isQuering.value = true;
},
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
if (this.lastTable !== this.table)
this.results = [];
try { // Table data try { // Table data
const { status, response } = await Schema.getProcesses(this.connection.uid); const { status, response } = await Schema.getProcesses(props.connection.uid);
if (status === 'success') { if (status === 'success') {
this.results = response; results.value = response;
this.fields = response.length ? Object.keys(response[0]) : []; fields.value = response.length ? Object.keys(response[0]) : [];
} }
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.isQuering = false; isQuering.value = false;
}, };
setRefreshInterval () {
this.clearRefresh();
if (+this.autorefreshTimer) { const setRefreshInterval = () => {
this.refreshInterval = setInterval(() => { clearRefresh();
if (!this.isQuering)
this.getProcessesList(); if (+autorefreshTimer.value) {
}, this.autorefreshTimer * 1000); refreshInterval.value = setInterval(() => {
if (!isQuering.value)
getProcessesList();
}, autorefreshTimer.value * 1000);
} }
}, };
clearRefresh () {
if (this.refreshInterval) const clearRefresh = () => {
clearInterval(this.refreshInterval); if (refreshInterval.value)
}, clearInterval(refreshInterval.value);
resizeResults () { };
if (this.$refs.resultTable) {
const el = this.$refs.tableWrapper.parentElement; const resizeResults = () => {
if (resultTable.value) {
const el = tableWrapper.value.parentElement;
if (el) { if (el) {
const size = el.offsetHeight; const size = el.offsetHeight;
this.resultsSize = size; resultsSize.value = 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();
} }
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 => row.id === selectedRow.value);
const valueToCopy = row[selectedCell.value.field];
navigator.clipboard.writeText(valueToCopy);
};
const copyRow = () => {
const row = results.value.find(row => 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>

View File

@ -56,7 +56,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, Ref } from 'vue'; 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';
@ -64,7 +64,6 @@ import { useWorkspacesStore } from '@/stores/workspaces';
import * as Draggable from 'vuedraggable'; import * as Draggable from 'vuedraggable';
import SettingBarContext from '@/components/SettingBarContext.vue'; import SettingBarContext from '@/components/SettingBarContext.vue';
import { ConnectionParams } from 'common/interfaces/antares'; import { ConnectionParams } from 'common/interfaces/antares';
import { computed } from '@vue/reactivity';
const applicationStore = useApplicationStore(); const applicationStore = useApplicationStore();
const connectionsStore = useConnectionsStore(); const connectionsStore = useConnectionsStore();
@ -80,7 +79,7 @@ const { getWorkspace, selectWorkspace } = workspacesStore;
const isContext: Ref<boolean> = ref(false); const isContext: Ref<boolean> = ref(false);
const isDragging: Ref<boolean> = ref(false); const isDragging: Ref<boolean> = ref(false);
const contextEvent: Ref<Event> = ref(null); const contextEvent: Ref<MouseEvent> = ref(null);
const contextConnection: Ref<ConnectionParams> = ref(null); const contextConnection: Ref<ConnectionParams> = ref(null);
const connections = computed({ const connections = computed({
@ -94,7 +93,7 @@ const connections = computed({
const hasUpdates = computed(() => ['available', 'downloading', 'downloaded', 'link'].includes(updateStatus.value)); const hasUpdates = computed(() => ['available', 'downloading', 'downloaded', 'link'].includes(updateStatus.value));
const contextMenu = (event: Event, connection: ConnectionParams) => { const contextMenu = (event: MouseEvent, connection: ConnectionParams) => {
contextEvent.value = event; contextEvent.value = event;
contextConnection.value = connection; contextConnection.value = connection;
isContext.value = true; isContext.value = true;
@ -133,110 +132,6 @@ const dragStop = (e: any) => { // TODO: temp
}; };
</script> </script>
<!-- <script>
import { storeToRefs } from 'pinia';
import { useApplicationStore } from '@/stores/application';
import { useConnectionsStore } from '@/stores/connections';
import { useWorkspacesStore } from '@/stores/workspaces';
import Draggable from 'vuedraggable';
import SettingBarContext from '@/components/SettingBarContext';
export default {
name: 'TheSettingBar',
components: {
Draggable,
SettingBarContext
},
setup () {
const applicationStore = useApplicationStore();
const connectionsStore = useConnectionsStore();
const workspacesStore = useWorkspacesStore();
const { updateStatus } = storeToRefs(applicationStore);
const { connections: getConnections } = storeToRefs(connectionsStore);
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { showSettingModal, showScratchpad } = applicationStore;
const { getConnectionName, updateConnections } = connectionsStore;
const { getWorkspace, selectWorkspace } = workspacesStore;
return {
applicationStore,
updateStatus,
showSettingModal,
showScratchpad,
getConnections,
getConnectionName,
updateConnections,
selectedWorkspace,
getWorkspace,
selectWorkspace
};
},
data () {
return {
dragElement: null,
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 = window.pageYOffset + 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) {
case 'connected':
return 'badge badge-connected';
case 'connecting':
return 'badge badge-connecting';
case 'failed':
return 'badge badge-failed';
default:
return '';
}
}
},
dragStop (e) {
this.isDragging = false;
setTimeout(() => {
this.tooltipPosition(e.originalEvent.target.parentNode);
}, 200);
}
}
};
</script> -->
<style lang="scss"> <style lang="scss">
#settingbar { #settingbar {
width: $settingbar-width; width: $settingbar-width;

View File

@ -53,13 +53,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onUnmounted, ref } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
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 { onUnmounted, ref } from 'vue';
import { computed } from '@vue/reactivity';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
const { t } = useI18n(); const { t } = useI18n();

View File

@ -326,7 +326,7 @@
/> />
<WorkspaceTabTable <WorkspaceTabTable
v-else-if="['temp-data', 'data'].includes(tab.type)" v-else-if="['temp-data', 'data'].includes(tab.type)"
:key="tab.uid" :key="tab.uid+'_data'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid"
@ -336,7 +336,7 @@
/> />
<WorkspaceTabNewTable <WorkspaceTabNewTable
v-else-if="tab.type === 'new-table'" v-else-if="tab.type === 'new-table'"
:key="tab.uid" :key="tab.uid+'_new-table'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
@ -345,7 +345,7 @@
/> />
<WorkspaceTabPropsTable <WorkspaceTabPropsTable
v-else-if="tab.type === 'table-props'" v-else-if="tab.type === 'table-props'"
:key="tab.uid" :key="tab.uid+'_table-props'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid"
@ -354,7 +354,7 @@
/> />
<WorkspaceTabNewView <WorkspaceTabNewView
v-else-if="tab.type === 'new-view'" v-else-if="tab.type === 'new-view'"
:key="tab.uid" :key="tab.uid+'_new-view'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
@ -363,7 +363,7 @@
/> />
<WorkspaceTabPropsView <WorkspaceTabPropsView
v-else-if="tab.type === 'view-props'" v-else-if="tab.type === 'view-props'"
:key="tab.uid" :key="tab.uid+'_view-props'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid"
:connection="connection" :connection="connection"
@ -372,7 +372,7 @@
/> />
<WorkspaceTabNewTrigger <WorkspaceTabNewTrigger
v-else-if="tab.type === 'new-trigger'" v-else-if="tab.type === 'new-trigger'"
:key="tab.uid" :key="tab.uid+'_new-trigger'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
@ -382,7 +382,7 @@
/> />
<WorkspaceTabPropsTrigger <WorkspaceTabPropsTrigger
v-else-if="['temp-trigger-props', 'trigger-props'].includes(tab.type)" v-else-if="['temp-trigger-props', 'trigger-props'].includes(tab.type)"
:key="tab.uid" :key="tab.uid+'_trigger-props'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid"
@ -391,7 +391,7 @@
/> />
<WorkspaceTabNewTriggerFunction <WorkspaceTabNewTriggerFunction
v-else-if="tab.type === 'new-trigger-function'" v-else-if="tab.type === 'new-trigger-function'"
:key="tab.uid" :key="tab.uid+'_new-trigger-function'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
@ -401,7 +401,7 @@
/> />
<WorkspaceTabPropsTriggerFunction <WorkspaceTabPropsTriggerFunction
v-else-if="['temp-trigger-function-props', 'trigger-function-props'].includes(tab.type)" v-else-if="['temp-trigger-function-props', 'trigger-function-props'].includes(tab.type)"
:key="tab.uid" :key="tab.uid+'_trigger-function-props'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid"
@ -410,7 +410,7 @@
/> />
<WorkspaceTabNewRoutine <WorkspaceTabNewRoutine
v-else-if="tab.type === 'new-routine'" v-else-if="tab.type === 'new-routine'"
:key="tab.uid" :key="tab.uid+'_new-routine'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
@ -420,7 +420,7 @@
/> />
<WorkspaceTabPropsRoutine <WorkspaceTabPropsRoutine
v-else-if="['temp-routine-props', 'routine-props'].includes(tab.type)" v-else-if="['temp-routine-props', 'routine-props'].includes(tab.type)"
:key="tab.uid" :key="tab.uid+'_routine-props'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid"
@ -429,7 +429,7 @@
/> />
<WorkspaceTabNewFunction <WorkspaceTabNewFunction
v-else-if="tab.type === 'new-function'" v-else-if="tab.type === 'new-function'"
:key="tab.uid" :key="tab.uid+'_new-function'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
@ -439,7 +439,7 @@
/> />
<WorkspaceTabPropsFunction <WorkspaceTabPropsFunction
v-else-if="['temp-function-props', 'function-props'].includes(tab.type)" v-else-if="['temp-function-props', 'function-props'].includes(tab.type)"
:key="tab.uid" :key="tab.uid+'_function-props'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid"
@ -448,7 +448,7 @@
/> />
<WorkspaceTabNewScheduler <WorkspaceTabNewScheduler
v-else-if="tab.type === 'new-scheduler'" v-else-if="tab.type === 'new-scheduler'"
:key="tab.uid" :key="tab.uid+'_new-scheduler'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
@ -458,7 +458,7 @@
/> />
<WorkspaceTabPropsScheduler <WorkspaceTabPropsScheduler
v-else-if="['temp-scheduler-props', 'scheduler-props'].includes(tab.type)" v-else-if="['temp-scheduler-props', 'scheduler-props'].includes(tab.type)"
:key="tab.uid" :key="tab.uid+'_scheduler-props'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid"

View File

@ -272,7 +272,13 @@ export default {
sql = `CALL \`${this.localElement.name}\`(${params.join(',')})`; sql = `CALL \`${this.localElement.name}\`(${params.join(',')})`;
} }
this.newTab({ uid: this.workspace.uid, content: sql, type: 'query', autorun: true }); this.newTab({
uid: this.workspace.uid,
content: sql,
type: 'query',
schema: this.selectedSchema,
autorun: true
});
this.closeContext(); this.closeContext();
}, },
async runFunctionCheck () { async runFunctionCheck () {
@ -317,7 +323,13 @@ export default {
sql = `SELECT \`${this.localElement.name}\` (${params.join(',')})`; sql = `SELECT \`${this.localElement.name}\` (${params.join(',')})`;
} }
this.newTab({ uid: this.workspace.uid, content: sql, type: 'query', autorun: true }); this.newTab({
uid: this.workspace.uid,
content: sql,
type: 'query',
schema: this.selectedSchema,
autorun: true
});
this.closeContext(); this.closeContext();
}, },
async toggleTrigger () { async toggleTrigger () {

View File

@ -156,14 +156,6 @@
@hard-sort="hardSort" @hard-sort="hardSort"
/> />
</div> </div>
<ModalNewTableRow
v-if="isAddModal"
:fields="fields"
:key-usage="keyUsage"
:tab-uid="tabUid"
@hide="hideAddModal"
@reload="reloadTable"
/>
<ModalFakerRows <ModalFakerRows
v-if="isFakerModal" v-if="isFakerModal"
:fields="fields" :fields="fields"
@ -184,7 +176,6 @@ import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader'; import BaseLoader from '@/components/BaseLoader';
import WorkspaceTabQueryTable from '@/components/WorkspaceTabQueryTable'; import WorkspaceTabQueryTable from '@/components/WorkspaceTabQueryTable';
import WorkspaceTabTableFilters from '@/components/WorkspaceTabTableFilters'; import WorkspaceTabTableFilters from '@/components/WorkspaceTabTableFilters';
import ModalNewTableRow from '@/components/ModalNewTableRow';
import ModalFakerRows from '@/components/ModalFakerRows'; import ModalFakerRows from '@/components/ModalFakerRows';
import tableTabs from '@/mixins/tableTabs'; import tableTabs from '@/mixins/tableTabs';
@ -194,7 +185,6 @@ export default {
BaseLoader, BaseLoader,
WorkspaceTabQueryTable, WorkspaceTabQueryTable,
WorkspaceTabTableFilters, WorkspaceTabTableFilters,
ModalNewTableRow,
ModalFakerRows ModalFakerRows
}, },
mixins: [tableTabs], mixins: [tableTabs],
@ -231,7 +221,6 @@ export default {
isSearch: false, isSearch: false,
results: [], results: [],
lastTable: null, lastTable: null,
isAddModal: false,
isFakerModal: false, isFakerModal: false,
autorefreshTimer: 0, autorefreshTimer: 0,
refreshInterval: null, refreshInterval: null,
@ -410,12 +399,6 @@ export default {
else if (direction === 'prev' && this.page > 1) else if (direction === 'prev' && this.page > 1)
this.page--; this.page--;
}, },
showAddModal () {
this.isAddModal = true;
},
hideAddModal () {
this.isAddModal = false;
},
showFakerModal () { showFakerModal () {
if (this.isQuering) return; if (this.isQuering) return;
this.isFakerModal = true; this.isFakerModal = true;

View File

View File

@ -1,22 +1,24 @@
import formatter from 'pg-connection-string'; // parses a connection string import { ConnectionParams } from 'common/interfaces/antares';
import * as formatter from 'pg-connection-string'; // parses a connection string
const formatHost = host => { const formatHost = (host: string) => {
const results = host === 'localhost' ? '127.0.0.1' : host; const results = host === 'localhost' ? '127.0.0.1' : host;
return results; return results;
}; };
const checkForSSl = conn => { const checkForSSl = (conn: string) => {
return conn.includes('ssl=true'); return conn.includes('ssl=true');
}; };
const connStringConstruct = (args) => { const connStringConstruct = (args: ConnectionParams & { pgConnString: string }): ConnectionParams => {
if (!args.pgConnString) if (!args.pgConnString)
return args; return args;
if (typeof args.pgConnString !== 'string') if (typeof args.pgConnString !== 'string')
return args; return args;
const stringArgs = formatter.parse(args.pgConnString); // eslint-disable-next-line @typescript-eslint/no-explicit-any
const stringArgs: any = formatter.parse(args.pgConnString);
const client = args.client || 'pg'; const client = args.client || 'pg';

View File

@ -1,14 +1,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable */
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-this-alias */
/* eslint-disable no-labels */
/* eslint-disable no-return-assign */
/* eslint-disable no-cond-assign */
/* eslint-disable no-prototype-builtins */
/* eslint-disable no-mixed-operators */
/* /*
Got from 'ace-builds/src-noconflict/ext-language_tools' and edited to support icons. Got from 'ace-builds/src-noconflict/ext-language_tools' and edited to support icons.
I'm not responsible of this crazy code. I'm not responsible of this crazy code 🤯.
*/ */
ace.define('ace/snippets', ['require', 'exports', 'module', 'ace/lib/oop', 'ace/lib/event_emitter', 'ace/lib/lang', 'ace/range', 'ace/range_list', 'ace/keyboard/hash_handler', 'ace/tokenizer', 'ace/clipboard', 'ace/lib/dom', 'ace/editor'], function (require, exports, module) { ace.define('ace/snippets', ['require', 'exports', 'module', 'ace/lib/oop', 'ace/lib/event_emitter', 'ace/lib/lang', 'ace/range', 'ace/range_list', 'ace/keyboard/hash_handler', 'ace/tokenizer', 'ace/clipboard', 'ace/lib/dom', 'ace/editor'], function (require, exports, module) {