mirror of https://github.com/Fabio286/antares.git
feat: views edit
This commit is contained in:
parent
dcf469ebed
commit
56f2a27f00
|
@ -20,4 +20,14 @@ export default (connections) => {
|
|||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('alter-view', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].alterView(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -295,6 +295,18 @@ export class MySQLClient extends AntaresCore {
|
|||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* ALTER VIEW
|
||||
*
|
||||
* @returns {Array.<Object>} parameters
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async alterView (params) {
|
||||
const { view } = params;
|
||||
const sql = `ALTER ALGORITHM = ${view.algorithm} DEFINER=${view.definer} SQL SECURITY ${view.security} VIEW \`${view.name}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''}`;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* SHOW COLLATION
|
||||
*
|
||||
|
|
|
@ -182,8 +182,13 @@ export default {
|
|||
if (this.autoFocus) {
|
||||
setTimeout(() => {
|
||||
this.editor.focus();
|
||||
this.editor.resize();
|
||||
}, 20);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.editor.resize();
|
||||
}, 20);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -13,25 +13,25 @@
|
|||
</a>
|
||||
</li>
|
||||
<li
|
||||
v-if="workspace.breadcrumbs.table"
|
||||
v-if="schemaChild"
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === 'prop'}"
|
||||
@click="selectTab({uid: workspace.uid, tab: 'prop'})"
|
||||
>
|
||||
<a class="tab-link">
|
||||
<i class="mdi mdi-18px mdi-tune mr-1" />
|
||||
<span :title="workspace.breadcrumbs.table">{{ $t('word.properties').toUpperCase() }}: {{ workspace.breadcrumbs.table }}</span>
|
||||
<span :title="schemaChild">{{ $t('word.properties').toUpperCase() }}: {{ schemaChild }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
v-if="workspace.breadcrumbs.table"
|
||||
v-if="workspace.breadcrumbs.table || workspace.breadcrumbs.view"
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === 'data'}"
|
||||
@click="selectTab({uid: workspace.uid, tab: 'data'})"
|
||||
>
|
||||
<a class="tab-link">
|
||||
<i class="mdi mdi-18px mdi-table mr-1" />
|
||||
<span :title="workspace.breadcrumbs.table">{{ $t('word.data').toUpperCase() }}: {{ workspace.breadcrumbs.table }}</span>
|
||||
<i class="mdi mdi-18px mr-1" :class="workspace.breadcrumbs.table ? 'mdi-table' : 'mdi-table-eye'" />
|
||||
<span :title="schemaChild">{{ $t('word.data').toUpperCase() }}: {{ schemaChild }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
|
@ -66,15 +66,21 @@
|
|||
</li>
|
||||
</ul>
|
||||
<WorkspacePropsTab
|
||||
v-show="selectedTab === 'prop'"
|
||||
v-show="selectedTab === 'prop' && workspace.breadcrumbs.table"
|
||||
:is-selected="selectedTab === 'prop'"
|
||||
:connection="connection"
|
||||
:table="workspace.breadcrumbs.table"
|
||||
/>
|
||||
<WorkspacePropsTabView
|
||||
v-show="selectedTab === 'prop' && workspace.breadcrumbs.view"
|
||||
:is-selected="selectedTab === 'prop'"
|
||||
:connection="connection"
|
||||
:view="workspace.breadcrumbs.view"
|
||||
/>
|
||||
<WorkspaceTableTab
|
||||
v-show="selectedTab === 'data'"
|
||||
:connection="connection"
|
||||
:table="workspace.breadcrumbs.table"
|
||||
:table="workspace.breadcrumbs.table || workspace.breadcrumbs.view"
|
||||
/>
|
||||
<WorkspaceQueryTab
|
||||
v-for="tab of queryTabs"
|
||||
|
@ -94,6 +100,7 @@ import WorkspaceExploreBar from '@/components/WorkspaceExploreBar';
|
|||
import WorkspaceQueryTab from '@/components/WorkspaceQueryTab';
|
||||
import WorkspaceTableTab from '@/components/WorkspaceTableTab';
|
||||
import WorkspacePropsTab from '@/components/WorkspacePropsTab';
|
||||
import WorkspacePropsTabView from '@/components/WorkspacePropsTabView';
|
||||
|
||||
export default {
|
||||
name: 'Workspace',
|
||||
|
@ -101,7 +108,8 @@ export default {
|
|||
WorkspaceExploreBar,
|
||||
WorkspaceQueryTab,
|
||||
WorkspaceTableTab,
|
||||
WorkspacePropsTab
|
||||
WorkspacePropsTab,
|
||||
WorkspacePropsTabView
|
||||
},
|
||||
props: {
|
||||
connection: Object
|
||||
|
@ -123,7 +131,7 @@ export default {
|
|||
return this.selectedWorkspace === this.connection.uid;
|
||||
},
|
||||
selectedTab () {
|
||||
if (this.workspace.breadcrumbs.table === null && ['data', 'prop'].includes(this.workspace.selected_tab))
|
||||
if (this.workspace.breadcrumbs.table === null && this.workspace.breadcrumbs.view === null && ['data', 'prop'].includes(this.workspace.selected_tab))
|
||||
return this.queryTabs[0].uid;
|
||||
|
||||
return this.queryTabs.find(tab => tab.uid === this.workspace.selected_tab) ||
|
||||
|
@ -133,6 +141,13 @@ export default {
|
|||
},
|
||||
queryTabs () {
|
||||
return this.workspace.tabs.filter(tab => tab.type === 'query');
|
||||
},
|
||||
schemaChild () {
|
||||
for (const key in this.workspace.breadcrumbs) {
|
||||
if (key === 'schema') continue;
|
||||
if (this.workspace.breadcrumbs[key]) return this.workspace.breadcrumbs[key];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
|
|
|
@ -10,6 +10,18 @@
|
|||
<div class="context-element" @click="showCreateTableModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-table text-light pr-1" /> {{ $t('word.table') }}</span>
|
||||
</div>
|
||||
<div class="context-element" @click="showCreateTableModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-table-eye text-light pr-1" /> {{ $t('word.view') }}</span>
|
||||
</div>
|
||||
<div class="context-element d-none" @click="false">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-table-cog text-light pr-1" /> {{ $t('word.trigger') }}</span>
|
||||
</div>
|
||||
<div class="context-element d-none" @click="false">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-cog-box pr-1" /> {{ $t('word.storedRoutine') }}</span>
|
||||
</div>
|
||||
<div class="context-element d-none" @click="false">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-calendar-clock text-light pr-1" /> {{ $t('word.scheduler') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="context-element" @click="showEditModal">
|
||||
|
@ -124,3 +136,8 @@ export default {
|
|||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.context-submenu {
|
||||
min-width: 150px !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,296 @@
|
|||
<template>
|
||||
<div class="workspace-query-tab column col-12 columns col-gapless">
|
||||
<div class="workspace-query-runner column col-12">
|
||||
<div class="workspace-query-runner-footer">
|
||||
<div class="workspace-query-buttons">
|
||||
<button
|
||||
class="btn btn-primary btn-sm"
|
||||
:disabled="!isChanged"
|
||||
:class="{'loading':isSaving}"
|
||||
@click="saveChanges"
|
||||
>
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
<i class="mdi mdi-24px mdi-content-save ml-1" />
|
||||
</button>
|
||||
<button
|
||||
:disabled="!isChanged"
|
||||
class="btn btn-link btn-sm mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
@click="clearChanges"
|
||||
>
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="columns mb-4">
|
||||
<div class="column col-3">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
<input
|
||||
v-model="localView.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-3">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.definer') }}</label>
|
||||
<input
|
||||
v-model="localView.definer"
|
||||
class="form-input"
|
||||
type="text"
|
||||
readonly
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column col-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('message.sqlSecurity') }}</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.security"
|
||||
type="radio"
|
||||
name="security"
|
||||
value="DEFINER"
|
||||
>
|
||||
<i class="form-icon" /> DEFINER
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.security"
|
||||
type="radio"
|
||||
name="security"
|
||||
value="INVOKER"
|
||||
>
|
||||
<i class="form-icon" /> INVOKER
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.algorithm') }}</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.algorithm"
|
||||
type="radio"
|
||||
name="algorithm"
|
||||
value="UNDEFINED"
|
||||
>
|
||||
<i class="form-icon" /> UNDEFINED
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.algorithm"
|
||||
type="radio"
|
||||
value="MERGE"
|
||||
name="algorithm"
|
||||
>
|
||||
<i class="form-icon" /> MERGE
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.algorithm"
|
||||
type="radio"
|
||||
value="TEMPTABLE"
|
||||
name="algorithm"
|
||||
>
|
||||
<i class="form-icon" /> TEMPTABLE
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('message.updateOption') }}</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.updateOption"
|
||||
type="radio"
|
||||
name="update"
|
||||
value=""
|
||||
>
|
||||
<i class="form-icon" /> None
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.updateOption"
|
||||
type="radio"
|
||||
name="update"
|
||||
value="CASCADED"
|
||||
>
|
||||
<i class="form-icon" /> CASCADED
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.updateOption"
|
||||
type="radio"
|
||||
name="update"
|
||||
value="LOCAL"
|
||||
>
|
||||
<i class="form-icon" /> LOCAL
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2">
|
||||
<label class="form-label ml-2">{{ $t('message.selectStatement') }}</label>
|
||||
<QueryEditor
|
||||
v-if="isSelected"
|
||||
ref="queryEditor"
|
||||
:value.sync="localView.sql"
|
||||
:workspace="workspace"
|
||||
:schema="schema"
|
||||
:height="editorHeight"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import Views from '@/ipc-api/Views';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsTabView',
|
||||
components: {
|
||||
QueryEditor
|
||||
},
|
||||
props: {
|
||||
connection: Object,
|
||||
view: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tabUid: 'prop',
|
||||
isQuering: false,
|
||||
isSaving: false,
|
||||
originalView: null,
|
||||
localView: { sql: '' },
|
||||
lastView: null,
|
||||
sqlProxy: '',
|
||||
editorHeight: 300
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
isSelected () {
|
||||
return this.workspace.selected_tab === 'prop';
|
||||
},
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.originalView) !== JSON.stringify(this.localView);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async view () {
|
||||
if (this.isSelected) {
|
||||
await this.getViewData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localView.sql);
|
||||
this.lastView = this.view;
|
||||
}
|
||||
},
|
||||
async isSelected (val) {
|
||||
if (val && this.lastView !== this.view) {
|
||||
await this.getViewData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localView.sql);
|
||||
this.lastView = this.view;
|
||||
}
|
||||
},
|
||||
isChanged (val) {
|
||||
if (this.isSelected && this.lastView === this.view && this.view !== null)
|
||||
this.setUnsavedChanges(val);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
refreshStructure: 'workspaces/refreshStructure',
|
||||
setUnsavedChanges: 'workspaces/setUnsavedChanges'
|
||||
}),
|
||||
async getViewData () {
|
||||
if (!this.view) return;
|
||||
this.isQuering = true;
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
view: this.workspace.breadcrumbs.view
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Views.getViewInformations(params);
|
||||
if (status === 'success') {
|
||||
this.originalView = response;
|
||||
this.localView = JSON.parse(JSON.stringify(this.originalView));
|
||||
this.sqlProxy = this.localView.sql;
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.resizeQueryEditor();
|
||||
this.isQuering = false;
|
||||
},
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
this.isSaving = true;
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
view: this.localView
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Views.alterView(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
this.getViewData();
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -9,4 +9,8 @@ export default class {
|
|||
static dropView (params) {
|
||||
return ipcRenderer.invoke('drop-view', params);
|
||||
}
|
||||
|
||||
static alterView (params) {
|
||||
return ipcRenderer.invoke('alter-view', params);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue