1
1
mirror of https://github.com/Fabio286/antares.git synced 2025-06-05 21:59:22 +02:00

refactor: ts and composition api on WorkspaceTabNew* components

This commit is contained in:
2022-06-14 20:02:17 +02:00
parent 33a4663694
commit 89e8d9fcdb
17 changed files with 1321 additions and 1471 deletions

View File

@ -93,6 +93,8 @@ export interface TableInfos {
} }
export interface TableField { export interface TableField {
// eslint-disable-next-line camelcase
_antares_id?: string;
name: string; name: string;
key: string; key: string;
type: string; type: string;
@ -120,6 +122,8 @@ export interface TableField {
} }
export interface TableIndex { export interface TableIndex {
// eslint-disable-next-line camelcase
_antares_id?: string;
name: string; name: string;
fields: string[]; fields: string[];
type: string; type: string;
@ -249,14 +253,20 @@ export interface FunctionParam {
export interface RoutineInfos { export interface RoutineInfos {
name: string; name: string;
type: string; type?: string;
definer: string; definer: string;
created: string; created?: string;
updated: string; sql?: string;
updated?: string;
comment?: string; comment?: string;
charset?: string; charset?: string;
security?: string; security?: string;
language?: string;
dataAccess?: string;
deterministic?: boolean;
parameters?: FunctionParam[]; parameters?: FunctionParam[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
returns?: any;
} }
export type FunctionInfos = RoutineInfos export type FunctionInfos = RoutineInfos

View File

@ -85,7 +85,7 @@ else {
ipcMain.on('refresh-theme-settings', () => { ipcMain.on('refresh-theme-settings', () => {
const appTheme = persistentStore.get('application_theme'); const appTheme = persistentStore.get('application_theme');
if(isWindows){ if (isWindows) {
mainWindow.setTitleBarOverlay({ mainWindow.setTitleBarOverlay({
color: appTheme === 'dark' ? '#3f3f3f' : '#fff', color: appTheme === 'dark' ? '#3f3f3f' : '#fff',
symbolColor: appTheme === 'dark' ? '#fff' : '#000' symbolColor: appTheme === 'dark' ? '#fff' : '#000'

View File

@ -139,7 +139,7 @@ export default defineComponent({
const hightlightedIndex = ref(0); const hightlightedIndex = ref(0);
const isOpen = ref(false); const isOpen = ref(false);
const isMouseDown = ref(false); const isMouseDown = ref(false);
const internalValue = ref(props.modelValue || props.value); const internalValue = ref(props.modelValue !== false ? props.modelValue : props.value);
const el = ref(null); const el = ref(null);
const searchInput = ref(null); const searchInput = ref(null);
const optionList = ref(null); const optionList = ref(null);
@ -380,7 +380,8 @@ export default defineComponent({
optionList, optionList,
optionRefs, optionRefs,
handleBlurEvent, handleBlurEvent,
handleMouseUpEvent handleMouseUpEvent,
internalValue
}; };
} }
}); });

View File

@ -133,8 +133,8 @@ const mode = computed(() => {
case 'mysql': case 'mysql':
case 'maria': case 'maria':
return 'mysql'; return 'mysql';
case 'mssql': // case 'mssql':
return 'sqlserver'; // return 'sqlserver';
case 'pg': case 'pg':
return 'pgsql'; return 'pgsql';
default: default:

View File

@ -4,7 +4,7 @@
@close-context="closeContext" @close-context="closeContext"
> >
<div <div
v-if="['procedure', 'function'].includes(selectedMisc.type)" v-if="['procedure', 'routine', 'function'].includes(selectedMisc.type)"
class="context-element" class="context-element"
@click="runElementCheck" @click="runElementCheck"
> >
@ -77,7 +77,7 @@ import Triggers from '@/ipc-api/Triggers';
import Routines from '@/ipc-api/Routines'; import Routines from '@/ipc-api/Routines';
import Functions from '@/ipc-api/Functions'; import Functions from '@/ipc-api/Functions';
import Schedulers from '@/ipc-api/Schedulers'; import Schedulers from '@/ipc-api/Schedulers';
import { EventInfos, FunctionInfos, RoutineInfos, TriggerInfos } from 'common/interfaces/antares'; import { EventInfos, FunctionInfos, IpcResponse, RoutineInfos, TriggerInfos } from 'common/interfaces/antares';
const { t } = useI18n(); const { t } = useI18n();
@ -153,7 +153,7 @@ const closeContext = () => {
const deleteMisc = async () => { const deleteMisc = async () => {
try { try {
let res; let res: IpcResponse;
switch (props.selectedMisc.type) { switch (props.selectedMisc.type) {
case 'trigger': case 'trigger':
@ -163,6 +163,7 @@ const deleteMisc = async () => {
trigger: props.selectedMisc.name trigger: props.selectedMisc.name
}); });
break; break;
case 'routine':
case 'procedure': case 'procedure':
res = await Routines.dropRoutine({ res = await Routines.dropRoutine({
uid: selectedWorkspace.value, uid: selectedWorkspace.value,
@ -187,6 +188,8 @@ const deleteMisc = async () => {
break; break;
} }
console.log(res);
const { status, response } = res; const { status, response } = res;
if (status === 'success') { if (status === 'success') {
@ -209,7 +212,7 @@ const deleteMisc = async () => {
}; };
const runElementCheck = () => { const runElementCheck = () => {
if (props.selectedMisc.type === 'procedure') if (['procedure', 'routine'].includes(props.selectedMisc.type))
runRoutineCheck(); runRoutineCheck();
else if (props.selectedMisc.type === 'function') else if (props.selectedMisc.type === 'function')
runFunctionCheck(); runFunctionCheck();
@ -257,9 +260,9 @@ const runRoutine = (params?: string[]) => {
case 'pg': case 'pg':
sql = `CALL ${localElement.value.name}(${params.join(',')})`; sql = `CALL ${localElement.value.name}(${params.join(',')})`;
break; break;
case 'mssql': // case 'mssql':
sql = `EXEC ${localElement.value.name} ${params.join(',')}`; // sql = `EXEC ${localElement.value.name} ${params.join(',')}`;
break; // break;
default: default:
sql = `CALL \`${localElement.value.name}\`(${params.join(',')})`; sql = `CALL \`${localElement.value.name}\`(${params.join(',')})`;
} }
@ -310,9 +313,9 @@ const runFunction = (params?: string[]) => {
case 'pg': case 'pg':
sql = `SELECT ${localElement.value.name}(${params.join(',')})`; sql = `SELECT ${localElement.value.name}(${params.join(',')})`;
break; break;
case 'mssql': // case 'mssql':
sql = `SELECT ${localElement.value.name} ${params.join(',')}`; // sql = `SELECT ${localElement.value.name} ${params.join(',')}`;
break; // break;
default: default:
sql = `SELECT \`${localElement.value.name}\` (${params.join(',')})`; sql = `SELECT \`${localElement.value.name}\` (${params.join(',')})`;
} }

View File

@ -11,7 +11,7 @@
<span class="d-flex"><i class="mdi mdi-18px mdi-table-cog text-light pr-1" /> {{ t('message.createNewTrigger') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-table-cog text-light pr-1" /> {{ t('message.createNewTrigger') }}</span>
</div> </div>
<div <div
v-if="props.selectedMisc === 'procedure'" v-if="['procedure', 'routine'].includes(props.selectedMisc)"
class="context-element" class="context-element"
@click="emit('open-create-routine-tab')" @click="emit('open-create-routine-tab')"
> >

View File

@ -72,8 +72,8 @@
<BaseSelect <BaseSelect
v-model="localFunction.definer" v-model="localFunction.definer"
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]" :options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]"
:option-label="(user) => user.value === '' ? user.name : `${user.name}@${user.host}`" :option-label="(user: any) => user.value === '' ? user.name : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``" :option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select" class="form-select"
/> />
</div> </div>
@ -87,7 +87,7 @@
<BaseSelect <BaseSelect
v-model="localFunction.returns" v-model="localFunction.returns"
class="form-select text-uppercase" class="form-select text-uppercase"
:options="[{ name: 'VOID' }, ...workspace.dataTypes]" :options="[{ name: 'VOID' }, ...(workspace.dataTypes as any)]"
group-label="group" group-label="group"
group-values="types" group-values="types"
option-label="name" option-label="name"
@ -175,213 +175,174 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds';
import Functions from '@/ipc-api/Functions';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader'; import BaseLoader from '@/components/BaseLoader.vue';
import QueryEditor from '@/components/QueryEditor'; import QueryEditor from '@/components/QueryEditor.vue';
import WorkspaceTabPropsFunctionParamsModal from '@/components/WorkspaceTabPropsFunctionParamsModal'; import WorkspaceTabPropsFunctionParamsModal from '@/components/WorkspaceTabPropsFunctionParamsModal.vue';
import Functions from '@/ipc-api/Functions';
import { storeToRefs } from 'pinia';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { FunctionInfos, FunctionParam } from 'common/interfaces/antares';
export default { const props = defineProps({
name: 'WorkspaceTabNewFunction', tabUid: String,
components: { connection: Object,
BaseLoader, tab: Object,
QueryEditor, isSelected: Boolean,
WorkspaceTabPropsFunctionParamsModal, schema: String
BaseSelect });
},
props: {
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { const {
getWorkspace, getWorkspace,
refreshStructure, refreshStructure,
changeBreadcrumbs, changeBreadcrumbs,
setUnsavedChanges, setUnsavedChanges,
newTab, newTab,
removeTab, removeTab
renameTabs } = workspacesStore;
} = workspacesStore;
return { const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
addNotification, const firstInput: Ref<HTMLInputElement> = ref(null);
selectedWorkspace, const isLoading = ref(false);
getWorkspace, const isSaving = ref(false);
refreshStructure, const isParamsModal = ref(false);
changeBreadcrumbs, const originalFunction: Ref<FunctionInfos> = ref(null);
setUnsavedChanges, const localFunction = ref(null);
newTab, const editorHeight = ref(300);
removeTab,
renameTabs
};
},
data () {
return {
isLoading: false,
isSaving: false,
isParamsModal: false,
originalFunction: {},
localFunction: {},
lastFunction: null,
sqlProxy: '',
editorHeight: 300
};
},
computed: {
workspace () {
return this.getWorkspace(this.connection.uid);
},
customizations () {
return this.workspace.customizations;
},
isChanged () {
return JSON.stringify(this.originalFunction) !== JSON.stringify(this.localFunction);
},
isDefinerInUsers () {
return this.originalFunction
? this.workspace.users.some(user => this.originalFunction.definer === `\`${user.name}\`@\`${user.host}\``)
: true;
},
schemaTables () {
const schemaTables = this.workspace.structure
.filter(schema => schema.name === this.schema)
.map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : []; const workspace = computed(() => {
return getWorkspace(props.connection.uid);
});
const customizations = computed(() => {
return workspace.value.customizations;
});
const isChanged = computed(() => {
return JSON.stringify(originalFunction.value) !== JSON.stringify(localFunction.value);
});
const saveChanges = async () => {
if (isSaving.value) return;
isSaving.value = true;
const params = {
uid: props.connection.uid,
schema: props.schema,
...localFunction.value
};
try {
const { status, response } = await Functions.createFunction(params);
if (status === 'success') {
await refreshStructure(props.connection.uid);
newTab({
uid: props.connection.uid,
schema: props.schema,
elementName: localFunction.value.name,
elementType: 'function',
type: 'function-props'
});
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
changeBreadcrumbs({ schema: props.schema, function: localFunction.value.name });
} }
}, else
watch: { addNotification({ status: 'error', message: response });
isSelected (val) { }
if (val) catch (err) {
this.changeBreadcrumbs({ schema: this.schema }); addNotification({ status: 'error', message: err.stack });
}, }
isChanged (val) {
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
created () {
this.originalFunction = {
sql: this.customizations.functionSql,
language: this.customizations.languages ? this.customizations.languages[0] : null,
name: '',
definer: '',
comment: '',
security: 'DEFINER',
dataAccess: 'CONTAINS SQL',
deterministic: false,
returns: this.workspace.dataTypes.length ? this.workspace.dataTypes[0].types[0].name : null
};
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction)); isSaving.value = false;
};
setTimeout(() => { const clearChanges = () => {
this.resizeQueryEditor(); localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
}, 50); queryEditor.value.editor.session.setValue(localFunction.value.sql);
};
window.addEventListener('keydown', this.onKey); const resizeQueryEditor = () => {
}, if (queryEditor.value) {
mounted () { const footer = document.getElementById('footer');
if (this.isSelected) const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
this.changeBreadcrumbs({ schema: this.schema }); editorHeight.value = size;
queryEditor.value.editor.resize();
}
};
setTimeout(() => { const parametersUpdate = (parameters: FunctionParam[]) => {
this.$refs.firstInput.focus(); localFunction.value = { ...localFunction.value, parameters };
}, 100); };
},
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
async saveChanges () {
if (this.isSaving) return;
this.isSaving = true;
const params = {
uid: this.connection.uid,
schema: this.schema,
...this.localFunction
};
try { const showParamsModal = () => {
const { status, response } = await Functions.createFunction(params); isParamsModal.value = true;
};
if (status === 'success') { const hideParamsModal = () => {
await this.refreshStructure(this.connection.uid); isParamsModal.value = false;
};
this.newTab({ const onKey = (e: KeyboardEvent) => {
uid: this.connection.uid, if (props.isSelected) {
schema: this.schema, e.stopPropagation();
elementName: this.localFunction.name, if (e.ctrlKey && e.key === 's') { // CTRL + S
elementType: 'function', if (isChanged.value)
type: 'function-props' saveChanges();
});
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
this.changeBreadcrumbs({ schema: this.schema, function: this.localFunction.name });
}
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isSaving = false;
},
clearChanges () {
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction));
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
},
resizeQueryEditor () {
if (this.$refs.queryEditor) {
const footer = document.getElementById('footer');
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
this.editorHeight = size;
this.$refs.queryEditor.editor.resize();
}
},
optionsUpdate (options) {
this.localFunction = options;
},
parametersUpdate (parameters) {
this.localFunction = { ...this.localFunction, parameters };
},
showParamsModal () {
this.isParamsModal = true;
},
hideParamsModal () {
this.isParamsModal = false;
},
showAskParamsModal () {
this.isAskingParameters = true;
},
hideAskParamsModal () {
this.isAskingParameters = false;
},
onKey (e) {
if (this.isSelected) {
e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
if (this.isChanged)
this.saveChanges();
}
}
} }
} }
}; };
watch(() => props.isSelected, (val) => {
if (val) changeBreadcrumbs({ schema: props.schema });
});
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
});
originalFunction.value = {
sql: customizations.value.functionSql,
language: customizations.value.languages ? customizations.value.languages[0] : null,
name: '',
definer: '',
comment: '',
security: 'DEFINER',
dataAccess: 'CONTAINS SQL',
deterministic: false,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
returns: workspace.value.dataTypes.length ? (workspace.value.dataTypes[0] as any).types[0].name : null
};
localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
setTimeout(() => {
resizeQueryEditor();
}, 50);
window.addEventListener('keydown', onKey);
onMounted(() => {
if (props.isSelected)
changeBreadcrumbs({ schema: props.schema });
setTimeout(() => {
firstInput.value.focus();
}, 100);
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
onUnmounted(() => {
window.removeEventListener('resize', resizeQueryEditor);
});
</script> </script>

View File

@ -72,8 +72,8 @@
<BaseSelect <BaseSelect
v-model="localRoutine.definer" v-model="localRoutine.definer"
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]" :options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]"
:option-label="(user) => user.value === '' ? user.name : `${user.name}@${user.host}`" :option-label="(user: any) => user.value === '' ? user.name : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``" :option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select" class="form-select"
/> />
</div> </div>
@ -129,7 +129,6 @@
<label class="form-label ml-2">{{ $t('message.routineBody') }}</label> <label class="form-label ml-2">{{ $t('message.routineBody') }}</label>
<QueryEditor <QueryEditor
v-show="isSelected" v-show="isSelected"
:key="`new-${_uid}`"
ref="queryEditor" ref="queryEditor"
v-model="localRoutine.sql" v-model="localRoutine.sql"
:workspace="workspace" :workspace="workspace"
@ -148,209 +147,174 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds';
import Routines from '@/ipc-api/Routines';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import QueryEditor from '@/components/QueryEditor'; import QueryEditor from '@/components/QueryEditor.vue';
import BaseLoader from '@/components/BaseLoader'; import BaseLoader from '@/components/BaseLoader.vue';
import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal'; import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal.vue';
import Routines from '@/ipc-api/Routines';
import { storeToRefs } from 'pinia';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { FunctionParam } from 'common/interfaces/antares';
export default { const props = defineProps({
name: 'WorkspaceTabNewRoutine', tabUid: String,
components: { connection: Object,
QueryEditor, tab: Object,
BaseLoader, isSelected: Boolean,
WorkspaceTabPropsRoutineParamsModal, schema: String
BaseSelect });
},
props: {
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { const {
getWorkspace, getWorkspace,
refreshStructure, refreshStructure,
changeBreadcrumbs, changeBreadcrumbs,
setUnsavedChanges, setUnsavedChanges,
newTab, newTab,
removeTab, removeTab
renameTabs } = workspacesStore;
} = workspacesStore;
return { const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
addNotification, const firstInput: Ref<HTMLInputElement> = ref(null);
selectedWorkspace, const isLoading = ref(false);
getWorkspace, const isSaving = ref(false);
refreshStructure, const isParamsModal = ref(false);
changeBreadcrumbs, const originalRoutine = ref(null);
setUnsavedChanges, const localRoutine = ref(null);
newTab, const editorHeight = ref(300);
removeTab,
renameTabs
};
},
data () {
return {
isLoading: false,
isSaving: false,
isParamsModal: false,
originalRoutine: {},
localRoutine: {},
lastRoutine: null,
sqlProxy: '',
editorHeight: 300
};
},
computed: {
workspace () {
return this.getWorkspace(this.connection.uid);
},
customizations () {
return this.workspace.customizations;
},
isChanged () {
return JSON.stringify(this.originalRoutine) !== JSON.stringify(this.localRoutine);
},
isDefinerInUsers () {
return this.originalRoutine ? this.workspace.users.some(user => this.originalRoutine.definer === `\`${user.name}\`@\`${user.host}\``) : true;
},
schemaTables () {
const schemaTables = this.workspace.structure
.filter(schema => schema.name === this.schema)
.map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : []; const workspace = computed(() => {
return getWorkspace(props.connection.uid);
});
const customizations = computed(() => {
return workspace.value.customizations;
});
const isChanged = computed(() => {
return JSON.stringify(originalRoutine.value) !== JSON.stringify(localRoutine.value);
});
const saveChanges = async () => {
if (isSaving.value) return;
isSaving.value = true;
const params = {
uid: props.connection.uid,
schema: props.schema,
...localRoutine.value
};
try {
const { status, response } = await Routines.createRoutine(params);
if (status === 'success') {
await refreshStructure(props.connection.uid);
newTab({
uid: props.connection.uid,
schema: props.schema,
elementName: localRoutine.value.name,
elementType: 'routine',
type: 'routine-props'
});
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
changeBreadcrumbs({ schema: props.schema, routine: localRoutine.value.name });
} }
}, else
watch: { addNotification({ status: 'error', message: response });
isSelected (val) { }
if (val) catch (err) {
this.changeBreadcrumbs({ schema: this.schema }); addNotification({ status: 'error', message: err.stack });
}, }
isChanged (val) {
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
created () {
this.originalRoutine = {
sql: this.customizations.procedureSql,
language: this.customizations.languages ? this.customizations.languages[0] : null,
name: '',
definer: '',
comment: '',
security: 'DEFINER',
dataAccess: 'CONTAINS SQL',
deterministic: false
};
this.localRoutine = JSON.parse(JSON.stringify(this.originalRoutine)); isSaving.value = false;
};
setTimeout(() => { const clearChanges = () => {
this.resizeQueryEditor(); localRoutine.value = JSON.parse(JSON.stringify(originalRoutine.value));
}, 50); queryEditor.value.editor.session.setValue(localRoutine.value.sql);
};
window.addEventListener('keydown', this.onKey); const resizeQueryEditor = () => {
}, if (queryEditor.value) {
mounted () { const footer = document.getElementById('footer');
if (this.isSelected) const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
this.changeBreadcrumbs({ schema: this.schema }); editorHeight.value = size;
queryEditor.value.editor.resize();
}
};
setTimeout(() => { const parametersUpdate = (parameters: FunctionParam[]) => {
this.$refs.firstInput.focus(); localRoutine.value = { ...localRoutine.value, parameters };
}, 100); };
window.addEventListener('resize', this.resizeQueryEditor); const showParamsModal = () => {
}, isParamsModal.value = true;
unmounted () { };
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
async saveChanges () {
if (this.isSaving) return;
this.isSaving = true;
const params = {
uid: this.connection.uid,
schema: this.schema,
...this.localRoutine
};
try { const hideParamsModal = () => {
const { status, response } = await Routines.createRoutine(params); isParamsModal.value = false;
};
if (status === 'success') { const onKey = (e: KeyboardEvent) => {
await this.refreshStructure(this.connection.uid); if (props.isSelected) {
e.stopPropagation();
this.newTab({ if (e.ctrlKey && e.key === 's') { // CTRL + S
uid: this.connection.uid, if (isChanged.value)
schema: this.schema, saveChanges();
elementName: this.localRoutine.name,
elementType: 'routine',
type: 'routine-props'
});
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
this.changeBreadcrumbs({ schema: this.schema, routine: this.localRoutine.name });
}
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isSaving = false;
},
clearChanges () {
this.localRoutine = JSON.parse(JSON.stringify(this.originalRoutine));
this.$refs.queryEditor.editor.session.setValue(this.localRoutine.sql);
},
resizeQueryEditor () {
if (this.$refs.queryEditor) {
const footer = document.getElementById('footer');
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
this.editorHeight = size;
this.$refs.queryEditor.editor.resize();
}
},
parametersUpdate (parameters) {
this.localRoutine = { ...this.localRoutine, parameters };
},
showParamsModal () {
this.isParamsModal = true;
},
hideParamsModal () {
this.isParamsModal = false;
},
showAskParamsModal () {
this.isAskingParameters = true;
},
hideAskParamsModal () {
this.isAskingParameters = false;
},
onKey (e) {
if (this.isSelected) {
e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
if (this.isChanged)
this.saveChanges();
}
}
} }
} }
}; };
watch(() => props.isSelected, (val) => {
if (val) changeBreadcrumbs({ schema: props.schema });
});
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
});
originalRoutine.value = {
sql: customizations.value.functionSql,
language: customizations.value.languages ? customizations.value.languages[0] : null,
name: '',
definer: '',
comment: '',
security: 'DEFINER',
dataAccess: 'CONTAINS SQL',
deterministic: false,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
returns: workspace.value.dataTypes.length ? (workspace.value.dataTypes[0] as any).types[0].name : null
};
localRoutine.value = JSON.parse(JSON.stringify(originalRoutine.value));
setTimeout(() => {
resizeQueryEditor();
}, 50);
window.addEventListener('keydown', onKey);
onMounted(() => {
if (props.isSelected)
changeBreadcrumbs({ schema: props.schema });
setTimeout(() => {
firstInput.value.focus();
}, 100);
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
onUnmounted(() => {
window.removeEventListener('resize', resizeQueryEditor);
});
</script> </script>

View File

@ -11,26 +11,26 @@
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span> <span>{{ t('word.save') }}</span>
</button> </button>
<button <button
:disabled="!isChanged" :disabled="!isChanged"
class="btn btn-link btn-sm mr-0" class="btn btn-link btn-sm mr-0"
:title="$t('message.clearChanges')" :title="t('message.clearChanges')"
@click="clearChanges" @click="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" /> <i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ t('word.clear') }}</span>
</button> </button>
<div class="divider-vert py-3" /> <div class="divider-vert py-3" />
<button class="btn btn-dark btn-sm" @click="showTimingModal"> <button class="btn btn-dark btn-sm" @click="showTimingModal">
<i class="mdi mdi-24px mdi-timer mr-1" /> <i class="mdi mdi-24px mdi-timer mr-1" />
<span>{{ $t('word.timing') }}</span> <span>{{ t('word.timing') }}</span>
</button> </button>
</div> </div>
<div class="workspace-query-info"> <div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')"> <div class="d-flex" :title="t('word.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b> <i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div> </div>
</div> </div>
@ -40,7 +40,7 @@
<div class="columns"> <div class="columns">
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.name') }}</label> <label class="form-label">{{ t('word.name') }}</label>
<input <input
ref="firstInput" ref="firstInput"
v-model="localScheduler.name" v-model="localScheduler.name"
@ -51,19 +51,19 @@
</div> </div>
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.definer') }}</label> <label class="form-label">{{ t('word.definer') }}</label>
<BaseSelect <BaseSelect
v-model="localScheduler.definer" v-model="localScheduler.definer"
:options="users" :options="users"
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`" :option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``" :option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select" class="form-select"
/> />
</div> </div>
</div> </div>
<div class="column"> <div class="column">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.comment') }}</label> <label class="form-label">{{ t('word.comment') }}</label>
<input <input
v-model="localScheduler.comment" v-model="localScheduler.comment"
class="form-input" class="form-input"
@ -73,7 +73,7 @@
</div> </div>
<div class="column"> <div class="column">
<div class="form-group"> <div class="form-group">
<label class="form-label mr-2">{{ $t('word.state') }}</label> <label class="form-label mr-2">{{ t('word.state') }}</label>
<label class="form-radio form-inline"> <label class="form-radio form-inline">
<input <input
v-model="localScheduler.state" v-model="localScheduler.state"
@ -104,7 +104,7 @@
</div> </div>
<div class="workspace-query-results column col-12 mt-2 p-relative"> <div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" /> <BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ $t('message.schedulerBody') }}</label> <label class="form-label ml-2">{{ t('message.schedulerBody') }}</label>
<QueryEditor <QueryEditor
v-show="isSelected" v-show="isSelected"
ref="queryEditor" ref="queryEditor"
@ -124,209 +124,186 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { storeToRefs } from 'pinia'; import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds';
import { EventInfos } from 'common/interfaces/antares';
import { useI18n } from 'vue-i18n';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader'; import BaseLoader from '@/components/BaseLoader.vue';
import QueryEditor from '@/components/QueryEditor'; import QueryEditor from '@/components/QueryEditor.vue';
import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal'; import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal.vue';
import Schedulers from '@/ipc-api/Schedulers'; import Schedulers from '@/ipc-api/Schedulers';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
export default { const { t } = useI18n();
name: 'WorkspaceTabNewScheduler',
components: {
BaseLoader,
QueryEditor,
WorkspaceTabPropsSchedulerTimingModal,
BaseSelect
},
props: {
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const props = defineProps({
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
});
const { const { addNotification } = useNotificationsStore();
getWorkspace, const workspacesStore = useWorkspacesStore();
refreshStructure,
changeBreadcrumbs,
setUnsavedChanges,
newTab,
removeTab,
renameTabs
} = workspacesStore;
return { const {
addNotification, getWorkspace,
selectedWorkspace, refreshStructure,
getWorkspace, changeBreadcrumbs,
refreshStructure, setUnsavedChanges,
changeBreadcrumbs, newTab,
setUnsavedChanges, removeTab
newTab, } = workspacesStore;
removeTab,
renameTabs
};
},
data () {
return {
isLoading: false,
isSaving: false,
isTimingModal: false,
originalScheduler: {},
localScheduler: {},
lastScheduler: null,
sqlProxy: '',
editorHeight: 300
};
},
computed: {
workspace () {
return this.getWorkspace(this.connection.uid);
},
isChanged () {
return JSON.stringify(this.originalScheduler) !== JSON.stringify(this.localScheduler);
},
isDefinerInUsers () {
return this.originalScheduler ? this.workspace.users.some(user => this.originalScheduler.definer === `\`${user.name}\`@\`${user.host}\``) : true;
},
schemaTables () {
const schemaTables = this.workspace.structure
.filter(schema => schema.name === this.schema)
.map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : []; const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
}, const firstInput: Ref<HTMLInputElement> = ref(null);
users () { const isLoading = ref(false);
const users = [{ value: '' }, ...this.workspace.users]; const isSaving = ref(false);
if (!this.isDefinerInUsers) { const isTimingModal = ref(false);
const [name, host] = this.originalScheduler.definer.replaceAll('`', '').split('@'); const originalScheduler = ref(null);
users.unshift({ name, host }); const localScheduler = ref(null);
} const editorHeight = ref(300);
return users; const workspace = computed(() => {
return getWorkspace(props.connection.uid);
});
const isChanged = computed(() => {
return JSON.stringify(originalScheduler.value) !== JSON.stringify(localScheduler.value);
});
const isDefinerInUsers = computed(() => {
return originalScheduler.value ? workspace.value.users.some(user => originalScheduler.value.definer === `\`${user.name}\`@\`${user.host}\``) : true;
});
const users = computed(() => {
const users = [{ value: '' }, ...workspace.value.users];
if (!isDefinerInUsers.value) {
const [name, host] = originalScheduler.value.definer.replaceAll('`', '').split('@');
users.unshift({ name, host });
}
return users;
});
const saveChanges = async () => {
if (isSaving.value) return;
isSaving.value = true;
const params = {
uid: props.connection.uid,
schema: props.schema,
...localScheduler.value
};
try {
const { status, response } = await Schedulers.createScheduler(params);
if (status === 'success') {
await refreshStructure(props.connection.uid);
newTab({
uid: props.connection.uid,
schema: props.schema,
elementName: localScheduler.value.name,
elementType: 'scheduler',
type: 'scheduler-props'
});
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
changeBreadcrumbs({ schema: props.schema, scheduler: localScheduler.value.name });
} }
}, else
watch: { addNotification({ status: 'error', message: response });
isSelected (val) { }
if (val) catch (err) {
this.changeBreadcrumbs({ schema: this.schema }); addNotification({ status: 'error', message: err.stack });
}, }
isChanged (val) {
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
async created () {
this.originalScheduler = {
definer: '',
sql: 'BEGIN\r\n\r\nEND',
name: '',
comment: '',
execution: 'EVERY',
every: ['1', 'DAY'],
preserve: true,
state: 'DISABLE'
};
this.localScheduler = JSON.parse(JSON.stringify(this.originalScheduler)); isSaving.value = false;
};
setTimeout(() => { const clearChanges = () => {
this.resizeQueryEditor(); localScheduler.value = JSON.parse(JSON.stringify(originalScheduler.value));
}, 50); queryEditor.value.editor.session.setValue(localScheduler.value.sql);
};
window.addEventListener('keydown', this.onKey); const resizeQueryEditor = () => {
}, if (queryEditor.value) {
mounted () { const footer = document.getElementById('footer');
if (this.isSelected) const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
this.changeBreadcrumbs({ schema: this.schema }); editorHeight.value = size;
queryEditor.value.editor.resize();
}
};
setTimeout(() => { const showTimingModal = () => {
this.$refs.firstInput.focus(); isTimingModal.value = true;
}, 100); };
window.addEventListener('resize', this.resizeQueryEditor); const hideTimingModal = () => {
}, isTimingModal.value = false;
unmounted () { };
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
async saveChanges () {
if (this.isSaving) return;
this.isSaving = true;
const params = {
uid: this.connection.uid,
schema: this.schema,
...this.localScheduler
};
try { const timingUpdate = (options: EventInfos) => {
const { status, response } = await Schedulers.createScheduler(params); localScheduler.value = options;
};
if (status === 'success') { const onKey = (e: KeyboardEvent) => {
await this.refreshStructure(this.connection.uid); if (props.isSelected) {
e.stopPropagation();
this.newTab({ if (e.ctrlKey && e.key === 's') { // CTRL + S
uid: this.connection.uid, if (isChanged.value)
schema: this.schema, saveChanges();
elementName: this.localScheduler.name,
elementType: 'scheduler',
type: 'scheduler-props'
});
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
this.changeBreadcrumbs({ schema: this.schema, scheduler: this.localScheduler.name });
}
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isSaving = false;
},
clearChanges () {
this.localScheduler = JSON.parse(JSON.stringify(this.originalScheduler));
this.$refs.queryEditor.editor.session.setValue(this.localScheduler.sql);
},
resizeQueryEditor () {
if (this.$refs.queryEditor) {
const footer = document.getElementById('footer');
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
this.editorHeight = size;
this.$refs.queryEditor.editor.resize();
}
},
showTimingModal () {
this.isTimingModal = true;
},
hideTimingModal () {
this.isTimingModal = false;
},
timingUpdate (options) {
this.localScheduler = options;
},
onKey (e) {
if (this.isSelected) {
e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
if (this.isChanged)
this.saveChanges();
}
}
} }
} }
}; };
watch(() => props.isSelected, (val) => {
if (val) changeBreadcrumbs({ schema: props.schema });
});
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
});
originalScheduler.value = {
definer: '',
sql: 'BEGIN\r\n\r\nEND',
name: '',
comment: '',
execution: 'EVERY',
every: ['1', 'DAY'],
preserve: true,
state: 'DISABLE'
};
originalScheduler.value = JSON.parse(JSON.stringify(originalScheduler.value));
setTimeout(() => {
resizeQueryEditor();
}, 50);
window.addEventListener('keydown', onKey);
onMounted(() => {
if (props.isSelected)
changeBreadcrumbs({ schema: props.schema });
setTimeout(() => {
firstInput.value.focus();
}, 100);
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
onUnmounted(() => {
window.removeEventListener('resize', resizeQueryEditor);
});
</script> </script>

View File

@ -11,16 +11,16 @@
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span> <span>{{ t('word.save') }}</span>
</button> </button>
<button <button
:disabled="!isChanged || isSaving" :disabled="!isChanged || isSaving"
class="btn btn-link btn-sm mr-0" class="btn btn-link btn-sm mr-0"
:title="$t('message.clearChanges')" :title="t('message.clearChanges')"
@click="clearChanges" @click="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" /> <i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ t('word.clear') }}</span>
</button> </button>
<div class="divider-vert py-3" /> <div class="divider-vert py-3" />
@ -28,20 +28,20 @@
<button <button
:disabled="isSaving" :disabled="isSaving"
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:title="$t('message.addNewField')" :title="t('message.addNewField')"
@click="addField" @click="addField"
> >
<i class="mdi mdi-24px mdi-playlist-plus mr-1" /> <i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span>{{ $t('word.add') }}</span> <span>{{ t('word.add') }}</span>
</button> </button>
<button <button
:disabled="isSaving || !localFields.length" :disabled="isSaving || !localFields.length"
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:title="$t('message.manageIndexes')" :title="t('message.manageIndexes')"
@click="showIntdexesModal" @click="showIntdexesModal"
> >
<i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" /> <i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" />
<span>{{ $t('word.indexes') }}</span> <span>{{ t('word.indexes') }}</span>
</button> </button>
<button <button
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
@ -49,11 +49,11 @@
@click="showForeignModal" @click="showForeignModal"
> >
<i class="mdi mdi-24px mdi-key-link mr-1" /> <i class="mdi mdi-24px mdi-key-link mr-1" />
<span>{{ $t('word.foreignKeys') }}</span> <span>{{ t('word.foreignKeys') }}</span>
</button> </button>
</div> </div>
<div class="workspace-query-info"> <div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')"> <div class="d-flex" :title="t('word.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b> <i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div> </div>
</div> </div>
@ -63,7 +63,7 @@
<div class="columns mb-4"> <div class="columns mb-4">
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.name') }}</label> <label class="form-label">{{ t('word.name') }}</label>
<input <input
ref="firstInput" ref="firstInput"
v-model="localOptions.name" v-model="localOptions.name"
@ -74,7 +74,7 @@
</div> </div>
<div v-if="workspace.customizations.comment" class="column"> <div v-if="workspace.customizations.comment" class="column">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.comment') }}</label> <label class="form-label">{{ t('word.comment') }}</label>
<input <input
v-model="localOptions.comment" v-model="localOptions.comment"
class="form-input" class="form-input"
@ -86,7 +86,7 @@
<div v-if="workspace.customizations.collations" class="column col-auto"> <div v-if="workspace.customizations.collations" class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
{{ $t('word.collation') }} {{ t('word.collation') }}
</label> </label>
<BaseSelect <BaseSelect
v-model="localOptions.collation" v-model="localOptions.collation"
@ -100,7 +100,7 @@
<div v-if="workspace.customizations.engines" class="column col-auto"> <div v-if="workspace.customizations.engines" class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
{{ $t('word.engine') }} {{ t('word.engine') }}
</label> </label>
<BaseSelect <BaseSelect
v-model="localOptions.engine" v-model="localOptions.engine"
@ -160,310 +160,302 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { Component, computed, onBeforeUnmount, onMounted, Prop, Ref, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
import Tables from '@/ipc-api/Tables'; import Tables from '@/ipc-api/Tables';
import BaseLoader from '@/components/BaseLoader'; import BaseLoader from '@/components/BaseLoader.vue';
import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields'; import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields.vue';
import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal'; import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal.vue';
import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal'; import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal.vue';
import WorkspaceTabNewTableEmptyState from '@/components/WorkspaceTabNewTableEmptyState'; import WorkspaceTabNewTableEmptyState from '@/components/WorkspaceTabNewTableEmptyState.vue';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { ConnectionParams, TableField, TableForeign, TableIndex, TableOptions } from 'common/interfaces/antares';
export default { const { t } = useI18n();
name: 'WorkspaceTabNewTable',
components: {
BaseLoader,
WorkspaceTabPropsTableFields,
WorkspaceTabPropsTableIndexesModal,
WorkspaceTabPropsTableForeignModal,
WorkspaceTabNewTableEmptyState,
BaseSelect
},
props: {
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const props = defineProps({
tabUid: String,
connection: Object as Prop<ConnectionParams>,
tab: Object,
isSelected: Boolean,
schema: String
});
const { const { addNotification } = useNotificationsStore();
getWorkspace, const workspacesStore = useWorkspacesStore();
getDatabaseVariable,
refreshStructure,
setUnsavedChanges,
newTab,
renameTabs,
removeTab,
changeBreadcrumbs
} = workspacesStore;
return { const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
addNotification,
getWorkspace,
getDatabaseVariable,
refreshStructure,
setUnsavedChanges,
newTab,
renameTabs,
removeTab,
changeBreadcrumbs,
selectedWorkspace
};
},
data () {
return {
isLoading: false,
isSaving: false,
isIndexesModal: false,
isForeignModal: false,
isOptionsChanging: false,
originalFields: [],
localFields: [],
originalKeyUsage: [],
localKeyUsage: [],
originalIndexes: [],
localIndexes: [],
tableOptions: {},
localOptions: {},
lastTable: null,
newFieldsCounter: 0
};
},
computed: {
workspace () {
return this.getWorkspace(this.connection.uid);
},
defaultCollation () {
if (this.workspace.customizations.collations)
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server')?.value || '';
return '';
},
defaultEngine () {
if (this.workspace.customizations.engines)
return this.workspace.engines?.find(engine => engine.isDefault)?.name || '';
return '';
},
schemaTables () {
const schemaTables = this.workspace.structure
.filter(schema => schema.name === this.schema)
.map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : []; const {
}, getWorkspace,
isChanged () { getDatabaseVariable,
return JSON.stringify(this.originalFields) !== JSON.stringify(this.localFields) || refreshStructure,
JSON.stringify(this.originalKeyUsage) !== JSON.stringify(this.localKeyUsage) || setUnsavedChanges,
JSON.stringify(this.originalIndexes) !== JSON.stringify(this.localIndexes) || newTab,
JSON.stringify(this.tableOptions) !== JSON.stringify(this.localOptions); removeTab,
}, changeBreadcrumbs
isValid () { } = workspacesStore;
return !!this.localFields.length && !!this.localOptions.name.trim().length;
const indexTable: Ref<Component & {$refs: { tableWrapper: HTMLDivElement }}> = ref(null);
const firstInput: Ref<HTMLInputElement> = ref(null);
const isLoading = ref(false);
const isSaving = ref(false);
const isIndexesModal = ref(false);
const isForeignModal = ref(false);
const originalFields: Ref<TableField[]> = ref([]);
const localFields: Ref<TableField[]> = ref([]);
const originalKeyUsage: Ref<TableForeign[]> = ref([]);
const localKeyUsage: Ref<TableForeign[]> = ref([]);
const originalIndexes: Ref<TableIndex[]> = ref([]);
const localIndexes: Ref<TableIndex[]> = ref([]);
const tableOptions: Ref<TableOptions> = ref(null);
const localOptions: Ref<TableOptions> = ref(null);
const newFieldsCounter = ref(0);
const workspace = computed(() => {
return getWorkspace(props.connection.uid);
});
const defaultCollation = computed(() => {
if (workspace.value.customizations.collations)
return getDatabaseVariable(selectedWorkspace.value, 'collation_server')?.value || '';
return '';
});
const defaultEngine = computed(() => {
if (workspace.value.customizations.engines)
return workspace.value.engines?.find(engine => engine.isDefault)?.name as string || '';
return '';
});
const schemaTables = computed(() => {
const schemaTables = workspace.value.structure
.filter(schema => schema.name === props.schema)
.map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
});
const isChanged = computed(() => {
return JSON.stringify(originalFields.value) !== JSON.stringify(localFields.value) ||
JSON.stringify(originalKeyUsage.value) !== JSON.stringify(localKeyUsage.value) ||
JSON.stringify(originalIndexes.value) !== JSON.stringify(localIndexes.value) ||
JSON.stringify(tableOptions.value) !== JSON.stringify(localOptions.value);
});
const isValid = computed(() => {
return !!localFields.value.length && !!localOptions.value.name.trim().length;
});
const saveChanges = async () => {
if (isSaving.value || !isValid.value) return;
isSaving.value = true;
const params = {
uid: props.connection.uid,
schema: props.schema,
fields: localFields.value,
foreigns: localKeyUsage.value,
indexes: localIndexes.value,
options: localOptions.value
};
try {
const { status, response } = await Tables.createTable(params);
if (status === 'success') {
await refreshStructure(props.connection.uid);
newTab({
uid: props.connection.uid,
schema: props.schema,
elementName: localOptions.value.name,
elementType: 'table',
type: 'table-props'
});
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
changeBreadcrumbs({ schema: props.schema, table: localOptions.value.name });
} }
}, else
watch: { addNotification({ status: 'error', message: response });
isSelected (val) { }
if (val) catch (err) {
this.changeBreadcrumbs({ schema: this.schema }); addNotification({ status: 'error', message: err.stack });
}, }
isChanged (val) {
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
created () {
this.tableOptions = {
name: '',
type: 'table',
engine: this.defaultEngine,
comment: '',
collation: this.defaultCollation
};
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions)); isSaving.value = false;
window.addEventListener('keydown', this.onKey); newFieldsCounter.value = 0;
}, };
mounted () {
if (this.isSelected)
this.changeBreadcrumbs({ schema: this.schema });
setTimeout(() => { const clearChanges = () => {
this.$refs.firstInput.focus(); localFields.value = JSON.parse(JSON.stringify(originalFields.value));
}, 100); localIndexes.value = JSON.parse(JSON.stringify(originalIndexes.value));
}, localKeyUsage.value = JSON.parse(JSON.stringify(originalKeyUsage.value));
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
async saveChanges () {
if (this.isSaving || !this.isValid) return;
this.isSaving = true;
const params = { tableOptions.value = {
uid: this.connection.uid, name: '',
schema: this.schema, type: 'table',
fields: this.localFields, engine: defaultEngine.value,
foreigns: this.localKeyUsage, comment: '',
indexes: this.localIndexes, collation: defaultCollation.value
options: this.localOptions };
};
try { localOptions.value = JSON.parse(JSON.stringify(tableOptions.value));
const { status, response } = await Tables.createTable(params); newFieldsCounter.value = 0;
};
if (status === 'success') { const addField = () => {
await this.refreshStructure(this.connection.uid); localFields.value.push({
_antares_id: uidGen(),
name: `${t('word.field', 1)}_${++newFieldsCounter.value}`,
key: '',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type: (workspace.value.dataTypes[0] as any).types[0].name,
schema: props.schema,
numPrecision: null,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
numLength: (workspace.value.dataTypes[0] as any).types[0].length,
datePrecision: null,
charLength: null,
nullable: false,
unsigned: false,
zerofill: false,
order: localFields.value.length + 1,
default: null,
charset: null,
collation: defaultCollation.value,
autoIncrement: false,
onUpdate: '',
comment: ''
});
this.newTab({ setTimeout(() => {
uid: this.connection.uid, const scrollable = indexTable.value.$refs.tableWrapper;
schema: this.schema, scrollable.scrollTop = scrollable.scrollHeight + 30;
elementName: this.localOptions.name, }, 20);
elementType: 'table', };
type: 'table-props'
});
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid }); const renameField = (payload: {index: string; new: string; old: string}) => {
this.changeBreadcrumbs({ schema: this.schema, table: this.localOptions.name }); localIndexes.value = localIndexes.value.map(index => {
} const fi = index.fields.findIndex(field => field === payload.old);
else if (fi !== -1)
this.addNotification({ status: 'error', message: response }); index.fields[fi] = payload.new;
} return index;
catch (err) { });
this.addNotification({ status: 'error', message: err.stack });
}
this.isSaving = false; localKeyUsage.value = localKeyUsage.value.map(key => {
this.newFieldsCounter = 0; if (key.field === payload.old)
}, key.field = payload.new;
clearChanges () { return key;
this.localFields = JSON.parse(JSON.stringify(this.originalFields)); });
this.localIndexes = JSON.parse(JSON.stringify(this.originalIndexes)); };
this.localKeyUsage = JSON.parse(JSON.stringify(this.originalKeyUsage));
this.tableOptions = { const duplicateField = (uid: string) => {
name: '', const fieldToClone = Object.assign({}, localFields.value.find(field => field._antares_id === uid));
type: 'table', fieldToClone._antares_id = uidGen();
engine: this.defaultEngine, fieldToClone.name = `${fieldToClone.name}_copy`;
comment: '', fieldToClone.order = localFields.value.length + 1;
collation: this.defaultCollation localFields.value = [...localFields.value, fieldToClone];
};
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions)); setTimeout(() => {
this.newFieldsCounter = 0; const scrollable = indexTable.value.$refs.tableWrapper;
}, scrollable.scrollTop = scrollable.scrollHeight + 30;
addField () { }, 20);
this.localFields.push({ };
_antares_id: uidGen(),
name: `${this.$tc('word.field', 1)}_${++this.newFieldsCounter}`,
key: '',
type: this.workspace.dataTypes[0].types[0].name,
schema: this.schema,
numPrecision: null,
numLength: this.workspace.dataTypes[0].types[0].length,
datePrecision: null,
charLength: null,
nullable: false,
unsigned: false,
zerofill: false,
order: this.localFields.length + 1,
default: null,
charset: null,
collation: null,
autoIncrement: false,
onUpdate: '',
comment: ''
});
setTimeout(() => { const removeField = (uid: string) => {
const scrollable = this.$refs.indexTable.$refs.tableWrapper; localFields.value = localFields.value.filter(field => field._antares_id !== uid);
scrollable.scrollTop = scrollable.scrollHeight + 30; };
}, 20);
},
renameField (payload) {
this.localIndexes = this.localIndexes.map(index => {
const fi = index.fields.findIndex(field => field === payload.old);
if (fi !== -1)
index.fields[fi] = payload.new;
return index;
});
this.localKeyUsage = this.localKeyUsage.map(key => { const addNewIndex = (payload: { index: string; field: string }) => {
if (key.field === payload.old) localIndexes.value = [...localIndexes.value, {
key.field = payload.new; _antares_id: uidGen(),
return key; name: payload.index === 'PRIMARY' ? 'PRIMARY' : payload.field,
}); fields: [payload.field],
}, type: payload.index,
duplicateField (uid) { comment: '',
const fieldToClone = Object.assign({}, this.localFields.find(field => field._antares_id === uid)); indexType: 'BTREE',
fieldToClone._antares_id = uidGen(); indexComment: '',
fieldToClone.name = `${fieldToClone.name}_copy`; cardinality: 0
fieldToClone.order = this.localFields.length + 1; }];
this.localFields = [...this.localFields, fieldToClone]; };
setTimeout(() => { const addToIndex = (payload: { index: string; field: string }) => {
const scrollable = this.$refs.indexTable.$refs.tableWrapper; localIndexes.value = localIndexes.value.map(index => {
scrollable.scrollTop = scrollable.scrollHeight + 30; if (index._antares_id === payload.index) index.fields.push(payload.field);
}, 20); return index;
}, });
removeField (uid) { };
this.localFields = this.localFields.filter(field => field._antares_id !== uid);
}, const showIntdexesModal = () => {
addNewIndex (payload) { isIndexesModal.value = true;
this.localIndexes = [...this.localIndexes, { };
_antares_id: uidGen(),
name: payload.index === 'PRIMARY' ? 'PRIMARY' : payload.field, const hideIndexesModal = () => {
fields: [payload.field], isIndexesModal.value = false;
type: payload.index, };
comment: '',
indexType: 'BTREE', const indexesUpdate = (indexes: TableIndex[]) => {
indexComment: '', localIndexes.value = indexes;
cardinality: 0 };
}];
}, const showForeignModal = () => {
addToIndex (payload) { isForeignModal.value = true;
this.localIndexes = this.localIndexes.map(index => { };
if (index._antares_id === payload.index) index.fields.push(payload.field);
return index; const hideForeignModal = () => {
}); isForeignModal.value = false;
}, };
optionsUpdate (options) {
this.localOptions = options; const foreignsUpdate = (foreigns: TableForeign[]) => {
}, localKeyUsage.value = foreigns;
showIntdexesModal () { };
this.isIndexesModal = true;
}, const onKey = (e: KeyboardEvent) => {
hideIndexesModal () { if (props.isSelected) {
this.isIndexesModal = false; e.stopPropagation();
}, if (e.ctrlKey && e.key === 's') { // CTRL + S
indexesUpdate (indexes) { if (isChanged.value)
this.localIndexes = indexes; saveChanges();
},
showForeignModal () {
this.isForeignModal = true;
},
hideForeignModal () {
this.isForeignModal = false;
},
foreignsUpdate (foreigns) {
this.localKeyUsage = foreigns;
},
onKey (e) {
if (this.isSelected) {
e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
if (this.isChanged)
this.saveChanges();
}
}
} }
} }
}; };
watch(() => props.isSelected, (val) => {
if (val) changeBreadcrumbs({ schema: props.schema });
});
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
});
tableOptions.value = {
name: '',
type: 'table',
engine: defaultEngine.value,
comment: '',
collation: defaultCollation.value
};
localOptions.value = JSON.parse(JSON.stringify(tableOptions.value));
window.addEventListener('keydown', onKey);
onMounted(() => {
if (props.isSelected)
changeBreadcrumbs({ schema: props.schema });
setTimeout(() => {
firstInput.value.focus();
}, 100);
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script> </script>

View File

@ -1,22 +1,22 @@
<template> <template>
<div class="column col-12 empty"> <div class="column col-12 empty">
<p class="h6 empty-subtitle"> <p class="h6 empty-subtitle">
{{ $t('message.thereAreNoTableFields') }} {{ t('message.thereAreNoTableFields') }}
</p> </p>
<div class="empty-action"> <div class="empty-action">
<button class="btn btn-gray d-flex" @click="$emit('new-field')"> <button class="btn btn-gray d-flex" @click="emit('new-field')">
<i class="mdi mdi-24px mdi-playlist-plus mr-2" /> <i class="mdi mdi-24px mdi-playlist-plus mr-2" />
{{ $t('message.addNewField') }} {{ t('message.addNewField') }}
</button> </button>
</div> </div>
</div> </div>
</template> </template>
<script> <script setup lang="ts">
export default { import { useI18n } from 'vue-i18n';
name: 'WorkspaceTabNewTableEmptyState',
emits: ['new-field'] const { t } = useI18n();
}; const emit = defineEmits(['new-field']);
</script> </script>
<style scoped> <style scoped>

View File

@ -11,20 +11,20 @@
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span> <span>{{ t('word.save') }}</span>
</button> </button>
<button <button
:disabled="!isChanged" :disabled="!isChanged"
class="btn btn-link btn-sm mr-0" class="btn btn-link btn-sm mr-0"
:title="$t('message.clearChanges')" :title="t('message.clearChanges')"
@click="clearChanges" @click="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" /> <i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ t('word.clear') }}</span>
</button> </button>
</div> </div>
<div class="workspace-query-info"> <div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')"> <div class="d-flex" :title="t('word.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b> <i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div> </div>
</div> </div>
@ -34,7 +34,7 @@
<div class="columns"> <div class="columns">
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.name') }}</label> <label class="form-label">{{ t('word.name') }}</label>
<input <input
ref="firstInput" ref="firstInput"
v-model="localTrigger.name" v-model="localTrigger.name"
@ -45,12 +45,12 @@
</div> </div>
<div v-if="customizations.definer" class="column col-auto"> <div v-if="customizations.definer" class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.definer') }}</label> <label class="form-label">{{ t('word.definer') }}</label>
<BaseSelect <BaseSelect
v-model="localTrigger.definer" v-model="localTrigger.definer"
:options="users" :options="users"
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`" :option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``" :option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select" class="form-select"
/> />
</div> </div>
@ -58,7 +58,7 @@
<fieldset class="column columns mb-0" :disabled="customizations.triggerOnlyRename"> <fieldset class="column columns mb-0" :disabled="customizations.triggerOnlyRename">
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.table') }}</label> <label class="form-label">{{ t('word.table') }}</label>
<BaseSelect <BaseSelect
v-model="localTrigger.table" v-model="localTrigger.table"
:options="schemaTables" :options="schemaTables"
@ -70,7 +70,7 @@
</div> </div>
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.event') }}</label> <label class="form-label">{{ t('word.event') }}</label>
<div class="input-group"> <div class="input-group">
<BaseSelect <BaseSelect
v-model="localTrigger.activation" v-model="localTrigger.activation"
@ -88,9 +88,9 @@
v-for="event in Object.keys(localEvents)" v-for="event in Object.keys(localEvents)"
:key="event" :key="event"
class="form-checkbox form-inline" class="form-checkbox form-inline"
@change.prevent="changeEvents(event)" @change.prevent="changeEvents(event as 'INSERT' | 'UPDATE' | 'DELETE')"
> >
<input :checked="localEvents[event]" type="checkbox"><i class="form-icon" /> {{ event }} <input :checked="localEvents[event as 'INSERT' | 'UPDATE' | 'DELETE']" type="checkbox"><i class="form-icon" /> {{ event }}
</label> </label>
</div> </div>
</div> </div>
@ -101,7 +101,7 @@
</div> </div>
<div class="workspace-query-results column col-12 mt-2 p-relative"> <div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" /> <BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ $t('message.triggerStatement') }}</label> <label class="form-label ml-2">{{ t('message.triggerStatement') }}</label>
<QueryEditor <QueryEditor
v-show="isSelected" v-show="isSelected"
ref="queryEditor" ref="queryEditor"
@ -114,219 +114,204 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { storeToRefs } from 'pinia'; import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds';
import { useI18n } from 'vue-i18n';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import QueryEditor from '@/components/QueryEditor'; import QueryEditor from '@/components/QueryEditor.vue';
import BaseLoader from '@/components/BaseLoader'; import BaseLoader from '@/components/BaseLoader.vue';
import Triggers from '@/ipc-api/Triggers'; import Triggers from '@/ipc-api/Triggers';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
export default { const { t } = useI18n();
name: 'WorkspaceTabNewTrigger',
components: {
BaseLoader,
QueryEditor,
BaseSelect
},
props: {
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const props = defineProps({
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
});
const { const { addNotification } = useNotificationsStore();
getWorkspace, const workspacesStore = useWorkspacesStore();
refreshStructure,
changeBreadcrumbs,
setUnsavedChanges,
newTab,
removeTab,
renameTabs
} = workspacesStore;
return { const {
addNotification, getWorkspace,
selectedWorkspace, refreshStructure,
getWorkspace, changeBreadcrumbs,
refreshStructure, setUnsavedChanges,
changeBreadcrumbs, newTab,
setUnsavedChanges, removeTab
newTab, } = workspacesStore;
removeTab,
renameTabs
};
},
data () {
return {
isLoading: false,
isSaving: false,
originalTrigger: {},
localTrigger: {},
lastTrigger: null,
sqlProxy: '',
editorHeight: 300,
localEvents: { INSERT: false, UPDATE: false, DELETE: false }
};
},
computed: {
workspace () {
return this.getWorkspace(this.connection.uid);
},
customizations () {
return this.workspace.customizations;
},
isChanged () {
return JSON.stringify(this.originalTrigger) !== JSON.stringify(this.localTrigger);
},
isDefinerInUsers () {
return this.originalTrigger ? this.workspace.users.some(user => this.originalTrigger.definer === `\`${user.name}\`@\`${user.host}\``) : true;
},
schemaTables () {
const schemaTables = this.workspace.structure
.filter(schema => schema.name === this.schema)
.map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : []; const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
}, const firstInput: Ref<HTMLInputElement> = ref(null);
users () { const isLoading = ref(false);
const users = [{ value: '' }, ...this.workspace.users]; const isSaving = ref(false);
if (!this.isDefinerInUsers) { const originalTrigger = ref(null);
const [name, host] = this.originalTrigger.definer.replaceAll('`', '').split('@'); const localTrigger = ref(null);
users.unshift({ name, host }); const editorHeight = ref(300);
} const localEvents = ref({ INSERT: false, UPDATE: false, DELETE: false });
return users; const workspace = computed(() => {
} return getWorkspace(props.connection.uid);
}, });
watch: {
isSelected (val) {
if (val)
this.changeBreadcrumbs({ schema: this.schema });
},
isChanged (val) {
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
created () {
this.originalTrigger = {
sql: this.customizations.triggerSql,
definer: '',
table: this.schemaTables.length ? this.schemaTables[0].name : null,
activation: 'BEFORE',
event: this.customizations.triggerMultipleEvents ? ['INSERT'] : 'INSERT',
name: ''
};
this.localTrigger = JSON.parse(JSON.stringify(this.originalTrigger)); const customizations = computed(() => {
return workspace.value.customizations;
});
setTimeout(() => { const isChanged = computed(() => {
this.resizeQueryEditor(); return JSON.stringify(originalTrigger.value) !== JSON.stringify(localTrigger.value);
}, 50); });
window.addEventListener('keydown', this.onKey); const isDefinerInUsers = computed(() => {
}, return originalTrigger.value ? workspace.value.users.some(user => originalTrigger.value.definer === `\`${user.name}\`@\`${user.host}\``) : true;
mounted () { });
if (this.isSelected)
this.changeBreadcrumbs({ schema: this.schema });
setTimeout(() => { const schemaTables = computed(() => {
this.$refs.firstInput.focus(); const schemaTables = workspace.value.structure
}, 100); .filter(schema => schema.name === props.schema)
.map(schema => schema.tables);
window.addEventListener('resize', this.resizeQueryEditor); return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
}, });
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
changeEvents (event) {
if (this.customizations.triggerMultipleEvents) {
this.localEvents[event] = !this.localEvents[event];
this.localTrigger.event = [];
for (const key in this.localEvents) {
if (this.localEvents[key])
this.localTrigger.event.push(key);
}
}
},
async saveChanges () {
if (this.isSaving) return;
this.isSaving = true;
const params = {
uid: this.connection.uid,
schema: this.schema,
...this.localTrigger
};
try { const users = computed(() => {
const { status, response } = await Triggers.createTrigger(params); const users = [{ value: '' }, ...workspace.value.users];
if (!isDefinerInUsers.value) {
const [name, host] = originalTrigger.value.definer.replaceAll('`', '').split('@');
users.unshift({ name, host });
}
if (status === 'success') { return users;
await this.refreshStructure(this.connection.uid); });
this.newTab({ const changeEvents = (event: 'INSERT' | 'UPDATE' | 'DELETE') => {
uid: this.connection.uid, if (customizations.value.triggerMultipleEvents) {
schema: this.schema, localEvents.value[event] = !localEvents.value[event];
elementName: this.localTrigger.name, localTrigger.value.event = [];
elementType: 'trigger', for (const key in localEvents.value) {
type: 'trigger-props' if (localEvents.value[key as 'INSERT' | 'UPDATE' | 'DELETE'])
}); localTrigger.value.event.push(key);
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
this.changeBreadcrumbs({ schema: this.schema, trigger: this.localTrigger.name });
}
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isSaving = false;
},
clearChanges () {
this.localTrigger = JSON.parse(JSON.stringify(this.originalTrigger));
this.$refs.queryEditor.editor.session.setValue(this.localTrigger.sql);
Object.keys(this.localEvents).forEach(event => {
this.localEvents[event] = false;
});
if (this.customizations.triggerMultipleEvents) {
this.originalTrigger.event.forEach(e => {
this.localEvents[e] = true;
});
}
},
resizeQueryEditor () {
if (this.$refs.queryEditor) {
const footer = document.getElementById('footer');
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
this.editorHeight = size;
this.$refs.queryEditor.editor.resize();
}
},
onKey (e) {
if (this.isSelected) {
e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
if (this.isChanged)
this.saveChanges();
}
}
} }
} }
}; };
const saveChanges = async () => {
if (isSaving.value) return;
isSaving.value = true;
const params = {
uid: props.connection.uid,
schema: props.schema,
...localTrigger.value
};
try {
const { status, response } = await Triggers.createTrigger(params);
if (status === 'success') {
await refreshStructure(props.connection.uid);
newTab({
uid: props.connection.uid,
schema: props.schema,
elementName: localTrigger.value.name,
elementType: 'trigger',
type: 'trigger-props'
});
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
changeBreadcrumbs({ schema: props.schema, trigger: localTrigger.value.name });
}
else
addNotification({ status: 'error', message: response });
}
catch (err) {
addNotification({ status: 'error', message: err.stack });
}
isSaving.value = false;
};
const clearChanges = () => {
localTrigger.value = JSON.parse(JSON.stringify(originalTrigger.value));
queryEditor.value.editor.session.setValue(localTrigger.value.sql);
Object.keys(localEvents.value).forEach((event: 'INSERT' | 'UPDATE' | 'DELETE') => {
localEvents.value[event] = false;
});
if (customizations.value.triggerMultipleEvents) {
originalTrigger.value.event.forEach((e: 'INSERT' | 'UPDATE' | 'DELETE') => {
localEvents.value[e] = true;
});
}
};
const resizeQueryEditor = () => {
if (queryEditor.value) {
const footer = document.getElementById('footer');
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
editorHeight.value = size;
queryEditor.value.editor.resize();
}
};
const onKey = (e: KeyboardEvent) => {
if (props.isSelected) {
e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
if (isChanged.value)
saveChanges();
}
}
};
watch(() => props.isSelected, (val) => {
if (val) changeBreadcrumbs({ schema: props.schema });
});
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
});
originalTrigger.value = {
sql: customizations.value.triggerSql,
definer: '',
table: schemaTables.value.length ? schemaTables.value[0].name : null,
activation: 'BEFORE',
event: customizations.value.triggerMultipleEvents ? ['INSERT'] : 'INSERT',
name: ''
};
localTrigger.value = JSON.parse(JSON.stringify(originalTrigger.value));
setTimeout(() => {
resizeQueryEditor();
}, 50);
window.addEventListener('keydown', onKey);
onMounted(() => {
if (props.isSelected)
changeBreadcrumbs({ schema: props.schema });
setTimeout(() => {
firstInput.value.focus();
}, 100);
window.addEventListener('resize', resizeQueryEditor);
});
onUnmounted(() => {
window.removeEventListener('resize', resizeQueryEditor);
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script> </script>

View File

@ -11,16 +11,16 @@
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span> <span>{{ t('word.save') }}</span>
</button> </button>
<button <button
:disabled="!isChanged" :disabled="!isChanged"
class="btn btn-link btn-sm mr-0" class="btn btn-link btn-sm mr-0"
:title="$t('message.clearChanges')" :title="t('message.clearChanges')"
@click="clearChanges" @click="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" /> <i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ t('word.clear') }}</span>
</button> </button>
</div> </div>
</div> </div>
@ -30,7 +30,7 @@
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
{{ $t('word.name') }} {{ t('word.name') }}
</label> </label>
<input <input
ref="firstInput" ref="firstInput"
@ -43,7 +43,7 @@
<div v-if="customizations.triggerFunctionlanguages" class="column col-auto"> <div v-if="customizations.triggerFunctionlanguages" class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
{{ $t('word.language') }} {{ t('word.language') }}
</label> </label>
<BaseSelect <BaseSelect
v-model="localFunction.language" v-model="localFunction.language"
@ -55,20 +55,20 @@
<div v-if="customizations.definer" class="column col-auto"> <div v-if="customizations.definer" class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
{{ $t('word.definer') }} {{ t('word.definer') }}
</label> </label>
<BaseSelect <BaseSelect
v-model="localFunction.definer" v-model="localFunction.definer"
:options="workspace.users" :options="workspace.users"
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`" :option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``" :option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select" class="form-select"
/> />
</div> </div>
</div> </div>
<div v-if="customizations.comment" class="form-group"> <div v-if="customizations.comment" class="form-group">
<label class="form-label"> <label class="form-label">
{{ $t('word.comment') }} {{ t('word.comment') }}
</label> </label>
<input <input
v-model="localFunction.comment" v-model="localFunction.comment"
@ -80,7 +80,7 @@
</div> </div>
<div class="workspace-query-results column col-12 mt-2 p-relative"> <div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" /> <BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ $t('message.functionBody') }}</label> <label class="form-label ml-2">{{ t('message.functionBody') }}</label>
<QueryEditor <QueryEditor
v-show="isSelected" v-show="isSelected"
ref="queryEditor" ref="queryEditor"
@ -93,190 +93,157 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { storeToRefs } from 'pinia'; import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds';
import { useI18n } from 'vue-i18n';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader'; import BaseLoader from '@/components/BaseLoader.vue';
import QueryEditor from '@/components/QueryEditor'; import QueryEditor from '@/components/QueryEditor.vue';
import Functions from '@/ipc-api/Functions'; import Functions from '@/ipc-api/Functions';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
export default { const { t } = useI18n();
name: 'WorkspaceTabNewTriggerFunction',
components: {
BaseLoader,
QueryEditor,
BaseSelect
},
props: {
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const props = defineProps({
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
});
const { const { addNotification } = useNotificationsStore();
getWorkspace, const workspacesStore = useWorkspacesStore();
refreshStructure,
changeBreadcrumbs,
setUnsavedChanges,
newTab,
removeTab,
renameTabs
} = workspacesStore;
return { const {
addNotification, getWorkspace,
selectedWorkspace, refreshStructure,
getWorkspace, changeBreadcrumbs,
refreshStructure, setUnsavedChanges,
changeBreadcrumbs, newTab,
setUnsavedChanges, removeTab
newTab, } = workspacesStore;
removeTab,
renameTabs
};
},
data () {
return {
isLoading: false,
isSaving: false,
isParamsModal: false,
isAskingParameters: false,
originalFunction: {},
localFunction: {},
lastFunction: null,
sqlProxy: '',
editorHeight: 300
};
},
computed: {
workspace () {
return this.getWorkspace(this.connection.uid);
},
customizations () {
return this.workspace.customizations;
},
isChanged () {
return JSON.stringify(this.originalFunction) !== JSON.stringify(this.localFunction);
},
isDefinerInUsers () {
return this.originalFunction
? this.workspace.users.some(user => this.originalFunction.definer === `\`${user.name}\`@\`${user.host}\``)
: true;
},
schemaTables () {
const schemaTables = this.workspace.structure
.filter(schema => schema.name === this.schema)
.map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : []; const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
const firstInput: Ref<HTMLInputElement> = ref(null);
const isLoading = ref(false);
const isSaving = ref(false);
const originalFunction = ref(null);
const localFunction = ref(null);
const editorHeight = ref(300);
const workspace = computed(() => {
return getWorkspace(props.connection.uid);
});
const customizations = computed(() => {
return workspace.value.customizations;
});
const isChanged = computed(() => {
return JSON.stringify(originalFunction.value) !== JSON.stringify(localFunction.value);
});
const saveChanges = async () => {
if (isSaving.value) return;
isSaving.value = true;
const params = {
uid: props.connection.uid,
schema: props.schema,
...localFunction.value
};
try {
const { status, response } = await Functions.createTriggerFunction(params);
if (status === 'success') {
await refreshStructure(props.connection.uid);
newTab({
uid: props.connection.uid,
schema: props.schema,
elementName: localFunction.value.name,
elementType: 'triggerFunction',
type: 'trigger-function-props'
});
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
changeBreadcrumbs({ schema: props.schema, triggerFunction: localFunction.value.name });
} }
}, else
watch: { addNotification({ status: 'error', message: response });
isSelected (val) { }
if (val) catch (err) {
this.changeBreadcrumbs({ schema: this.schema }); addNotification({ status: 'error', message: err.stack });
}, }
isChanged (val) {
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
created () {
this.originalFunction = {
sql: this.customizations.triggerFunctionSql,
language: this.customizations.triggerFunctionlanguages.length ? this.customizations.triggerFunctionlanguages[0] : null,
name: ''
};
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction)); isSaving.value = false;
};
setTimeout(() => { const clearChanges = () => {
this.resizeQueryEditor(); localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
}, 50); queryEditor.value.editor.session.setValue(localFunction.value.sql);
};
window.addEventListener('keydown', this.onKey); const resizeQueryEditor = () => {
}, if (queryEditor.value) {
mounted () { const footer = document.getElementById('footer');
if (this.isSelected) const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
this.changeBreadcrumbs({ schema: this.schema }); editorHeight.value = size;
queryEditor.value.editor.resize();
}
};
setTimeout(() => { const onKey = (e: KeyboardEvent) => {
this.$refs.firstInput.focus(); if (props.isSelected) {
}, 100); e.stopPropagation();
if (e.ctrlKey && e.key === 's') { // CTRL + S
window.addEventListener('resize', this.resizeQueryEditor); if (isChanged.value)
}, saveChanges();
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
async saveChanges () {
if (this.isSaving) return;
this.isSaving = true;
const params = {
uid: this.connection.uid,
schema: this.schema,
...this.localFunction
};
try {
const { status, response } = await Functions.createTriggerFunction(params);
if (status === 'success') {
await this.refreshStructure(this.connection.uid);
this.newTab({
uid: this.connection.uid,
schema: this.schema,
elementName: this.localFunction.name,
elementType: 'triggerFunction',
type: 'trigger-function-props'
});
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
this.changeBreadcrumbs({ schema: this.schema, triggerFunction: this.localFunction.name });
}
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isSaving = false;
},
clearChanges () {
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction));
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
},
resizeQueryEditor () {
if (this.$refs.queryEditor) {
const footer = document.getElementById('footer');
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
this.editorHeight = size;
this.$refs.queryEditor.editor.resize();
}
},
onKey (e) {
if (this.isSelected) {
e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
if (this.isChanged)
this.saveChanges();
}
}
} }
} }
}; };
originalFunction.value = {
sql: customizations.value.triggerFunctionSql,
language: customizations.value.triggerFunctionlanguages.length ? customizations.value.triggerFunctionlanguages[0] : null,
name: ''
};
localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
setTimeout(() => {
resizeQueryEditor();
}, 50);
window.addEventListener('keydown', onKey);
onMounted(() => {
if (props.isSelected)
changeBreadcrumbs({ schema: props.schema });
setTimeout(() => {
firstInput.value.focus();
}, 100);
window.addEventListener('resize', resizeQueryEditor);
});
onUnmounted(() => {
window.removeEventListener('resize', resizeQueryEditor);
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
watch(() => props.isSelected, (val) => {
if (val) changeBreadcrumbs({ schema: props.schema });
});
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
});
</script> </script>

