feat: views edit

This commit is contained in:
Fabio Di Stasio 2020-12-26 15:37:34 +01:00
parent dcf469ebed
commit 56f2a27f00
7 changed files with 368 additions and 9 deletions

View File

@ -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() };
}
});
};

View File

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

View File

@ -182,8 +182,13 @@ export default {
if (this.autoFocus) {
setTimeout(() => {
this.editor.focus();
this.editor.resize();
}, 20);
}
setTimeout(() => {
this.editor.resize();
}, 20);
}
};
</script>

View File

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

View File

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

View File

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

View File

@ -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);
}
}