多维表格
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

822 lines
25 KiB

<template>
<v-container fluid class="pa-0 ma-0" style="height: 100%">
<v-overlay v-if="isMetaTable" absolute>
<v-alert type="info"> Meta tables are not editable </v-alert>
</v-overlay>
<v-toolbar flat height="42" class="toolbar-border-bottom">
<v-toolbar-title>
<v-breadcrumbs
:items="[
{
text: nodes.env,
disabled: true,
href: '#',
},
{
text: nodes.dbAlias,
disabled: true,
href: '#',
},
{
text: nodes.title + ' (table)',
disabled: true,
href: '#',
},
]"
divider=">"
small
>
<template #divider>
<v-icon small color="grey lighten-2"> forward </v-icon>
</template>
</v-breadcrumbs>
</v-toolbar-title>
<v-spacer />
<x-btn
v-ge="['indexes', 'reload']"
outlined
tooltip="Reload Indexes"
small
color="primary"
class="primary"
@click="mtdRefreshIndexList"
>
<v-icon small left> refresh </v-icon>
<!-- Reload -->
{{ $t('general.reload') }}
</x-btn>
<x-btn
v-if="indexCreationAllowed"
v-ge="['indexes', 'new']"
outlined
tooltip="Add New Index"
icon="mdi-plus"
small
color="primary"
class="primary"
@click="mtdAddIndex"
>
New Index
</x-btn>
<x-btn
v-ge="['indexes', 'delete-table']"
outlined
:tooltip="$t('activity.deleteTable')"
small
color="error "
class="error text-right"
@click="deleteTable('showDialog')"
>
<v-icon small> mdi-delete-outline </v-icon>&nbsp;Delete Table
</x-btn>
<!-- <x-btn outlined tooltip=""-->
<!-- icon-->
<!-- class="text-right">-->
<!-- <v-icon>mdi-help-circle-outline</v-icon>-->
<!-- </x-btn>-->
</v-toolbar>
<!-- <v-row class="pa-0 ma-0">-->
<!-- <v-col class="pr-0 ma-0" cols="8">-->
<splitpanes style="height: calc(100% - 42px)" class="xc-theme">
<pane min-size="33" size="66" style="overflow: auto">
<v-card class="elevation-0" style="">
<v-card-title class="headline justify-center"> Indexes </v-card-title>
<v-col>
<v-skeleton-loader v-if="loading" type="table" />
<v-data-table
v-else
dense
:headers="indexHeaders"
:items="aggregatedIndexes"
footer-props.items-per-page-options="30"
>
<template #item="props">
<tr>
<td>
<v-radio-group
v-model="selected"
v-ge="['indexes', 'radio']"
name="rowSelector"
dense
@change="mtdSelectColumnsForIndex(props.item, props.index)"
>
<v-radio :value="props.index" color="primary" />
</v-radio-group>
</td>
<td>
<v-edit-dialog
v-ge="['indexes', 'name']"
@save="save(props.item)"
@cancel="cancel"
@open="open"
@close="close(props.item)"
>
{{ props.item.key_name }}
<template #input>
<v-text-field
v-model="props.item.key_name"
:disabled="props.index !== selected"
class="body-2"
:rules="[max25chars]"
:label="$t('general.edit')"
single-line
counter
/>
</template>
</v-edit-dialog>
</td>
<td>
<v-checkbox
v-model="props.item.non_unique"
v-ge="['indexes', 'unique']"
:disabled="props.index !== selected"
dense
color="primary lighten-1"
:true-value="0"
:false-value="1"
@change="mtdToggleNonUniqueStatus(props.item)"
/>
</td>
<td>
<v-hover v-slot="{ hover }">
<v-icon
v-ge="['indexes', 'delete']"
small
:color="hover ? 'error' : 'grey'"
@click="mtdIndexDelete('showDialog', props.item, props.index)"
>
mdi-delete
</v-icon>
</v-hover>
<x-btn
v-if="props.item.edited && props.index === selected"
v-ge="['indexes', 'create']"
outlined
tooltip="Index Create"
color="primary"
small
@click="mtdIndexCreate(props.item)"
>
<v-icon small> mdi-content-save </v-icon>&nbsp;Save
</x-btn>
</td>
</tr>
</template>
</v-data-table>
</v-col>
</v-card>
</pane>
<pane min-size="25" size="33" style="overflow: auto">
<v-card class="elevation-0" style="">
<v-card-title class="headline justify-center"> Columns in Index </v-card-title>
<v-col>
<v-skeleton-loader v-if="columnLoading" type="table" />
<v-data-table
v-else
dense
:headers="columnHeaders"
:items="indexColumns"
footer-props.items-per-page-options="30"
>
<template #item="props">
<tr>
<td>
<v-checkbox
v-model="props.item.is_index"
v-ge="['indexes', 'toggle-order']"
dense
color="primary lighten-1"
@change="mtdToggleColumnInIndex(props.item, props.index)"
/>
</td>
<td>{{ props.item.column_name }}</td>
<td>{{ props.item.seq_in_index }}</td>
</tr>
</template>
</v-data-table>
</v-col>
</v-card>
</pane>
</splitpanes>
<dlgLabelSubmitCancel
v-if="showIndexDeleteDialog"
type="error"
:dialog-show="showIndexDeleteDialog"
:actions-mtd="mtdIndexDelete"
heading="Click Submit to Delete the Index"
/>
</v-container>
</template>
<script>
import { mapGetters } from 'vuex';
import { Splitpanes, Pane } from 'splitpanes';
import dlgLabelSubmitCancel from '../../utils/DlgLabelSubmitCancel';
export default {
components: { dlgLabelSubmitCancel, Splitpanes, Pane },
data() {
return {
selected: [],
indexCreationAllowed: true,
applyChanges: true,
columns: [],
aggregatedIndexes: [],
indexes: [],
indexColumns: [],
indexHeaders: [
{
text: '#',
sortable: false,
},
{
text: 'Index',
sortable: false,
},
{
text: 'Unique',
sortable: false,
},
{
text: 'Actions',
sortable: false,
},
],
columnHeaders: [
{
text: '#',
sortable: false,
},
{
text: 'Column',
sortable: false,
},
{
text: 'Sequence',
sortable: false,
},
],
max25chars: v => v.length <= 25 || 'Input too long!',
showIndexDeleteDialog: false,
selectedIndexForDelete: null,
columnLoading: false,
loading: false,
};
},
methods: {
async handleKeyDown({ metaKey, key, altKey, shiftKey, ctrlKey }) {
console.log(metaKey, key, altKey, shiftKey, ctrlKey);
// cmd + s -> save
// cmd + l -> reload
// cmd + n -> new
// cmd + d -> delete
// cmd + enter -> send api
switch ([metaKey, key].join('_')) {
// case 'true_s' :
// await this.applyChanges();
// break;
case 'true_l':
await this.mtdRefreshIndexList();
break;
case 'true_n':
this.addIndex();
break;
case 'true_d':
await this.deleteTable('showDialog');
break;
}
},
async mtdRefreshIndexList() {
try {
this.$store.commit('notification/MutToggleProgressBar', true);
if (this.newTable) {
return;
}
await this.getColumnList();
await this.getIndexesAndCreateAggregatedIndexes();
this.mtdSelectColumnsForIndex();
this.calculateColumnSequenceValue();
} catch (e) {
console.log(e);
} finally {
this.$store.commit('notification/MutToggleProgressBar', false);
}
},
getColumnsForIndexCreation(columns) {
/** ************* START : get columns in this index ***************/
let tempIndexes = JSON.parse(JSON.stringify(columns));
console.log('columns in index', columns);
tempIndexes = tempIndexes.filter(function (a) {
return a.seq_in_index !== 0;
});
console.log('tempIndexes after filter', tempIndexes);
tempIndexes = tempIndexes.sort(function (a, b) {
return a.seq_in_index - b.seq_in_index;
});
console.log('tempIndexes after sort', tempIndexes);
const cols = [];
for (let i = 0; i < tempIndexes.length; i++) {
cols.push(tempIndexes[i].column_name);
}
return cols;
/** ************* END : get columns in this index ***************/
},
async mtdIndexCreate(aggregatedIndex) {
// const client = await this.sqlMgr.projectGetSqlClient({
// env: this.nodes.env,
// dbAlias: this.nodes.dbAlias
// });
console.log(aggregatedIndex);
if (aggregatedIndex.added) {
const cols = this.getColumnsForIndexCreation(aggregatedIndex.columns);
if (!cols.length) {
return;
}
await this.$store.dispatch('sqlMgr/ActSqlOpPlus', [
{
env: this.nodes.env,
dbAlias: this.nodes.dbAlias,
},
'indexCreate',
{
table_name: this.nodes.table_name,
columns: cols,
indexName: aggregatedIndex.key_name,
non_unique: aggregatedIndex.non_unique,
},
]);
} else {
console.log('IndexCreation involves dropping existing index and creating new one');
const colsToRemove = this.getColumnsForIndexCreation(aggregatedIndex.oldColumns);
if (!aggregatedIndex.editedIndexAttr && !colsToRemove.length) {
return;
}
console.log('indexDelete for :', colsToRemove);
// let result = await this.sqlMgr.sqlOpPlus(
// {
// env: this.nodes.env,
// dbAlias: this.nodes.dbAlias
// },
// "indexDelete",
// {
// table_name: this.nodes.table_name,
// columns: colsToRemove,
// indexName: aggregatedIndex.key_name,
// non_unique: aggregatedIndex.non_unique
// }
// );
await this.$store.dispatch('sqlMgr/ActSqlOpPlus', [
{
env: this.nodes.env,
dbAlias: this.nodes.dbAlias,
},
'indexDelete',
{
table_name: this.nodes.table_name,
columns: colsToRemove,
indexName: aggregatedIndex.key_name,
non_unique: aggregatedIndex.non_unique,
non_unique_original: aggregatedIndex.non_unique_original,
},
]);
const colsToCreate = this.getColumnsForIndexCreation(aggregatedIndex.columns);
if (!colsToCreate.length) {
return;
}
console.log('indexCreate for :', colsToCreate);
// result = await this.sqlMgr.sqlOpPlus(
// {
// env: this.nodes.env,
// dbAlias: this.nodes.dbAlias
// },
// "indexCreate",
// {
// tn: this.nodes.table_name,
// columns: colsToCreate,
// indexName: aggregatedIndex.key_name,
// non_unique: aggregatedIndex.non_unique
// }
// );
await this.$store.dispatch('sqlMgr/ActSqlOpPlus', [
{
env: this.nodes.env,
dbAlias: this.nodes.dbAlias,
},
'indexCreate',
{
tn: this.nodes.table_name,
columns: colsToCreate,
indexName: aggregatedIndex.key_name,
non_unique: aggregatedIndex.non_unique,
},
]);
}
await this.mtdRefreshIndexList();
},
async mtdIndexDelete(action = '', aggregatedIndex, index) {
if (action === 'showDialog') {
this.showIndexDeleteDialog = true;
this.selectedIndexForDelete = { aggregatedIndex, index };
} else if (action === 'hideDialog') {
this.showIndexDeleteDialog = false;
this.selectedIndexForDelete = null;
} else {
if (this.selectedIndexForDelete.aggregatedIndex.added === 1) {
this.aggregatedIndexes.splice(this.selectedIndexForDelete.index, 1);
console.log('This was newly added index : deletign without going to lib');
} else {
// const client = await this.sqlMgr.projectGetSqlClient({
// env: this.nodes.env,
// dbAlias: this.nodes.dbAlias
// });
/** ************* START : get columns in this index ***************/
let tempIndexes = JSON.parse(JSON.stringify(this.selectedIndexForDelete.aggregatedIndex.columns));
tempIndexes = tempIndexes.filter(function (a) {
return a.seq_in_index !== 0;
});
console.log('tempIndexes after filter', tempIndexes);
tempIndexes = tempIndexes.sort(function (a, b) {
return a.seq_in_index - b.seq_in_index;
});
console.log('tempIndexes after sort', tempIndexes);
const cols = [];
for (let i = 0; i < tempIndexes.length; i++) {
cols.push(tempIndexes[i].column_name);
}
/** ************* END : get columns in this index ***************/
// const result = await client.indexDelete({
// tn: this.nodes.table_name,
// columns: cols,
// indexName: this.selectedIndexForDelete.aggregatedIndex.key_name
// });
// let result = await this.sqlMgr.sqlOpPlus(
// {
// env: this.nodes.env,
// dbAlias: this.nodes.dbAlias
// },
// "indexDelete",
// {
// tn: this.nodes.table_name,
// columns: cols,
// indexName: this.selectedIndexForDelete.aggregatedIndex.key_name,
// non_unique: this.selectedIndexForDelete.aggregatedIndex.non_unique
// }
// );
await this.$store.dispatch('sqlMgr/ActSqlOpPlus', [
{
env: this.nodes.env,
dbAlias: this.nodes.dbAlias,
},
'indexDelete',
{
tn: this.nodes.table_name,
columns: cols,
indexName: this.selectedIndexForDelete.aggregatedIndex.key_name,
non_unique: this.selectedIndexForDelete.aggregatedIndex.non_unique,
non_unique_original: this.selectedIndexForDelete.aggregatedIndex.non_unique_original,
},
]);
await this.mtdRefreshIndexList();
}
this.showIndexDeleteDialog = false;
this.selectedIndexForDelete = null;
}
},
mtdToggleNonUniqueStatus(aggregatedIndex) {
console.log('mtdToggleNonUniqueStatus', aggregatedIndex);
// aggregatedIndex.non_unique = aggregatedIndex.non_unique ? 0 : 1;
aggregatedIndex.edited = true;
aggregatedIndex.editedIndexAttr = true;
},
mtdToggleColumnInIndex(cn, index) {
console.log('Index being edited:', this.selected);
const aggregatedIndex = this.aggregatedIndexes.find((o, i) => i === this.selected);
console.log('aggregatedIndex on toggle of column', aggregatedIndex);
if (aggregatedIndex.added) {
console.log('This was newly added index');
} else {
console.log('This is an existing index');
aggregatedIndex.edited = true;
if (!aggregatedIndex.oldColumns.length) {
console.log('Taking backup of old columns');
aggregatedIndex.oldColumns = JSON.parse(JSON.stringify(aggregatedIndex.columns));
} else {
console.log('Taking backup of old columns : old columns already exists');
}
}
if (cn.seq_in_index) {
for (let i = 0; i < this.indexColumns.length; i++) {
if (this.indexColumns[i].seq_in_index > cn.seq_in_index) {
this.indexColumns[i].seq_in_index -= 1;
}
}
cn.seq_in_index = 0;
} else {
const max = Math.max.apply(
Math,
this.indexColumns.map(function (o) {
return o.seq_in_index;
})
);
console.log('max value:', max);
cn.seq_in_index = max + 1;
}
},
mtdAddIndex() {
const itemIndex = this.aggregatedIndexes.length;
const index = {
cardinality: 'a',
collation: 'a',
cn: '',
comment: '',
expression: '',
index_comment: '',
index_type: 'BTREE',
key_name: 'idx_' + itemIndex,
non_unique: 1,
null: '',
packed: '',
seq_in_index: 0,
sub_part: '',
table: '',
visible: '',
added: 1,
edited: 1,
columns: JSON.parse(JSON.stringify(this.columns)),
};
this.aggregatedIndexes.push(index);
this.selected = itemIndex;
this.mtdSelectColumnsForIndex(index, itemIndex);
},
mtdResetColumnsIndexes() {
for (let j = 0; j < this.columns.length; j++) {
this.columns[j].seq_in_index = 0;
this.columns[j].is_index = false;
}
},
mtdSelectColumnsForIndex(indexObj = null, index = 0) {
console.log('selecting index', index);
if (!indexObj) {
if (this.aggregatedIndexes.length) {
indexObj = this.aggregatedIndexes[0];
this.selected = index;
// this.$nextTick(() => {
// this.selected = indexObj.key_name + ''
// })
}
}
//
//
// for (let i = 0; i < this.indexes.length; i++) {
// if (indexObj.key_name === this.indexes[i].key_name) {
// console.log(indexObj.key_name, this.indexes[i].key_name);
// for (let j = 0; j < this.columns.length; j++) {
// if (this.indexes[i].column_name === this.columns[j].column_name) {
// indexObj.columns[j].seq_in_index = this.indexes[i].seq_in_index;
// indexObj.columns[j].is_index = true;
// console.log("Column is in index: ", this.columns[j]);
// }
// }
// }
// }
this.indexColumns = indexObj.columns;
},
calculateColumnSequenceValue() {
this.aggregatedIndexes.forEach(indexObj => {
for (let i = 0; i < this.indexes.length; i++) {
if (indexObj.key_name === this.indexes[i].key_name) {
console.log(indexObj.key_name, this.indexes[i].key_name);
for (let j = 0; j < this.columns.length; j++) {
if (this.indexes[i].column_name === this.columns[j].column_name) {
indexObj.columns[j].seq_in_index = this.indexes[i].seq_in_index;
indexObj.columns[j].is_index = true;
console.log('Column is in index: ', this.columns[j]);
}
}
}
}
});
},
createAggregatedIndexesList(indexes) {
for (let i = 0; i < Indexes.length; i++) {
const index = { ...Indexes[i] };
let found = 0;
for (let j = 0; j < this.aggregatedIndexes.length; j++) {
const aggregatedIndex = this.aggregatedIndexes[j];
if (aggregatedIndex.key_name === index.key_name) {
found = 1;
break;
}
}
if (!found) {
index.edited = false;
index.columns = [];
index.oldColumns = [];
index.columns = JSON.parse(JSON.stringify(this.columns));
this.aggregatedIndexes.push(index);
}
}
},
async getIndexesAndCreateAggregatedIndexes() {
this.loading = true;
if (this.newTable) {
return;
}
this.indexes = [];
this.aggregatedIndexes = [];
// console.log("env: this.nodes.env", this.nodes.env, this.nodes.dbAlias);
// const client = await this.sqlMgr.projectGetSqlClient({
// env: this.nodes.env,
// dbAlias: this.nodes.dbAlias
// });
// const result = await client.indexList({
// tn: this.nodes.table_name
// });
// const result = await this.sqlMgr.sqlOp({
// env: this.nodes.env,
// dbAlias: this.nodes.dbAlias
// }, 'indexList', {
// tn: this.nodes.table_name
// })
const result = await this.$store.dispatch('sqlMgr/ActSqlOp', [
{
env: this.nodes.env,
dbAlias: this.nodes.dbAlias,
},
'indexList',
{
tn: this.nodes.table_name,
},
]);
this.createAggregatedIndexesList(result.data.list);
// this.indexes = JSON.parse(JSON.stringify(result.data.list));
this.indexes = [];
this.indexes = result.data.list;
console.log('Indexes List in table', this.indexes);
console.log('Aggregated Indexes in table', this.aggregatedIndexes);
this.loading = false;
},
async getColumnList() {
this.columnLoading = true;
// console.log("env: this.nodes.env", this.nodes.env, this.nodes.dbAlias);
// const client = await this.sqlMgr.projectGetSqlClient({
// env: this.nodes.env,
// dbAlias: this.nodes.dbAlias
// });
// const result = await client.columnList({
// tn: this.nodes.table_name
// });
// const result = await this.sqlMgr.sqlOp({
// env: this.nodes.env,
// dbAlias: this.nodes.dbAlias
// }, 'columnList', {
// tn: this.nodes.table_name
// })
const result = await this.$store.dispatch('sqlMgr/ActSqlOp', [
{
env: this.nodes.env,
dbAlias: this.nodes.dbAlias,
},
'columnList',
{
tn: this.nodes.table_name,
},
]);
this.columns = JSON.parse(JSON.stringify(result.data.list));
for (let i = 0; i < this.columns.length; i++) {
this.columns[i].seq_in_index = 0;
this.columns[i].is_index = false;
}
console.log('Columns in table:', this.columns);
this.columnLoading = false;
},
save(aggregatedIndex) {
aggregatedIndex.edited = true;
this.snack = true;
this.snackColor = 'success';
this.snackText = 'Data saved';
},
cancel() {
this.snack = true;
this.snackColor = 'error';
this.snackText = 'Canceled';
console.log('Dialog Canceled');
},
open() {
this.snack = true;
this.snackColor = 'info';
this.snackText = 'Dialog opened';
},
close(aggregatedIndex) {
aggregatedIndex.edited = true;
},
},
computed: {
...mapGetters({ sqlMgr: 'sqlMgr/sqlMgr' }),
},
beforeCreated() {},
watch: {
columns() {
return this.columns;
},
aggregatedIndexes(v, ov) {
console.log(v, ov);
},
},
async created() {
await this.mtdRefreshIndexList();
},
mounted() {},
beforeDestroy() {},
destroy() {},
directives: {},
validate({ params }) {
return true;
},
head() {
return {};
},
props: ['nodes', 'newTable', 'deleteTable', 'isMetaTable'],
};
</script>
<style scoped></style>
<!--
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd
*
* @author Naveen MR <oof1lab@gmail.com>
* @author Pranav C Balan <pranavxc@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
-->