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:
@ -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
|
||||||
|
@ -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'
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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:
|
||||||
|
@ -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(',')})`;
|
||||||
}
|
}
|
||||||
|
@ -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')"
|
||||||
>
|
>
|
||||||
|
@ -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>
|
||||||
|
@ -72,8 +72,8 @@
|
|||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localRoutine.definer"
|
v-model="localRoutine.definer"
|
||||||
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]"
|
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]"
|
||||||
:option-label="(user) => user.value === '' ? user.name : `${user.name}@${user.host}`"
|
:option-label="(user: any) => user.value === '' ? user.name : `${user.name}@${user.host}`"
|
||||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -129,7 +129,6 @@
|
|||||||
<label class="form-label ml-2">{{ $t('message.routineBody') }}</label>
|
<label class="form-label ml-2">{{ $t('message.routineBody') }}</label>
|
||||||
<QueryEditor
|
<QueryEditor
|
||||||
v-show="isSelected"
|
v-show="isSelected"
|
||||||
:key="`new-${_uid}`"
|
|
||||||
ref="queryEditor"
|
ref="queryEditor"
|
||||||
v-model="localRoutine.sql"
|
v-model="localRoutine.sql"
|
||||||
:workspace="workspace"
|
:workspace="workspace"
|
||||||
@ -148,209 +147,174 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
||||||
|
import { Ace } from 'ace-builds';
|
||||||
|
import Routines from '@/ipc-api/Routines';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import QueryEditor from '@/components/QueryEditor';
|
import QueryEditor from '@/components/QueryEditor.vue';
|
||||||
import BaseLoader from '@/components/BaseLoader';
|
import BaseLoader from '@/components/BaseLoader.vue';
|
||||||
import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal';
|
import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal.vue';
|
||||||
import Routines from '@/ipc-api/Routines';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
import { FunctionParam } from 'common/interfaces/antares';
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'WorkspaceTabNewRoutine',
|
tabUid: String,
|
||||||
components: {
|
connection: Object,
|
||||||
QueryEditor,
|
tab: Object,
|
||||||
BaseLoader,
|
isSelected: Boolean,
|
||||||
WorkspaceTabPropsRoutineParamsModal,
|
schema: String
|
||||||
BaseSelect
|
});
|
||||||
},
|
|
||||||
props: {
|
|
||||||
tabUid: String,
|
|
||||||
connection: Object,
|
|
||||||
tab: Object,
|
|
||||||
isSelected: Boolean,
|
|
||||||
schema: String
|
|
||||||
},
|
|
||||||
setup () {
|
|
||||||
const { addNotification } = useNotificationsStore();
|
|
||||||
const workspacesStore = useWorkspacesStore();
|
|
||||||
|
|
||||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const { addNotification } = useNotificationsStore();
|
||||||
|
const workspacesStore = useWorkspacesStore();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getWorkspace,
|
getWorkspace,
|
||||||
refreshStructure,
|
refreshStructure,
|
||||||
changeBreadcrumbs,
|
changeBreadcrumbs,
|
||||||
setUnsavedChanges,
|
setUnsavedChanges,
|
||||||
newTab,
|
newTab,
|
||||||
removeTab,
|
removeTab
|
||||||
renameTabs
|
} = workspacesStore;
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
|
||||||
addNotification,
|
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||||
selectedWorkspace,
|
const isLoading = ref(false);
|
||||||
getWorkspace,
|
const isSaving = ref(false);
|
||||||
refreshStructure,
|
const isParamsModal = ref(false);
|
||||||
changeBreadcrumbs,
|
const originalRoutine = ref(null);
|
||||||
setUnsavedChanges,
|
const localRoutine = ref(null);
|
||||||
newTab,
|
const editorHeight = ref(300);
|
||||||
removeTab,
|
|
||||||
renameTabs
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isLoading: false,
|
|
||||||
isSaving: false,
|
|
||||||
isParamsModal: false,
|
|
||||||
originalRoutine: {},
|
|
||||||
localRoutine: {},
|
|
||||||
lastRoutine: null,
|
|
||||||
sqlProxy: '',
|
|
||||||
editorHeight: 300
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
workspace () {
|
|
||||||
return this.getWorkspace(this.connection.uid);
|
|
||||||
},
|
|
||||||
customizations () {
|
|
||||||
return this.workspace.customizations;
|
|
||||||
},
|
|
||||||
isChanged () {
|
|
||||||
return JSON.stringify(this.originalRoutine) !== JSON.stringify(this.localRoutine);
|
|
||||||
},
|
|
||||||
isDefinerInUsers () {
|
|
||||||
return this.originalRoutine ? this.workspace.users.some(user => this.originalRoutine.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
|
||||||
},
|
|
||||||
schemaTables () {
|
|
||||||
const schemaTables = this.workspace.structure
|
|
||||||
.filter(schema => schema.name === this.schema)
|
|
||||||
.map(schema => schema.tables);
|
|
||||||
|
|
||||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
const workspace = computed(() => {
|
||||||
|
return getWorkspace(props.connection.uid);
|
||||||
|
});
|
||||||
|
|
||||||
|
const customizations = computed(() => {
|
||||||
|
return workspace.value.customizations;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isChanged = computed(() => {
|
||||||
|
return JSON.stringify(originalRoutine.value) !== JSON.stringify(localRoutine.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const saveChanges = async () => {
|
||||||
|
if (isSaving.value) return;
|
||||||
|
isSaving.value = true;
|
||||||
|
const params = {
|
||||||
|
uid: props.connection.uid,
|
||||||
|
schema: props.schema,
|
||||||
|
...localRoutine.value
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { status, response } = await Routines.createRoutine(params);
|
||||||
|
|
||||||
|
if (status === 'success') {
|
||||||
|
await refreshStructure(props.connection.uid);
|
||||||
|
|
||||||
|
newTab({
|
||||||
|
uid: props.connection.uid,
|
||||||
|
schema: props.schema,
|
||||||
|
elementName: localRoutine.value.name,
|
||||||
|
elementType: 'routine',
|
||||||
|
type: 'routine-props'
|
||||||
|
});
|
||||||
|
|
||||||
|
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
|
||||||
|
changeBreadcrumbs({ schema: props.schema, routine: localRoutine.value.name });
|
||||||
}
|
}
|
||||||
},
|
else
|
||||||
watch: {
|
addNotification({ status: 'error', message: response });
|
||||||
isSelected (val) {
|
}
|
||||||
if (val)
|
catch (err) {
|
||||||
this.changeBreadcrumbs({ schema: this.schema });
|
addNotification({ status: 'error', message: err.stack });
|
||||||
},
|
}
|
||||||
isChanged (val) {
|
|
||||||
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.originalRoutine = {
|
|
||||||
sql: this.customizations.procedureSql,
|
|
||||||
language: this.customizations.languages ? this.customizations.languages[0] : null,
|
|
||||||
name: '',
|
|
||||||
definer: '',
|
|
||||||
comment: '',
|
|
||||||
security: 'DEFINER',
|
|
||||||
dataAccess: 'CONTAINS SQL',
|
|
||||||
deterministic: false
|
|
||||||
};
|
|
||||||
|
|
||||||
this.localRoutine = JSON.parse(JSON.stringify(this.originalRoutine));
|
isSaving.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
setTimeout(() => {
|
const clearChanges = () => {
|
||||||
this.resizeQueryEditor();
|
localRoutine.value = JSON.parse(JSON.stringify(originalRoutine.value));
|
||||||
}, 50);
|
queryEditor.value.editor.session.setValue(localRoutine.value.sql);
|
||||||
|
};
|
||||||
|
|
||||||
window.addEventListener('keydown', this.onKey);
|
const resizeQueryEditor = () => {
|
||||||
},
|
if (queryEditor.value) {
|
||||||
mounted () {
|
const footer = document.getElementById('footer');
|
||||||
if (this.isSelected)
|
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||||
this.changeBreadcrumbs({ schema: this.schema });
|
editorHeight.value = size;
|
||||||
|
queryEditor.value.editor.resize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
setTimeout(() => {
|
const parametersUpdate = (parameters: FunctionParam[]) => {
|
||||||
this.$refs.firstInput.focus();
|
localRoutine.value = { ...localRoutine.value, parameters };
|
||||||
}, 100);
|
};
|
||||||
|
|
||||||
window.addEventListener('resize', this.resizeQueryEditor);
|
const showParamsModal = () => {
|
||||||
},
|
isParamsModal.value = true;
|
||||||
unmounted () {
|
};
|
||||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
|
||||||
},
|
|
||||||
beforeUnmount () {
|
|
||||||
window.removeEventListener('keydown', this.onKey);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async saveChanges () {
|
|
||||||
if (this.isSaving) return;
|
|
||||||
this.isSaving = true;
|
|
||||||
const params = {
|
|
||||||
uid: this.connection.uid,
|
|
||||||
schema: this.schema,
|
|
||||||
...this.localRoutine
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
const hideParamsModal = () => {
|
||||||
const { status, response } = await Routines.createRoutine(params);
|
isParamsModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
if (status === 'success') {
|
const onKey = (e: KeyboardEvent) => {
|
||||||
await this.refreshStructure(this.connection.uid);
|
if (props.isSelected) {
|
||||||
|
e.stopPropagation();
|
||||||
this.newTab({
|
if (e.ctrlKey && e.key === 's') { // CTRL + S
|
||||||
uid: this.connection.uid,
|
if (isChanged.value)
|
||||||
schema: this.schema,
|
saveChanges();
|
||||||
elementName: this.localRoutine.name,
|
|
||||||
elementType: 'routine',
|
|
||||||
type: 'routine-props'
|
|
||||||
});
|
|
||||||
|
|
||||||
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
|
|
||||||
this.changeBreadcrumbs({ schema: this.schema, routine: this.localRoutine.name });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
this.addNotification({ status: 'error', message: response });
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
this.addNotification({ status: 'error', message: err.stack });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isSaving = false;
|
|
||||||
},
|
|
||||||
clearChanges () {
|
|
||||||
this.localRoutine = JSON.parse(JSON.stringify(this.originalRoutine));
|
|
||||||
this.$refs.queryEditor.editor.session.setValue(this.localRoutine.sql);
|
|
||||||
},
|
|
||||||
resizeQueryEditor () {
|
|
||||||
if (this.$refs.queryEditor) {
|
|
||||||
const footer = document.getElementById('footer');
|
|
||||||
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
|
|
||||||
this.editorHeight = size;
|
|
||||||
this.$refs.queryEditor.editor.resize();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
parametersUpdate (parameters) {
|
|
||||||
this.localRoutine = { ...this.localRoutine, parameters };
|
|
||||||
},
|
|
||||||
showParamsModal () {
|
|
||||||
this.isParamsModal = true;
|
|
||||||
},
|
|
||||||
hideParamsModal () {
|
|
||||||
this.isParamsModal = false;
|
|
||||||
},
|
|
||||||
showAskParamsModal () {
|
|
||||||
this.isAskingParameters = true;
|
|
||||||
},
|
|
||||||
hideAskParamsModal () {
|
|
||||||
this.isAskingParameters = false;
|
|
||||||
},
|
|
||||||
onKey (e) {
|
|
||||||
if (this.isSelected) {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
|
||||||
if (this.isChanged)
|
|
||||||
this.saveChanges();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
watch(() => props.isSelected, (val) => {
|
||||||
|
if (val) changeBreadcrumbs({ schema: props.schema });
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(isChanged, (val) => {
|
||||||
|
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
|
||||||
|
});
|
||||||
|
|
||||||
|
originalRoutine.value = {
|
||||||
|
sql: customizations.value.functionSql,
|
||||||
|
language: customizations.value.languages ? customizations.value.languages[0] : null,
|
||||||
|
name: '',
|
||||||
|
definer: '',
|
||||||
|
comment: '',
|
||||||
|
security: 'DEFINER',
|
||||||
|
dataAccess: 'CONTAINS SQL',
|
||||||
|
deterministic: false,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
returns: workspace.value.dataTypes.length ? (workspace.value.dataTypes[0] as any).types[0].name : null
|
||||||
|
};
|
||||||
|
|
||||||
|
localRoutine.value = JSON.parse(JSON.stringify(originalRoutine.value));
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
resizeQueryEditor();
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
window.addEventListener('keydown', onKey);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.isSelected)
|
||||||
|
changeBreadcrumbs({ schema: props.schema });
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
firstInput.value.focus();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('keydown', onKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', resizeQueryEditor);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -11,26 +11,26 @@
|
|||||||
@click="saveChanges"
|
@click="saveChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||||
<span>{{ $t('word.save') }}</span>
|
<span>{{ t('word.save') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:disabled="!isChanged"
|
:disabled="!isChanged"
|
||||||
class="btn btn-link btn-sm mr-0"
|
class="btn btn-link btn-sm mr-0"
|
||||||
:title="$t('message.clearChanges')"
|
:title="t('message.clearChanges')"
|
||||||
@click="clearChanges"
|
@click="clearChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||||
<span>{{ $t('word.clear') }}</span>
|
<span>{{ t('word.clear') }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="divider-vert py-3" />
|
<div class="divider-vert py-3" />
|
||||||
<button class="btn btn-dark btn-sm" @click="showTimingModal">
|
<button class="btn btn-dark btn-sm" @click="showTimingModal">
|
||||||
<i class="mdi mdi-24px mdi-timer mr-1" />
|
<i class="mdi mdi-24px mdi-timer mr-1" />
|
||||||
<span>{{ $t('word.timing') }}</span>
|
<span>{{ t('word.timing') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-info">
|
<div class="workspace-query-info">
|
||||||
<div class="d-flex" :title="$t('word.schema')">
|
<div class="d-flex" :title="t('word.schema')">
|
||||||
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -40,7 +40,7 @@
|
|||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.name') }}</label>
|
<label class="form-label">{{ t('word.name') }}</label>
|
||||||
<input
|
<input
|
||||||
ref="firstInput"
|
ref="firstInput"
|
||||||
v-model="localScheduler.name"
|
v-model="localScheduler.name"
|
||||||
@ -51,19 +51,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.definer') }}</label>
|
<label class="form-label">{{ t('word.definer') }}</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localScheduler.definer"
|
v-model="localScheduler.definer"
|
||||||
:options="users"
|
:options="users"
|
||||||
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
|
:option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
|
||||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.comment') }}</label>
|
<label class="form-label">{{ t('word.comment') }}</label>
|
||||||
<input
|
<input
|
||||||
v-model="localScheduler.comment"
|
v-model="localScheduler.comment"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
@ -73,7 +73,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mr-2">{{ $t('word.state') }}</label>
|
<label class="form-label mr-2">{{ t('word.state') }}</label>
|
||||||
<label class="form-radio form-inline">
|
<label class="form-radio form-inline">
|
||||||
<input
|
<input
|
||||||
v-model="localScheduler.state"
|
v-model="localScheduler.state"
|
||||||
@ -104,7 +104,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||||
<BaseLoader v-if="isLoading" />
|
<BaseLoader v-if="isLoading" />
|
||||||
<label class="form-label ml-2">{{ $t('message.schedulerBody') }}</label>
|
<label class="form-label ml-2">{{ t('message.schedulerBody') }}</label>
|
||||||
<QueryEditor
|
<QueryEditor
|
||||||
v-show="isSelected"
|
v-show="isSelected"
|
||||||
ref="queryEditor"
|
ref="queryEditor"
|
||||||
@ -124,209 +124,186 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { storeToRefs } from 'pinia';
|
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
||||||
|
import { Ace } from 'ace-builds';
|
||||||
|
import { EventInfos } from 'common/interfaces/antares';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import BaseLoader from '@/components/BaseLoader';
|
import BaseLoader from '@/components/BaseLoader.vue';
|
||||||
import QueryEditor from '@/components/QueryEditor';
|
import QueryEditor from '@/components/QueryEditor.vue';
|
||||||
import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal';
|
import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal.vue';
|
||||||
import Schedulers from '@/ipc-api/Schedulers';
|
import Schedulers from '@/ipc-api/Schedulers';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
name: 'WorkspaceTabNewScheduler',
|
|
||||||
components: {
|
|
||||||
BaseLoader,
|
|
||||||
QueryEditor,
|
|
||||||
WorkspaceTabPropsSchedulerTimingModal,
|
|
||||||
BaseSelect
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
tabUid: String,
|
|
||||||
connection: Object,
|
|
||||||
tab: Object,
|
|
||||||
isSelected: Boolean,
|
|
||||||
schema: String
|
|
||||||
},
|
|
||||||
setup () {
|
|
||||||
const { addNotification } = useNotificationsStore();
|
|
||||||
const workspacesStore = useWorkspacesStore();
|
|
||||||
|
|
||||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const props = defineProps({
|
||||||
|
tabUid: String,
|
||||||
|
connection: Object,
|
||||||
|
tab: Object,
|
||||||
|
isSelected: Boolean,
|
||||||
|
schema: String
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const { addNotification } = useNotificationsStore();
|
||||||
getWorkspace,
|
const workspacesStore = useWorkspacesStore();
|
||||||
refreshStructure,
|
|
||||||
changeBreadcrumbs,
|
|
||||||
setUnsavedChanges,
|
|
||||||
newTab,
|
|
||||||
removeTab,
|
|
||||||
renameTabs
|
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const {
|
||||||
addNotification,
|
getWorkspace,
|
||||||
selectedWorkspace,
|
refreshStructure,
|
||||||
getWorkspace,
|
changeBreadcrumbs,
|
||||||
refreshStructure,
|
setUnsavedChanges,
|
||||||
changeBreadcrumbs,
|
newTab,
|
||||||
setUnsavedChanges,
|
removeTab
|
||||||
newTab,
|
} = workspacesStore;
|
||||||
removeTab,
|
|
||||||
renameTabs
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isLoading: false,
|
|
||||||
isSaving: false,
|
|
||||||
isTimingModal: false,
|
|
||||||
originalScheduler: {},
|
|
||||||
localScheduler: {},
|
|
||||||
lastScheduler: null,
|
|
||||||
sqlProxy: '',
|
|
||||||
editorHeight: 300
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
workspace () {
|
|
||||||
return this.getWorkspace(this.connection.uid);
|
|
||||||
},
|
|
||||||
isChanged () {
|
|
||||||
return JSON.stringify(this.originalScheduler) !== JSON.stringify(this.localScheduler);
|
|
||||||
},
|
|
||||||
isDefinerInUsers () {
|
|
||||||
return this.originalScheduler ? this.workspace.users.some(user => this.originalScheduler.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
|
||||||
},
|
|
||||||
schemaTables () {
|
|
||||||
const schemaTables = this.workspace.structure
|
|
||||||
.filter(schema => schema.name === this.schema)
|
|
||||||
.map(schema => schema.tables);
|
|
||||||
|
|
||||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
|
||||||
},
|
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||||
users () {
|
const isLoading = ref(false);
|
||||||
const users = [{ value: '' }, ...this.workspace.users];
|
const isSaving = ref(false);
|
||||||
if (!this.isDefinerInUsers) {
|
const isTimingModal = ref(false);
|
||||||
const [name, host] = this.originalScheduler.definer.replaceAll('`', '').split('@');
|
const originalScheduler = ref(null);
|
||||||
users.unshift({ name, host });
|
const localScheduler = ref(null);
|
||||||
}
|
const editorHeight = ref(300);
|
||||||
|
|
||||||
return users;
|
const workspace = computed(() => {
|
||||||
|
return getWorkspace(props.connection.uid);
|
||||||
|
});
|
||||||
|
|
||||||
|
const isChanged = computed(() => {
|
||||||
|
return JSON.stringify(originalScheduler.value) !== JSON.stringify(localScheduler.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const isDefinerInUsers = computed(() => {
|
||||||
|
return originalScheduler.value ? workspace.value.users.some(user => originalScheduler.value.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const users = computed(() => {
|
||||||
|
const users = [{ value: '' }, ...workspace.value.users];
|
||||||
|
if (!isDefinerInUsers.value) {
|
||||||
|
const [name, host] = originalScheduler.value.definer.replaceAll('`', '').split('@');
|
||||||
|
users.unshift({ name, host });
|
||||||
|
}
|
||||||
|
|
||||||
|
return users;
|
||||||
|
});
|
||||||
|
|
||||||
|
const saveChanges = async () => {
|
||||||
|
if (isSaving.value) return;
|
||||||
|
isSaving.value = true;
|
||||||
|
const params = {
|
||||||
|
uid: props.connection.uid,
|
||||||
|
schema: props.schema,
|
||||||
|
...localScheduler.value
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { status, response } = await Schedulers.createScheduler(params);
|
||||||
|
|
||||||
|
if (status === 'success') {
|
||||||
|
await refreshStructure(props.connection.uid);
|
||||||
|
|
||||||
|
newTab({
|
||||||
|
uid: props.connection.uid,
|
||||||
|
schema: props.schema,
|
||||||
|
elementName: localScheduler.value.name,
|
||||||
|
elementType: 'scheduler',
|
||||||
|
type: 'scheduler-props'
|
||||||
|
});
|
||||||
|
|
||||||
|
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
|
||||||
|
changeBreadcrumbs({ schema: props.schema, scheduler: localScheduler.value.name });
|
||||||
}
|
}
|
||||||
},
|
else
|
||||||
watch: {
|
addNotification({ status: 'error', message: response });
|
||||||
isSelected (val) {
|
}
|
||||||
if (val)
|
catch (err) {
|
||||||
this.changeBreadcrumbs({ schema: this.schema });
|
addNotification({ status: 'error', message: err.stack });
|
||||||
},
|
}
|
||||||
isChanged (val) {
|
|
||||||
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async created () {
|
|
||||||
this.originalScheduler = {
|
|
||||||
definer: '',
|
|
||||||
sql: 'BEGIN\r\n\r\nEND',
|
|
||||||
name: '',
|
|
||||||
comment: '',
|
|
||||||
execution: 'EVERY',
|
|
||||||
every: ['1', 'DAY'],
|
|
||||||
preserve: true,
|
|
||||||
state: 'DISABLE'
|
|
||||||
};
|
|
||||||
|
|
||||||
this.localScheduler = JSON.parse(JSON.stringify(this.originalScheduler));
|
isSaving.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
setTimeout(() => {
|
const clearChanges = () => {
|
||||||
this.resizeQueryEditor();
|
localScheduler.value = JSON.parse(JSON.stringify(originalScheduler.value));
|
||||||
}, 50);
|
queryEditor.value.editor.session.setValue(localScheduler.value.sql);
|
||||||
|
};
|
||||||
|
|
||||||
window.addEventListener('keydown', this.onKey);
|
const resizeQueryEditor = () => {
|
||||||
},
|
if (queryEditor.value) {
|
||||||
mounted () {
|
const footer = document.getElementById('footer');
|
||||||
if (this.isSelected)
|
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||||
this.changeBreadcrumbs({ schema: this.schema });
|
editorHeight.value = size;
|
||||||
|
queryEditor.value.editor.resize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
setTimeout(() => {
|
const showTimingModal = () => {
|
||||||
this.$refs.firstInput.focus();
|
isTimingModal.value = true;
|
||||||
}, 100);
|
};
|
||||||
|
|
||||||
window.addEventListener('resize', this.resizeQueryEditor);
|
const hideTimingModal = () => {
|
||||||
},
|
isTimingModal.value = false;
|
||||||
unmounted () {
|
};
|
||||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
|
||||||
},
|
|
||||||
beforeUnmount () {
|
|
||||||
window.removeEventListener('keydown', this.onKey);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async saveChanges () {
|
|
||||||
if (this.isSaving) return;
|
|
||||||
this.isSaving = true;
|
|
||||||
const params = {
|
|
||||||
uid: this.connection.uid,
|
|
||||||
schema: this.schema,
|
|
||||||
...this.localScheduler
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
const timingUpdate = (options: EventInfos) => {
|
||||||
const { status, response } = await Schedulers.createScheduler(params);
|
localScheduler.value = options;
|
||||||
|
};
|
||||||
|
|
||||||
if (status === 'success') {
|
const onKey = (e: KeyboardEvent) => {
|
||||||
await this.refreshStructure(this.connection.uid);
|
if (props.isSelected) {
|
||||||
|
e.stopPropagation();
|
||||||
this.newTab({
|
if (e.ctrlKey && e.key === 's') { // CTRL + S
|
||||||
uid: this.connection.uid,
|
if (isChanged.value)
|
||||||
schema: this.schema,
|
saveChanges();
|
||||||
elementName: this.localScheduler.name,
|
|
||||||
elementType: 'scheduler',
|
|
||||||
type: 'scheduler-props'
|
|
||||||
});
|
|
||||||
|
|
||||||
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
|
|
||||||
this.changeBreadcrumbs({ schema: this.schema, scheduler: this.localScheduler.name });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
this.addNotification({ status: 'error', message: response });
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
this.addNotification({ status: 'error', message: err.stack });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isSaving = false;
|
|
||||||
},
|
|
||||||
clearChanges () {
|
|
||||||
this.localScheduler = JSON.parse(JSON.stringify(this.originalScheduler));
|
|
||||||
this.$refs.queryEditor.editor.session.setValue(this.localScheduler.sql);
|
|
||||||
},
|
|
||||||
resizeQueryEditor () {
|
|
||||||
if (this.$refs.queryEditor) {
|
|
||||||
const footer = document.getElementById('footer');
|
|
||||||
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
|
|
||||||
this.editorHeight = size;
|
|
||||||
this.$refs.queryEditor.editor.resize();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showTimingModal () {
|
|
||||||
this.isTimingModal = true;
|
|
||||||
},
|
|
||||||
hideTimingModal () {
|
|
||||||
this.isTimingModal = false;
|
|
||||||
},
|
|
||||||
timingUpdate (options) {
|
|
||||||
this.localScheduler = options;
|
|
||||||
},
|
|
||||||
onKey (e) {
|
|
||||||
if (this.isSelected) {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
|
||||||
if (this.isChanged)
|
|
||||||
this.saveChanges();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
watch(() => props.isSelected, (val) => {
|
||||||
|
if (val) changeBreadcrumbs({ schema: props.schema });
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(isChanged, (val) => {
|
||||||
|
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
|
||||||
|
});
|
||||||
|
|
||||||
|
originalScheduler.value = {
|
||||||
|
definer: '',
|
||||||
|
sql: 'BEGIN\r\n\r\nEND',
|
||||||
|
name: '',
|
||||||
|
comment: '',
|
||||||
|
execution: 'EVERY',
|
||||||
|
every: ['1', 'DAY'],
|
||||||
|
preserve: true,
|
||||||
|
state: 'DISABLE'
|
||||||
|
};
|
||||||
|
|
||||||
|
originalScheduler.value = JSON.parse(JSON.stringify(originalScheduler.value));
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
resizeQueryEditor();
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
window.addEventListener('keydown', onKey);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.isSelected)
|
||||||
|
changeBreadcrumbs({ schema: props.schema });
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
firstInput.value.focus();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('keydown', onKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', resizeQueryEditor);
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -11,16 +11,16 @@
|
|||||||
@click="saveChanges"
|
@click="saveChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||||
<span>{{ $t('word.save') }}</span>
|
<span>{{ t('word.save') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:disabled="!isChanged || isSaving"
|
:disabled="!isChanged || isSaving"
|
||||||
class="btn btn-link btn-sm mr-0"
|
class="btn btn-link btn-sm mr-0"
|
||||||
:title="$t('message.clearChanges')"
|
:title="t('message.clearChanges')"
|
||||||
@click="clearChanges"
|
@click="clearChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||||
<span>{{ $t('word.clear') }}</span>
|
<span>{{ t('word.clear') }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="divider-vert py-3" />
|
<div class="divider-vert py-3" />
|
||||||
@ -28,20 +28,20 @@
|
|||||||
<button
|
<button
|
||||||
:disabled="isSaving"
|
:disabled="isSaving"
|
||||||
class="btn btn-dark btn-sm"
|
class="btn btn-dark btn-sm"
|
||||||
:title="$t('message.addNewField')"
|
:title="t('message.addNewField')"
|
||||||
@click="addField"
|
@click="addField"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
|
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
|
||||||
<span>{{ $t('word.add') }}</span>
|
<span>{{ t('word.add') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:disabled="isSaving || !localFields.length"
|
:disabled="isSaving || !localFields.length"
|
||||||
class="btn btn-dark btn-sm"
|
class="btn btn-dark btn-sm"
|
||||||
:title="$t('message.manageIndexes')"
|
:title="t('message.manageIndexes')"
|
||||||
@click="showIntdexesModal"
|
@click="showIntdexesModal"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" />
|
<i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" />
|
||||||
<span>{{ $t('word.indexes') }}</span>
|
<span>{{ t('word.indexes') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn-dark btn-sm"
|
class="btn btn-dark btn-sm"
|
||||||
@ -49,11 +49,11 @@
|
|||||||
@click="showForeignModal"
|
@click="showForeignModal"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-key-link mr-1" />
|
<i class="mdi mdi-24px mdi-key-link mr-1" />
|
||||||
<span>{{ $t('word.foreignKeys') }}</span>
|
<span>{{ t('word.foreignKeys') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-info">
|
<div class="workspace-query-info">
|
||||||
<div class="d-flex" :title="$t('word.schema')">
|
<div class="d-flex" :title="t('word.schema')">
|
||||||
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -63,7 +63,7 @@
|
|||||||
<div class="columns mb-4">
|
<div class="columns mb-4">
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.name') }}</label>
|
<label class="form-label">{{ t('word.name') }}</label>
|
||||||
<input
|
<input
|
||||||
ref="firstInput"
|
ref="firstInput"
|
||||||
v-model="localOptions.name"
|
v-model="localOptions.name"
|
||||||
@ -74,7 +74,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="workspace.customizations.comment" class="column">
|
<div v-if="workspace.customizations.comment" class="column">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.comment') }}</label>
|
<label class="form-label">{{ t('word.comment') }}</label>
|
||||||
<input
|
<input
|
||||||
v-model="localOptions.comment"
|
v-model="localOptions.comment"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
@ -86,7 +86,7 @@
|
|||||||
<div v-if="workspace.customizations.collations" class="column col-auto">
|
<div v-if="workspace.customizations.collations" class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.collation') }}
|
{{ t('word.collation') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localOptions.collation"
|
v-model="localOptions.collation"
|
||||||
@ -100,7 +100,7 @@
|
|||||||
<div v-if="workspace.customizations.engines" class="column col-auto">
|
<div v-if="workspace.customizations.engines" class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.engine') }}
|
{{ t('word.engine') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localOptions.engine"
|
v-model="localOptions.engine"
|
||||||
@ -160,310 +160,302 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { Component, computed, onBeforeUnmount, onMounted, Prop, Ref, ref, watch } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import { uidGen } from 'common/libs/uidGen';
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
import Tables from '@/ipc-api/Tables';
|
import Tables from '@/ipc-api/Tables';
|
||||||
import BaseLoader from '@/components/BaseLoader';
|
import BaseLoader from '@/components/BaseLoader.vue';
|
||||||
import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields';
|
import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields.vue';
|
||||||
import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal';
|
import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal.vue';
|
||||||
import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal';
|
import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal.vue';
|
||||||
import WorkspaceTabNewTableEmptyState from '@/components/WorkspaceTabNewTableEmptyState';
|
import WorkspaceTabNewTableEmptyState from '@/components/WorkspaceTabNewTableEmptyState.vue';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
import { ConnectionParams, TableField, TableForeign, TableIndex, TableOptions } from 'common/interfaces/antares';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
name: 'WorkspaceTabNewTable',
|
|
||||||
components: {
|
|
||||||
BaseLoader,
|
|
||||||
WorkspaceTabPropsTableFields,
|
|
||||||
WorkspaceTabPropsTableIndexesModal,
|
|
||||||
WorkspaceTabPropsTableForeignModal,
|
|
||||||
WorkspaceTabNewTableEmptyState,
|
|
||||||
BaseSelect
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
tabUid: String,
|
|
||||||
connection: Object,
|
|
||||||
tab: Object,
|
|
||||||
isSelected: Boolean,
|
|
||||||
schema: String
|
|
||||||
},
|
|
||||||
setup () {
|
|
||||||
const { addNotification } = useNotificationsStore();
|
|
||||||
const workspacesStore = useWorkspacesStore();
|
|
||||||
|
|
||||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const props = defineProps({
|
||||||
|
tabUid: String,
|
||||||
|
connection: Object as Prop<ConnectionParams>,
|
||||||
|
tab: Object,
|
||||||
|
isSelected: Boolean,
|
||||||
|
schema: String
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const { addNotification } = useNotificationsStore();
|
||||||
getWorkspace,
|
const workspacesStore = useWorkspacesStore();
|
||||||
getDatabaseVariable,
|
|
||||||
refreshStructure,
|
|
||||||
setUnsavedChanges,
|
|
||||||
newTab,
|
|
||||||
renameTabs,
|
|
||||||
removeTab,
|
|
||||||
changeBreadcrumbs
|
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||||
addNotification,
|
|
||||||
getWorkspace,
|
|
||||||
getDatabaseVariable,
|
|
||||||
refreshStructure,
|
|
||||||
setUnsavedChanges,
|
|
||||||
newTab,
|
|
||||||
renameTabs,
|
|
||||||
removeTab,
|
|
||||||
changeBreadcrumbs,
|
|
||||||
selectedWorkspace
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isLoading: false,
|
|
||||||
isSaving: false,
|
|
||||||
isIndexesModal: false,
|
|
||||||
isForeignModal: false,
|
|
||||||
isOptionsChanging: false,
|
|
||||||
originalFields: [],
|
|
||||||
localFields: [],
|
|
||||||
originalKeyUsage: [],
|
|
||||||
localKeyUsage: [],
|
|
||||||
originalIndexes: [],
|
|
||||||
localIndexes: [],
|
|
||||||
tableOptions: {},
|
|
||||||
localOptions: {},
|
|
||||||
lastTable: null,
|
|
||||||
newFieldsCounter: 0
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
workspace () {
|
|
||||||
return this.getWorkspace(this.connection.uid);
|
|
||||||
},
|
|
||||||
defaultCollation () {
|
|
||||||
if (this.workspace.customizations.collations)
|
|
||||||
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server')?.value || '';
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
defaultEngine () {
|
|
||||||
if (this.workspace.customizations.engines)
|
|
||||||
return this.workspace.engines?.find(engine => engine.isDefault)?.name || '';
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
schemaTables () {
|
|
||||||
const schemaTables = this.workspace.structure
|
|
||||||
.filter(schema => schema.name === this.schema)
|
|
||||||
.map(schema => schema.tables);
|
|
||||||
|
|
||||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
const {
|
||||||
},
|
getWorkspace,
|
||||||
isChanged () {
|
getDatabaseVariable,
|
||||||
return JSON.stringify(this.originalFields) !== JSON.stringify(this.localFields) ||
|
refreshStructure,
|
||||||
JSON.stringify(this.originalKeyUsage) !== JSON.stringify(this.localKeyUsage) ||
|
setUnsavedChanges,
|
||||||
JSON.stringify(this.originalIndexes) !== JSON.stringify(this.localIndexes) ||
|
newTab,
|
||||||
JSON.stringify(this.tableOptions) !== JSON.stringify(this.localOptions);
|
removeTab,
|
||||||
},
|
changeBreadcrumbs
|
||||||
isValid () {
|
} = workspacesStore;
|
||||||
return !!this.localFields.length && !!this.localOptions.name.trim().length;
|
|
||||||
|
const indexTable: Ref<Component & {$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>
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="column col-12 empty">
|
<div class="column col-12 empty">
|
||||||
<p class="h6 empty-subtitle">
|
<p class="h6 empty-subtitle">
|
||||||
{{ $t('message.thereAreNoTableFields') }}
|
{{ t('message.thereAreNoTableFields') }}
|
||||||
</p>
|
</p>
|
||||||
<div class="empty-action">
|
<div class="empty-action">
|
||||||
<button class="btn btn-gray d-flex" @click="$emit('new-field')">
|
<button class="btn btn-gray d-flex" @click="emit('new-field')">
|
||||||
<i class="mdi mdi-24px mdi-playlist-plus mr-2" />
|
<i class="mdi mdi-24px mdi-playlist-plus mr-2" />
|
||||||
{{ $t('message.addNewField') }}
|
{{ t('message.addNewField') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
export default {
|
import { useI18n } from 'vue-i18n';
|
||||||
name: 'WorkspaceTabNewTableEmptyState',
|
|
||||||
emits: ['new-field']
|
const { t } = useI18n();
|
||||||
};
|
const emit = defineEmits(['new-field']);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -11,20 +11,20 @@
|
|||||||
@click="saveChanges"
|
@click="saveChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||||
<span>{{ $t('word.save') }}</span>
|
<span>{{ t('word.save') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:disabled="!isChanged"
|
:disabled="!isChanged"
|
||||||
class="btn btn-link btn-sm mr-0"
|
class="btn btn-link btn-sm mr-0"
|
||||||
:title="$t('message.clearChanges')"
|
:title="t('message.clearChanges')"
|
||||||
@click="clearChanges"
|
@click="clearChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||||
<span>{{ $t('word.clear') }}</span>
|
<span>{{ t('word.clear') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-info">
|
<div class="workspace-query-info">
|
||||||
<div class="d-flex" :title="$t('word.schema')">
|
<div class="d-flex" :title="t('word.schema')">
|
||||||
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -34,7 +34,7 @@
|
|||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.name') }}</label>
|
<label class="form-label">{{ t('word.name') }}</label>
|
||||||
<input
|
<input
|
||||||
ref="firstInput"
|
ref="firstInput"
|
||||||
v-model="localTrigger.name"
|
v-model="localTrigger.name"
|
||||||
@ -45,12 +45,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="customizations.definer" class="column col-auto">
|
<div v-if="customizations.definer" class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.definer') }}</label>
|
<label class="form-label">{{ t('word.definer') }}</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localTrigger.definer"
|
v-model="localTrigger.definer"
|
||||||
:options="users"
|
:options="users"
|
||||||
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
|
:option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
|
||||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -58,7 +58,7 @@
|
|||||||
<fieldset class="column columns mb-0" :disabled="customizations.triggerOnlyRename">
|
<fieldset class="column columns mb-0" :disabled="customizations.triggerOnlyRename">
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.table') }}</label>
|
<label class="form-label">{{ t('word.table') }}</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localTrigger.table"
|
v-model="localTrigger.table"
|
||||||
:options="schemaTables"
|
:options="schemaTables"
|
||||||
@ -70,7 +70,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.event') }}</label>
|
<label class="form-label">{{ t('word.event') }}</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localTrigger.activation"
|
v-model="localTrigger.activation"
|
||||||
@ -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>
|
||||||
|
@ -11,16 +11,16 @@
|
|||||||
@click="saveChanges"
|
@click="saveChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||||
<span>{{ $t('word.save') }}</span>
|
<span>{{ t('word.save') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:disabled="!isChanged"
|
:disabled="!isChanged"
|
||||||
class="btn btn-link btn-sm mr-0"
|
class="btn btn-link btn-sm mr-0"
|
||||||
:title="$t('message.clearChanges')"
|
:title="t('message.clearChanges')"
|
||||||
@click="clearChanges"
|
@click="clearChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||||
<span>{{ $t('word.clear') }}</span>
|
<span>{{ t('word.clear') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -30,7 +30,7 @@
|
|||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.name') }}
|
{{ t('word.name') }}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
ref="firstInput"
|
ref="firstInput"
|
||||||
@ -43,7 +43,7 @@
|
|||||||
<div v-if="customizations.triggerFunctionlanguages" class="column col-auto">
|
<div v-if="customizations.triggerFunctionlanguages" class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.language') }}
|
{{ t('word.language') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localFunction.language"
|
v-model="localFunction.language"
|
||||||
@ -55,20 +55,20 @@
|
|||||||
<div v-if="customizations.definer" class="column col-auto">
|
<div v-if="customizations.definer" class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.definer') }}
|
{{ t('word.definer') }}
|
||||||
</label>
|
</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localFunction.definer"
|
v-model="localFunction.definer"
|
||||||
:options="workspace.users"
|
:options="workspace.users"
|
||||||
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
|
:option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
|
||||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="customizations.comment" class="form-group">
|
<div v-if="customizations.comment" class="form-group">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('word.comment') }}
|
{{ t('word.comment') }}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
v-model="localFunction.comment"
|
v-model="localFunction.comment"
|
||||||
@ -80,7 +80,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||||
<BaseLoader v-if="isLoading" />
|
<BaseLoader v-if="isLoading" />
|
||||||
<label class="form-label ml-2">{{ $t('message.functionBody') }}</label>
|
<label class="form-label ml-2">{{ t('message.functionBody') }}</label>
|
||||||
<QueryEditor
|
<QueryEditor
|
||||||
v-show="isSelected"
|
v-show="isSelected"
|
||||||
ref="queryEditor"
|
ref="queryEditor"
|
||||||
@ -93,190 +93,157 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { storeToRefs } from 'pinia';
|
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
||||||
|
import { Ace } from 'ace-builds';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import BaseLoader from '@/components/BaseLoader';
|
import BaseLoader from '@/components/BaseLoader.vue';
|
||||||
import QueryEditor from '@/components/QueryEditor';
|
import QueryEditor from '@/components/QueryEditor.vue';
|
||||||
import Functions from '@/ipc-api/Functions';
|
import Functions from '@/ipc-api/Functions';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
name: 'WorkspaceTabNewTriggerFunction',
|
|
||||||
components: {
|
|
||||||
BaseLoader,
|
|
||||||
QueryEditor,
|
|
||||||
BaseSelect
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
tabUid: String,
|
|
||||||
connection: Object,
|
|
||||||
tab: Object,
|
|
||||||
isSelected: Boolean,
|
|
||||||
schema: String
|
|
||||||
},
|
|
||||||
setup () {
|
|
||||||
const { addNotification } = useNotificationsStore();
|
|
||||||
const workspacesStore = useWorkspacesStore();
|
|
||||||
|
|
||||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const props = defineProps({
|
||||||
|
tabUid: String,
|
||||||
|
connection: Object,
|
||||||
|
tab: Object,
|
||||||
|
isSelected: Boolean,
|
||||||
|
schema: String
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const { addNotification } = useNotificationsStore();
|
||||||
getWorkspace,
|
const workspacesStore = useWorkspacesStore();
|
||||||
refreshStructure,
|
|
||||||
changeBreadcrumbs,
|
|
||||||
setUnsavedChanges,
|
|
||||||
newTab,
|
|
||||||
removeTab,
|
|
||||||
renameTabs
|
|
||||||
} = workspacesStore;
|
|
||||||
|
|
||||||
return {
|
const {
|
||||||
addNotification,
|
getWorkspace,
|
||||||
selectedWorkspace,
|
refreshStructure,
|
||||||
getWorkspace,
|
changeBreadcrumbs,
|
||||||
refreshStructure,
|
setUnsavedChanges,
|
||||||
changeBreadcrumbs,
|
newTab,
|
||||||
setUnsavedChanges,
|
removeTab
|
||||||
newTab,
|
} = workspacesStore;
|
||||||
removeTab,
|
|
||||||
renameTabs
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isLoading: false,
|
|
||||||
isSaving: false,
|
|
||||||
isParamsModal: false,
|
|
||||||
isAskingParameters: false,
|
|
||||||
originalFunction: {},
|
|
||||||
localFunction: {},
|
|
||||||
lastFunction: null,
|
|
||||||
sqlProxy: '',
|
|
||||||
editorHeight: 300
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
workspace () {
|
|
||||||
return this.getWorkspace(this.connection.uid);
|
|
||||||
},
|
|
||||||
customizations () {
|
|
||||||
return this.workspace.customizations;
|
|
||||||
},
|
|
||||||
isChanged () {
|
|
||||||
return JSON.stringify(this.originalFunction) !== JSON.stringify(this.localFunction);
|
|
||||||
},
|
|
||||||
isDefinerInUsers () {
|
|
||||||
return this.originalFunction
|
|
||||||
? this.workspace.users.some(user => this.originalFunction.definer === `\`${user.name}\`@\`${user.host}\``)
|
|
||||||
: true;
|
|
||||||
},
|
|
||||||
schemaTables () {
|
|
||||||
const schemaTables = this.workspace.structure
|
|
||||||
.filter(schema => schema.name === this.schema)
|
|
||||||
.map(schema => schema.tables);
|
|
||||||
|
|
||||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
|
||||||
|
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const isSaving = ref(false);
|
||||||
|
const originalFunction = ref(null);
|
||||||
|
const localFunction = ref(null);
|
||||||
|
const editorHeight = ref(300);
|
||||||
|
|
||||||
|
const workspace = computed(() => {
|
||||||
|
return getWorkspace(props.connection.uid);
|
||||||
|
});
|
||||||
|
|
||||||
|
const customizations = computed(() => {
|
||||||
|
return workspace.value.customizations;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isChanged = computed(() => {
|
||||||
|
return JSON.stringify(originalFunction.value) !== JSON.stringify(localFunction.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const saveChanges = async () => {
|
||||||
|
if (isSaving.value) return;
|
||||||
|
isSaving.value = true;
|
||||||
|
const params = {
|
||||||
|
uid: props.connection.uid,
|
||||||
|
schema: props.schema,
|
||||||
|
...localFunction.value
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { status, response } = await Functions.createTriggerFunction(params);
|
||||||
|
|
||||||
|
if (status === 'success') {
|
||||||
|
await refreshStructure(props.connection.uid);
|
||||||
|
|
||||||
|
newTab({
|
||||||
|
uid: props.connection.uid,
|
||||||
|
schema: props.schema,
|
||||||
|
elementName: localFunction.value.name,
|
||||||
|
elementType: 'triggerFunction',
|
||||||
|
type: 'trigger-function-props'
|
||||||
|
});
|
||||||
|
|
||||||
|
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
|
||||||
|
changeBreadcrumbs({ schema: props.schema, triggerFunction: localFunction.value.name });
|
||||||
}
|
}
|
||||||
},
|
else
|
||||||
watch: {
|
addNotification({ status: 'error', message: response });
|
||||||
isSelected (val) {
|
}
|
||||||
if (val)
|
catch (err) {
|
||||||
this.changeBreadcrumbs({ schema: this.schema });
|
addNotification({ status: 'error', message: err.stack });
|
||||||
},
|
}
|
||||||
isChanged (val) {
|
|
||||||
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.originalFunction = {
|
|
||||||
sql: this.customizations.triggerFunctionSql,
|
|
||||||
language: this.customizations.triggerFunctionlanguages.length ? this.customizations.triggerFunctionlanguages[0] : null,
|
|
||||||
name: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction));
|
isSaving.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
setTimeout(() => {
|
const clearChanges = () => {
|
||||||
this.resizeQueryEditor();
|
localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
|
||||||
}, 50);
|
queryEditor.value.editor.session.setValue(localFunction.value.sql);
|
||||||
|
};
|
||||||
|
|
||||||
window.addEventListener('keydown', this.onKey);
|
const resizeQueryEditor = () => {
|
||||||
},
|
if (queryEditor.value) {
|
||||||
mounted () {
|
const footer = document.getElementById('footer');
|
||||||
if (this.isSelected)
|
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||||
this.changeBreadcrumbs({ schema: this.schema });
|
editorHeight.value = size;
|
||||||
|
queryEditor.value.editor.resize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
setTimeout(() => {
|
const onKey = (e: KeyboardEvent) => {
|
||||||
this.$refs.firstInput.focus();
|
if (props.isSelected) {
|
||||||
}, 100);
|
e.stopPropagation();
|
||||||
|
if (e.ctrlKey && e.key === 's') { // CTRL + S
|
||||||
window.addEventListener('resize', this.resizeQueryEditor);
|
if (isChanged.value)
|
||||||
},
|
saveChanges();
|
||||||
unmounted () {
|
|
||||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
|
||||||
},
|
|
||||||
beforeUnmount () {
|
|
||||||
window.removeEventListener('keydown', this.onKey);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async saveChanges () {
|
|
||||||
if (this.isSaving) return;
|
|
||||||
this.isSaving = true;
|
|
||||||
const params = {
|
|
||||||
uid: this.connection.uid,
|
|
||||||
schema: this.schema,
|
|
||||||
...this.localFunction
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { status, response } = await Functions.createTriggerFunction(params);
|
|
||||||
|
|
||||||
if (status === 'success') {
|
|
||||||
await this.refreshStructure(this.connection.uid);
|
|
||||||
|
|
||||||
this.newTab({
|
|
||||||
uid: this.connection.uid,
|
|
||||||
schema: this.schema,
|
|
||||||
elementName: this.localFunction.name,
|
|
||||||
elementType: 'triggerFunction',
|
|
||||||
type: 'trigger-function-props'
|
|
||||||
});
|
|
||||||
|
|
||||||
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
|
|
||||||
this.changeBreadcrumbs({ schema: this.schema, triggerFunction: this.localFunction.name });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
this.addNotification({ status: 'error', message: response });
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
this.addNotification({ status: 'error', message: err.stack });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isSaving = false;
|
|
||||||
},
|
|
||||||
clearChanges () {
|
|
||||||
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction));
|
|
||||||
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
|
|
||||||
},
|
|
||||||
resizeQueryEditor () {
|
|
||||||
if (this.$refs.queryEditor) {
|
|
||||||
const footer = document.getElementById('footer');
|
|
||||||
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
|
|
||||||
this.editorHeight = size;
|
|
||||||
this.$refs.queryEditor.editor.resize();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onKey (e) {
|
|
||||||
if (this.isSelected) {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
|
||||||
if (this.isChanged)
|
|
||||||
this.saveChanges();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
originalFunction.value = {
|
||||||
|
sql: customizations.value.triggerFunctionSql,
|
||||||
|
language: customizations.value.triggerFunctionlanguages.length ? customizations.value.triggerFunctionlanguages[0] : null,
|
||||||
|
name: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
resizeQueryEditor();
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
window.addEventListener('keydown', onKey);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.isSelected)
|
||||||
|
changeBreadcrumbs({ schema: props.schema });
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
firstInput.value.focus();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
window.addEventListener('resize', resizeQueryEditor);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', resizeQueryEditor);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('keydown', onKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => props.isSelected, (val) => {
|
||||||
|
if (val) changeBreadcrumbs({ schema: props.schema });
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(isChanged, (val) => {
|
||||||
|
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -11,20 +11,20 @@
|
|||||||
@click="saveChanges"
|
@click="saveChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
<i class="mdi mdi-24px mdi-content-save mr-1" />
|
||||||
<span>{{ $t('word.save') }}</span>
|
<span>{{ t('word.save') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:disabled="!isChanged"
|
:disabled="!isChanged"
|
||||||
class="btn btn-link btn-sm mr-0"
|
class="btn btn-link btn-sm mr-0"
|
||||||
:title="$t('message.clearChanges')"
|
:title="t('message.clearChanges')"
|
||||||
@click="clearChanges"
|
@click="clearChanges"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
|
||||||
<span>{{ $t('word.clear') }}</span>
|
<span>{{ t('word.clear') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-info">
|
<div class="workspace-query-info">
|
||||||
<div class="d-flex" :title="$t('word.schema')">
|
<div class="d-flex" :title="t('word.schema')">
|
||||||
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -34,7 +34,7 @@
|
|||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('word.name') }}</label>
|
<label class="form-label">{{ t('word.name') }}</label>
|
||||||
<input
|
<input
|
||||||
ref="firstInput"
|
ref="firstInput"
|
||||||
v-model="localView.name"
|
v-model="localView.name"
|
||||||
@ -45,19 +45,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="column col-auto">
|
<div class="column col-auto">
|
||||||
<div v-if="workspace.customizations.definer" class="form-group">
|
<div v-if="workspace.customizations.definer" class="form-group">
|
||||||
<label class="form-label">{{ $t('word.definer') }}</label>
|
<label class="form-label">{{ t('word.definer') }}</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localView.definer"
|
v-model="localView.definer"
|
||||||
:options="users"
|
:options="users"
|
||||||
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
|
:option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
|
||||||
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-auto mr-2">
|
<div class="column col-auto mr-2">
|
||||||
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
|
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
|
||||||
<label class="form-label">{{ $t('message.sqlSecurity') }}</label>
|
<label class="form-label">{{ t('message.sqlSecurity') }}</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localView.security"
|
v-model="localView.security"
|
||||||
:options="['DEFINER', 'INVOKER']"
|
:options="['DEFINER', 'INVOKER']"
|
||||||
@ -67,7 +67,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="column col-auto mr-2">
|
<div class="column col-auto mr-2">
|
||||||
<div v-if="workspace.customizations.viewAlgorithm" class="form-group">
|
<div v-if="workspace.customizations.viewAlgorithm" class="form-group">
|
||||||
<label class="form-label">{{ $t('word.algorithm') }}</label>
|
<label class="form-label">{{ t('word.algorithm') }}</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localView.algorithm"
|
v-model="localView.algorithm"
|
||||||
:options="['UNDEFINED', 'MERGE', 'TEMPTABLE']"
|
:options="['UNDEFINED', 'MERGE', 'TEMPTABLE']"
|
||||||
@ -77,10 +77,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2">
|
<div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('message.updateOption') }}</label>
|
<label class="form-label">{{ t('message.updateOption') }}</label>
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
v-model="localView.updateOption"
|
v-model="localView.updateOption"
|
||||||
:option-track-by="(user) => user.value"
|
:option-track-by="(user: any) => user.value"
|
||||||
:options="[{label: 'None', value: ''}, {label: 'CASCADED', value: 'CASCADED'}, {label: 'LOCAL', value: 'LOCAL'}]"
|
:options="[{label: 'None', value: ''}, {label: 'CASCADED', value: 'CASCADED'}, {label: 'LOCAL', value: 'LOCAL'}]"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
/>
|
/>
|
||||||
@ -90,7 +90,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||||
<BaseLoader v-if="isLoading" />
|
<BaseLoader v-if="isLoading" />
|
||||||
<label class="form-label ml-2">{{ $t('message.selectStatement') }}</label>
|
<label class="form-label ml-2">{{ t('message.selectStatement') }}</label>
|
||||||
<QueryEditor
|
<QueryEditor
|
||||||
v-show="isSelected"
|
v-show="isSelected"
|
||||||
ref="queryEditor"
|
ref="queryEditor"
|
||||||
@ -103,194 +103,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>
|
||||||
|
@ -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: ''
|
||||||
|
@ -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';
|
||||||
|
@ -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>;
|
||||||
|
Reference in New Issue
Block a user