View File

@ -11,20 +11,20 @@
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span> <span>{{ t('word.save') }}</span>
</button> </button>
<button <button
:disabled="!isChanged" :disabled="!isChanged"
class="btn btn-link btn-sm mr-0" class="btn btn-link btn-sm mr-0"
:title="$t('message.clearChanges')" :title="t('message.clearChanges')"
@click="clearChanges" @click="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" /> <i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ t('word.clear') }}</span>
</button> </button>
</div> </div>
<div class="workspace-query-info"> <div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')"> <div class="d-flex" :title="t('word.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b> <i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div> </div>
</div> </div>
@ -34,7 +34,7 @@
<div class="columns"> <div class="columns">
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.name') }}</label> <label class="form-label">{{ t('word.name') }}</label>
<input <input
ref="firstInput" ref="firstInput"
v-model="localView.name" v-model="localView.name"
@ -45,19 +45,19 @@
</div> </div>
<div class="column col-auto"> <div class="column col-auto">
<div v-if="workspace.customizations.definer" class="form-group"> <div v-if="workspace.customizations.definer" class="form-group">
<label class="form-label">{{ $t('word.definer') }}</label> <label class="form-label">{{ t('word.definer') }}</label>
<BaseSelect <BaseSelect
v-model="localView.definer" v-model="localView.definer"
:options="users" :options="users"
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`" :option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``" :option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select" class="form-select"
/> />
</div> </div>
</div> </div>
<div class="column col-auto mr-2"> <div class="column col-auto mr-2">
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group"> <div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
<label class="form-label">{{ $t('message.sqlSecurity') }}</label> <label class="form-label">{{ t('message.sqlSecurity') }}</label>
<BaseSelect <BaseSelect
v-model="localView.security" v-model="localView.security"
:options="['DEFINER', 'INVOKER']" :options="['DEFINER', 'INVOKER']"
@ -67,7 +67,7 @@
</div> </div>
<div class="column col-auto mr-2"> <div class="column col-auto mr-2">
<div v-if="workspace.customizations.viewAlgorithm" class="form-group"> <div v-if="workspace.customizations.viewAlgorithm" class="form-group">
<label class="form-label">{{ $t('word.algorithm') }}</label> <label class="form-label">{{ t('word.algorithm') }}</label>
<BaseSelect <BaseSelect
v-model="localView.algorithm" v-model="localView.algorithm"
:options="['UNDEFINED', 'MERGE', 'TEMPTABLE']" :options="['UNDEFINED', 'MERGE', 'TEMPTABLE']"
@ -77,10 +77,10 @@
</div> </div>
<div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2"> <div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('message.updateOption') }}</label> <label class="form-label">{{ t('message.updateOption') }}</label>
<BaseSelect <BaseSelect
v-model="localView.updateOption" v-model="localView.updateOption"
:option-track-by="(user) => user.value" :option-track-by="(user: any) => user.value"
:options="[{label: 'None', value: ''}, {label: 'CASCADED', value: 'CASCADED'}, {label: 'LOCAL', value: 'LOCAL'}]" :options="[{label: 'None', value: ''}, {label: 'CASCADED', value: 'CASCADED'}, {label: 'LOCAL', value: 'LOCAL'}]"
class="form-select" class="form-select"
/> />
@ -90,7 +90,7 @@
</div> </div>
<div class="workspace-query-results column col-12 mt-2 p-relative"> <div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" /> <BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ $t('message.selectStatement') }}</label> <label class="form-label ml-2">{{ t('message.selectStatement') }}</label>
<QueryEditor <QueryEditor
v-show="isSelected" v-show="isSelected"
ref="queryEditor" ref="queryEditor"
@ -103,194 +103,178 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { storeToRefs } from 'pinia'; import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds';
import { useI18n } from 'vue-i18n';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader'; import BaseLoader from '@/components/BaseLoader.vue';
import QueryEditor from '@/components/QueryEditor'; import QueryEditor from '@/components/QueryEditor.vue';
import Views from '@/ipc-api/Views'; import Views from '@/ipc-api/Views';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
export default { const { t } = useI18n();
name: 'WorkspaceTabNewView',
components: {
BaseLoader,
QueryEditor,
BaseSelect
},
props: {
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const props = defineProps({
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
});
const { const { addNotification } = useNotificationsStore();
getWorkspace, const workspacesStore = useWorkspacesStore();
refreshStructure,
setUnsavedChanges,
changeBreadcrumbs,
newTab,
removeTab,
renameTabs
} = workspacesStore;
return { const {
addNotification, getWorkspace,
selectedWorkspace, refreshStructure,
getWorkspace, setUnsavedChanges,
refreshStructure, changeBreadcrumbs,
setUnsavedChanges, newTab,
changeBreadcrumbs, removeTab
newTab, } = workspacesStore;
removeTab,
renameTabs
};
},
data () {
return {
isLoading: false,
isSaving: false,
originalView: {},
localView: {},
lastView: null,
sqlProxy: '',
editorHeight: 300
};
},
computed: {
workspace () {
return this.getWorkspace(this.connection.uid);
},
isChanged () {
return JSON.stringify(this.originalView) !== JSON.stringify(this.localView);
},
isDefinerInUsers () {
return this.originalView ? this.workspace.users.some(user => this.originalView.definer === `\`${user.name}\`@\`${user.host}\``) : true;
},
users () {
const users = [{ value: '' }, ...this.workspace.users];
if (!this.isDefinerInUsers) {
const [name, host] = this.originalView.definer.replaceAll('`', '').split('@');
users.unshift({ name, host });
}
return users; const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
const firstInput: Ref<HTMLInputElement> = ref(null);
const isLoading = ref(false);
const isSaving = ref(false);
const originalView = ref(null);
const localView = ref(null);
const editorHeight = ref(300);
const workspace = computed(() => {
return getWorkspace(props.connection.uid);
});
const isChanged = computed(() => {
return JSON.stringify(originalView.value) !== JSON.stringify(localView.value);
});
const isDefinerInUsers = computed(() => {
return originalView.value ? workspace.value.users.some(user => originalView.value.definer === `\`${user.name}\`@\`${user.host}\``) : true;
});
const users = computed(() => {
const users = [{ value: '' }, ...workspace.value.users];
if (!isDefinerInUsers.value) {
const [name, host] = originalView.value.definer.replaceAll('`', '').split('@');
users.unshift({ name, host });
}
return users;
});
const saveChanges = async () => {
if (isSaving.value) return;
isSaving.value = true;
const params = {
uid: props.connection.uid,
schema: props.schema,
...localView.value
};
try {
const { status, response } = await Views.createView(params);
if (status === 'success') {
await refreshStructure(props.connection.uid);
newTab({
uid: props.connection.uid,
schema: props.schema,
elementName: localView.value.name,
elementType: 'view',
type: 'view-props'
});
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
changeBreadcrumbs({ schema: props.schema, view: localView.value.name });
} }
}, else
watch: { addNotification({ status: 'error', message: response });
isSelected (val) { }
if (val) { catch (err) {
this.changeBreadcrumbs({ schema: this.schema, view: this.view }); addNotification({ status: 'error', message: err.stack });
}
setTimeout(() => { isSaving.value = false;
this.resizeQueryEditor(); };
}, 50);
}
},
isChanged (val) {
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
async created () {
this.originalView = {
algorithm: 'UNDEFINED',
definer: '',
security: 'DEFINER',
updateOption: '',
sql: '',
name: ''
};
this.localView = JSON.parse(JSON.stringify(this.originalView)); const clearChanges = () => {
localView.value = JSON.parse(JSON.stringify(originalView.value));
queryEditor.value.editor.session.setValue(localView.value.sql);
};
setTimeout(() => { const resizeQueryEditor = () => {
this.resizeQueryEditor(); if (queryEditor.value) {
}, 50); const footer = document.getElementById('footer');
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
editorHeight.value = size;
queryEditor.value.editor.resize();
}
};
window.addEventListener('keydown', this.onKey); const onKey = (e: KeyboardEvent) => {
}, if (props.isSelected) {
mounted () { e.stopPropagation();
if (this.isSelected) if (e.ctrlKey && e.key === 's') { // CTRL + S
this.changeBreadcrumbs({ schema: this.schema }); if (isChanged.value)
saveChanges();
setTimeout(() => {
this.$refs.firstInput.focus();
}, 100);
window.addEventListener('resize', this.resizeQueryEditor);
},
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
async saveChanges () {
if (this.isSaving) return;
this.isSaving = true;
const params = {
uid: this.connection.uid,
schema: this.schema,
...this.localView
};
try {
const { status, response } = await Views.createView(params);
if (status === 'success') {
await this.refreshStructure(this.connection.uid);
this.newTab({
uid: this.connection.uid,
schema: this.schema,
elementName: this.localView.name,
elementType: 'view',
type: 'view-props'
});
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
this.changeBreadcrumbs({ schema: this.schema, view: this.localView.name });
}
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isSaving = false;
},
clearChanges () {
this.localView = JSON.parse(JSON.stringify(this.originalView));
this.$refs.queryEditor.editor.session.setValue(this.localView.sql);
},
resizeQueryEditor () {
if (this.$refs.queryEditor) {
const footer = document.getElementById('footer');
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
this.editorHeight = size;
this.$refs.queryEditor.editor.resize();
}
},
onKey (e) {
if (this.isSelected) {
e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
if (this.isChanged)
this.saveChanges();
}
}
} }
} }
}; };
watch(() => props.isSelected, (val) => {
if (val) {
changeBreadcrumbs({ schema: props.schema, view: localView.value.name });
setTimeout(() => {
resizeQueryEditor();
}, 50);
}
});
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
});
originalView.value = {
algorithm: 'UNDEFINED',
definer: '',
security: 'DEFINER',
updateOption: '',
sql: '',
name: ''
};
localView.value = JSON.parse(JSON.stringify(originalView.value));
setTimeout(() => {
resizeQueryEditor();
}, 50);
window.addEventListener('keydown', onKey);
onMounted(() => {
if (props.isSelected)
changeBreadcrumbs({ schema: props.schema });
setTimeout(() => {
firstInput.value.focus();
}, 100);
window.addEventListener('resize', resizeQueryEditor);
});
onUnmounted(() => {
window.removeEventListener('resize', resizeQueryEditor);
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script> </script>

View File

@ -249,6 +249,11 @@ export default {
workspace () { workspace () {
return this.getWorkspace(this.connection.uid); return this.getWorkspace(this.connection.uid);
}, },
defaultCollation () {
if (this.workspace.customizations.collations)
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server')?.value || '';
return '';
},
defaultEngine () { defaultEngine () {
const engine = this.getDatabaseVariable(this.connection.uid, 'default_storage_engine'); const engine = this.getDatabaseVariable(this.connection.uid, 'default_storage_engine');
return engine ? engine.value : ''; return engine ? engine.value : '';
@ -440,6 +445,7 @@ export default {
const changes = []; const changes = [];
this.localFields.forEach((field, i) => { this.localFields.forEach((field, i) => {
const originalField = this.originalFields.find(oField => oField._antares_id === field._antares_id); const originalField = this.originalFields.find(oField => oField._antares_id === field._antares_id);
if (!originalField) return;
const after = i > 0 ? this.localFields[i - 1].name : false; const after = i > 0 ? this.localFields[i - 1].name : false;
const orgName = originalField.name; const orgName = originalField.name;
@ -586,7 +592,7 @@ export default {
order: this.localFields.length + 1, order: this.localFields.length + 1,
default: null, default: null,
charset: null, charset: null,
collation: null, collation: this.defaultCollation,
autoIncrement: false, autoIncrement: false,
onUpdate: '', onUpdate: '',
comment: '' comment: ''

View File

@ -124,7 +124,7 @@
</div> </div>
</template> </template>
<script> <script>// TODO: expose tableWrapper
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';

View File

@ -72,7 +72,7 @@ export interface Workspace {
structure: WorkspaceStructure[]; structure: WorkspaceStructure[];
variables: { name: string; value: string }[]; variables: { name: string; value: string }[];
collations: CollationInfos[]; collations: CollationInfos[];
users: { host: string; name: string; password: string }[]; users: { host: string; name: string; password?: string }[];
breadcrumbs: Breadcrumb; breadcrumbs: Breadcrumb;
loadingElements: { name: string; schema: string; type: string }[]; loadingElements: { name: string; schema: string; type: string }[];
loadedSchemas: Set<string>; loadedSchemas: Set<string>;