mirror of
				https://github.com/Fabio286/antares.git
				synced 2025-06-05 21:59:22 +02:00 
			
		
		
		
	Merge pull request #890 from antares-sql/feat/mysql-check-support
Feat: MySQL check support
This commit is contained in:
		| @@ -55,6 +55,7 @@ export const defaults: Customizations = { | ||||
|    tableArray: false, | ||||
|    tableRealCount: false, | ||||
|    tableDuplicate: false, | ||||
|    tableCheck: false, | ||||
|    viewSettings: false, | ||||
|    triggerSettings: false, | ||||
|    triggerFunctionSettings: false, | ||||
|   | ||||
| @@ -47,6 +47,7 @@ export const customizations: Customizations = { | ||||
|    tableTruncateDisableFKCheck: true, | ||||
|    tableDuplicate: true, | ||||
|    tableDdl: true, | ||||
|    tableCheck: true, | ||||
|    viewAdd: true, | ||||
|    triggerAdd: true, | ||||
|    routineAdd: true, | ||||
|   | ||||
| @@ -159,6 +159,13 @@ export interface TableForeign { | ||||
|    oldName?: string; | ||||
| } | ||||
|  | ||||
| export interface TableCheck { | ||||
|    // eslint-disable-next-line camelcase | ||||
|    _antares_id?: string; | ||||
|    name: string; | ||||
|    clause: string; | ||||
| } | ||||
|  | ||||
| export interface CreateTableParams { | ||||
|    /** Connection UID */ | ||||
|    uid?: string; | ||||
| @@ -166,6 +173,7 @@ export interface CreateTableParams { | ||||
|    fields: TableField[]; | ||||
|    foreigns: TableForeign[]; | ||||
|    indexes: TableIndex[]; | ||||
|    checks: TableCheck[]; | ||||
|    options: TableOptions; | ||||
| } | ||||
|  | ||||
| @@ -193,6 +201,11 @@ export interface AlterTableParams { | ||||
|       changes: TableForeign[]; | ||||
|       deletions: TableForeign[]; | ||||
|    }; | ||||
|    checkChanges: { | ||||
|       additions: TableCheck[]; | ||||
|       changes: TableCheck[]; | ||||
|       deletions: TableCheck[]; | ||||
|    }; | ||||
|    options: TableOptions; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -43,6 +43,7 @@ export interface Customizations { | ||||
|    tableArray?: boolean; | ||||
|    tableRealCount?: boolean; | ||||
|    tableTruncateDisableFKCheck?: boolean; | ||||
|    tableCheck?: boolean; | ||||
|    tableDdl?: boolean; | ||||
|    viewAdd?: boolean; | ||||
|    viewSettings?: boolean; | ||||
|   | ||||
| @@ -87,6 +87,19 @@ export default (connections: Record<string, antares.Client>) => { | ||||
|       } | ||||
|    }); | ||||
|  | ||||
|    ipcMain.handle('get-table-checks', async (event, params) => { | ||||
|       if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; | ||||
|  | ||||
|       try { | ||||
|          const result = await connections[params.uid].getTableChecks(params); | ||||
|  | ||||
|          return { status: 'success', response: result }; | ||||
|       } | ||||
|       catch (err) { | ||||
|          return { status: 'error', response: err.toString() }; | ||||
|       } | ||||
|    }); | ||||
|  | ||||
|    ipcMain.handle('get-table-ddl', async (event, params) => { | ||||
|       if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; | ||||
|  | ||||
|   | ||||
| @@ -189,6 +189,10 @@ export abstract class BaseClient { | ||||
|       throw new Error('Method "dropSchema" not implemented'); | ||||
|    } | ||||
|  | ||||
|    getTableChecks (...args: any) { | ||||
|       throw new Error('Method "getTableDll" not implemented'); | ||||
|    } | ||||
|  | ||||
|    getTableDll (...args: any) { | ||||
|       throw new Error('Method "getTableDll" not implemented'); | ||||
|    } | ||||
|   | ||||
| @@ -161,6 +161,8 @@ export class MySQLClient extends BaseClient { | ||||
|  | ||||
|             this._ssh = new SSH2Promise({ | ||||
|                ...this._params.ssh, | ||||
|                reconnect: true, | ||||
|                reconnectTries: 3, | ||||
|                debug: process.env.NODE_ENV !== 'production' ? (s) => console.log(s) : null | ||||
|             }); | ||||
|  | ||||
| @@ -689,6 +691,34 @@ export class MySQLClient extends BaseClient { | ||||
|       return rows.length ? rows[0].count : 0; | ||||
|    } | ||||
|  | ||||
|    async getTableChecks ({ schema, table }: { schema: string; table: string }): Promise<antares.TableCheck[]> { | ||||
|       const { rows } = await this.raw(` | ||||
|             SELECT  | ||||
|                CONSTRAINT_NAME as name,  | ||||
|                CHECK_CLAUSE as clausole  | ||||
|             FROM information_schema.CHECK_CONSTRAINTS  | ||||
|             WHERE CONSTRAINT_SCHEMA = "${schema}"  | ||||
|             AND CONSTRAINT_NAME IN ( | ||||
|                SELECT | ||||
|                   CONSTRAINT_NAME | ||||
|                FROM | ||||
|                   information_schema.TABLE_CONSTRAINTS | ||||
|                WHERE | ||||
|                   TABLE_SCHEMA = "${schema}" | ||||
|                   AND TABLE_NAME = "${table}" | ||||
|                   AND CONSTRAINT_TYPE = 'CHECK' | ||||
|             ) | ||||
|          `); | ||||
|  | ||||
|       if (rows.length) { | ||||
|          return rows.map(row => ({ | ||||
|             name: row.name, | ||||
|             clause: row.clausole | ||||
|          })); | ||||
|       } | ||||
|       return []; | ||||
|    } | ||||
|  | ||||
|    async getTableOptions ({ schema, table }: { schema: string; table: string }) { | ||||
|       /* eslint-disable camelcase */ | ||||
|       interface TableOptionsResult { | ||||
| @@ -865,11 +895,13 @@ export class MySQLClient extends BaseClient { | ||||
|          fields, | ||||
|          foreigns, | ||||
|          indexes, | ||||
|          checks, | ||||
|          options | ||||
|       } = params; | ||||
|       const newColumns: string[] = []; | ||||
|       const newIndexes: string[] = []; | ||||
|       const newForeigns: string[] = []; | ||||
|       const newChecks: string[] = []; | ||||
|  | ||||
|       let sql = `CREATE TABLE \`${schema}\`.\`${options.name}\``; | ||||
|  | ||||
| @@ -910,7 +942,13 @@ export class MySQLClient extends BaseClient { | ||||
|          newForeigns.push(`CONSTRAINT \`${foreign.constraintName}\` FOREIGN KEY (\`${foreign.field}\`) REFERENCES \`${foreign.refTable}\` (\`${foreign.refField}\`) ON UPDATE ${foreign.onUpdate} ON DELETE ${foreign.onDelete}`); | ||||
|       }); | ||||
|  | ||||
|       sql = `${sql} (${[...newColumns, ...newIndexes, ...newForeigns].join(', ')}) COMMENT='${options.comment}', COLLATE='${options.collation}', ENGINE=${options.engine}`; | ||||
|       // ADD TABLE CHECKS | ||||
|       checks.forEach(check => { | ||||
|          if (!check.clause.trim().length) return; | ||||
|          newChecks.push(`${check.name ? `CONSTRAINT \`${check.name}\` ` : ''}CHECK (${check.clause})`); | ||||
|       }); | ||||
|  | ||||
|       sql = `${sql} (${[...newColumns, ...newIndexes, ...newForeigns, ...newChecks].join(', ')}) COMMENT='${options.comment}', COLLATE='${options.collation}', ENGINE=${options.engine}`; | ||||
|  | ||||
|       return await this.raw(sql); | ||||
|    } | ||||
| @@ -924,6 +962,7 @@ export class MySQLClient extends BaseClient { | ||||
|          changes, | ||||
|          indexChanges, | ||||
|          foreignChanges, | ||||
|          checkChanges, | ||||
|          options | ||||
|       } = params; | ||||
|  | ||||
| @@ -931,6 +970,7 @@ export class MySQLClient extends BaseClient { | ||||
|       const alterColumnsAdd: string[] = []; | ||||
|       const alterColumnsChange: string[] = []; | ||||
|       const alterColumnsDrop: string[] = []; | ||||
|       const alterQueryes: string[] = []; | ||||
|  | ||||
|       // OPTIONS | ||||
|       if ('comment' in options) alterColumnsChange.push(`COMMENT='${options.comment}'`); | ||||
| @@ -976,6 +1016,12 @@ export class MySQLClient extends BaseClient { | ||||
|          alterColumnsAdd.push(`ADD CONSTRAINT \`${addition.constraintName}\` FOREIGN KEY (\`${addition.field}\`) REFERENCES \`${addition.refTable}\` (\`${addition.refField}\`) ON UPDATE ${addition.onUpdate} ON DELETE ${addition.onDelete}`); | ||||
|       }); | ||||
|  | ||||
|       // ADD TABLE CHECKS | ||||
|       checkChanges.additions.forEach(addition => { | ||||
|          if (!addition.clause.trim().length) return; | ||||
|          alterColumnsAdd.push(`ADD ${addition.name ? `CONSTRAINT \`${addition.name}\` ` : ''}CHECK (${addition.clause})`); | ||||
|       }); | ||||
|  | ||||
|       // CHANGE FIELDS | ||||
|       changes.forEach(change => { | ||||
|          const typeInfo = this.getTypeInfo(change.type); | ||||
| @@ -987,9 +1033,9 @@ export class MySQLClient extends BaseClient { | ||||
|             ${change.zerofill ? 'ZEROFILL' : ''} | ||||
|             ${change.nullable ? 'NULL' : 'NOT NULL'} | ||||
|             ${change.autoIncrement ? 'AUTO_INCREMENT' : ''} | ||||
|             ${change.collation ? `COLLATE ${change.collation}` : ''} | ||||
|             ${change.default !== null ? `DEFAULT ${change.default || '\'\''}` : ''} | ||||
|             ${change.comment ? `COMMENT '${change.comment}'` : ''} | ||||
|             ${change.collation ? `COLLATE ${change.collation}` : ''} | ||||
|             ${change.onUpdate ? `ON UPDATE ${change.onUpdate}` : ''} | ||||
|             ${change.after ? `AFTER \`${change.after}\`` : 'FIRST'}`); | ||||
|       }); | ||||
| @@ -1020,6 +1066,13 @@ export class MySQLClient extends BaseClient { | ||||
|          alterColumnsChange.push(`ADD CONSTRAINT \`${change.constraintName}\` FOREIGN KEY (\`${change.field}\`) REFERENCES \`${change.refTable}\` (\`${change.refField}\`) ON UPDATE ${change.onUpdate} ON DELETE ${change.onDelete}`); | ||||
|       }); | ||||
|  | ||||
|       // CHANGE CHECK TABLE | ||||
|       checkChanges.changes.forEach(change => { | ||||
|          if (!change.clause.trim().length) return; | ||||
|          alterQueryes.push(`${sql} DROP CONSTRAINT \`${change.name}\``); | ||||
|          alterQueryes.push(`${sql} ADD ${change.name ? `CONSTRAINT \`${change.name}\` ` : ''}CHECK (${change.clause})`); | ||||
|       }); | ||||
|  | ||||
|       // DROP FIELDS | ||||
|       deletions.forEach(deletion => { | ||||
|          alterColumnsDrop.push(`DROP COLUMN \`${deletion.name}\``); | ||||
| @@ -1038,7 +1091,11 @@ export class MySQLClient extends BaseClient { | ||||
|          alterColumnsDrop.push(`DROP FOREIGN KEY \`${deletion.constraintName}\``); | ||||
|       }); | ||||
|  | ||||
|       const alterQueryes = []; | ||||
|       // DROP CHECK TABLE | ||||
|       checkChanges.deletions.forEach(deletion => { | ||||
|          alterQueryes.push(`${sql} DROP CONSTRAINT \`${deletion.name}\``); | ||||
|       }); | ||||
|  | ||||
|       if (alterColumnsAdd.length) alterQueryes.push(sql+alterColumnsAdd.join(', ')); | ||||
|       if (alterColumnsChange.length) alterQueryes.push(sql+alterColumnsChange.join(', ')); | ||||
|       if (alterColumnsDrop.length) alterQueryes.push(sql+alterColumnsDrop.join(', ')); | ||||
|   | ||||
| @@ -168,6 +168,8 @@ export class PostgreSQLClient extends BaseClient { | ||||
|          try { | ||||
|             this._ssh = new SSH2Promise({ | ||||
|                ...this._params.ssh, | ||||
|                reconnect: true, | ||||
|                reconnectTries: 3, | ||||
|                debug: process.env.NODE_ENV !== 'production' ? (s) => console.log(s) : null | ||||
|             }); | ||||
|  | ||||
|   | ||||
| @@ -72,6 +72,19 @@ | ||||
|                   /> | ||||
|                   <span>{{ t('database.foreignKeys') }}</span> | ||||
|                </button> | ||||
|                <button | ||||
|                   class="btn btn-dark btn-sm ml-2 mr-0" | ||||
|                   :disabled="isSaving || !localFields.length" | ||||
|                   :title="t('database.manageTableChecks')" | ||||
|                   @click="showTableChecksModal" | ||||
|                > | ||||
|                   <BaseIcon | ||||
|                      class="mr-1" | ||||
|                      icon-name="mdiTableCheck" | ||||
|                      :size="24" | ||||
|                   /> | ||||
|                   <span>{{ t('database.tableChecks') }}</span> | ||||
|                </button> | ||||
|             </div> | ||||
|             <div class="workspace-query-info"> | ||||
|                <div class="d-flex" :title="t('database.schema')"> | ||||
| @@ -183,11 +196,19 @@ | ||||
|          @hide="hideForeignModal" | ||||
|          @foreigns-update="foreignsUpdate" | ||||
|       /> | ||||
|       <WorkspaceTabPropsTableChecksModal | ||||
|          v-if="isTableChecksModal" | ||||
|          :local-checks="localTableChecks" | ||||
|          table="new" | ||||
|          :workspace="workspace" | ||||
|          @hide="hideTableChecksModal" | ||||
|          @checks-update="checksUpdate" | ||||
|       /> | ||||
|    </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { ConnectionParams, TableField, TableForeign, TableIndex, TableOptions } from 'common/interfaces/antares'; | ||||
| import { ConnectionParams, TableCheck, TableField, TableForeign, TableIndex, TableOptions } from 'common/interfaces/antares'; | ||||
| import { uidGen } from 'common/libs/uidGen'; | ||||
| import { ipcRenderer } from 'electron'; | ||||
| import { storeToRefs } from 'pinia'; | ||||
| @@ -198,6 +219,7 @@ import BaseIcon from '@/components/BaseIcon.vue'; | ||||
| import BaseLoader from '@/components/BaseLoader.vue'; | ||||
| import BaseSelect from '@/components/BaseSelect.vue'; | ||||
| import WorkspaceTabNewTableEmptyState from '@/components/WorkspaceTabNewTableEmptyState.vue'; | ||||
| import WorkspaceTabPropsTableChecksModal from '@/components/WorkspaceTabPropsTableChecksModal.vue'; | ||||
| import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields.vue'; | ||||
| import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal.vue'; | ||||
| import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal.vue'; | ||||
| @@ -236,12 +258,16 @@ const isLoading = ref(false); | ||||
| const isSaving = ref(false); | ||||
| const isIndexesModal = ref(false); | ||||
| const isForeignModal = ref(false); | ||||
| const isTableChecksModal = 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 originalTableChecks: Ref<TableCheck[]> = ref([]); | ||||
| const localTableChecks: Ref<TableCheck[]> = ref([]); | ||||
| const tableOptions: Ref<TableOptions> = ref(null); | ||||
| const localOptions: Ref<TableOptions> = ref(null); | ||||
| const newFieldsCounter = ref(0); | ||||
| @@ -274,6 +300,7 @@ 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(originalTableChecks.value) !== JSON.stringify(localTableChecks.value) || | ||||
|       JSON.stringify(tableOptions.value) !== JSON.stringify(localOptions.value); | ||||
| }); | ||||
|  | ||||
| @@ -291,6 +318,7 @@ const saveChanges = async () => { | ||||
|       fields: localFields.value, | ||||
|       foreigns: localKeyUsage.value, | ||||
|       indexes: localIndexes.value, | ||||
|       checks: localTableChecks.value, | ||||
|       options: localOptions.value | ||||
|    }; | ||||
|  | ||||
| @@ -326,6 +354,7 @@ const clearChanges = () => { | ||||
|    localFields.value = JSON.parse(JSON.stringify(originalFields.value)); | ||||
|    localIndexes.value = JSON.parse(JSON.stringify(originalIndexes.value)); | ||||
|    localKeyUsage.value = JSON.parse(JSON.stringify(originalKeyUsage.value)); | ||||
|    localTableChecks.value = JSON.parse(JSON.stringify(originalTableChecks.value)); | ||||
|  | ||||
|    tableOptions.value = { | ||||
|       name: '', | ||||
| @@ -446,10 +475,22 @@ const hideForeignModal = () => { | ||||
|    isForeignModal.value = false; | ||||
| }; | ||||
|  | ||||
| const showTableChecksModal = () => { | ||||
|    isTableChecksModal.value = true; | ||||
| }; | ||||
|  | ||||
| const hideTableChecksModal = () => { | ||||
|    isTableChecksModal.value = false; | ||||
| }; | ||||
|  | ||||
| const foreignsUpdate = (foreigns: TableForeign[]) => { | ||||
|    localKeyUsage.value = foreigns; | ||||
| }; | ||||
|  | ||||
| const checksUpdate = (checks: TableCheck[]) => { | ||||
|    localTableChecks.value = checks; | ||||
| }; | ||||
|  | ||||
| const saveContentListener = () => { | ||||
|    const hasModalOpen = !!document.querySelectorAll('.modal.active').length; | ||||
|    if (props.isSelected && !hasModalOpen && isChanged.value) | ||||
|   | ||||
| @@ -62,7 +62,7 @@ | ||||
|                <button | ||||
|                   class="btn btn-dark btn-sm mr-0" | ||||
|                   :disabled="isSaving" | ||||
|                   :title="t('database.manageIndexes')" | ||||
|                   :title="t('database.manageForeignKeys')" | ||||
|                   @click="showForeignModal" | ||||
|                > | ||||
|                   <BaseIcon | ||||
| @@ -72,6 +72,19 @@ | ||||
|                   /> | ||||
|                   <span>{{ t('database.foreignKeys') }}</span> | ||||
|                </button> | ||||
|                <button | ||||
|                   class="btn btn-dark btn-sm ml-2 mr-0" | ||||
|                   :disabled="isSaving" | ||||
|                   :title="t('database.manageTableChecks')" | ||||
|                   @click="showTableChecksModal" | ||||
|                > | ||||
|                   <BaseIcon | ||||
|                      class="mr-1" | ||||
|                      icon-name="mdiTableCheck" | ||||
|                      :size="24" | ||||
|                   /> | ||||
|                   <span>{{ t('database.tableChecks') }}</span> | ||||
|                </button> | ||||
|  | ||||
|                <div class="divider-vert py-3" /> | ||||
|  | ||||
| @@ -218,11 +231,19 @@ | ||||
|          :workspace="workspace" | ||||
|          @hide="hideDdlModal" | ||||
|       /> | ||||
|       <WorkspaceTabPropsTableChecksModal | ||||
|          v-if="isTableChecksModal" | ||||
|          :local-checks="localTableChecks" | ||||
|          :table="table" | ||||
|          :workspace="workspace" | ||||
|          @hide="hideTableChecksModal" | ||||
|          @checks-update="checksUpdate" | ||||
|       /> | ||||
|    </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { AlterTableParams, TableField, TableForeign, TableIndex, TableInfos, TableOptions } from 'common/interfaces/antares'; | ||||
| import { AlterTableParams, TableCheck, TableField, TableForeign, TableIndex, TableInfos, TableOptions } from 'common/interfaces/antares'; | ||||
| import { uidGen } from 'common/libs/uidGen'; | ||||
| import { ipcRenderer } from 'electron'; | ||||
| import { storeToRefs } from 'pinia'; | ||||
| @@ -232,6 +253,7 @@ import { useI18n } from 'vue-i18n'; | ||||
| import BaseIcon from '@/components/BaseIcon.vue'; | ||||
| import BaseLoader from '@/components/BaseLoader.vue'; | ||||
| import BaseSelect from '@/components/BaseSelect.vue'; | ||||
| import WorkspaceTabPropsTableChecksModal from '@/components/WorkspaceTabPropsTableChecksModal.vue'; | ||||
| import WorkspaceTabPropsTableDdlModal from '@/components/WorkspaceTabPropsTableDdlModal.vue'; | ||||
| import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields.vue'; | ||||
| import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal.vue'; | ||||
| @@ -273,13 +295,17 @@ const isLoading = ref(false); | ||||
| const isSaving = ref(false); | ||||
| const isIndexesModal = ref(false); | ||||
| const isForeignModal = ref(false); | ||||
| const isTableChecksModal = ref(false); | ||||
| const isDdlModal = 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 originalTableChecks: Ref<TableCheck[]> = ref([]); | ||||
| const localTableChecks: Ref<TableCheck[]> = ref([]); | ||||
| const tableOptions: Ref<TableOptions> = ref(null); | ||||
| const localOptions: Ref<TableOptions> = ref({} as TableOptions); | ||||
| const lastTable = ref(null); | ||||
| @@ -307,6 +333,7 @@ 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(originalTableChecks.value) !== JSON.stringify(localTableChecks.value) || | ||||
|       JSON.stringify(tableOptions.value) !== JSON.stringify(localOptions.value); | ||||
| }); | ||||
|  | ||||
| @@ -430,6 +457,27 @@ const getFieldsData = async () => { | ||||
|       addNotification({ status: 'error', message: err.stack }); | ||||
|    } | ||||
|  | ||||
|    if (workspace.value.customizations.tableCheck) { | ||||
|       try { // Table checks | ||||
|          const { status, response } = await Tables.getTableChecks(params); | ||||
|  | ||||
|          if (status === 'success') { | ||||
|             originalTableChecks.value = response.map((check: TableCheck) => { | ||||
|                return { | ||||
|                   _antares_id: uidGen(), | ||||
|                   ...check | ||||
|                }; | ||||
|             }); | ||||
|             localTableChecks.value = JSON.parse(JSON.stringify(originalTableChecks.value)); | ||||
|          } | ||||
|          else | ||||
|             addNotification({ status: 'error', message: response }); | ||||
|       } | ||||
|       catch (err) { | ||||
|          addNotification({ status: 'error', message: err.stack }); | ||||
|       } | ||||
|    } | ||||
|  | ||||
|    isLoading.value = false; | ||||
| }; | ||||
|  | ||||
| @@ -527,6 +575,33 @@ const saveChanges = async () => { | ||||
|    // Foreigns Deletions | ||||
|    foreignChanges.deletions = originalKeyUsage.value.filter(foreign => !localForeignIDs.includes(foreign._antares_id)); | ||||
|  | ||||
|    // CHECKS | ||||
|    const checkChanges = { | ||||
|       additions: [] as TableCheck[], | ||||
|       changes: [] as TableCheck[], | ||||
|       deletions: [] as TableCheck[] | ||||
|    }; | ||||
|    const originalCheckIDs = originalTableChecks.value.reduce((acc, curr) => [...acc, curr._antares_id], []); | ||||
|    const localCheckIDs = localTableChecks.value.reduce((acc, curr) => [...acc, curr._antares_id], []); | ||||
|  | ||||
|    // Check Additions | ||||
|    checkChanges.additions = localTableChecks.value.filter(check => !originalCheckIDs.includes(check._antares_id)); | ||||
|  | ||||
|    // Check Changes | ||||
|    originalTableChecks.value.forEach(originalCheck => { | ||||
|       const lI = localTableChecks.value.findIndex(localCheck => localCheck._antares_id === originalCheck._antares_id); | ||||
|       if (JSON.stringify(originalCheck) !== JSON.stringify(localTableChecks.value[lI])) { | ||||
|          if (localTableChecks.value[lI]) { | ||||
|             checkChanges.changes.push({ | ||||
|                ...localTableChecks.value[lI] | ||||
|             }); | ||||
|          } | ||||
|       } | ||||
|    }); | ||||
|  | ||||
|    // Check Deletions | ||||
|    checkChanges.deletions = originalTableChecks.value.filter(check => !localCheckIDs.includes(check._antares_id)); | ||||
|  | ||||
|    // ALTER | ||||
|    const params = { | ||||
|       uid: props.connection.uid, | ||||
| @@ -543,6 +618,7 @@ const saveChanges = async () => { | ||||
|       deletions, | ||||
|       indexChanges, | ||||
|       foreignChanges, | ||||
|       checkChanges, | ||||
|       options | ||||
|    } as unknown as AlterTableParams; | ||||
|  | ||||
| @@ -583,6 +659,7 @@ const clearChanges = () => { | ||||
|    localFields.value = JSON.parse(JSON.stringify(originalFields.value)); | ||||
|    localIndexes.value = JSON.parse(JSON.stringify(originalIndexes.value)); | ||||
|    localKeyUsage.value = JSON.parse(JSON.stringify(originalKeyUsage.value)); | ||||
|    localTableChecks.value = JSON.parse(JSON.stringify(originalTableChecks.value)); | ||||
|    localOptions.value = JSON.parse(JSON.stringify(tableOptions.value)); | ||||
|    newFieldsCounter.value = 0; | ||||
| }; | ||||
| @@ -702,6 +779,14 @@ const hideForeignModal = () => { | ||||
|    isForeignModal.value = false; | ||||
| }; | ||||
|  | ||||
| const showTableChecksModal = () => { | ||||
|    isTableChecksModal.value = true; | ||||
| }; | ||||
|  | ||||
| const hideTableChecksModal = () => { | ||||
|    isTableChecksModal.value = false; | ||||
| }; | ||||
|  | ||||
| const showDdlModal = () => { | ||||
|    isDdlModal.value = true; | ||||
| }; | ||||
| @@ -714,6 +799,10 @@ const foreignsUpdate = (foreigns: TableForeign[]) => { | ||||
|    localKeyUsage.value = foreigns; | ||||
| }; | ||||
|  | ||||
| const checksUpdate = (checks: TableCheck[]) => { | ||||
|    localTableChecks.value = checks; | ||||
| }; | ||||
|  | ||||
| const saveContentListener = () => { | ||||
|    const hasModalOpen = !!document.querySelectorAll('.modal.active').length; | ||||
|    if (props.isSelected && !hasModalOpen && isChanged.value) | ||||
|   | ||||
							
								
								
									
										268
									
								
								src/renderer/components/WorkspaceTabPropsTableChecksModal.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								src/renderer/components/WorkspaceTabPropsTableChecksModal.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,268 @@ | ||||
| <template> | ||||
|    <ConfirmModal | ||||
|       :confirm-text="t('general.confirm')" | ||||
|       size="medium" | ||||
|       class="options-modal" | ||||
|       @confirm="confirmChecksChange" | ||||
|       @hide="$emit('hide')" | ||||
|    > | ||||
|       <template #header> | ||||
|          <div class="d-flex"> | ||||
|             <BaseIcon | ||||
|                class="mr-1" | ||||
|                icon-name="mdiTableCheck" | ||||
|                :size="24" | ||||
|             /> | ||||
|             <span class="cut-text">{{ t('database.tableChecks') }} "{{ table }}"</span> | ||||
|          </div> | ||||
|       </template> | ||||
|       <template #body> | ||||
|          <div class="columns col-gapless"> | ||||
|             <div class="column col-5"> | ||||
|                <div class="panel" :style="{ height: modalInnerHeight + 'px'}"> | ||||
|                   <div class="panel-header pt-0 pl-0"> | ||||
|                      <div class="d-flex"> | ||||
|                         <button class="btn btn-dark btn-sm d-flex" @click="addCheck"> | ||||
|                            <BaseIcon | ||||
|                               class="mr-1" | ||||
|                               icon-name="mdiCheckboxMarkedCirclePlusOutline" | ||||
|                               :size="24" | ||||
|                            /> | ||||
|                            <span>{{ t('general.add') }}</span> | ||||
|                         </button> | ||||
|                         <button | ||||
|                            class="btn btn-dark btn-sm d-flex ml-2 mr-0" | ||||
|                            :title="t('database.clearChanges')" | ||||
|                            :disabled="!isChanged" | ||||
|                            @click.prevent="clearChanges" | ||||
|                         > | ||||
|                            <BaseIcon | ||||
|                               class="mr-1" | ||||
|                               icon-name="mdiDeleteSweep" | ||||
|                               :size="24" | ||||
|                            /> | ||||
|                            <span>{{ t('general.clear') }}</span> | ||||
|                         </button> | ||||
|                      </div> | ||||
|                   </div> | ||||
|                   <div ref="checksPanel" class="panel-body p-0 pr-1"> | ||||
|                      <div | ||||
|                         v-for="check in checksProxy" | ||||
|                         :key="check._antares_id" | ||||
|                         class="tile tile-centered c-hand mb-1 p-1" | ||||
|                         :class="{'selected-element': selectedCheckID === check._antares_id}" | ||||
|                         @click="selectCheck($event, check._antares_id)" | ||||
|                      > | ||||
|                         <div class="tile-icon"> | ||||
|                            <div> | ||||
|                               <BaseIcon | ||||
|                                  class="mt-2 column-key" | ||||
|                                  icon-name="mdiCheckboxMarkedCircleOutline" | ||||
|                                  :size="24" | ||||
|                               /> | ||||
|                            </div> | ||||
|                         </div> | ||||
|                         <div class="tile-content"> | ||||
|                            <div class="tile-title"> | ||||
|                               {{ check.name }} | ||||
|                            </div> | ||||
|                            <small class="tile-subtitle text-gray d-inline-block cut-text" style="width: 100%;">{{ check.clause }}</small> | ||||
|                         </div> | ||||
|                         <div class="tile-action"> | ||||
|                            <button | ||||
|                               class="btn btn-link remove-field p-0 mr-2" | ||||
|                               :title="t('general.delete')" | ||||
|                               @click.prevent="removeCheck(check._antares_id)" | ||||
|                            > | ||||
|                               <BaseIcon | ||||
|                                  icon-name="mdiClose" | ||||
|                                  :size="18" | ||||
|                                  class="mt-2" | ||||
|                               /> | ||||
|                            </button> | ||||
|                         </div> | ||||
|                      </div> | ||||
|                   </div> | ||||
|                </div> | ||||
|             </div> | ||||
|  | ||||
|             <div class="column col-7 pl-2 editor-col"> | ||||
|                <form | ||||
|                   v-if="selectedCheckObj" | ||||
|                   :style="{ height: modalInnerHeight + 'px'}" | ||||
|                   class="form-horizontal" | ||||
|                > | ||||
|                   <div class="form-group"> | ||||
|                      <label class="form-label col-3"> | ||||
|                         {{ t('general.name') }} | ||||
|                      </label> | ||||
|                      <div class="column"> | ||||
|                         <input | ||||
|                            v-model="selectedCheckObj.name" | ||||
|                            class="form-input" | ||||
|                            type="text" | ||||
|                         > | ||||
|                      </div> | ||||
|                   </div> | ||||
|                   <div class="form-group"> | ||||
|                      <label class="form-label col-3"> | ||||
|                         {{ t('database.checkClause') }} | ||||
|                      </label> | ||||
|                      <div class="column"> | ||||
|                         <textarea | ||||
|                            v-model="selectedCheckObj.clause" | ||||
|                            class="form-input" | ||||
|                            style="resize: vertical;" | ||||
|                            rows="5" | ||||
|                         /> | ||||
|                      </div> | ||||
|                   </div> | ||||
|                </form> | ||||
|                <div v-if="!checksProxy.length" class="empty"> | ||||
|                   <div class="empty-icon"> | ||||
|                      <BaseIcon | ||||
|                         class="mr-1" | ||||
|                         icon-name="mdiCheckboxMarkedCircleOutline" | ||||
|                         :size="48" | ||||
|                      /> | ||||
|                   </div> | ||||
|                   <p class="empty-title h5"> | ||||
|                      {{ t('database.thereAreNoTableChecks') }} | ||||
|                   </p> | ||||
|                   <div class="empty-action"> | ||||
|                      <button class="btn btn-primary" @click="addCheck"> | ||||
|                         {{ t('database.createNewCheck') }} | ||||
|                      </button> | ||||
|                   </div> | ||||
|                </div> | ||||
|             </div> | ||||
|          </div> | ||||
|       </template> | ||||
|    </ConfirmModal> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { TableCheck } from 'common/interfaces/antares'; | ||||
| import { uidGen } from 'common/libs/uidGen'; | ||||
| import { computed, onMounted, onUnmounted, Ref, ref } from 'vue'; | ||||
| import { useI18n } from 'vue-i18n'; | ||||
|  | ||||
| import ConfirmModal from '@/components/BaseConfirmModal.vue'; | ||||
| import BaseIcon from '@/components/BaseIcon.vue'; | ||||
|  | ||||
| const { t } = useI18n(); | ||||
|  | ||||
| const props = defineProps({ | ||||
|    localChecks: Array, | ||||
|    table: String, | ||||
|    workspace: Object | ||||
| }); | ||||
|  | ||||
| const emit = defineEmits(['hide', 'checks-update']); | ||||
|  | ||||
| const checksPanel: Ref<HTMLDivElement> = ref(null); | ||||
| const checksProxy: Ref<TableCheck[]> = ref([]); | ||||
| const selectedCheckID = ref(''); | ||||
| const modalInnerHeight = ref(400); | ||||
|  | ||||
| const selectedCheckObj = computed(() => checksProxy.value.find(index => index._antares_id === selectedCheckID.value)); | ||||
| const isChanged = computed(() => JSON.stringify(props.localChecks) !== JSON.stringify(checksProxy.value)); | ||||
|  | ||||
| const confirmChecksChange = () => { | ||||
|    const filteredChecks = checksProxy.value.filter(check => check.clause.trim().length); | ||||
|    emit('checks-update', filteredChecks); | ||||
| }; | ||||
|  | ||||
| const selectCheck = (event: MouseEvent, id: string) => { | ||||
|    // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
|    if (selectedCheckID.value !== id && !(event.target as any).classList.contains('remove-field')) | ||||
|       selectedCheckID.value = id; | ||||
| }; | ||||
|  | ||||
| const getModalInnerHeight = () => { | ||||
|    const modalBody = document.querySelector('.modal-body'); | ||||
|    if (modalBody) | ||||
|       modalInnerHeight.value = modalBody.clientHeight - (parseFloat(getComputedStyle(modalBody).paddingTop) + parseFloat(getComputedStyle(modalBody).paddingBottom)); | ||||
| }; | ||||
|  | ||||
| const addCheck = () => { | ||||
|    const uid = uidGen(); | ||||
|    checksProxy.value = [...checksProxy.value, { | ||||
|       _antares_id: uid, | ||||
|       name: `CHK_${uid.substring(0, 4)}`, | ||||
|       clause: '' | ||||
|    }]; | ||||
|  | ||||
|    if (checksProxy.value.length === 1) | ||||
|       resetSelectedID(); | ||||
|  | ||||
|    setTimeout(() => { | ||||
|       checksPanel.value.scrollTop = checksPanel.value.scrollHeight + 60; | ||||
|       selectedCheckID.value = uid; | ||||
|    }, 20); | ||||
| }; | ||||
|  | ||||
| const removeCheck = (id: string) => { | ||||
|    checksProxy.value = checksProxy.value.filter(index => index._antares_id !== id); | ||||
|  | ||||
|    if (selectedCheckID.value === id && checksProxy.value.length) | ||||
|       resetSelectedID(); | ||||
| }; | ||||
|  | ||||
| const clearChanges = () => { | ||||
|    checksProxy.value = JSON.parse(JSON.stringify(props.localChecks)); | ||||
|    if (!checksProxy.value.some(index => index._antares_id === selectedCheckID.value)) | ||||
|       resetSelectedID(); | ||||
| }; | ||||
|  | ||||
| const resetSelectedID = () => { | ||||
|    selectedCheckID.value = checksProxy.value.length ? checksProxy.value[0]._antares_id : ''; | ||||
| }; | ||||
|  | ||||
| onMounted(() => { | ||||
|    checksProxy.value = JSON.parse(JSON.stringify(props.localChecks)); | ||||
|  | ||||
|    if (checksProxy.value.length) | ||||
|       resetSelectedID(); | ||||
|  | ||||
|    getModalInnerHeight(); | ||||
|    window.addEventListener('resize', getModalInnerHeight); | ||||
| }); | ||||
|  | ||||
| onUnmounted(() => { | ||||
|    window.removeEventListener('resize', getModalInnerHeight); | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .tile { | ||||
|   border-radius: $border-radius; | ||||
|   opacity: 0.5; | ||||
|   transition: background 0.2s; | ||||
|   transition: opacity 0.2s; | ||||
|  | ||||
|   .tile-action { | ||||
|     opacity: 0; | ||||
|     transition: opacity 0.2s; | ||||
|   } | ||||
|  | ||||
|   &:hover { | ||||
|     .tile-action { | ||||
|       opacity: 1; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   &.selected-element { | ||||
|     opacity: 1; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .fields-list { | ||||
|   max-height: 300px; | ||||
|   overflow: auto; | ||||
| } | ||||
|  | ||||
| .remove-field svg { | ||||
|   pointer-events: none; | ||||
| } | ||||
| </style> | ||||
| @@ -126,6 +126,7 @@ export const enUS = { | ||||
|       insert: 'Insert', | ||||
|       indexes: 'Indexes', | ||||
|       foreignKeys: 'Foreign keys', | ||||
|       tableChecks: 'Table checks', | ||||
|       length: 'Length', | ||||
|       unsigned: 'Unsigned', | ||||
|       default: 'Default', | ||||
| @@ -190,12 +191,15 @@ export const enUS = { | ||||
|       addNewField: 'Add new field', | ||||
|       manageIndexes: 'Manage indexes', | ||||
|       manageForeignKeys: 'Manage foreign keys', | ||||
|       manageTableChecks: 'Manage table checks', | ||||
|       allowNull: 'Allow NULL', | ||||
|       zeroFill: 'Zero fill', | ||||
|       customValue: 'Custom value', | ||||
|       onUpdate: 'On update', | ||||
|       deleteField: 'Delete field', | ||||
|       createNewIndex: 'Create new index', | ||||
|       createNewCheck: 'Create new check', | ||||
|       checkClause: 'Check clause', | ||||
|       addToIndex: 'Add to index', | ||||
|       createNewTable: 'Create new table', | ||||
|       emptyTable: 'Empty table', | ||||
| @@ -205,6 +209,7 @@ export const enUS = { | ||||
|       emptyConfirm: 'Do you confirm to empty', | ||||
|       thereAreNoIndexes: 'There are no indexes', | ||||
|       thereAreNoForeign: 'There are no foreign keys', | ||||
|       thereAreNoTableChecks: 'There are no table checks', | ||||
|       createNewForeign: 'Create new foreign key', | ||||
|       referenceTable: 'Ref. table', | ||||
|       referenceField: 'Ref. field', | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -36,6 +36,10 @@ export default class { | ||||
|       return ipcRenderer.invoke('get-table-indexes', unproxify(params)); | ||||
|    } | ||||
|  | ||||
|    static getTableChecks (params: { uid: string; schema: string; table: string }): Promise<IpcResponse> { | ||||
|       return ipcRenderer.invoke('get-table-checks', unproxify(params)); | ||||
|    } | ||||
|  | ||||
|    static getTableDll (params: { uid: string; schema: string; table: string }): Promise<IpcResponse<string>> { | ||||
|       return ipcRenderer.invoke('get-table-ddl', unproxify(params)); | ||||
|    } | ||||
|   | ||||
| @@ -10,7 +10,7 @@ export interface QueryLog { | ||||
| } | ||||
|  | ||||
| export interface DebugLog { | ||||
|    level: 'log' | 'info' | 'warn' | 'error'; | ||||
|    level: 'log' | 'info' | 'warn' | 'error' | string; | ||||
|    process: 'renderer' | 'main' | 'worker'; | ||||
|    message: string; | ||||
|    date: Date; | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| import { uidGen } from 'common/libs/uidGen'; | ||||
| import { defineStore } from 'pinia'; | ||||
|  | ||||
| import { useConsoleStore } from './console'; | ||||
|  | ||||
| export interface Notification { | ||||
|    uid: string; | ||||
|    status: string; | ||||
| @@ -15,6 +17,13 @@ export const useNotificationsStore = defineStore('notifications', { | ||||
|       addNotification (payload: { status: string; message: string }) { | ||||
|          const notification: Notification = { uid: uidGen('N'), ...payload }; | ||||
|          this.notifications.unshift(notification); | ||||
|  | ||||
|          useConsoleStore().putLog('debug', { | ||||
|             level: notification.status, | ||||
|             process: 'renderer', | ||||
|             message: notification.message, | ||||
|             date: new Date() | ||||
|          }); | ||||
|       }, | ||||
|       removeNotification (uid: string) { | ||||
|          this.notifications = (this.notifications as Notification[]).filter(item => item.uid !== uid); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user