├── .all-contributorsrc ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── build-beta.yml │ ├── build.yml │ ├── codeql-analysis.yml │ ├── create-artifact-linux-arm64.yml │ ├── create-artifact-linux.yml │ ├── create-artifact-macos.yml │ ├── create-artifact-windows-appx.yml │ ├── create-generated-sources.yml │ ├── test-builds.yml │ └── test-e2e-win.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .nvmrc ├── .stylelintrc ├── .versionrc.json ├── .vscode ├── launch.json └── settings.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets ├── appx │ ├── Square150x150Logo.png │ ├── Square44x44Logo.png │ ├── Square44x44Logo.targetsize-256_altform-unplated.png │ ├── StoreLogo.png │ └── Wide310x150Logo.png ├── flatpak │ ├── it.fabiodistasio.AntaresSQL.desktop │ ├── it.fabiodistasio.AntaresSQL.metainfo.xml │ └── it.fabiodistasio.AntaresSQL.png ├── icon.icns ├── icon.ico └── linux │ ├── 128x128.png │ ├── 16x16.png │ ├── 256x256.png │ ├── 32x32.png │ └── 64x64.png ├── commitlint.config.js ├── docs ├── aur-badge.svg ├── gh-logo-2.png ├── gh-logo.png ├── logo.png └── screen-alpha.png ├── misc └── .gitkeep ├── package-lock.json ├── package.json ├── scripts ├── devRunner.js ├── devtoolsInstaller.js └── translationCheck.ts ├── snap └── snapcraft.yaml ├── src ├── common │ ├── FakerMethods.ts │ ├── customizations │ │ ├── defaults.ts │ │ ├── firebird.ts │ │ ├── index.ts │ │ ├── mysql.ts │ │ ├── postgresql.ts │ │ └── sqlite.ts │ ├── data-types │ │ ├── firebird.ts │ │ ├── mysql.ts │ │ ├── postgresql.ts │ │ └── sqlite.ts │ ├── fieldTypes.ts │ ├── interfaces │ │ ├── antares.ts │ │ ├── customizations.ts │ │ ├── exporter.ts │ │ ├── importer.ts │ │ ├── tableApis.ts │ │ └── workers.ts │ ├── libs │ │ ├── bufferToBase64.ts │ │ ├── encrypter.ts │ │ ├── fakerCustom.ts │ │ ├── formatBytes.ts │ │ ├── getArrayDepth.ts │ │ ├── hexToBinary.ts │ │ ├── langDetector.ts │ │ ├── mimeFromHex.ts │ │ ├── sqlUtils.ts │ │ └── uidGen.ts │ └── shortcuts.ts ├── main │ ├── ipc-handlers │ │ ├── application.ts │ │ ├── connection.ts │ │ ├── database.ts │ │ ├── functions.ts │ │ ├── index.ts │ │ ├── routines.ts │ │ ├── schedulers.ts │ │ ├── schema.ts │ │ ├── tables.ts │ │ ├── triggers.ts │ │ ├── updates.ts │ │ ├── users.ts │ │ └── views.ts │ ├── libs │ │ ├── ClientsFactory.ts │ │ ├── ShortcutRegister.ts │ │ ├── clients │ │ │ ├── BaseClient.ts │ │ │ ├── FirebirdSQLClient.ts │ │ │ ├── MySQLClient.ts │ │ │ ├── PostgreSQLClient.ts │ │ │ └── SQLiteClient.ts │ │ ├── exporters │ │ │ ├── BaseExporter.ts │ │ │ └── sql │ │ │ │ ├── MysqlExporter.ts │ │ │ │ ├── PostgreSQLExporter.ts │ │ │ │ └── SqlExporter.ts │ │ ├── importers │ │ │ ├── BaseImporter.ts │ │ │ └── sql │ │ │ │ ├── MySQLlImporter.ts │ │ │ │ └── PostgreSQLImporter.ts │ │ ├── misc │ │ │ ├── ipcLogger.ts │ │ │ └── validateSender.ts │ │ └── parsers │ │ │ ├── MySQLParser.ts │ │ │ └── PostgreSQLParser.ts │ ├── main.ts │ └── workers │ │ ├── exporter.ts │ │ └── importer.ts └── renderer │ ├── App.vue │ ├── components │ ├── BaseConfirmModal.vue │ ├── BaseContextMenu.vue │ ├── BaseIcon.vue │ ├── BaseLoader.vue │ ├── BaseMap.vue │ ├── BaseNotification.vue │ ├── BaseSelect.vue │ ├── BaseTextEditor.vue │ ├── BaseUploadInput.vue │ ├── BaseVirtualScroll.vue │ ├── DebugConsole.vue │ ├── FakerSelect.vue │ ├── ForeignKeySelect.vue │ ├── KeyPressDetector.vue │ ├── ModalAllConnections.vue │ ├── ModalAskCredentials.vue │ ├── ModalAskParameters.vue │ ├── ModalConnectionAppearance.vue │ ├── ModalDiscardChanges.vue │ ├── ModalEditSchema.vue │ ├── ModalExportSchema.vue │ ├── ModalFakerRows.vue │ ├── ModalFolderAppearance.vue │ ├── ModalHistory.vue │ ├── ModalImportSchema.vue │ ├── ModalNewSchema.vue │ ├── ModalNoteEdit.vue │ ├── ModalNoteNew.vue │ ├── ModalProcessesList.vue │ ├── ModalProcessesListContext.vue │ ├── ModalProcessesListRow.vue │ ├── ModalSettings.vue │ ├── ModalSettingsChangelog.vue │ ├── ModalSettingsData.vue │ ├── ModalSettingsDataExport.vue │ ├── ModalSettingsDataImport.vue │ ├── ModalSettingsShortcuts.vue │ ├── ModalSettingsUpdate.vue │ ├── QueryEditor.vue │ ├── ScratchpadNote.vue │ ├── SettingBarConnections.vue │ ├── SettingBarConnectionsFolder.vue │ ├── SettingBarContext.vue │ ├── TheFooter.vue │ ├── TheNotificationsBoard.vue │ ├── TheScratchpad.vue │ ├── TheSettingBar.vue │ ├── TheTitleBar.vue │ ├── Workspace.vue │ ├── WorkspaceAddConnectionPanel.vue │ ├── WorkspaceEditConnectionPanel.vue │ ├── WorkspaceEmptyState.vue │ ├── WorkspaceExploreBar.vue │ ├── WorkspaceExploreBarMiscContext.vue │ ├── WorkspaceExploreBarMiscFolderContext.vue │ ├── WorkspaceExploreBarSchema.vue │ ├── WorkspaceExploreBarSchemaContext.vue │ ├── WorkspaceExploreBarTableContext.vue │ ├── WorkspaceTabNewFunction.vue │ ├── WorkspaceTabNewMaterializedView.vue │ ├── WorkspaceTabNewRoutine.vue │ ├── WorkspaceTabNewScheduler.vue │ ├── WorkspaceTabNewTable.vue │ ├── WorkspaceTabNewTableEmptyState.vue │ ├── WorkspaceTabNewTrigger.vue │ ├── WorkspaceTabNewTriggerFunction.vue │ ├── WorkspaceTabNewView.vue │ ├── WorkspaceTabPropsFunction.vue │ ├── WorkspaceTabPropsFunctionParamsModal.vue │ ├── WorkspaceTabPropsMaterializedView.vue │ ├── WorkspaceTabPropsRoutine.vue │ ├── WorkspaceTabPropsRoutineParamsModal.vue │ ├── WorkspaceTabPropsScheduler.vue │ ├── WorkspaceTabPropsSchedulerTimingModal.vue │ ├── WorkspaceTabPropsTable.vue │ ├── WorkspaceTabPropsTableChecksModal.vue │ ├── WorkspaceTabPropsTableContext.vue │ ├── WorkspaceTabPropsTableDdlModal.vue │ ├── WorkspaceTabPropsTableFields.vue │ ├── WorkspaceTabPropsTableForeignModal.vue │ ├── WorkspaceTabPropsTableIndexesModal.vue │ ├── WorkspaceTabPropsTableRow.vue │ ├── WorkspaceTabPropsTrigger.vue │ ├── WorkspaceTabPropsTriggerFunction.vue │ ├── WorkspaceTabPropsView.vue │ ├── WorkspaceTabQuery.vue │ ├── WorkspaceTabQueryEmptyState.vue │ ├── WorkspaceTabQueryTable.vue │ ├── WorkspaceTabQueryTableContext.vue │ ├── WorkspaceTabQueryTableRow.vue │ ├── WorkspaceTabTable.vue │ ├── WorkspaceTabTableFilters.vue │ └── WorkspaceTabsContext.vue │ ├── composables │ ├── useFilters.ts │ ├── useFocusTrap.ts │ └── useResultTables.ts │ ├── i18n │ ├── ar-SA.ts │ ├── ca-ES.ts │ ├── cs-CZ.ts │ ├── de-DE.ts │ ├── en-US.ts │ ├── es-ES.ts │ ├── fr-FR.ts │ ├── he-IL.ts │ ├── id-ID.ts │ ├── index.ts │ ├── it-IT.ts │ ├── ja-JP.ts │ ├── ko-KR.ts │ ├── nl-NL.ts │ ├── pt-BR.ts │ ├── ru-RU.ts │ ├── supported-locales.ts │ ├── uk-UA.ts │ ├── uz-UZ.ts │ ├── vi-VN.ts │ ├── zh-CN.ts │ └── zh-TW.ts │ ├── images │ ├── dark.png │ ├── light.png │ ├── logo-16.png │ ├── logo-32.png │ ├── logo-64.png │ ├── logo-dark.svg │ ├── logo-light.svg │ ├── logo.png │ ├── logo.svg │ └── svg │ │ ├── alphabetical-variant.svg │ │ ├── arrow-right-bold-box.svg │ │ ├── calendar-clock.svg │ │ ├── circle.svg │ │ ├── code-braces.svg │ │ ├── cube.svg │ │ ├── firebird.svg │ │ ├── mariadb.svg │ │ ├── mssql.svg │ │ ├── mysql.svg │ │ ├── oracledb.svg │ │ ├── pg.svg │ │ ├── rhombus-split-outline.svg │ │ ├── sqlite.svg │ │ ├── sync-circle.svg │ │ ├── table-cog.svg │ │ ├── table-eye.svg │ │ └── table.svg │ ├── index.ejs │ ├── index.ts │ ├── ipc-api │ ├── Application.ts │ ├── Connection.ts │ ├── Databases.ts │ ├── Functions.ts │ ├── Routines.ts │ ├── Schedulers.ts │ ├── Schema.ts │ ├── Tables.ts │ ├── Triggers.ts │ ├── Users.ts │ └── Views.ts │ ├── libs │ ├── camelize.ts │ ├── colorShade.ts │ ├── copyText.ts │ ├── exportRows.ts │ ├── ext-language_tools.js │ ├── getContrast.ts │ ├── hexToRgba.ts │ └── unproxify.ts │ ├── scss │ ├── _data-types.scss │ ├── _db-icons.scss │ ├── _editor-icons.scss │ ├── _fake-tables.scss │ ├── _table-keys.scss │ ├── _transitions.scss │ ├── _variables.scss │ ├── main.scss │ └── themes │ │ ├── dark-theme.scss │ │ └── light-theme.scss │ ├── stores │ ├── application.ts │ ├── connections.ts │ ├── console.ts │ ├── history.ts │ ├── notifications.ts │ ├── schemaExport.ts │ ├── scratchpad.ts │ ├── settings.ts │ └── workspaces.ts │ └── untyped.d.ts ├── tests └── app.spec.ts ├── tsconfig.json ├── webpack.main.config.js ├── webpack.renderer.config.js └── webpack.workers.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | assets 3 | out 4 | dist 5 | build 6 | misc -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "standard", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:vue/vue3-recommended" 11 | ], 12 | "parser": "vue-eslint-parser", 13 | "parserOptions": { 14 | "parser": "@typescript-eslint/parser", 15 | "ecmaVersion": 9, 16 | "sourceType": "module", 17 | "requireConfigFile": false 18 | }, 19 | "plugins": [ 20 | "vue", 21 | "@typescript-eslint", 22 | "simple-import-sort" 23 | ], 24 | "rules": { 25 | "space-infix-ops": "off", 26 | "object-curly-newline": "off", 27 | "indent": [ 28 | "error", 29 | 3, 30 | { 31 | "SwitchCase": 1 32 | } 33 | ], 34 | "linebreak-style": [ 35 | "error", 36 | "unix" 37 | ], 38 | "brace-style": [ 39 | "error", 40 | "stroustrup" 41 | ], 42 | "quotes": [ 43 | "error", 44 | "single" 45 | ], 46 | "semi": [ 47 | "error", 48 | "always" 49 | ], 50 | "curly": [ 51 | "error", 52 | "multi-or-nest" 53 | ], 54 | "no-console": "off", 55 | "no-undef": "off", 56 | "vue/no-side-effects-in-computed-properties": "off", 57 | "vue/multi-word-component-names": "off", 58 | "vue/require-default-prop": "off", 59 | "vue/comment-directive": "off", 60 | "vue/no-v-html": "off", 61 | "vue/html-indent": [ 62 | "error", 63 | 3, 64 | { 65 | "attribute": 1, 66 | "baseIndent": 1, 67 | "closeBracket": 0, 68 | "ignores": [] 69 | } 70 | ], 71 | "vue/max-attributes-per-line": [ 72 | "error", 73 | { 74 | "singleline": { 75 | "max": 2 76 | }, 77 | "multiline": { 78 | "max": 1 79 | } 80 | } 81 | ], 82 | "@typescript-eslint/member-delimiter-style": [ 83 | "warn", 84 | { 85 | "multiline": { 86 | "delimiter": "semi", 87 | "requireLast": true 88 | }, 89 | "singleline": { 90 | "delimiter": "semi", 91 | "requireLast": false 92 | } 93 | } 94 | ], 95 | "@typescript-eslint/no-var-requires": "off", 96 | "simple-import-sort/imports": "error", 97 | "simple-import-sort/exports": "error" 98 | } 99 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.jpg binary 3 | *.png binary 4 | *.gif binary 5 | *.ico binary 6 | *.icns binary -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # [antares-sql,fabio286] 4 | patreon: #fabio286 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # ['https://paypal.me/fabiodistasio'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: Fabio286 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **Application (please complete the following information):** 28 | 29 | - App client [e.g. MySQL] 30 | - App version [e.g. 0.5.2] 31 | - Installation source: [e.g. exe, Linux Store, AppImage, dmg] 32 | 33 | **Environment (please complete the following information):** 34 | 35 | - OS name: [e.g. Windows 11] 36 | - OS version [e.g. 21H2] 37 | - DB name [e.g. MariaDB] 38 | - DB version [e.g. 10.3.34] 39 | 40 | **Additional context** 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: Fabio286 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" 9 | allow: 10 | - dependency-type: "production" 11 | directory: "/" 12 | schedule: 13 | interval: "monthly" 14 | target-branch: "develop" 15 | -------------------------------------------------------------------------------- /.github/workflows/build-beta.yml: -------------------------------------------------------------------------------- 1 | name: Build & release [BETA] 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+' 7 | 8 | env: 9 | BRANCH_NAME: ${{ github.head_ref || github.ref_name }} 10 | 11 | jobs: 12 | release: 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: [macos-latest, ubuntu-latest, windows-latest] 19 | 20 | steps: 21 | - name: Check out Git repository 22 | uses: actions/checkout@v4 23 | with: 24 | ref: beta 25 | 26 | - name: Install Node.js 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: 20 30 | 31 | - name: Install dependencies 32 | run: | 33 | npm i 34 | npm install "dmg-license" --save-optional 35 | 36 | - name: "Build" 37 | run: npm run build 38 | 39 | - name: Release 40 | uses: ncipollo/release-action@v1 41 | with: 42 | artifacts: "build/*.AppImage,build/*.yml,build/*.deb,build/*.dmg,build/*.blockmap,build/*.zip,build/*.exe" 43 | allowUpdates: true 44 | draft: true 45 | generateReleaseNotes: true 46 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build & release [STABLE] 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v[0-9]+.[0-9]+.[0-9]+' 7 | 8 | env: 9 | BRANCH_NAME: ${{ github.head_ref || github.ref_name }} 10 | 11 | jobs: 12 | release: 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: [macos-latest, ubuntu-latest, windows-latest] 19 | 20 | steps: 21 | - name: Exit if not on master branch 22 | if: contains(env.BRANCH_NAME, 'master') == false 23 | run: | 24 | echo "Wrong environment ${{env.BRANCH_NAME}}" 25 | exit 0 26 | 27 | - name: Check out Git repository 28 | uses: actions/checkout@v4 29 | with: 30 | ref: master 31 | 32 | - name: Install Node.js 33 | uses: actions/setup-node@v4 34 | with: 35 | node-version: 20 36 | 37 | - name: Install dependencies 38 | run: | 39 | npm i 40 | npm install "dmg-license" --save-optional 41 | 42 | - name: "Build" 43 | run: npm run build 44 | 45 | - name: Release 46 | uses: ncipollo/release-action@v1 47 | with: 48 | artifacts: "build/*.AppImage,build/*.yml,build/*.deb,build/*.dmg,build/*.blockmap,build/*.zip,build/*.exe" 49 | allowUpdates: true 50 | draft: true 51 | generateReleaseNotes: true 52 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [master] 9 | schedule: 10 | - cron: '0 15 * * 0' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | # Override automatic language detection by changing the below list 21 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 22 | language: ['javascript'] 23 | # Learn more... 24 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | with: 30 | # We must fetch at least the immediate parents so that if this is 31 | # a pull request then we can checkout the head. 32 | fetch-depth: 2 33 | 34 | # Initializes the CodeQL tools for scanning. 35 | - name: Initialize CodeQL 36 | uses: github/codeql-action/init@v1 37 | with: 38 | languages: ${{ matrix.language }} 39 | 40 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 41 | # If this step fails, then you should remove it and run the build manually (see below) 42 | - name: Autobuild 43 | uses: github/codeql-action/autobuild@v1 44 | 45 | # ℹ️ Command-line programs to run using the OS shell. 46 | # 📚 https://git.io/JvXDl 47 | 48 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 49 | # and modify them (or add more) to build your code if your project 50 | # uses a compiled language 51 | 52 | #- run: | 53 | # make bootstrap 54 | # make release 55 | 56 | - name: Perform CodeQL Analysis 57 | uses: github/codeql-action/analyze@v1 58 | -------------------------------------------------------------------------------- /.github/workflows/create-artifact-linux-arm64.yml: -------------------------------------------------------------------------------- 1 | name: Create artifact [LINUX ARM64] 2 | 3 | on: 4 | workflow_dispatch: {} 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Check out Git repository 11 | uses: actions/checkout@v4 12 | with: 13 | ref: master 14 | 15 | - name: Install Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 20 19 | 20 | - name: Install dependencies 21 | run: npm i 22 | 23 | - name: "Build" 24 | run: npm run build -- --arm64 --linux deb AppImage 25 | 26 | - name: Upload Artifact 27 | uses: actions/upload-artifact@v4 28 | with: 29 | name: linux-build 30 | retention-days: 3 31 | path: | 32 | build 33 | !build/*-unpacked 34 | !build/.icon-ico 35 | -------------------------------------------------------------------------------- /.github/workflows/create-artifact-linux.yml: -------------------------------------------------------------------------------- 1 | name: Create artifact [LINUX] 2 | 3 | on: 4 | workflow_dispatch: {} 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-22.04 9 | steps: 10 | - name: Check out Git repository 11 | uses: actions/checkout@v4 12 | 13 | - name: Install Node.js 14 | uses: actions/setup-node@v4 15 | with: 16 | node-version: 20 17 | 18 | - name: Install dependencies 19 | run: npm i 20 | 21 | - name: "Build" 22 | run: npm run build 23 | 24 | - name: Upload Artifact 25 | uses: actions/upload-artifact@v4 26 | with: 27 | name: linux-build 28 | retention-days: 3 29 | path: | 30 | build 31 | !build/*-unpacked 32 | !build/.icon-ico 33 | -------------------------------------------------------------------------------- /.github/workflows/create-artifact-macos.yml: -------------------------------------------------------------------------------- 1 | name: Create artifact [MAC] 2 | 3 | on: 4 | workflow_dispatch: {} 5 | 6 | jobs: 7 | build: 8 | runs-on: macos-latest 9 | steps: 10 | - name: Check out Git repository 11 | uses: actions/checkout@v4 12 | 13 | - name: Install Node.js 14 | uses: actions/setup-node@v4 15 | with: 16 | node-version: 20 17 | 18 | - name: npm install & build 19 | run: | 20 | npm install 21 | npm install "dmg-license" --save-optional 22 | npm run build 23 | 24 | - name: Upload Artifact 25 | uses: actions/upload-artifact@v4 26 | with: 27 | name: macos-build 28 | retention-days: 3 29 | path: | 30 | build 31 | !build/*-unpacked 32 | !build/.icon-ico 33 | build-beta: 34 | runs-on: macos-latest 35 | steps: 36 | - name: Check out Git repository 37 | uses: actions/checkout@v3 38 | with: 39 | ref: beta 40 | 41 | - name: Install Node.js 42 | uses: actions/setup-node@v4 43 | with: 44 | node-version: 20 45 | 46 | - name: npm install & build 47 | run: | 48 | npm install 49 | npm install "dmg-license" --save-optional 50 | npm run build 51 | 52 | - name: Upload Artifact 53 | uses: actions/upload-artifact@v4 54 | with: 55 | name: macos-build-beta 56 | retention-days: 3 57 | path: | 58 | build 59 | !build/*-unpacked 60 | !build/.icon-ico 61 | -------------------------------------------------------------------------------- /.github/workflows/create-artifact-windows-appx.yml: -------------------------------------------------------------------------------- 1 | name: Create artifact [WINDOWS APPX] 2 | 3 | on: 4 | workflow_dispatch: {} 5 | 6 | jobs: 7 | build: 8 | runs-on: windows-2022 9 | steps: 10 | - name: Check out Git repository 11 | uses: actions/checkout@v4 12 | 13 | - name: Install Node.js 14 | uses: actions/setup-node@v4 15 | with: 16 | node-version: 20 17 | 18 | - name: Install dependencies 19 | run: npm i 20 | 21 | - name: "Build" 22 | run: npm run build:appx 23 | 24 | - name: Upload Artifact 25 | uses: actions/upload-artifact@v4 26 | with: 27 | name: windows-build 28 | retention-days: 3 29 | path: | 30 | build 31 | !build/*-unpacked 32 | !build/.icon-ico 33 | -------------------------------------------------------------------------------- /.github/workflows/create-generated-sources.yml: -------------------------------------------------------------------------------- 1 | name: Create generated-rources.json 2 | 3 | on: 4 | workflow_dispatch: {} 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | # Install flatpak-node-generator 11 | - name: Install Python 12 | uses: actions/setup-python@v5 13 | with: 14 | python-version: '3.11' 15 | 16 | - name: Install pipx 17 | uses: CfirTsabari/actions-pipx@v1 18 | 19 | - name: Install flatpak-node-generator 20 | run: | 21 | cd ../ 22 | git clone https://github.com/flatpak/flatpak-builder-tools.git 23 | cd flatpak-builder-tools/node 24 | pipx install . 25 | 26 | # Install Antares 27 | - name: Check out Git repository 28 | uses: actions/checkout@v3 29 | 30 | - name: Install Node.js 31 | uses: actions/setup-node@v3 32 | with: 33 | node-version: 20 34 | 35 | # - name: Delete old package-lock.json 36 | # run: rm package-lock.json 37 | 38 | - name: Install dependencies 39 | run: npm i --lockfile-version 2 40 | 41 | - name: Generate generated-sources.json 42 | run: flatpak-node-generator npm -r package-lock.json --electron-node-headers 43 | 44 | - name: Upload Artifact 45 | uses: actions/upload-artifact@v3 46 | with: 47 | name: generated-sources 48 | retention-days: 3 49 | path: | 50 | generated-sources.json 51 | -------------------------------------------------------------------------------- /.github/workflows/test-builds.yml: -------------------------------------------------------------------------------- 1 | name: Test build [DEVELOP] 2 | 3 | on: 4 | workflow_dispatch: {} 5 | 6 | env: 7 | BRANCH_NAME: ${{ github.head_ref || github.ref_name }} 8 | 9 | jobs: 10 | release: 11 | runs-on: ${{ matrix.os }} 12 | 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: [macos-latest, ubuntu-latest, windows-latest] 17 | 18 | steps: 19 | - name: Check out Git repository 20 | uses: actions/checkout@v4 21 | with: 22 | ref: develop 23 | 24 | - name: Install Node.js 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: 20 28 | 29 | - name: Install dependencies 30 | run: npm i 31 | 32 | - name: "Build" 33 | run: npm run build 34 | -------------------------------------------------------------------------------- /.github/workflows/test-e2e-win.yml: -------------------------------------------------------------------------------- 1 | name: Test end-to-end 2 | 3 | on: 4 | workflow_dispatch: {} 5 | # push: 6 | # branches: 7 | # - develop 8 | 9 | jobs: 10 | release: 11 | runs-on: ${{ matrix.os }} 12 | 13 | strategy: 14 | matrix: 15 | os: [windows-latest] 16 | 17 | steps: 18 | - name: Check out Git repository 19 | uses: actions/checkout@v4 20 | 21 | - name: Install Node.js 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: 20 25 | 26 | - name: Install dependencies 27 | run: npm i 28 | 29 | - name: Run tests 30 | run: npm run test:e2e 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist 3 | build 4 | misc/* 5 | !misc/.gitkeep 6 | node_modules 7 | thumbs.db 8 | NOTES.md 9 | *.txt 10 | *.heapsnapshot -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | npx --no -- commitlint --edit $1 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npm run lint 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/.nvmrc -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-standard", 4 | "stylelint-config-recommended-vue" 5 | ], 6 | "fix": true, 7 | "formatter": "verbose", 8 | "customSyntax": "postcss-html", 9 | "plugins": [ 10 | "stylelint-scss" 11 | ], 12 | "rules": { 13 | "at-rule-no-unknown": null, 14 | "no-descending-specificity": null, 15 | "font-family-no-missing-generic-family-keyword": null 16 | }, 17 | "syntax": "scss" 18 | } -------------------------------------------------------------------------------- /.versionrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | {"type":"feat","section":"Features"}, 4 | {"type":"perf","section":"Improvements"}, 5 | {"type":"fix","section":"Bug Fixes"} 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Electron: Main", 6 | "cwd": "${workspaceFolder}", 7 | "port": 9222, 8 | "request": "attach", 9 | "sourceMaps": true, 10 | "type": "node", 11 | "timeout": 1000000 12 | }, 13 | { 14 | "name": "Electron: Renderer", 15 | "port": 9223, 16 | "request": "attach", 17 | "sourceMaps": true, 18 | "type": "chrome", 19 | "webRoot": "${workspaceFolder}" 20 | } 21 | ], 22 | "compounds": [ 23 | { 24 | "name": "Electron: All", 25 | "configurations": ["Electron: Main", "Electron: Renderer"] 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "conventionalCommits.scopes": [ 3 | "UI", 4 | "core", 5 | "MySQL", 6 | "PostgreSQL", 7 | "SQLite", 8 | "Firebird SQL", 9 | "Windows", 10 | "translation", 11 | "Linux", 12 | "MacOS", 13 | "deps", 14 | "Flatpak" 15 | ], 16 | "svg.preview.background": "transparent" 17 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Fabio Di Stasio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /assets/appx/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/assets/appx/Square150x150Logo.png -------------------------------------------------------------------------------- /assets/appx/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/assets/appx/Square44x44Logo.png -------------------------------------------------------------------------------- /assets/appx/Square44x44Logo.targetsize-256_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/assets/appx/Square44x44Logo.targetsize-256_altform-unplated.png -------------------------------------------------------------------------------- /assets/appx/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/assets/appx/StoreLogo.png -------------------------------------------------------------------------------- /assets/appx/Wide310x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/assets/appx/Wide310x150Logo.png -------------------------------------------------------------------------------- /assets/flatpak/it.fabiodistasio.AntaresSQL.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Antares SQL 3 | Exec=startantares 4 | Terminal=false 5 | Type=Application 6 | Icon=it.fabiodistasio.AntaresSQL 7 | StartupWMClass=antares 8 | Comment=A modern, fast and productivity driven SQL client with a focus in UX 9 | Categories=Development; -------------------------------------------------------------------------------- /assets/flatpak/it.fabiodistasio.AntaresSQL.metainfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | it.fabiodistasio.AntaresSQL 4 | Antares SQL 5 | CC0-1.0 6 | MIT 7 | Fabio Di Stasio 8 | A modern, fast and productivity driven SQL client with a focus in UX 9 | https://antares-sql.app/ 10 | https://github.com/antares-sql/antares/issues 11 | https://github.com/antares-sql/antares/discussions 12 | https://paypal.me/fabiodistasio 13 | 14 |

Antares is an SQL client that aims to become an useful and complete tool, especially for developers.

15 |

The main goal is to develop a totally free, full featured, cross platform and open source alternative. 16 | A modern application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons, nested tabs or submenu; productivity comes first.

17 |

Supported database:

18 | 24 |
25 | 26 | 27 | https://lh3.googleusercontent.com/drive-viewer/AK7aPaC00fbmJIUcfwSPv-hjoxEmHS8NapR8qyOqOpopMIdcDFqYKNDs5mdIK08hnhZdHMrozTfR4Hx3Yj6bQ0zgfStEEFhxWg=s1600 28 | 29 | 30 | 31 | 32 | 33 | 34 |
-------------------------------------------------------------------------------- /assets/flatpak/it.fabiodistasio.AntaresSQL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/assets/flatpak/it.fabiodistasio.AntaresSQL.png -------------------------------------------------------------------------------- /assets/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/assets/icon.icns -------------------------------------------------------------------------------- /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/assets/icon.ico -------------------------------------------------------------------------------- /assets/linux/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/assets/linux/128x128.png -------------------------------------------------------------------------------- /assets/linux/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/assets/linux/16x16.png -------------------------------------------------------------------------------- /assets/linux/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/assets/linux/256x256.png -------------------------------------------------------------------------------- /assets/linux/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/assets/linux/32x32.png -------------------------------------------------------------------------------- /assets/linux/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/assets/linux/64x64.png -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | // TODO Add Scope Enum Here 5 | // 'scope-enum': [2, 'always', ['yourscope', 'yourscope']], 6 | 'type-enum': [ 7 | 2, 8 | 'always', 9 | [ 10 | 'feat', 11 | 'fix', 12 | 'docs', 13 | 'chore', 14 | 'style', 15 | 'refactor', 16 | 'build', 17 | 'ci', 18 | 'test', 19 | 'revert', 20 | 'perf' 21 | ] 22 | ], 23 | 'subject-case': [ 24 | 2, 25 | 'never', 26 | [ 27 | 'upper-case', 28 | 'pascal-case', 29 | 'start-case' 30 | ] 31 | ] 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /docs/gh-logo-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/docs/gh-logo-2.png -------------------------------------------------------------------------------- /docs/gh-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/docs/gh-logo.png -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/docs/logo.png -------------------------------------------------------------------------------- /docs/screen-alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/docs/screen-alpha.png -------------------------------------------------------------------------------- /misc/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/misc/.gitkeep -------------------------------------------------------------------------------- /scripts/devtoolsInstaller.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 2 | // @ts-check 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const https = require('https'); 6 | const unzip = require('unzip-crx-3'); 7 | const { antares } = require('../package.json'); 8 | 9 | const extensionID = antares.devtoolsId; 10 | const chromiumVersion = '124'; 11 | const destFolder = path.resolve(__dirname, `../misc/${extensionID}`); 12 | const filePath = path.resolve(__dirname, `${destFolder}/${extensionID}.crx`); 13 | const fileUrl = `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${extensionID}%26uc&prodversion=${chromiumVersion}`; 14 | 15 | if (!fs.existsSync(destFolder)) 16 | fs.mkdirSync(destFolder, { recursive: true }); 17 | 18 | const fileStream = fs.createWriteStream(filePath); 19 | 20 | const downloadFile = url => { 21 | return /** @type {Promise} */(new Promise((resolve, reject) => { 22 | const request = https.get(url); 23 | 24 | request.on('response', response => { 25 | if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) { 26 | return downloadFile(response.headers.location) 27 | .then(resolve) 28 | .catch(reject); 29 | } 30 | 31 | response.pipe(fileStream); 32 | 33 | response.on('close', () => { 34 | console.log('Devtools download completed!'); 35 | resolve(); 36 | }); 37 | response.on('error', reject); 38 | }); 39 | request.on('error', reject); 40 | request.end(); 41 | })); 42 | }; 43 | 44 | (async () => { 45 | try { 46 | await downloadFile(fileUrl); 47 | await unzip(filePath, destFolder); 48 | fs.unlinkSync(filePath); 49 | fs.unlinkSync(`${destFolder}/package.json`);// <- Avoid to display annoyng npm script in vscode 50 | process.exit(); 51 | } 52 | catch (error) { 53 | console.log(error); 54 | } 55 | })(); 56 | -------------------------------------------------------------------------------- /scripts/translationCheck.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | import { enUS } from '../src/renderer/i18n/en-US'; 3 | import { localesNames } from '../src/renderer/i18n/supported-locales'; 4 | const locale = process.argv[2]; 5 | let fullCount = 0; 6 | let checkCount = 0; 7 | 8 | if (!locale) { 9 | console.log('Please specify locale code as first argument.'); 10 | process.exit(); 11 | } 12 | 13 | if (!Object.keys(localesNames).includes(locale)) { 14 | console.log(`Translation ${locale} not fount in supported locales.`); 15 | process.exit(); 16 | } 17 | 18 | console.log('Checking missing translations for:', locale); 19 | 20 | const i18nFile = require(`../src/renderer/i18n/${locale}`)[locale.replace('-', '')]; 21 | 22 | for (const group in enUS) { 23 | // @ts-ignore 24 | fullCount += Object.keys(enUS[group]).length; 25 | 26 | if (!Object.keys(i18nFile).includes(group)) { 27 | console.log(`Group "\u001b[31m${group}\u001b[0m" missing!`); 28 | continue; 29 | } 30 | 31 | // @ts-ignore 32 | for (const term in enUS[group]) { 33 | if (!Object.keys(i18nFile[group]).includes(term)) 34 | console.log(`Translation "\u001b[33m${group}.${term}\u001b[0m" missing!`); 35 | // @ts-ignore 36 | else if (i18nFile[group][term] === enUS[group][term]) { 37 | console.log(`Term "\u001b[36m${group}.${term}\u001b[0m" not translated!`); 38 | checkCount++; 39 | } 40 | else 41 | checkCount++; 42 | } 43 | } 44 | 45 | console.log(checkCount, 'of', fullCount, 'strings are present in', locale, `(\u001b[32m${(checkCount*100/fullCount).toFixed(1)}%\u001b[0m)`); 46 | -------------------------------------------------------------------------------- /src/common/customizations/defaults.ts: -------------------------------------------------------------------------------- 1 | import { Customizations } from '../interfaces/customizations'; 2 | 3 | // Everything OFF 4 | export const defaults: Customizations = { 5 | // Defaults 6 | defaultPort: null, 7 | defaultUser: null, 8 | defaultDatabase: null, 9 | dataTypes: [], 10 | indexTypes: [], 11 | foreignActions: [], 12 | operators: ['=', '!=', '>', '<', '>=', '<=', 'IN', 'NOT IN', 'LIKE', 'NOT LIKE', 'BETWEEN', 'IS NULL', 'IS NOT NULL'], 13 | // Core 14 | database: false, 15 | collations: false, 16 | engines: false, 17 | connectionSchema: false, 18 | sslConnection: false, 19 | sshConnection: false, 20 | fileConnection: false, 21 | cancelQueries: false, 22 | singleConnectionMode: false, 23 | // Tools 24 | processesList: false, 25 | usersManagement: false, 26 | variables: false, 27 | // Structure 28 | schemas: false, 29 | tables: false, 30 | views: false, 31 | triggers: false, 32 | triggerFunctions: false, 33 | routines: false, 34 | functions: false, 35 | schedulers: false, 36 | // Misc 37 | elementsWrapper: '', 38 | stringsWrapper: '"', 39 | tableAdd: false, 40 | tableTruncateDisableFKCheck: false, 41 | tableDdl: false, 42 | viewAdd: false, 43 | triggerAdd: false, 44 | triggerFunctionAdd: false, 45 | routineAdd: false, 46 | functionAdd: false, 47 | schedulerAdd: false, 48 | databaseEdit: false, 49 | schemaEdit: false, 50 | schemaDrop: false, 51 | schemaExport: false, 52 | exportByChunks: false, 53 | schemaImport: false, 54 | tableSettings: false, 55 | tableArray: false, 56 | tableRealCount: false, 57 | tableDuplicate: false, 58 | tableCheck: false, 59 | viewSettings: false, 60 | triggerSettings: false, 61 | triggerFunctionSettings: false, 62 | routineSettings: false, 63 | functionSettings: false, 64 | schedulerSettings: false, 65 | indexes: false, 66 | foreigns: false, 67 | sortableFields: false, 68 | unsigned: false, 69 | nullable: false, 70 | nullablePrimary: false, 71 | zerofill: false, 72 | autoIncrement: false, 73 | comment: false, 74 | collation: false, 75 | definer: false, 76 | onUpdate: false, 77 | viewAlgorithm: false, 78 | viewSqlSecurity: false, 79 | viewUpdateOption: false, 80 | procedureDeterministic: false, 81 | procedureDataAccess: false, 82 | procedureSql: null, 83 | procedureContext: false, 84 | procedureContextValues: [], 85 | procedureLanguage: false, 86 | functionDeterministic: false, 87 | functionDataAccess: false, 88 | functionSql: null, 89 | functionContext: false, 90 | functionLanguage: false, 91 | triggerSql: null, 92 | triggerStatementInCreation: false, 93 | triggerMultipleEvents: false, 94 | triggerTableInName: false, 95 | triggerUpdateColumns: false, 96 | triggerOnlyRename: false, 97 | triggerEnableDisable: false, 98 | triggerFunctionSql: null, 99 | triggerFunctionlanguages: null, 100 | parametersLength: false, 101 | languages: null, 102 | readOnlyMode: false 103 | }; 104 | -------------------------------------------------------------------------------- /src/common/customizations/firebird.ts: -------------------------------------------------------------------------------- 1 | import firebirdTypes from '../data-types/firebird'; 2 | import { Customizations } from '../interfaces/customizations'; 3 | import { defaults } from './defaults'; 4 | 5 | export const customizations: Customizations = { 6 | ...defaults, 7 | // Defaults 8 | defaultPort: 3050, 9 | defaultUser: 'SYSDBA', 10 | defaultDatabase: null, 11 | dataTypes: firebirdTypes, 12 | indexTypes: [ 13 | 'PRIMARY', 14 | // 'CHECK', 15 | 'UNIQUE' 16 | ], 17 | foreignActions: [ 18 | 'RESTRICT', 19 | 'NO ACTION', 20 | 'CASCADE', 21 | 'SET NULL', 22 | 'SET DEFAULT' 23 | ], 24 | // Core 25 | database: true, 26 | collations: false, 27 | engines: false, 28 | connectionSchema: false, 29 | sslConnection: false, 30 | sshConnection: false, 31 | fileConnection: false, 32 | cancelQueries: false, 33 | // Tools 34 | processesList: false, 35 | usersManagement: false, 36 | variables: false, 37 | // Structure 38 | schemas: false, 39 | tables: true, 40 | views: true, 41 | triggers: true, 42 | routines: true, 43 | functions: false, 44 | // Settings 45 | elementsWrapper: '"', 46 | stringsWrapper: '\'', 47 | tableAdd: true, 48 | tableSettings: true, 49 | tableRealCount: true, 50 | viewAdd: true, 51 | viewSettings: true, 52 | triggerAdd: true, 53 | triggerMultipleEvents: true, 54 | triggerSql: 'BEGIN\r\n\r\nEND', 55 | routineAdd: true, 56 | procedureContext: true, 57 | procedureContextValues: ['IN', 'OUT'], 58 | procedureSql: 'BEGIN\r\n\r\nEND', 59 | parametersLength: true, 60 | indexes: true, 61 | foreigns: true, 62 | nullable: true 63 | }; 64 | -------------------------------------------------------------------------------- /src/common/customizations/index.ts: -------------------------------------------------------------------------------- 1 | import * as firebird from 'common/customizations/firebird'; 2 | import * as mysql from 'common/customizations/mysql'; 3 | import * as postgresql from 'common/customizations/postgresql'; 4 | import * as sqlite from 'common/customizations/sqlite'; 5 | import { Customizations } from 'common/interfaces/customizations'; 6 | 7 | export default { 8 | maria: mysql.customizations, 9 | mysql: mysql.customizations, 10 | pg: postgresql.customizations, 11 | sqlite: sqlite.customizations, 12 | firebird: firebird.customizations 13 | } as { 14 | maria: Customizations; 15 | mysql: Customizations; 16 | pg: Customizations; 17 | sqlite: Customizations; 18 | firebird: Customizations; 19 | }; 20 | -------------------------------------------------------------------------------- /src/common/customizations/mysql.ts: -------------------------------------------------------------------------------- 1 | import mysqlTypes from '../data-types/mysql'; 2 | import { Customizations } from '../interfaces/customizations'; 3 | import { defaults } from './defaults'; 4 | 5 | export const customizations: Customizations = { 6 | ...defaults, 7 | // Defaults 8 | defaultPort: 3306, 9 | defaultUser: 'root', 10 | defaultDatabase: null, 11 | dataTypes: mysqlTypes, 12 | operators: ['=', '!=', '>', '<', '>=', '<=', 'IN', 'NOT IN', 'LIKE', 'NOT LIKE', 'RLIKE', 'NOT RLIKE', 'BETWEEN', 'IS NULL', 'IS NOT NULL'], 13 | indexTypes: [ 14 | 'PRIMARY', 15 | 'INDEX', 16 | 'UNIQUE', 17 | 'FULLTEXT' 18 | ], 19 | foreignActions: [ 20 | 'RESTRICT', 21 | 'CASCADE', 22 | 'SET NULL', 23 | 'NO ACTION' 24 | ], 25 | // Core 26 | connectionSchema: true, 27 | collations: true, 28 | engines: true, 29 | sslConnection: true, 30 | sshConnection: true, 31 | cancelQueries: true, 32 | singleConnectionMode: true, 33 | // Tools 34 | processesList: true, 35 | // Structure 36 | schemas: true, 37 | tables: true, 38 | views: true, 39 | triggers: true, 40 | routines: true, 41 | functions: true, 42 | schedulers: true, 43 | // Settings 44 | elementsWrapper: '`', 45 | stringsWrapper: '"', 46 | tableAdd: true, 47 | tableTruncateDisableFKCheck: true, 48 | tableDuplicate: true, 49 | tableDdl: true, 50 | tableCheck: true, 51 | viewAdd: true, 52 | triggerAdd: true, 53 | routineAdd: true, 54 | functionAdd: true, 55 | schedulerAdd: true, 56 | schemaEdit: true, 57 | schemaDrop: true, 58 | schemaExport: true, 59 | exportByChunks: true, 60 | schemaImport: true, 61 | tableSettings: true, 62 | viewSettings: true, 63 | triggerSettings: true, 64 | routineSettings: true, 65 | functionSettings: true, 66 | schedulerSettings: true, 67 | indexes: true, 68 | foreigns: true, 69 | sortableFields: true, 70 | unsigned: true, 71 | nullable: true, 72 | zerofill: true, 73 | autoIncrement: true, 74 | comment: true, 75 | collation: true, 76 | definer: true, 77 | onUpdate: true, 78 | viewAlgorithm: true, 79 | viewSqlSecurity: true, 80 | viewUpdateOption: true, 81 | procedureDeterministic: true, 82 | procedureDataAccess: true, 83 | procedureSql: 'BEGIN\r\n\r\nEND', 84 | procedureContext: true, 85 | procedureContextValues: ['IN', 'OUT', 'INOUT'], 86 | triggerSql: 'BEGIN\r\n\r\nEND', 87 | functionDeterministic: true, 88 | functionDataAccess: true, 89 | functionSql: 'BEGIN\r\n\r\nEND', 90 | parametersLength: true, 91 | readOnlyMode: true 92 | }; 93 | -------------------------------------------------------------------------------- /src/common/customizations/postgresql.ts: -------------------------------------------------------------------------------- 1 | import postgresqlTypes from '../data-types/postgresql'; 2 | import { Customizations } from '../interfaces/customizations'; 3 | import { defaults } from './defaults'; 4 | 5 | export const customizations: Customizations = { 6 | ...defaults, 7 | // Defaults 8 | defaultPort: 5432, 9 | defaultUser: 'postgres', 10 | defaultDatabase: 'postgres', 11 | dataTypes: postgresqlTypes, 12 | indexTypes: [ 13 | 'PRIMARY', 14 | 'INDEX', 15 | 'UNIQUE' 16 | ], 17 | foreignActions: [ 18 | 'RESTRICT', 19 | 'CASCADE', 20 | 'SET NULL', 21 | 'NO ACTION' 22 | ], 23 | // Core 24 | database: true, 25 | sslConnection: true, 26 | sshConnection: true, 27 | cancelQueries: true, 28 | // Tools 29 | processesList: true, 30 | // Structure 31 | schemas: true, 32 | tables: true, 33 | views: true, 34 | materializedViews: true, 35 | triggers: true, 36 | triggerFunctions: true, 37 | routines: true, 38 | functions: true, 39 | // Misc 40 | elementsWrapper: '"', 41 | stringsWrapper: '\'', 42 | tableAdd: true, 43 | tableDuplicate: true, 44 | tableDdl: true, 45 | viewAdd: true, 46 | materializedViewAdd: true, 47 | triggerAdd: true, 48 | triggerFunctionAdd: true, 49 | routineAdd: true, 50 | functionAdd: true, 51 | schemaDrop: true, 52 | schemaExport: true, 53 | schemaImport: true, 54 | databaseEdit: false, 55 | tableSettings: true, 56 | viewSettings: true, 57 | materializedViewSettings: true, 58 | triggerSettings: true, 59 | triggerFunctionSettings: true, 60 | routineSettings: true, 61 | functionSettings: true, 62 | indexes: true, 63 | foreigns: true, 64 | nullable: true, 65 | comment: true, 66 | tableArray: true, 67 | procedureSql: '$procedure$\r\n\r\n$procedure$', 68 | procedureContext: true, 69 | procedureContextValues: ['IN', 'OUT', 'INOUT'], 70 | procedureLanguage: true, 71 | functionSql: '$function$\r\n\r\n$function$', 72 | triggerFunctionSql: '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$', 73 | triggerFunctionlanguages: ['plpgsql'], 74 | functionContext: true, 75 | functionLanguage: true, 76 | triggerSql: 'EXECUTE PROCEDURE ', 77 | triggerStatementInCreation: true, 78 | triggerMultipleEvents: true, 79 | triggerTableInName: true, 80 | triggerOnlyRename: false, 81 | triggerEnableDisable: true, 82 | languages: ['sql', 'plpgsql', 'c', 'internal'], 83 | readOnlyMode: true 84 | }; 85 | -------------------------------------------------------------------------------- /src/common/customizations/sqlite.ts: -------------------------------------------------------------------------------- 1 | import sqliteTypes from '../data-types/sqlite'; 2 | import { Customizations } from '../interfaces/customizations'; 3 | import { defaults } from './defaults'; 4 | 5 | export const customizations: Customizations = { 6 | ...defaults, 7 | dataTypes: sqliteTypes, 8 | indexTypes: [ 9 | 'PRIMARY', 10 | 'INDEX', 11 | 'UNIQUE' 12 | ], 13 | foreignActions: [ 14 | 'RESTRICT', 15 | 'CASCADE', 16 | 'SET NULL', 17 | 'NO ACTION' 18 | ], 19 | // Core 20 | fileConnection: true, 21 | // Structure 22 | schemas: false, 23 | tables: true, 24 | views: true, 25 | triggers: true, 26 | // Settings 27 | elementsWrapper: '"', 28 | stringsWrapper: '\'', 29 | tableAdd: true, 30 | tableDuplicate: true, 31 | viewAdd: true, 32 | triggerAdd: true, 33 | schemaEdit: false, 34 | tableSettings: true, 35 | tableRealCount: true, 36 | viewSettings: true, 37 | triggerSettings: true, 38 | indexes: true, 39 | foreigns: true, 40 | sortableFields: true, 41 | nullable: true, 42 | nullablePrimary: true, 43 | triggerSql: 'BEGIN\r\n\r\nEND', 44 | readOnlyMode: true 45 | }; 46 | -------------------------------------------------------------------------------- /src/common/data-types/firebird.ts: -------------------------------------------------------------------------------- 1 | import { TypesGroup } from 'common/interfaces/antares'; 2 | 3 | export default [ 4 | { 5 | group: 'integer', 6 | types: [ 7 | { 8 | name: 'SMALLINT', 9 | length: false, 10 | collation: false, 11 | unsigned: true, 12 | zerofill: true 13 | }, 14 | { 15 | name: 'INTEGER', 16 | length: false, 17 | collation: false, 18 | unsigned: true, 19 | zerofill: true 20 | }, 21 | { 22 | name: 'BIGINT', 23 | length: false, 24 | collation: false, 25 | unsigned: true, 26 | zerofill: true 27 | } 28 | ] 29 | }, 30 | { 31 | group: 'float', 32 | types: [ 33 | { 34 | name: 'DECIMAL', 35 | length: true, 36 | scale: true, 37 | collation: false, 38 | unsigned: false, 39 | zerofill: false 40 | }, 41 | { 42 | name: 'NUMERIC', 43 | length: true, 44 | scale: true, 45 | collation: false, 46 | unsigned: false, 47 | zerofill: false 48 | }, 49 | { 50 | name: 'FLOAT', 51 | length: false, 52 | collation: false, 53 | unsigned: false, 54 | zerofill: false 55 | }, 56 | { 57 | name: 'DOUBLE PRECISION', 58 | length: false, 59 | collation: false, 60 | unsigned: false, 61 | zerofill: false 62 | } 63 | ] 64 | }, 65 | { 66 | group: 'string', 67 | types: [ 68 | { 69 | name: 'CHAR', 70 | length: true, 71 | collation: true, 72 | unsigned: false, 73 | zerofill: false 74 | }, 75 | { 76 | name: 'VARCHAR', 77 | length: true, 78 | collation: true, 79 | unsigned: false, 80 | zerofill: false 81 | }, 82 | { 83 | name: 'BLOB-TEXT', 84 | length: false, 85 | collation: true, 86 | unsigned: false, 87 | zerofill: false 88 | } 89 | ] 90 | }, 91 | { 92 | group: 'binary', 93 | types: [ 94 | { 95 | name: 'BLOB', 96 | length: false, 97 | collation: false, 98 | unsigned: false, 99 | zerofill: false 100 | }, 101 | { 102 | name: 'CHAR-BINARY', 103 | length: false, 104 | collation: false, 105 | unsigned: false, 106 | zerofill: false 107 | } 108 | ] 109 | }, 110 | { 111 | group: 'time', 112 | types: [ 113 | { 114 | name: 'DATE', 115 | length: false, 116 | collation: false, 117 | unsigned: false, 118 | zerofill: false 119 | }, 120 | { 121 | name: 'TIME', 122 | length: true, 123 | collation: false, 124 | unsigned: false, 125 | zerofill: false 126 | }, 127 | { 128 | name: 'TIMESTAMP', 129 | length: false, 130 | collation: false, 131 | unsigned: false, 132 | zerofill: false 133 | } 134 | ] 135 | } 136 | ] as TypesGroup[]; 137 | -------------------------------------------------------------------------------- /src/common/fieldTypes.ts: -------------------------------------------------------------------------------- 1 | export const TEXT = [ 2 | 'CHAR', 3 | 'VARCHAR', 4 | 'CHARACTER', 5 | 'CHARACTER VARYING' 6 | ]; 7 | 8 | export const LONG_TEXT = [ 9 | 'TEXT', 10 | 'MEDIUMTEXT', 11 | 'LONGTEXT', 12 | 'JSON', 13 | 'VARBINARY', 14 | 'BLOB-TEXT' 15 | ]; 16 | 17 | export const ARRAY = [ 18 | 'ARRAY', 19 | 'ANYARRAY' 20 | ]; 21 | 22 | export const TEXT_SEARCH = [ 23 | 'TSVECTOR', 24 | 'TSQUERY' 25 | ]; 26 | 27 | export const NUMBER = [ 28 | 'INT', 29 | 'TINYINT', 30 | 'SMALLINT', 31 | 'MEDIUMINT', 32 | 'BIGINT', 33 | 'NUMERIC', 34 | 'INTEGER', 35 | 'SMALLSERIAL', 36 | 'SERIAL', 37 | 'BIGSERIAL', 38 | 'OID', 39 | 'XID', 40 | 'INT64' 41 | ]; 42 | 43 | export const FLOAT = [ 44 | 'FLOAT', 45 | 'DECIMAL', 46 | 'DOUBLE', 47 | 'REAL', 48 | 'DOUBLE PRECISION', 49 | 'MONEY' 50 | ]; 51 | 52 | export const IS_BIGINT = [ 53 | 'BIGINT', 54 | 'BIGSERIAL', 55 | 'DOUBLE PRECISION' 56 | ]; 57 | 58 | export const BOOLEAN = [ 59 | 'BOOL', 60 | 'BOOLEAN' 61 | ]; 62 | 63 | export const DATE = ['DATE']; 64 | 65 | export const TIME = [ 66 | 'TIME', 67 | 'TIME WITH TIME ZONE' 68 | ]; 69 | 70 | export const DATETIME = [ 71 | 'DATETIME', 72 | 'TIMESTAMP', 73 | 'TIMESTAMP WITHOUT TIME ZONE', 74 | 'TIMESTAMP WITH TIME ZONE' 75 | ]; 76 | 77 | // Used to check datetime fields only 78 | export const HAS_TIMEZONE = [ 79 | 'TIMESTAMP WITH TIME ZONE', 80 | 'TIME WITH TIME ZONE' 81 | ]; 82 | 83 | export const BLOB = [ 84 | 'BLOB', 85 | 'TINYBLOB', 86 | 'MEDIUMBLOB', 87 | 'LONGBLOB', 88 | 'LONG_BLOB', 89 | 'BYTEA', 90 | 'CHAR-BINARY' 91 | ]; 92 | 93 | export const BIT = [ 94 | 'BIT', 95 | 'BIT VARYING' 96 | ]; 97 | 98 | export const BINARY = [ 99 | 'BINARY' 100 | ]; 101 | 102 | export const UUID = [ 103 | 'UUID' 104 | ]; 105 | 106 | export const SPATIAL = [ 107 | 'POINT', 108 | 'LINESTRING', 109 | 'POLYGON', 110 | 'GEOMETRY', 111 | 'MULTIPOINT', 112 | 'MULTILINESTRING', 113 | 'MULTIPOLYGON', 114 | 'GEOMCOLLECTION', 115 | 'GEOMETRYCOLLECTION' 116 | ]; 117 | 118 | // Used to check multi spatial fields only 119 | export const IS_MULTI_SPATIAL = [ 120 | 'MULTIPOINT', 121 | 'MULTILINESTRING', 122 | 'MULTIPOLYGON', 123 | 'GEOMCOLLECTION', 124 | 'GEOMETRYCOLLECTION' 125 | ]; 126 | -------------------------------------------------------------------------------- /src/common/interfaces/customizations.ts: -------------------------------------------------------------------------------- 1 | import { TypesGroup } from './antares'; 2 | import { TableFilterOperator } from './tableApis'; 3 | 4 | export interface Customizations { 5 | // Defaults 6 | defaultPort?: number; 7 | defaultUser?: string; 8 | defaultDatabase?: string; 9 | dataTypes?: TypesGroup[]; 10 | indexTypes?: string[]; 11 | foreignActions?: string[]; 12 | operators?: TableFilterOperator[]; 13 | // Core 14 | database?: boolean; 15 | collations?: boolean; 16 | engines?: boolean; 17 | connectionSchema?: boolean; 18 | sslConnection?: boolean; 19 | sshConnection?: boolean; 20 | fileConnection?: boolean; 21 | cancelQueries?: boolean; 22 | singleConnectionMode?: boolean; 23 | // Tools 24 | processesList?: boolean; 25 | usersManagement?: boolean; 26 | variables?: boolean; 27 | // Structure 28 | schemas?: boolean; 29 | tables?: boolean; 30 | views?: boolean; 31 | materializedViews?: boolean; 32 | triggers?: boolean; 33 | triggerFunctions?: boolean; 34 | routines?: boolean; 35 | functions?: boolean; 36 | schedulers?: boolean; 37 | // Misc 38 | elementsWrapper: string; 39 | stringsWrapper: string; 40 | tableAdd?: boolean; 41 | tableSettings?: boolean; 42 | tableDuplicate?: boolean; 43 | tableArray?: boolean; 44 | tableRealCount?: boolean; 45 | tableTruncateDisableFKCheck?: boolean; 46 | tableCheck?: boolean; 47 | tableDdl?: boolean; 48 | viewAdd?: boolean; 49 | viewSettings?: boolean; 50 | materializedViewAdd?: boolean; 51 | materializedViewSettings?: boolean; 52 | triggerAdd?: boolean; 53 | triggerFunctionAdd?: boolean; 54 | routineAdd?: boolean; 55 | functionAdd?: boolean; 56 | schedulerAdd?: boolean; 57 | databaseEdit?: boolean; 58 | schemaEdit?: boolean; 59 | schemaDrop?: boolean; 60 | schemaExport?: boolean; 61 | exportByChunks?: boolean; 62 | schemaImport?: boolean; 63 | triggerSettings?: boolean; 64 | triggerFunctionSettings?: boolean; 65 | routineSettings?: boolean; 66 | functionSettings?: boolean; 67 | schedulerSettings?: boolean; 68 | indexes?: boolean; 69 | foreigns?: boolean; 70 | sortableFields?: boolean; 71 | unsigned?: boolean; 72 | nullable?: boolean; 73 | nullablePrimary?: boolean; 74 | zerofill?: boolean; 75 | autoIncrement?: boolean; 76 | comment?: boolean; 77 | collation?: boolean; 78 | definer?: boolean; 79 | onUpdate?: boolean; 80 | viewAlgorithm?: boolean; 81 | viewSqlSecurity?: boolean; 82 | viewUpdateOption?: boolean; 83 | procedureDeterministic?: boolean; 84 | procedureDataAccess?: boolean; 85 | procedureSql?: string; 86 | procedureContext?: boolean; 87 | procedureContextValues?: string[]; 88 | procedureLanguage?: boolean; 89 | functionDeterministic?: boolean; 90 | functionDataAccess?: boolean; 91 | functionSql?: string; 92 | functionContext?: boolean; 93 | functionLanguage?: boolean; 94 | triggerSql?: string; 95 | triggerStatementInCreation?: boolean; 96 | triggerMultipleEvents?: boolean; 97 | triggerTableInName?: boolean; 98 | triggerUpdateColumns?: boolean; 99 | triggerOnlyRename?: boolean; 100 | triggerEnableDisable?: boolean; 101 | triggerFunctionSql?: string; 102 | triggerFunctionlanguages?: string[]; 103 | parametersLength?: boolean; 104 | languages?: string[]; 105 | readOnlyMode?: boolean; 106 | } 107 | -------------------------------------------------------------------------------- /src/common/interfaces/exporter.ts: -------------------------------------------------------------------------------- 1 | export interface TableParams { 2 | table: string; 3 | includeStructure: boolean; 4 | includeContent: boolean; 5 | includeDropStatement: boolean; 6 | } 7 | 8 | export interface ExportOptions { 9 | schema: string; 10 | tables: { 11 | table: string; 12 | includeStructure: boolean; 13 | includeContent: boolean; 14 | includeDropStatement: boolean; 15 | }[]; 16 | includes: Record; 17 | outputFormat: 'sql' | 'sql.zip'; 18 | outputFile: string; 19 | sqlInsertAfter: number; 20 | sqlInsertDivider: 'bytes' | 'rows'; 21 | } 22 | 23 | export interface ExportState { 24 | totalItems?: number; 25 | currentItemIndex?: number; 26 | currentItem?: string; 27 | op?: string; 28 | } 29 | -------------------------------------------------------------------------------- /src/common/interfaces/importer.ts: -------------------------------------------------------------------------------- 1 | import * as antares from './antares'; 2 | 3 | export interface ImportOptions { 4 | uid: string; 5 | schema: string; 6 | type: antares.ClientCode; 7 | file: string; 8 | } 9 | 10 | export interface ImportState { 11 | fileSize?: number; 12 | readPosition?: number; 13 | percentage?: number; 14 | queryCount?: number; 15 | op?: string; 16 | } 17 | -------------------------------------------------------------------------------- /src/common/interfaces/tableApis.ts: -------------------------------------------------------------------------------- 1 | import { UsableLocale } from '@faker-js/faker'; 2 | 3 | export interface TableUpdateParams { 4 | uid: string; 5 | schema: string; 6 | table: string; 7 | primary?: string; 8 | id: number | string; 9 | content: number | string | boolean | Date | Blob | null; 10 | type: string; 11 | field: string; 12 | } 13 | 14 | export interface TableDeleteParams { 15 | uid: string; 16 | schema: string; 17 | table: string; 18 | primary?: string; 19 | field: string; 20 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 21 | rows: Record; 22 | } 23 | 24 | export type TableFilterOperator = '=' | '!=' | '>' | '<' | '>=' | '<=' | 'IN' | 'NOT IN' | 'LIKE' | 'NOT LIKE' | 'RLIKE' | 'NOT RLIKE' | 'BETWEEN' | 'IS NULL' | 'IS NOT NULL' 25 | 26 | export interface TableFilterClausole { 27 | active: boolean; 28 | field: string; 29 | op:TableFilterOperator; 30 | value: ''; 31 | value2: ''; 32 | } 33 | 34 | export interface InsertRowsParams { 35 | uid: string; 36 | schema: string; 37 | table: string; 38 | row: Record; 47 | repeat: number; 48 | fields: Record; 49 | locale: UsableLocale; 50 | } 51 | -------------------------------------------------------------------------------- /src/common/interfaces/workers.ts: -------------------------------------------------------------------------------- 1 | export type WorkerEvent = 'export-progress' | 'import-progress' | 'query-error' | 'end' | 'cancel' | 'error' 2 | 3 | export interface WorkerIpcMessage { 4 | type: WorkerEvent; 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 6 | payload: any; 7 | } 8 | -------------------------------------------------------------------------------- /src/common/libs/bufferToBase64.ts: -------------------------------------------------------------------------------- 1 | export function bufferToBase64 (buf: Buffer) { 2 | const binstr = Array.prototype.map.call(buf, (ch: number) => { 3 | return String.fromCharCode(ch); 4 | }).join(''); 5 | return Buffer.from(binstr, 'binary').toString('base64'); 6 | } 7 | -------------------------------------------------------------------------------- /src/common/libs/encrypter.ts: -------------------------------------------------------------------------------- 1 | import * as crypto from 'crypto'; 2 | 3 | const algorithm = 'aes-256-gcm'; 4 | 5 | function encrypt (text: string, password: string) { 6 | const iv = crypto.randomBytes(16); 7 | const key = crypto.scryptSync(password, 'antares', 32); 8 | const cipher = crypto.createCipheriv(algorithm, key, iv); 9 | const encrypted = Buffer.concat([cipher.update(text), cipher.final()]); 10 | const authTag = cipher.getAuthTag(); 11 | 12 | return { 13 | iv: iv.toString('hex'), 14 | authTag: authTag.toString('hex'), 15 | content: encrypted.toString('hex') 16 | }; 17 | } 18 | 19 | function decrypt (hash: { iv: string; content: string; authTag: string }, password: string) { 20 | const key = crypto.scryptSync(password, 'antares', 32); 21 | const decipher = crypto.createDecipheriv(algorithm, key, Buffer.from(hash.iv, 'hex')); 22 | decipher.setAuthTag(Buffer.from(hash.authTag, 'hex')); 23 | const decrpyted = decipher.update(hash.content, 'hex', 'utf8') + decipher.final('utf8'); 24 | 25 | return decrpyted; 26 | } 27 | 28 | export { decrypt, encrypt }; 29 | -------------------------------------------------------------------------------- /src/common/libs/fakerCustom.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | import * as moment from 'moment'; 3 | 4 | export const fakerCustom = { 5 | seed: faker.seed, 6 | setLocale: faker.setLocale, 7 | ...faker, 8 | date: { 9 | now: () => moment().format('YYYY-MM-DD HH:mm:ss'), 10 | ...faker.date 11 | }, 12 | time: { 13 | now: () => moment().format('HH:mm:ss'), 14 | random: () => moment(faker.date.recent()).format('HH:mm:ss'), 15 | ...faker.time 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /src/common/libs/formatBytes.ts: -------------------------------------------------------------------------------- 1 | export function formatBytes (bytes: number, decimals = 2) { 2 | if (bytes === 0) return '0 Bytes'; 3 | 4 | const k = 1024; 5 | const dm = decimals < 0 ? 0 : decimals; 6 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; 7 | 8 | const i = Math.floor(Math.log(bytes) / Math.log(k)); 9 | 10 | return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; 11 | } 12 | -------------------------------------------------------------------------------- /src/common/libs/getArrayDepth.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | export function getArrayDepth (array: any[]): number { 3 | return Array.isArray(array) 4 | ? 1 + Math.max(0, ...array.map(getArrayDepth)) 5 | : 0; 6 | } 7 | -------------------------------------------------------------------------------- /src/common/libs/hexToBinary.ts: -------------------------------------------------------------------------------- 1 | const lookup = { 2 | 0: '0000', 3 | 1: '0001', 4 | 2: '0010', 5 | 3: '0011', 6 | 4: '0100', 7 | 5: '0101', 8 | 6: '0110', 9 | 7: '0111', 10 | 8: '1000', 11 | 9: '1001', 12 | a: '1010', 13 | b: '1011', 14 | c: '1100', 15 | d: '1101', 16 | e: '1110', 17 | f: '1111', 18 | A: '1010', 19 | B: '1011', 20 | C: '1100', 21 | D: '1101', 22 | E: '1110', 23 | F: '1111' 24 | } as const; 25 | 26 | export type HexChar = keyof typeof lookup 27 | 28 | export default function hexToBinary (hex: HexChar[]) { 29 | let binary = ''; 30 | for (let i = 0; i < hex.length; i++) 31 | binary += lookup[hex[i]]; 32 | 33 | return binary; 34 | } 35 | -------------------------------------------------------------------------------- /src/common/libs/mimeFromHex.ts: -------------------------------------------------------------------------------- 1 | import { match } from 'ciaplu'; 2 | 3 | export function mimeFromHex (hex: string) { 4 | return match(hex.substring(0, 4)) // 2 bytes 5 | .with('424D', () => ({ ext: 'bmp', mime: 'image/bmp' })) 6 | .with('1F8B', () => ({ ext: 'tar.gz', mime: 'application/gzip' })) 7 | .with('0B77', () => ({ ext: 'ac3', mime: 'audio/vnd.dolby.dd-raw' })) 8 | .with('7801', () => ({ ext: 'dmg', mime: 'application/x-apple-diskimage' })) 9 | .with('4D5A', () => ({ ext: 'exe', mime: 'application/x-msdownload' })) 10 | .when((val) => ['1FA0', '1F9D'].includes(val), () => ({ ext: 'Z', mime: 'application/x-compress' })) 11 | .extracting(() => hex.substring(0, 6)) // 3 bytes 12 | .with('FFD8FF', () => ({ ext: 'jpg', mime: 'image/jpeg' })) 13 | .with('4949BC', () => ({ ext: 'jxr', mime: 'image/vnd.ms-photo' })) 14 | .with('425A68', () => ({ ext: 'bz2', mime: 'application/x-bzip2' })) 15 | .extracting(() => hex) // 4 bytes 16 | .with('89504E47', () => ({ ext: 'png', mime: 'image/png' })) 17 | .with('47494638', () => ({ ext: 'gif', mime: 'image/gif' })) 18 | .with('25504446', () => ({ ext: 'pdf', mime: 'application/pdf' })) 19 | .with('504B0304', () => ({ ext: 'zip', mime: 'application/zip' })) 20 | .with('425047FB', () => ({ ext: 'bpg', mime: 'image/bpg' })) 21 | .with('4D4D002A', () => ({ ext: 'tif', mime: 'image/tiff' })) 22 | .with('00000100', () => ({ ext: 'ico', mime: 'image/x-icon' })) 23 | .otherwise(() => ({ ext: '', mime: 'unknown ' + hex })) 24 | .return(); 25 | } 26 | -------------------------------------------------------------------------------- /src/common/libs/uidGen.ts: -------------------------------------------------------------------------------- 1 | export function uidGen (prefix?: string) { 2 | return (prefix ? `${prefix}:` : '') + Math.random().toString(36).substring(2, 11).toUpperCase(); 3 | } 4 | -------------------------------------------------------------------------------- /src/main/ipc-handlers/database.ts: -------------------------------------------------------------------------------- 1 | import * as antares from 'common/interfaces/antares'; 2 | import { ipcMain } from 'electron'; 3 | 4 | import { validateSender } from '../libs/misc/validateSender'; 5 | 6 | export default (connections: Record) => { 7 | ipcMain.handle('get-databases', async (event, uid) => { 8 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 9 | 10 | try { 11 | const result = await connections[uid].getDatabases(); 12 | return { status: 'success', response: result }; 13 | } 14 | catch (err) { 15 | return { status: 'error', response: err.toString() }; 16 | } 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /src/main/ipc-handlers/functions.ts: -------------------------------------------------------------------------------- 1 | import * as antares from 'common/interfaces/antares'; 2 | import { ipcMain } from 'electron'; 3 | 4 | import { validateSender } from '../libs/misc/validateSender'; 5 | 6 | export default (connections: Record) => { 7 | ipcMain.handle('get-function-informations', async (event, params) => { 8 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 9 | 10 | try { 11 | const result = await connections[params.uid].getFunctionInformations(params); 12 | return { status: 'success', response: result }; 13 | } 14 | catch (err) { 15 | return { status: 'error', response: err.toString() }; 16 | } 17 | }); 18 | 19 | ipcMain.handle('drop-function', async (event, params) => { 20 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 21 | 22 | try { 23 | await connections[params.uid].dropFunction(params); 24 | return { status: 'success' }; 25 | } 26 | catch (err) { 27 | return { status: 'error', response: err.toString() }; 28 | } 29 | }); 30 | 31 | ipcMain.handle('alter-function', async (event, params) => { 32 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 33 | 34 | try { 35 | await connections[params.uid].alterFunction(params); 36 | return { status: 'success' }; 37 | } 38 | catch (err) { 39 | return { status: 'error', response: err.toString() }; 40 | } 41 | }); 42 | 43 | ipcMain.handle('alter-trigger-function', async (event, params) => { 44 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 45 | 46 | try { 47 | await connections[params.uid].alterTriggerFunction(params); 48 | return { status: 'success' }; 49 | } 50 | catch (err) { 51 | return { status: 'error', response: err.toString() }; 52 | } 53 | }); 54 | 55 | ipcMain.handle('create-function', async (event, params) => { 56 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 57 | 58 | try { 59 | await connections[params.uid].createFunction(params); 60 | return { status: 'success' }; 61 | } 62 | catch (err) { 63 | return { status: 'error', response: err.toString() }; 64 | } 65 | }); 66 | 67 | ipcMain.handle('create-trigger-function', async (event, params) => { 68 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 69 | 70 | try { 71 | await connections[params.uid].createTriggerFunction(params); 72 | return { status: 'success' }; 73 | } 74 | catch (err) { 75 | return { status: 'error', response: err.toString() }; 76 | } 77 | }); 78 | }; 79 | -------------------------------------------------------------------------------- /src/main/ipc-handlers/index.ts: -------------------------------------------------------------------------------- 1 | import * as antares from 'common/interfaces/antares'; 2 | 3 | import application from './application'; 4 | import connection from './connection'; 5 | import database from './database'; 6 | import functions from './functions'; 7 | import routines from './routines'; 8 | import schedulers from './schedulers'; 9 | import schema from './schema'; 10 | import tables from './tables'; 11 | import triggers from './triggers'; 12 | import updates from './updates'; 13 | import users from './users'; 14 | import views from './views'; 15 | 16 | const connections: Record = {}; 17 | 18 | export default () => { 19 | connection(connections); 20 | tables(connections); 21 | views(connections); 22 | triggers(connections); 23 | routines(connections); 24 | functions(connections); 25 | schedulers(connections); 26 | database(connections); 27 | schema(connections); 28 | users(connections); 29 | updates(); 30 | application(); 31 | }; 32 | -------------------------------------------------------------------------------- /src/main/ipc-handlers/routines.ts: -------------------------------------------------------------------------------- 1 | import * as antares from 'common/interfaces/antares'; 2 | import { ipcMain } from 'electron'; 3 | 4 | import { validateSender } from '../libs/misc/validateSender'; 5 | 6 | export default (connections: Record) => { 7 | ipcMain.handle('get-routine-informations', async (event, params) => { 8 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 9 | 10 | try { 11 | const result = await connections[params.uid].getRoutineInformations(params); 12 | return { status: 'success', response: result }; 13 | } 14 | catch (err) { 15 | return { status: 'error', response: err.toString() }; 16 | } 17 | }); 18 | 19 | ipcMain.handle('drop-routine', async (event, params) => { 20 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 21 | 22 | try { 23 | await connections[params.uid].dropRoutine(params); 24 | return { status: 'success' }; 25 | } 26 | catch (err) { 27 | return { status: 'error', response: err.toString() }; 28 | } 29 | }); 30 | 31 | ipcMain.handle('alter-routine', async (event, params) => { 32 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 33 | 34 | try { 35 | await connections[params.uid].alterRoutine(params); 36 | return { status: 'success' }; 37 | } 38 | catch (err) { 39 | return { status: 'error', response: err.toString() }; 40 | } 41 | }); 42 | 43 | ipcMain.handle('create-routine', async (event, params) => { 44 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 45 | 46 | try { 47 | await connections[params.uid].createRoutine(params); 48 | return { status: 'success' }; 49 | } 50 | catch (err) { 51 | return { status: 'error', response: err.toString() }; 52 | } 53 | }); 54 | }; 55 | -------------------------------------------------------------------------------- /src/main/ipc-handlers/schedulers.ts: -------------------------------------------------------------------------------- 1 | import * as antares from 'common/interfaces/antares'; 2 | import { ipcMain } from 'electron'; 3 | 4 | import { validateSender } from '../libs/misc/validateSender'; 5 | 6 | export default (connections: Record) => { 7 | ipcMain.handle('get-scheduler-informations', async (event, params) => { 8 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 9 | 10 | try { 11 | const result = await connections[params.uid].getEventInformations(params); 12 | return { status: 'success', response: result }; 13 | } 14 | catch (err) { 15 | return { status: 'error', response: err.toString() }; 16 | } 17 | }); 18 | 19 | ipcMain.handle('drop-scheduler', async (event, params) => { 20 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 21 | 22 | try { 23 | await connections[params.uid].dropEvent(params); 24 | return { status: 'success' }; 25 | } 26 | catch (err) { 27 | return { status: 'error', response: err.toString() }; 28 | } 29 | }); 30 | 31 | ipcMain.handle('alter-scheduler', async (event, params) => { 32 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 33 | 34 | try { 35 | await connections[params.uid].alterEvent(params); 36 | return { status: 'success' }; 37 | } 38 | catch (err) { 39 | return { status: 'error', response: err.toString() }; 40 | } 41 | }); 42 | 43 | ipcMain.handle('create-scheduler', async (event, params) => { 44 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 45 | 46 | try { 47 | await connections[params.uid].createEvent(params); 48 | return { status: 'success' }; 49 | } 50 | catch (err) { 51 | return { status: 'error', response: err.toString() }; 52 | } 53 | }); 54 | 55 | ipcMain.handle('toggle-scheduler', async (event, params) => { 56 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 57 | 58 | try { 59 | if (!params.enabled) 60 | await connections[params.uid].enableEvent({ ...params }); 61 | else 62 | await connections[params.uid].disableEvent({ ...params }); 63 | return { status: 'success' }; 64 | } 65 | catch (err) { 66 | return { status: 'error', response: err.toString() }; 67 | } 68 | }); 69 | }; 70 | -------------------------------------------------------------------------------- /src/main/ipc-handlers/triggers.ts: -------------------------------------------------------------------------------- 1 | import * as antares from 'common/interfaces/antares'; 2 | import { ipcMain } from 'electron'; 3 | 4 | import { validateSender } from '../libs/misc/validateSender'; 5 | 6 | export default (connections: Record) => { 7 | ipcMain.handle('get-trigger-informations', async (event, params) => { 8 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 9 | 10 | try { 11 | const result = await connections[params.uid].getTriggerInformations(params); 12 | return { status: 'success', response: result }; 13 | } 14 | catch (err) { 15 | return { status: 'error', response: err.toString() }; 16 | } 17 | }); 18 | 19 | ipcMain.handle('drop-trigger', async (event, params) => { 20 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 21 | 22 | try { 23 | await connections[params.uid].dropTrigger(params); 24 | return { status: 'success' }; 25 | } 26 | catch (err) { 27 | return { status: 'error', response: err.toString() }; 28 | } 29 | }); 30 | 31 | ipcMain.handle('alter-trigger', async (event, params) => { 32 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 33 | 34 | try { 35 | await connections[params.uid].alterTrigger(params); 36 | return { status: 'success' }; 37 | } 38 | catch (err) { 39 | return { status: 'error', response: err.toString() }; 40 | } 41 | }); 42 | 43 | ipcMain.handle('create-trigger', async (event, params) => { 44 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 45 | 46 | try { 47 | await connections[params.uid].createTrigger(params); 48 | return { status: 'success' }; 49 | } 50 | catch (err) { 51 | return { status: 'error', response: err.toString() }; 52 | } 53 | }); 54 | 55 | ipcMain.handle('toggle-trigger', async (event, params) => { 56 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 57 | 58 | try { 59 | if (!params.enabled) 60 | await connections[params.uid].enableTrigger(params); 61 | else 62 | await connections[params.uid].disableTrigger(params); 63 | return { status: 'success' }; 64 | } 65 | catch (err) { 66 | return { status: 'error', response: err.toString() }; 67 | } 68 | }); 69 | }; 70 | -------------------------------------------------------------------------------- /src/main/ipc-handlers/updates.ts: -------------------------------------------------------------------------------- 1 | import { ipcMain } from 'electron'; 2 | import * as log from 'electron-log/main'; 3 | import * as Store from 'electron-store'; 4 | import { autoUpdater } from 'electron-updater'; 5 | 6 | const persistentStore = new Store({ 7 | name: 'settings', 8 | clearInvalidConfig: true, 9 | migrations: { 10 | '0.7.15': store => { 11 | store.set('allow_prerelease', false); 12 | } 13 | } 14 | }); 15 | 16 | const isMacOS = process.platform === 'darwin'; 17 | let mainWindow: Electron.IpcMainEvent; 18 | autoUpdater.allowPrerelease = persistentStore.get('allow_prerelease', false) as boolean; 19 | 20 | export default () => { 21 | ipcMain.on('check-for-updates', event => { 22 | mainWindow = event; 23 | if (process.windowsStore || (process.platform === 'linux' && !process.env.APPIMAGE)) 24 | mainWindow.reply('no-auto-update'); 25 | else if (isMacOS) { // Temporary solution on MacOS for unsigned app updates 26 | autoUpdater.autoDownload = false; 27 | } 28 | else { 29 | autoUpdater.checkForUpdatesAndNotify().catch(() => { 30 | mainWindow.reply('check-failed'); 31 | }); 32 | } 33 | }); 34 | 35 | ipcMain.on('restart-to-update', () => { 36 | autoUpdater.quitAndInstall(); 37 | }); 38 | 39 | // auto-updater events 40 | autoUpdater.on('checking-for-update', () => { 41 | mainWindow.reply('checking-for-update'); 42 | }); 43 | 44 | autoUpdater.on('update-available', () => { 45 | if (isMacOS) 46 | mainWindow.reply('link-to-download'); 47 | else 48 | mainWindow.reply('update-available'); 49 | }); 50 | 51 | autoUpdater.on('update-not-available', () => { 52 | mainWindow.reply('update-not-available'); 53 | }); 54 | 55 | autoUpdater.on('download-progress', event => { 56 | mainWindow.reply('download-progress', event); 57 | }); 58 | 59 | autoUpdater.on('update-downloaded', () => { 60 | mainWindow.reply('update-downloaded'); 61 | }); 62 | 63 | log.transports.file.level = 'info'; 64 | // log.transports.console.format = '{h}:{i}:{s} {text}'; 65 | autoUpdater.logger = log; 66 | }; 67 | -------------------------------------------------------------------------------- /src/main/ipc-handlers/users.ts: -------------------------------------------------------------------------------- 1 | import * as antares from 'common/interfaces/antares'; 2 | import { ipcMain } from 'electron'; 3 | 4 | import { validateSender } from '../libs/misc/validateSender'; 5 | 6 | export default (connections: Record) => { 7 | ipcMain.handle('get-users', async (event, uid) => { 8 | if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; 9 | 10 | try { 11 | const result = await connections[uid].getUsers(); 12 | return { status: 'success', response: result }; 13 | } 14 | catch (err) { 15 | if (err.code === 'ER_TABLEACCESS_DENIED_ERROR') 16 | return { status: 'success', response: [] }; 17 | return { status: 'error', response: err.toString() }; 18 | } 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /src/main/libs/ClientsFactory.ts: -------------------------------------------------------------------------------- 1 | import * as antares from 'common/interfaces/antares'; 2 | 3 | import { FirebirdSQLClient } from './clients/FirebirdSQLClient'; 4 | import { MySQLClient } from './clients/MySQLClient'; 5 | import { PostgreSQLClient } from './clients/PostgreSQLClient'; 6 | import { SQLiteClient } from './clients/SQLiteClient'; 7 | 8 | export class ClientsFactory { 9 | static getClient (args: antares.ClientParams) { 10 | switch (args.client) { 11 | case 'mysql': 12 | case 'maria': 13 | return new MySQLClient(args); 14 | case 'pg': 15 | return new PostgreSQLClient(args); 16 | case 'sqlite': 17 | return new SQLiteClient(args); 18 | case 'firebird': 19 | return new FirebirdSQLClient(args); 20 | default: 21 | throw new Error(`Unknown database client: ${args.client}`); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/libs/exporters/BaseExporter.ts: -------------------------------------------------------------------------------- 1 | import * as exporter from 'common/interfaces/exporter'; 2 | import * as EventEmitter from 'events'; 3 | import * as fs from 'fs'; 4 | import * as path from 'path'; 5 | import { createGzip, Gzip } from 'zlib'; 6 | 7 | export class BaseExporter extends EventEmitter { 8 | protected _tables; 9 | protected _options; 10 | protected _isCancelled; 11 | protected _outputFileStream: fs.WriteStream; 12 | protected _processedStream: fs.WriteStream | Gzip; 13 | protected _state; 14 | 15 | constructor (tables: exporter.TableParams[], options: exporter.ExportOptions) { 16 | super(); 17 | this._tables = tables; 18 | this._options = options; 19 | this._isCancelled = false; 20 | this._outputFileStream = fs.createWriteStream(this._options.outputFile, { flags: 'w' }); 21 | this._processedStream = null; 22 | this._state = {}; 23 | 24 | if (this._options.outputFormat === 'sql.zip') { 25 | const outputZipStream = createGzip(); 26 | outputZipStream.pipe(this._outputFileStream); 27 | this._processedStream = outputZipStream; 28 | } 29 | else 30 | this._processedStream = this._outputFileStream; 31 | 32 | this._processedStream.once('error', err => { 33 | this._isCancelled = true; 34 | this.emit('error', err); 35 | }); 36 | } 37 | 38 | async run () { 39 | try { 40 | this.emit('start', this); 41 | await this.dump(); 42 | } 43 | catch (err) { 44 | this.emit('error', err); 45 | throw err; 46 | } 47 | finally { 48 | this._processedStream.end(); 49 | this.emit('end'); 50 | } 51 | } 52 | 53 | get isCancelled () { 54 | return this._isCancelled; 55 | } 56 | 57 | get outputFile () { 58 | return this._options.outputFile; 59 | } 60 | 61 | outputFileExists () { 62 | return fs.existsSync(this._options.outputFile); 63 | } 64 | 65 | cancel () { 66 | this._isCancelled = true; 67 | this.emit('cancel'); 68 | this.emitUpdate({ op: 'cancelling' }); 69 | } 70 | 71 | emitUpdate (state: exporter.ExportState) { 72 | this.emit('progress', { ...this._state, ...state }); 73 | } 74 | 75 | writeString (data: string) { 76 | if (this._isCancelled) return; 77 | 78 | try { 79 | fs.accessSync(this._options.outputFile); 80 | } 81 | catch (err) { 82 | this._isCancelled = true; 83 | 84 | const fileName = path.basename(this._options.outputFile); 85 | this.emit('error', `The file ${fileName} is not accessible`); 86 | } 87 | this._processedStream.write(data); 88 | } 89 | 90 | dump () { 91 | throw new Error('Exporter must implement the "dump" method'); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/libs/importers/BaseImporter.ts: -------------------------------------------------------------------------------- 1 | import * as importer from 'common/interfaces/importer'; 2 | import * as EventEmitter from 'events'; 3 | import * as fs from 'fs'; 4 | 5 | export class BaseImporter extends EventEmitter { 6 | protected _options; 7 | protected _isCancelled; 8 | protected _fileHandler; 9 | protected _state; 10 | 11 | constructor (options: importer.ImportOptions) { 12 | super(); 13 | this._options = options; 14 | this._isCancelled = false; 15 | this._fileHandler = fs.createReadStream(this._options.file, { 16 | flags: 'r', 17 | highWaterMark: 4 * 1024 18 | }); 19 | this._state = {}; 20 | 21 | this._fileHandler.once('error', err => { 22 | this._isCancelled = true; 23 | this.emit('error', err); 24 | }); 25 | } 26 | 27 | async run () { 28 | try { 29 | this.emit('start', this); 30 | await this.import(); 31 | } 32 | catch (err) { 33 | this.emit('error', err); 34 | throw err; 35 | } 36 | finally { 37 | this._fileHandler.close(); 38 | this.emit('end'); 39 | } 40 | } 41 | 42 | get isCancelled () { 43 | return this._isCancelled; 44 | } 45 | 46 | cancel () { 47 | this._isCancelled = true; 48 | this.emit('cancel'); 49 | this.emitUpdate({ op: 'cancelling' }); 50 | } 51 | 52 | emitUpdate (state: importer.ImportState) { 53 | this.emit('progress', { ...this._state, ...state }); 54 | } 55 | 56 | import () { 57 | throw new Error('Exporter must implement the "import" method'); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/libs/importers/sql/MySQLlImporter.ts: -------------------------------------------------------------------------------- 1 | import * as importer from 'common/interfaces/importer'; 2 | import * as fs from 'fs/promises'; 3 | import * as mysql from 'mysql2'; 4 | 5 | import MySQLParser from '../../parsers/MySQLParser'; 6 | import { BaseImporter } from '../BaseImporter'; 7 | 8 | export default class MySQLImporter extends BaseImporter { 9 | protected _client: mysql.Pool 10 | 11 | constructor (client: mysql.Pool, options: importer.ImportOptions) { 12 | super(options); 13 | this._client = client; 14 | } 15 | 16 | async import (): Promise { 17 | try { 18 | const { size: totalFileSize } = await fs.stat(this._options.file); 19 | const parser = new MySQLParser(); 20 | let readPosition = 0; 21 | let queryCount = 0; 22 | 23 | this.emitUpdate({ 24 | fileSize: totalFileSize, 25 | readPosition: 0, 26 | percentage: 0, 27 | queryCount: 0 28 | }); 29 | 30 | return new Promise((resolve, reject) => { 31 | this._fileHandler.pipe(parser); 32 | 33 | parser.on('error', reject); 34 | 35 | parser.on('close', async () => { 36 | // console.log('TOTAL QUERIES', queryCount); 37 | // console.log('import end'); 38 | resolve(); 39 | }); 40 | 41 | parser.on('data', async (query) => { 42 | queryCount++; 43 | parser.pause(); 44 | 45 | try { 46 | await this._client.query(query); 47 | } 48 | catch (error) { 49 | this.emit('query-error', { 50 | sql: query, 51 | message: error.sqlMessage || error.message, 52 | sqlSnippet: error.sql, 53 | time: new Date().getTime() 54 | }); 55 | } 56 | 57 | this.emitUpdate({ 58 | queryCount, 59 | readPosition, 60 | percentage: readPosition / totalFileSize * 100 61 | }); 62 | this._fileHandler.pipe(parser); 63 | parser.resume(); 64 | }); 65 | 66 | parser.on('pause', () => { 67 | this._fileHandler.unpipe(parser); 68 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 69 | (this._fileHandler as any).readableFlowing = false; 70 | }); 71 | 72 | this._fileHandler.on('data', (chunk) => { 73 | readPosition += chunk.length; 74 | }); 75 | 76 | this._fileHandler.on('error', (err) => { 77 | console.log(err); 78 | reject(err); 79 | }); 80 | }); 81 | } 82 | catch (err) { 83 | console.log(err); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/libs/importers/sql/PostgreSQLImporter.ts: -------------------------------------------------------------------------------- 1 | import * as importer from 'common/interfaces/importer'; 2 | import * as fs from 'fs/promises'; 3 | import * as pg from 'pg'; 4 | 5 | import PostgreSQLParser from '../../parsers/PostgreSQLParser'; 6 | import { BaseImporter } from '../BaseImporter'; 7 | 8 | export default class PostgreSQLImporter extends BaseImporter { 9 | protected _client: pg.PoolClient; 10 | 11 | constructor (client: pg.PoolClient, options: importer.ImportOptions) { 12 | super(options); 13 | this._client = client; 14 | } 15 | 16 | async import (): Promise { 17 | try { 18 | const { size: totalFileSize } = await fs.stat(this._options.file); 19 | const parser = new PostgreSQLParser(); 20 | let readPosition = 0; 21 | let queryCount = 0; 22 | 23 | this.emitUpdate({ 24 | fileSize: totalFileSize, 25 | readPosition: 0, 26 | percentage: 0, 27 | queryCount: 0 28 | }); 29 | 30 | return new Promise((resolve, reject) => { 31 | this._fileHandler.pipe(parser); 32 | 33 | parser.on('error', reject); 34 | 35 | parser.on('close', async () => { 36 | // console.log('TOTAL QUERIES', queryCount); 37 | // console.log('import end'); 38 | resolve(); 39 | }); 40 | 41 | parser.on('data', async (query) => { 42 | queryCount++; 43 | parser.pause(); 44 | 45 | try { 46 | await this._client.query(query); 47 | } 48 | catch (error) { 49 | this.emit('query-error', { 50 | sql: query, 51 | message: error.hint || error.toString(), 52 | sqlSnippet: error.sql, 53 | time: new Date().getTime() 54 | }); 55 | } 56 | 57 | this.emitUpdate({ 58 | queryCount, 59 | readPosition, 60 | percentage: readPosition / totalFileSize * 100 61 | }); 62 | this._fileHandler.pipe(parser); 63 | parser.resume(); 64 | }); 65 | 66 | parser.on('pause', () => { 67 | this._fileHandler.unpipe(parser); 68 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 69 | (this._fileHandler as any).readableFlowing = false; 70 | }); 71 | 72 | this._fileHandler.on('data', (chunk) => { 73 | readPosition += chunk.length; 74 | }); 75 | 76 | this._fileHandler.on('error', (err) => { 77 | console.log(err); 78 | reject(err); 79 | }); 80 | }); 81 | } 82 | catch (err) { 83 | console.log(err); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/libs/misc/ipcLogger.ts: -------------------------------------------------------------------------------- 1 | export type LoggerLevel = 'query' | 'error' 2 | 3 | export const ipcLogger = ({ content, cUid, level }: {content: string; cUid: string; level: LoggerLevel}) => { 4 | if (level === 'error') { 5 | if (process.type !== undefined) { 6 | const mainWindow = require('electron').webContents.fromId(1); 7 | mainWindow.send('non-blocking-exception', { cUid, message: content, date: new Date() }); 8 | } 9 | if (process.env.NODE_ENV === 'development' && process.type === 'browser') console.log(content); 10 | } 11 | else if (level === 'query') { 12 | // Remove comments, newlines and multiple spaces 13 | const escapedSql = content.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' '); 14 | if (process.type !== undefined) { 15 | const mainWindow = require('electron').webContents.fromId(1); 16 | mainWindow.send('query-log', { cUid, sql: escapedSql, date: new Date() }); 17 | } 18 | if (process.env.NODE_ENV === 'development' && process.type === 'browser') console.log(escapedSql); 19 | } 20 | }; -------------------------------------------------------------------------------- /src/main/libs/misc/validateSender.ts: -------------------------------------------------------------------------------- 1 | import { WebFrameMain } from 'electron'; 2 | import * as path from 'path'; 3 | 4 | const isDevelopment = process.env.NODE_ENV !== 'production'; 5 | const isWindows = process.platform === 'win32'; 6 | const indexPath = path.resolve(__dirname, 'index.html').split(path.sep).join('/'); 7 | 8 | export function validateSender (frame: WebFrameMain) { 9 | if (isWindows) return true; // TEMP HOTFIX 10 | const frameUrl = new URL(frame.url); 11 | const prefix = isWindows ? 'file:///' : 'file://'; 12 | const framePath = frameUrl.href.replace(prefix, ''); 13 | 14 | if ((isDevelopment && frameUrl.host === 'localhost:9080') || framePath === indexPath) return true; 15 | return false; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/libs/parsers/MySQLParser.ts: -------------------------------------------------------------------------------- 1 | import { Transform, TransformCallback, TransformOptions } from 'stream'; 2 | 3 | export default class MySQLParser extends Transform { 4 | private _buffer: string; 5 | private _lastChar: string; 6 | private _last9Chars: string; 7 | 8 | encoding: BufferEncoding; 9 | delimiter: string; 10 | isEscape: boolean; 11 | currentQuote: string; 12 | isDelimiter: boolean; 13 | 14 | constructor (opts?: TransformOptions & { delimiter: string }) { 15 | opts = { 16 | delimiter: ';', 17 | encoding: 'utf8', 18 | writableObjectMode: true, 19 | readableObjectMode: true, 20 | ...opts 21 | }; 22 | super(opts); 23 | this._buffer = ''; 24 | this._lastChar = ''; 25 | this._last9Chars = ''; 26 | this.encoding = opts.encoding; 27 | this.delimiter = opts.delimiter; 28 | 29 | this.isEscape = false; 30 | this.currentQuote = null; 31 | this.isDelimiter = false; 32 | } 33 | 34 | _transform (chunk: Buffer, encoding: BufferEncoding, next: TransformCallback) { 35 | for (const char of chunk.toString(this.encoding)) { 36 | this.checkEscape(); 37 | this._buffer += char; 38 | this._lastChar = char; 39 | this._last9Chars += char.trim().toLocaleLowerCase(); 40 | 41 | if (this._last9Chars.length > 9) 42 | this._last9Chars = this._last9Chars.slice(-9); 43 | 44 | this.checkNewDelimiter(char); 45 | this.checkQuote(char); 46 | const query = this.getQuery(); 47 | 48 | if (query) 49 | this.push(query); 50 | } 51 | next(); 52 | } 53 | 54 | checkEscape () { 55 | if (this._buffer.length > 0) { 56 | this.isEscape = this._lastChar === '\\' 57 | ? !this.isEscape 58 | : false; 59 | } 60 | } 61 | 62 | checkNewDelimiter (char: string) { 63 | if (this.currentQuote === null && this._last9Chars === 'delimiter') { 64 | this.isDelimiter = true; 65 | this._buffer = ''; 66 | } 67 | else { 68 | const isNewLine = char === '\n' || char === '\r'; 69 | if (isNewLine && this.isDelimiter) { 70 | this.isDelimiter = false; 71 | this.delimiter = this._buffer.trim(); 72 | this._buffer = ''; 73 | } 74 | } 75 | } 76 | 77 | checkQuote (char: string) { 78 | const isQuote = !this.isEscape && (char === '\'' || char === '"'); 79 | if (isQuote && this.currentQuote === char) 80 | this.currentQuote = null; 81 | 82 | else if (isQuote && this.currentQuote === null) 83 | this.currentQuote = char; 84 | } 85 | 86 | getQuery () { 87 | if (this.isDelimiter) 88 | return false; 89 | 90 | let query: false | string = false; 91 | let demiliterFound = false; 92 | if (this.currentQuote === null && this._buffer.length >= this.delimiter.length) 93 | demiliterFound = this._last9Chars.slice(-this.delimiter.length) === this.delimiter; 94 | 95 | if (demiliterFound) { 96 | const parsedStr = this._buffer.trim(); 97 | query = parsedStr.slice(0, parsedStr.length - this.delimiter.length); 98 | this._buffer = ''; 99 | } 100 | 101 | return query; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/workers/exporter.ts: -------------------------------------------------------------------------------- 1 | import * as antares from 'common/interfaces/antares'; 2 | import * as log from 'electron-log/main'; 3 | import * as fs from 'fs'; 4 | import { parentPort } from 'worker_threads'; 5 | 6 | import { MySQLClient } from '../libs/clients/MySQLClient'; 7 | import { PostgreSQLClient } from '../libs/clients/PostgreSQLClient'; 8 | import { ClientsFactory } from '../libs/ClientsFactory'; 9 | import MysqlExporter from '../libs/exporters/sql/MysqlExporter'; 10 | import PostgreSQLExporter from '../libs/exporters/sql/PostgreSQLExporter'; 11 | let exporter: antares.Exporter; 12 | 13 | log.transports.file.fileName = 'workers.log'; 14 | log.transports.console = null; 15 | log.errorHandler.startCatching(); 16 | 17 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 18 | const exportHandler = async (data: any) => { 19 | const { type, client, tables, options } = data; 20 | 21 | if (type === 'init') { 22 | try { 23 | const connection = await ClientsFactory.getClient({ 24 | client: client.name, 25 | params: client.config, 26 | poolSize: 5 27 | }) as MySQLClient | PostgreSQLClient; 28 | await connection.connect(); 29 | 30 | switch (client.name) { 31 | case 'mysql': 32 | case 'maria': 33 | exporter = new MysqlExporter(connection as MySQLClient, tables, options); 34 | break; 35 | case 'pg': 36 | exporter = new PostgreSQLExporter(connection as PostgreSQLClient, tables, options); 37 | break; 38 | default: 39 | parentPort.postMessage({ 40 | type: 'error', 41 | payload: `"${client.name}" exporter not aviable` 42 | }); 43 | return; 44 | } 45 | 46 | exporter.once('error', err => { 47 | log.error(err.toString()); 48 | parentPort.postMessage({ 49 | type: 'error', 50 | payload: err.toString() 51 | }); 52 | }); 53 | 54 | exporter.once('end', () => { 55 | parentPort.postMessage({ 56 | type: 'end', 57 | payload: { cancelled: exporter.isCancelled } 58 | }); 59 | }); 60 | 61 | exporter.once('cancel', () => { 62 | fs.unlinkSync(exporter.outputFile); 63 | parentPort.postMessage({ type: 'cancel' }); 64 | }); 65 | 66 | exporter.on('progress', state => { 67 | parentPort.postMessage({ 68 | type: 'export-progress', 69 | payload: state 70 | }); 71 | }); 72 | 73 | exporter.run(); 74 | } 75 | catch (err) { 76 | log.error(err.toString()); 77 | parentPort.postMessage({ 78 | type: 'error', 79 | payload: err.toString() 80 | }); 81 | } 82 | } 83 | else if (type === 'cancel') 84 | exporter.cancel(); 85 | }; 86 | 87 | parentPort.on('message', exportHandler); 88 | -------------------------------------------------------------------------------- /src/renderer/components/BaseIcon.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 71 | 72 | 85 | -------------------------------------------------------------------------------- /src/renderer/components/BaseLoader.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | -------------------------------------------------------------------------------- /src/renderer/components/BaseMap.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 92 | 93 | 107 | -------------------------------------------------------------------------------- /src/renderer/components/BaseNotification.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 75 | 76 | 102 | -------------------------------------------------------------------------------- /src/renderer/components/BaseUploadInput.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 63 | 64 | 116 | -------------------------------------------------------------------------------- /src/renderer/components/BaseVirtualScroll.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 96 | -------------------------------------------------------------------------------- /src/renderer/components/ModalAskCredentials.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 92 | -------------------------------------------------------------------------------- /src/renderer/components/ModalDiscardChanges.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 48 | 49 | 54 | -------------------------------------------------------------------------------- /src/renderer/components/ModalProcessesListContext.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | 98 | -------------------------------------------------------------------------------- /src/renderer/components/ModalSettingsChangelog.vue: -------------------------------------------------------------------------------- 1 | 16 | 82 | 93 | -------------------------------------------------------------------------------- /src/renderer/components/ModalSettingsData.vue: -------------------------------------------------------------------------------- 1 | 56 | 70 | -------------------------------------------------------------------------------- /src/renderer/components/TheNotificationsBoard.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 68 | 69 | 77 | -------------------------------------------------------------------------------- /src/renderer/components/WorkspaceEmptyState.vue: -------------------------------------------------------------------------------- 1 | 21 | 53 | 54 | 67 | -------------------------------------------------------------------------------- /src/renderer/components/WorkspaceTabNewTableEmptyState.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 27 | 28 | 41 | -------------------------------------------------------------------------------- /src/renderer/components/WorkspaceTabPropsTableDdlModal.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 77 | -------------------------------------------------------------------------------- /src/renderer/components/WorkspaceTabQueryEmptyState.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 46 | 47 | 53 | -------------------------------------------------------------------------------- /src/renderer/components/WorkspaceTabsContext.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 89 | -------------------------------------------------------------------------------- /src/renderer/composables/useFilters.ts: -------------------------------------------------------------------------------- 1 | import * as moment from 'moment'; 2 | 3 | export function useFilters () { 4 | const cutText = (string: string, length: number, escape?: boolean) => { 5 | if (typeof string !== 'string') return string; 6 | if (escape) string = string.replace(/\s{2,}/g, ' '); 7 | return string.length > length ? `${string.substring(0, length)}...` : string; 8 | }; 9 | 10 | const lastPart = (string: string, length: number) => { 11 | if (!string) return ''; 12 | 13 | string = string.split(/[/\\]+/).pop(); 14 | if (string.length >= length) 15 | string = `...${string.slice(-length)}`; 16 | return string; 17 | }; 18 | 19 | const formatDate = (date: Date) => moment(date).isValid() ? moment(date).format('HH:mm:ss - YYYY/MM/DD') : date; 20 | 21 | const localeString = (number: number | null) => { 22 | if (number !== null) 23 | return number.toLocaleString(); 24 | }; 25 | 26 | const wrapNumber = (num: number) => { 27 | if (!num) return ''; 28 | return `(${num})`; 29 | }; 30 | 31 | const parseKeys = (keys: Record[]) => { 32 | const isMacOS = process.platform === 'darwin'; 33 | return (keys as string[]).map(k => ( 34 | k.split('+') 35 | .map(sk => ( 36 | `${sk}` 37 | ))) 38 | .join('+') 39 | .replaceAll('CommandOrControl', isMacOS ? 'Command' : 'Control') 40 | ).join(', '); 41 | }; 42 | 43 | return { 44 | cutText, 45 | formatDate, 46 | wrapNumber, 47 | lastPart, 48 | localeString, 49 | parseKeys 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /src/renderer/composables/useFocusTrap.ts: -------------------------------------------------------------------------------- 1 | import { customRef, ref } from 'vue'; 2 | 3 | const focusableElementsSelector = 4 | 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'; 5 | 6 | const useFocusTrap = (args?: {disableAutofocus?: boolean}) => { 7 | let localArgs = { 8 | disableAutofocus: false 9 | }; 10 | 11 | if (args) { 12 | localArgs = { 13 | ...localArgs, 14 | ...args 15 | }; 16 | } 17 | 18 | let focusableElements: NodeListOf; 19 | let $firstFocusable: HTMLElement; 20 | let $lastFocusable: HTMLElement; 21 | const isInitiated = ref(false); 22 | 23 | const trapRef = customRef((track, trigger) => { 24 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 25 | let $trapEl: any = null; 26 | return { 27 | get () { 28 | track(); 29 | return $trapEl; 30 | }, 31 | set (value) { 32 | $trapEl = value; 33 | value ? initFocusTrap() : clearFocusTrap(); 34 | trigger(); 35 | } 36 | }; 37 | }); 38 | 39 | function keyHandler (e: KeyboardEvent) { 40 | const isTabPressed = e.key === 'Tab'; 41 | 42 | if (!isTabPressed) return; 43 | 44 | if (e.shiftKey) { 45 | if (document.activeElement === $firstFocusable) { 46 | $lastFocusable.focus(); 47 | e.preventDefault(); 48 | } 49 | } 50 | else { 51 | if (document.activeElement === $lastFocusable) { 52 | $firstFocusable.focus(); 53 | e.preventDefault(); 54 | } 55 | } 56 | } 57 | 58 | function initFocusTrap () { 59 | if (!trapRef.value || (isInitiated.value)) return; 60 | 61 | focusableElements = (trapRef.value as HTMLElement).querySelectorAll( 62 | focusableElementsSelector 63 | ); 64 | 65 | if (focusableElements.length) { 66 | $firstFocusable = focusableElements[0]; 67 | $lastFocusable = focusableElements[focusableElements.length - 1]; 68 | document.addEventListener('keydown', keyHandler); 69 | isInitiated.value = true; 70 | if (!localArgs.disableAutofocus) $firstFocusable.focus(); 71 | } 72 | } 73 | 74 | function clearFocusTrap () { 75 | document.removeEventListener('keydown', keyHandler); 76 | } 77 | 78 | return { 79 | trapRef, 80 | initFocusTrap, 81 | clearFocusTrap 82 | }; 83 | }; 84 | 85 | export { useFocusTrap }; 86 | -------------------------------------------------------------------------------- /src/renderer/composables/useResultTables.ts: -------------------------------------------------------------------------------- 1 | import { TableDeleteParams, TableUpdateParams } from 'common/interfaces/tableApis'; 2 | import { Component, Ref, ref } from 'vue'; 3 | 4 | import Tables from '@/ipc-api/Tables'; 5 | import { useNotificationsStore } from '@/stores/notifications'; 6 | const { addNotification } = useNotificationsStore(); 7 | 8 | export function useResultTables (uid: string, reloadTable: () => void) { 9 | const queryTable: Ref void; 11 | resizeResults: () => void; 12 | refreshScroller: () => void; 13 | downloadTable: (format: string, fileName: string) => void; 14 | applyUpdate: (payload: TableUpdateParams) => void; 15 | }> = ref(null); 16 | const isQuering = ref(false); 17 | 18 | async function updateField (payload: TableUpdateParams) { 19 | isQuering.value = true; 20 | 21 | const params = { 22 | uid: uid, 23 | ...payload 24 | }; 25 | 26 | try { 27 | const { status, response } = await Tables.updateTableCell(params); 28 | if (status === 'success') { 29 | if (response.reload)// Needed for blob fields 30 | reloadTable(); 31 | else 32 | queryTable.value.applyUpdate(payload); 33 | } 34 | else 35 | addNotification({ status: 'error', message: response }); 36 | } 37 | catch (err) { 38 | addNotification({ status: 'error', message: err.stack }); 39 | } 40 | 41 | isQuering.value = false; 42 | } 43 | 44 | async function deleteSelected (payload: TableDeleteParams) { 45 | isQuering.value = true; 46 | 47 | const params = { 48 | uid: uid, 49 | ...payload 50 | }; 51 | 52 | try { 53 | const { status, response } = await Tables.deleteTableRows(params); 54 | isQuering.value = false; 55 | 56 | if (status === 'success') 57 | reloadTable(); 58 | else 59 | addNotification({ status: 'error', message: response }); 60 | } 61 | catch (err) { 62 | addNotification({ status: 'error', message: err.stack }); 63 | isQuering.value = false; 64 | } 65 | } 66 | 67 | return { 68 | queryTable, 69 | isQuering, 70 | updateField, 71 | deleteSelected 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /src/renderer/i18n/ar-SA.ts: -------------------------------------------------------------------------------- 1 | export const arSA = { 2 | general: { 3 | edit: 'تعديل', 4 | save: 'حفظ', 5 | close: 'إغلاق', 6 | delete: 'حفظ', 7 | confirm: 'تأكيد', 8 | cancel: 'إلغاء', 9 | send: 'إرسال', 10 | refresh: 'تحديث', 11 | version: 'النسخة', 12 | donate: 'إدعم', 13 | run: 'شغل', 14 | results: 'النتائج', 15 | size: 'الحجم', 16 | mimeType: 'نوع الميديا', 17 | download: 'تحميل', 18 | add: 'أضف', 19 | data: 'بيانات', 20 | properties: 'خصائص', 21 | insert: 'أدرج', 22 | seconds: 'ثواني', 23 | deleteConfirm: 'هل أنت متأكد من حذف الإتصال؟', 24 | uploadFile: 'رفع ملف' 25 | }, 26 | connection: { 27 | connectionName: 'إسم الإتصال', 28 | client: 'العميل', 29 | hostName: 'إسم المستضيف', 30 | port: 'المنفذ', 31 | user: 'المستخدم', 32 | password: 'الرقم السري', 33 | credentials: 'بيانات الدخول', 34 | connect: 'إتصال', 35 | connected: 'متصل', 36 | disconnect: 'إلغاء الإتصال', 37 | disconnected: 'غير متصل', 38 | addConnection: 'إضافة إتصال', 39 | createConnection: 'إنشاء إتصال', 40 | createNewConnection: 'إنشاء إتصال جديد', 41 | askCredentials: 'إطلب بيانات الدخول', 42 | testConnection: 'إختبر الإتصال', 43 | editConnection: 'عدل الإتصال', 44 | deleteConnection: 'إحذف الإتصال', 45 | connectionSuccessfullyMade: 'تم الإتصال بنجاح!' 46 | }, 47 | database: { 48 | schema: 'Schema', 49 | type: 'نوع', 50 | unableEditFieldWithoutPrimary: 'لا يمكن تعديل الخانة بدون وجود مفتاح رئيسي في النتائج', 51 | editCell: 'تعديل الخلية', 52 | deleteRows: 'حذف صف | حذف {count} صفوف', 53 | confirmToDeleteRows: 'هل أنت متأكد من حذف صف واحد؟? | هل أنت متأكد من حذف {count} صف?', 54 | addNewRow: 'إضافة صف جديد', 55 | numberOfInserts: 'عدد الإدراجات' 56 | }, 57 | application: { 58 | settings: 'الإعدادات', 59 | general: 'عام', 60 | themes: 'الأنماط', 61 | update: 'تحديث', 62 | about: 'حول', 63 | language: 'اللغة', 64 | madeWithJS: 'بني بـ 💛 و جافاسكربت!', 65 | checkForUpdates: 'تأكد من التحديثات', 66 | noUpdatesAvailable: 'لا توجد تحديثات', 67 | checkingForUpdate: 'البحث عن تحديثات', 68 | checkFailure: 'فشل البحث, نرجوا المحاولة في وقت لاحق', 69 | updateAvailable: 'تحديث جديد متوفر', 70 | downloadingUpdate: 'جاري تحميل التحديث', 71 | updateDownloaded: 'تم تحميل التحديث', 72 | restartToInstall: 'قم بإعادة تشغيل انتاريس للتحديث', 73 | notificationsTimeout: 'إنتهاء التنبيهات' 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /src/renderer/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import { createI18n } from 'vue-i18n'; 2 | 3 | import { arSA } from './ar-SA'; 4 | import { caES } from './ca-ES'; 5 | import { csCZ } from './cs-CZ'; 6 | import { deDE } from './de-DE'; 7 | import { enUS } from './en-US'; 8 | import { esES } from './es-ES'; 9 | import { frFR } from './fr-FR'; 10 | import { heIL } from './he-IL'; 11 | import { idID } from './id-ID'; 12 | import { itIT } from './it-IT'; 13 | import { jaJP } from './ja-JP'; 14 | import { koKR } from './ko-KR'; 15 | import { nlNL } from './nl-NL'; 16 | import { ptBR } from './pt-BR'; 17 | import { ruRU } from './ru-RU'; 18 | import { ukUA } from './uk-UA'; 19 | import { uzUZ } from './uz-UZ'; 20 | import { viVN } from './vi-VN'; 21 | import { zhCN } from './zh-CN'; 22 | import { zhTW } from './zh-TW'; 23 | 24 | const messages = { 25 | 'en-US': enUS, 26 | 'it-IT': itIT, 27 | 'ar-SA': arSA, 28 | 'es-ES': esES, 29 | 'fr-FR': frFR, 30 | 'pt-BR': ptBR, 31 | 'de-DE': deDE, 32 | 'vi-VN': viVN, 33 | 'ja-JP': jaJP, 34 | 'zh-CN': zhCN, 35 | 'ru-RU': ruRU, 36 | 'id-ID': idID, 37 | 'ko-KR': koKR, 38 | 'nl-NL': nlNL, 39 | 'ca-ES': caES, 40 | 'cs-CZ': csCZ, 41 | 'uk-UA': ukUA, 42 | 'zh-TW': zhTW, 43 | 'he-IL': heIL, 44 | 'uz-UZ': uzUZ 45 | }; 46 | 47 | type NestedPartial = { 48 | [K in keyof T]?: T[K] extends Array ? Array> : (T[K] extends unknown ? unknown : NestedPartial) 49 | }; 50 | 51 | export type MessageSchema = typeof enUS 52 | export type AvailableLocale = keyof typeof messages 53 | 54 | const i18n = createI18n<[NestedPartial], AvailableLocale>({ 55 | fallbackLocale: 'en-US', 56 | silentTranslationWarn: true, 57 | silentFallbackWarn: true, 58 | allowComposition: true, 59 | messages 60 | }); 61 | 62 | export { i18n }; 63 | -------------------------------------------------------------------------------- /src/renderer/i18n/supported-locales.ts: -------------------------------------------------------------------------------- 1 | export const localesNames: Record = { 2 | 'en-US': 'English', 3 | 'it-IT': 'Italiano', 4 | 'ar-SA': 'العربية', 5 | 'es-ES': 'Español', 6 | 'fr-FR': 'Français', 7 | 'pt-BR': 'Português (Brasil)', 8 | 'de-DE': 'Deutsch (Deutschland)', 9 | 'vi-VN': 'Tiếng Việt', 10 | 'ja-JP': '日本語', 11 | 'zh-CN': '简体中文', 12 | 'zh-TW': '正體中文', 13 | 'ru-RU': 'Русский', 14 | 'id-ID': 'Bahasa Indonesia', 15 | 'ko-KR': '한국어', 16 | 'nl-NL': 'Nederlands', 17 | 'ca-ES': 'Català', 18 | 'cs-CZ': 'Čeština', 19 | 'uk-UA': 'Українська', 20 | 'uz-UZ': 'O`zbek', 21 | 'he-IL': 'עברית' 22 | }; 23 | -------------------------------------------------------------------------------- /src/renderer/images/dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/src/renderer/images/dark.png -------------------------------------------------------------------------------- /src/renderer/images/light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/src/renderer/images/light.png -------------------------------------------------------------------------------- /src/renderer/images/logo-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/src/renderer/images/logo-16.png -------------------------------------------------------------------------------- /src/renderer/images/logo-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/src/renderer/images/logo-32.png -------------------------------------------------------------------------------- /src/renderer/images/logo-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/src/renderer/images/logo-64.png -------------------------------------------------------------------------------- /src/renderer/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antares-sql/antares/6c9792f512fa6d0552d074b7ebf73c86e18d0184/src/renderer/images/logo.png -------------------------------------------------------------------------------- /src/renderer/images/svg/alphabetical-variant.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/images/svg/arrow-right-bold-box.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/images/svg/calendar-clock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/images/svg/circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/images/svg/code-braces.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/images/svg/cube.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/images/svg/mariadb.svg: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/renderer/images/svg/oracledb.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/images/svg/pg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/images/svg/rhombus-split-outline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/images/svg/sqlite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/images/svg/sync-circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/images/svg/table-cog.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/images/svg/table-eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/images/svg/table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | <% if (htmlWebpackPlugin.options.nodeModules) { %> 11 | 16 | <% } %> 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/renderer/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import 'floating-vue/dist/style.css'; 3 | import 'leaflet/dist/leaflet.css'; 4 | import '@/scss/main.scss'; 5 | 6 | import { ipcRenderer } from 'electron'; 7 | import * as FloatingVue from 'floating-vue'; 8 | import { createPinia } from 'pinia'; 9 | import { VueMaskDirective } from 'v-mask'; 10 | import { createApp } from 'vue'; 11 | 12 | import App from '@/App.vue'; 13 | import { i18n } from '@/i18n'; 14 | import { useApplicationStore } from '@/stores/application'; 15 | import { QueryLog, useConsoleStore } from '@/stores/console'; 16 | import { useNotificationsStore } from '@/stores/notifications'; 17 | import { useSettingsStore } from '@/stores/settings'; 18 | 19 | // https://github.com/probil/v-mask/issues/498#issuecomment-827027834 20 | const vMaskV2 = VueMaskDirective; 21 | const vMaskV3 = { 22 | beforeMount: vMaskV2.bind, 23 | updated: vMaskV2.componentUpdated, 24 | unmounted: vMaskV2.unbind 25 | }; 26 | 27 | createApp(App) 28 | .directive('mask', vMaskV3) 29 | .use(createPinia()) 30 | .use(i18n) 31 | .use(FloatingVue) 32 | .mount('#app'); 33 | 34 | const { locale } = useSettingsStore(); 35 | i18n.global.locale = locale; 36 | 37 | // IPC exceptions 38 | ipcRenderer.on('unhandled-exception', (event, error) => { 39 | useNotificationsStore().addNotification({ status: 'error', message: error.message }); 40 | useConsoleStore().putLog('debug', { 41 | level: 'error', 42 | process: 'main', 43 | message: error.message, 44 | date: new Date() 45 | }); 46 | }); 47 | ipcRenderer.on('non-blocking-exception', (event, error) => { 48 | useNotificationsStore().addNotification({ status: 'error', message: error.message }); 49 | useConsoleStore().putLog('debug', { 50 | level: 'error', 51 | process: 'main', 52 | message: error.message, 53 | date: new Date() 54 | }); 55 | }); 56 | 57 | // IPC query logs 58 | ipcRenderer.on('query-log', (event, logRecord: QueryLog) => { 59 | useConsoleStore().putLog('query', logRecord); 60 | }); 61 | 62 | ipcRenderer.on('toggle-console', () => { 63 | useConsoleStore().toggleConsole(); 64 | }); 65 | 66 | // IPC app updates 67 | ipcRenderer.on('checking-for-update', () => { 68 | useApplicationStore().updateStatus = 'checking'; 69 | }); 70 | 71 | ipcRenderer.on('update-available', () => { 72 | useApplicationStore().updateStatus = 'available'; 73 | }); 74 | 75 | ipcRenderer.on('update-not-available', () => { 76 | useApplicationStore().updateStatus = 'noupdate'; 77 | }); 78 | 79 | ipcRenderer.on('check-failed', () => { 80 | useApplicationStore().updateStatus = 'nocheck'; 81 | }); 82 | 83 | ipcRenderer.on('no-auto-update', () => { 84 | useApplicationStore().updateStatus = 'disabled'; 85 | }); 86 | 87 | ipcRenderer.on('download-progress', (event, data) => { 88 | useApplicationStore().updateStatus = 'downloading'; 89 | useApplicationStore().downloadProgress = data.percent; 90 | }); 91 | 92 | ipcRenderer.on('update-downloaded', () => { 93 | useApplicationStore().updateStatus = 'downloaded'; 94 | }); 95 | 96 | ipcRenderer.on('link-to-download', () => { 97 | useApplicationStore().updateStatus = 'link'; 98 | }); 99 | 100 | // IPC shortcuts 101 | ipcRenderer.on('toggle-preferences', () => { 102 | useApplicationStore().showSettingModal('general'); 103 | }); 104 | 105 | ipcRenderer.on('open-updates-preferences', () => { 106 | useApplicationStore().showSettingModal('update'); 107 | ipcRenderer.send('check-for-updates'); 108 | }); 109 | 110 | ipcRenderer.on('update-shortcuts', (event, shortcuts) => { 111 | useSettingsStore().updateShortcuts(shortcuts); 112 | }); 113 | -------------------------------------------------------------------------------- /src/renderer/ipc-api/Application.ts: -------------------------------------------------------------------------------- 1 | import { ShortcutRecord } from 'common/shortcuts'; 2 | import { ipcRenderer, OpenDialogOptions, OpenDialogReturnValue } from 'electron'; 3 | 4 | import { unproxify } from '../libs/unproxify'; 5 | 6 | export default class { 7 | static showOpenDialog (options: OpenDialogOptions): Promise { 8 | return ipcRenderer.invoke('show-open-dialog', unproxify(options)); 9 | } 10 | 11 | static showSaveDialog (options: OpenDialogOptions): Promise { 12 | return ipcRenderer.invoke('show-save-dialog', unproxify(options)); 13 | } 14 | 15 | static getDownloadPathDirectory (): Promise { 16 | return ipcRenderer.invoke('get-download-dir-path'); 17 | } 18 | 19 | static reloadShortcuts () { 20 | return ipcRenderer.invoke('reload-shortcuts'); 21 | } 22 | 23 | static updateShortcuts (shortcuts: ShortcutRecord[]) { 24 | return ipcRenderer.invoke('update-shortcuts', unproxify(shortcuts)); 25 | } 26 | 27 | static restoreDefaultShortcuts () { 28 | return ipcRenderer.invoke('resotre-default-shortcuts'); 29 | } 30 | 31 | static unregisterShortcuts () { 32 | return ipcRenderer.invoke('unregister-shortcuts'); 33 | } 34 | 35 | static readFile (params: {filePath: string; encoding: string}): Promise { 36 | return ipcRenderer.invoke('read-file', params); 37 | } 38 | 39 | static writeFile (path: string, content: unknown) { 40 | return ipcRenderer.invoke('write-file', path, content); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/renderer/ipc-api/Connection.ts: -------------------------------------------------------------------------------- 1 | import { ConnectionParams, IpcResponse } from 'common/interfaces/antares'; 2 | import { ipcRenderer } from 'electron'; 3 | 4 | import { unproxify } from '../libs/unproxify'; 5 | 6 | export default class { 7 | static makeTest (params: ConnectionParams & { connString?: string }): Promise { 8 | return ipcRenderer.invoke('test-connection', unproxify(params)); 9 | } 10 | 11 | static connect (params: ConnectionParams & { connString?: string }): Promise { 12 | return ipcRenderer.invoke('connect', unproxify(params)); 13 | } 14 | 15 | static abortConnection (uid: string): void { 16 | ipcRenderer.send('abort-connection', uid); 17 | } 18 | 19 | static checkConnection (uid: string): Promise { 20 | return ipcRenderer.invoke('check-connection', uid); 21 | } 22 | 23 | static disconnect (uid: string): Promise { 24 | return ipcRenderer.invoke('disconnect', uid); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/renderer/ipc-api/Databases.ts: -------------------------------------------------------------------------------- 1 | import { IpcResponse } from 'common/interfaces/antares'; 2 | import { ipcRenderer } from 'electron'; 3 | 4 | import { unproxify } from '../libs/unproxify'; 5 | 6 | export default class { 7 | static getDatabases (params: string): Promise { 8 | return ipcRenderer.invoke('get-databases', unproxify(params)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/renderer/ipc-api/Functions.ts: -------------------------------------------------------------------------------- 1 | import { AlterFunctionParams, CreateFunctionParams, IpcResponse } from 'common/interfaces/antares'; 2 | import { ipcRenderer } from 'electron'; 3 | 4 | import { unproxify } from '../libs/unproxify'; 5 | 6 | export default class { 7 | static getFunctionInformations (params: { uid: string; schema: string; func: string}): Promise { 8 | return ipcRenderer.invoke('get-function-informations', unproxify(params)); 9 | } 10 | 11 | static dropFunction (params: { uid: string; schema: string; func: string}): Promise { 12 | return ipcRenderer.invoke('drop-function', unproxify(params)); 13 | } 14 | 15 | static alterFunction (params: { func: AlterFunctionParams }): Promise { 16 | return ipcRenderer.invoke('alter-function', unproxify(params)); 17 | } 18 | 19 | static alterTriggerFunction (params: { uid: string; func: AlterFunctionParams }): Promise { 20 | return ipcRenderer.invoke('alter-trigger-function', unproxify(params)); 21 | } 22 | 23 | static createFunction (params: CreateFunctionParams & { uid: string }): Promise { 24 | return ipcRenderer.invoke('create-function', unproxify(params)); 25 | } 26 | 27 | static createTriggerFunction (params: CreateFunctionParams & { uid: string }): Promise { 28 | return ipcRenderer.invoke('create-trigger-function', unproxify(params)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/renderer/ipc-api/Routines.ts: -------------------------------------------------------------------------------- 1 | import { AlterRoutineParams, CreateRoutineParams, IpcResponse } from 'common/interfaces/antares'; 2 | import { ipcRenderer } from 'electron'; 3 | 4 | import { unproxify } from '../libs/unproxify'; 5 | 6 | export default class { 7 | static getRoutineInformations (params: { uid: string; schema: string; routine: string}): Promise { 8 | return ipcRenderer.invoke('get-routine-informations', unproxify(params)); 9 | } 10 | 11 | static dropRoutine (params: { uid: string; schema: string; routine: string}): Promise { 12 | return ipcRenderer.invoke('drop-routine', unproxify(params)); 13 | } 14 | 15 | static alterRoutine (params: { uid: string; routine: AlterRoutineParams }): Promise { 16 | return ipcRenderer.invoke('alter-routine', unproxify(params)); 17 | } 18 | 19 | static createRoutine (params: { routine: CreateRoutineParams & { uid: string } }): Promise { 20 | return ipcRenderer.invoke('create-routine', unproxify(params)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/renderer/ipc-api/Schedulers.ts: -------------------------------------------------------------------------------- 1 | import { AlterEventParams, CreateEventParams, IpcResponse } from 'common/interfaces/antares'; 2 | import { ipcRenderer } from 'electron'; 3 | 4 | import { unproxify } from '../libs/unproxify'; 5 | 6 | export default class { 7 | static getSchedulerInformations (params: { uid: string; schema: string; scheduler: string}): Promise { 8 | return ipcRenderer.invoke('get-scheduler-informations', unproxify(params)); 9 | } 10 | 11 | static dropScheduler (params: { uid: string; schema: string; scheduler: string}): Promise { 12 | return ipcRenderer.invoke('drop-scheduler', unproxify(params)); 13 | } 14 | 15 | static alterScheduler (params: { uid: string; scheduler: AlterEventParams }): Promise { 16 | return ipcRenderer.invoke('alter-scheduler', unproxify(params)); 17 | } 18 | 19 | static createScheduler (params: CreateEventParams & { uid: string }): Promise { 20 | return ipcRenderer.invoke('create-scheduler', unproxify(params)); 21 | } 22 | 23 | static toggleScheduler (params: { uid: string; schema: string; scheduler: string; enabled: boolean}): Promise { 24 | return ipcRenderer.invoke('toggle-scheduler', unproxify(params)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/renderer/ipc-api/Triggers.ts: -------------------------------------------------------------------------------- 1 | import { AlterTriggerParams, CreateTriggerParams, IpcResponse } from 'common/interfaces/antares'; 2 | import { ipcRenderer } from 'electron'; 3 | 4 | import { unproxify } from '../libs/unproxify'; 5 | 6 | export default class { 7 | static getTriggerInformations (params: { uid: string; schema: string; trigger: string }): Promise { 8 | return ipcRenderer.invoke('get-trigger-informations', unproxify(params)); 9 | } 10 | 11 | static dropTrigger (params: { uid: string; schema: string; trigger: string }): Promise { 12 | return ipcRenderer.invoke('drop-trigger', unproxify(params)); 13 | } 14 | 15 | static alterTrigger (params: { trigger: AlterTriggerParams & { uid: string }}): Promise { 16 | return ipcRenderer.invoke('alter-trigger', unproxify(params)); 17 | } 18 | 19 | static createTrigger (params: CreateTriggerParams & { uid: string }): Promise { 20 | return ipcRenderer.invoke('create-trigger', unproxify(params)); 21 | } 22 | 23 | static toggleTrigger (params: { uid: string; schema: string; trigger: string; enabled: boolean }): Promise { 24 | return ipcRenderer.invoke('toggle-trigger', unproxify(params)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/renderer/ipc-api/Users.ts: -------------------------------------------------------------------------------- 1 | import { IpcResponse } from 'common/interfaces/antares'; 2 | import { ipcRenderer } from 'electron'; 3 | 4 | import { unproxify } from '../libs/unproxify'; 5 | 6 | export default class { 7 | static getUsers (params: string): Promise { 8 | return ipcRenderer.invoke('get-users', unproxify(params)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/renderer/ipc-api/Views.ts: -------------------------------------------------------------------------------- 1 | import { AlterViewParams, CreateViewParams, IpcResponse } from 'common/interfaces/antares'; 2 | import { ipcRenderer } from 'electron'; 3 | 4 | import { unproxify } from '../libs/unproxify'; 5 | 6 | export default class { 7 | static getViewInformations (params: { uid: string; schema: string; view: string }): Promise { 8 | return ipcRenderer.invoke('get-view-informations', unproxify(params)); 9 | } 10 | 11 | static dropView (params: { uid: string; schema: string; view: string }): Promise { 12 | return ipcRenderer.invoke('drop-view', unproxify(params)); 13 | } 14 | 15 | static alterView (params: { view: AlterViewParams & { uid: string }}): Promise { 16 | return ipcRenderer.invoke('alter-view', unproxify(params)); 17 | } 18 | 19 | static createView (params: CreateViewParams & { uid: string }): Promise { 20 | return ipcRenderer.invoke('create-view', unproxify(params)); 21 | } 22 | 23 | static createMaterializedView (params: CreateViewParams & { uid: string }): Promise { 24 | return ipcRenderer.invoke('create-materialized-view', unproxify(params)); 25 | } 26 | 27 | static getMaterializedViewInformations (params: { uid: string; schema: string; view: string }): Promise { 28 | return ipcRenderer.invoke('get-materialized-view-informations', unproxify(params)); 29 | } 30 | 31 | static dropMaterializedView (params: { uid: string; schema: string; view: string }): Promise { 32 | return ipcRenderer.invoke('drop-materialized-view', unproxify(params)); 33 | } 34 | 35 | static alterMaterializedView (params: { view: AlterViewParams & { uid: string }}): Promise { 36 | return ipcRenderer.invoke('alter-materialized-view', unproxify(params)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/renderer/libs/camelize.ts: -------------------------------------------------------------------------------- 1 | export const camelize = (text: string) => { 2 | const textArr = text.split('-'); 3 | for (let i = 0; i < textArr.length; i++) { 4 | if (i === 0) continue; 5 | textArr[i] = textArr[i].charAt(0).toUpperCase() + textArr[i].slice(1); 6 | } 7 | 8 | return textArr.join(''); 9 | }; 10 | -------------------------------------------------------------------------------- /src/renderer/libs/colorShade.ts: -------------------------------------------------------------------------------- 1 | export const colorShade = (color: string, amount: number) => { 2 | color = color.replaceAll('#', ''); 3 | if (color.length === 3) color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2]; 4 | 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 6 | let [r, g, b] = color.match(/.{2}/g) as any; 7 | ([r, g, b] = [parseInt(r, 16) + amount, parseInt(g, 16) + amount, parseInt(b, 16) + amount]); 8 | 9 | r = Math.max(Math.min(255, r), 0).toString(16); 10 | g = Math.max(Math.min(255, g), 0).toString(16); 11 | b = Math.max(Math.min(255, b), 0).toString(16); 12 | 13 | const rr = (r.length < 2 ? '0' : '') + r; 14 | const gg = (g.length < 2 ? '0' : '') + g; 15 | const bb = (b.length < 2 ? '0' : '') + b; 16 | 17 | return `#${rr}${gg}${bb}`; 18 | }; 19 | -------------------------------------------------------------------------------- /src/renderer/libs/copyText.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copy a string on clipboard 3 | * @param text 4 | */ 5 | export const copyText = (text: string) => { 6 | navigator.clipboard.writeText(text); 7 | }; 8 | -------------------------------------------------------------------------------- /src/renderer/libs/exportRows.ts: -------------------------------------------------------------------------------- 1 | import { ClientCode } from 'common/interfaces/antares'; 2 | import { jsonToSqlInsert } from 'common/libs/sqlUtils'; 3 | import * as json2php from 'json2php'; 4 | import * as moment from 'moment'; 5 | 6 | export const exportRows = (args: { 7 | type: 'csv' | 'json'| 'sql' | 'php'; 8 | content: object[]; 9 | table: string; 10 | page?: number; 11 | client?: ClientCode; 12 | fields?: { 13 | [key: string]: {type: string; datePrecision: number}; 14 | }; 15 | sqlOptions?: { 16 | sqlInsertAfter: number; 17 | sqlInsertDivider: 'bytes' | 'rows'; 18 | targetTable: string; 19 | }; 20 | csvOptions?: { 21 | header: boolean; 22 | fieldDelimiter: string; 23 | linesTerminator: string; 24 | stringDelimiter: string; 25 | }; 26 | }) => { 27 | let mime; 28 | let content; 29 | 30 | switch (args.type) { 31 | case 'csv': { 32 | mime = 'text/csv'; 33 | const csv = []; 34 | const sd = args.csvOptions.stringDelimiter === 'single' 35 | ? '\'' 36 | : args.csvOptions.stringDelimiter === 'double' 37 | ? '"' 38 | : ''; 39 | 40 | if (args.content.length && args.csvOptions.header) 41 | csv.push(Object.keys(args.content[0]).join(args.csvOptions.fieldDelimiter)); 42 | 43 | for (const row of args.content) { 44 | csv.push(Object.values(row).map(col => { 45 | if (typeof col === 'string') return `${sd}${col}${sd}`; 46 | if (col instanceof Date) return `${sd}${moment(col).format('YYYY-MM-DD HH:mm:ss')}${sd}`; 47 | if (col instanceof Buffer) return col.toString('base64'); 48 | if (col instanceof Uint8Array) return Buffer.from(col).toString('base64'); 49 | return col; 50 | }).join(args.csvOptions.fieldDelimiter)); 51 | } 52 | 53 | content = csv.join(args.csvOptions.linesTerminator.replaceAll('\\n', '\n').replaceAll('\\r', '\r')); 54 | break; 55 | } 56 | case 'sql': { 57 | mime = 'text/sql'; 58 | const sql = jsonToSqlInsert({ 59 | json: args.content, 60 | client: 61 | args.client, 62 | fields: args.fields, 63 | table: args.sqlOptions?.targetTable || args.table, 64 | options: args.sqlOptions 65 | }); 66 | 67 | content = sql; 68 | break; 69 | } 70 | case 'php': { 71 | mime = 'application/x-httpd-php'; 72 | const printer = json2php.make({ linebreak: '\n', indent: '\t', shortArraySyntax: true }); 73 | content = printer(args.content); 74 | content = ` { 2 | if (!hexcolor) return ''; 3 | return (parseInt(hexcolor.replace('#', ''), 16) > 0xffffff / 2) ? 'dark' : 'light'; 4 | }; 5 | -------------------------------------------------------------------------------- /src/renderer/libs/hexToRgba.ts: -------------------------------------------------------------------------------- 1 | export const hexToRGBA = (hexCode: string, opacity = 1) => { 2 | let hex = hexCode.replace('#', ''); 3 | 4 | if (hex.length === 3) 5 | hex = `${hex[0]}${hex[0]}${hex[1]}${hex[1]}${hex[2]}${hex[2]}`; 6 | 7 | const r = parseInt(hex.substring(0, 2), 16); 8 | const g = parseInt(hex.substring(2, 4), 16); 9 | const b = parseInt(hex.substring(4, 6), 16); 10 | 11 | /* Backward compatibility for whole number based opacity values. */ 12 | if (opacity > 1 && opacity <= 100) 13 | opacity = opacity / 100; 14 | 15 | return `rgba(${r},${g},${b},${opacity})`; 16 | }; 17 | -------------------------------------------------------------------------------- /src/renderer/libs/unproxify.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { toRaw } from 'vue'; 3 | 4 | /** 5 | * @param {*} val 6 | * @param {Boolean} json converts the value in JSON object (default true) 7 | */ 8 | export function unproxify (val: T, json = true): T { 9 | if (json)// JSON conversion 10 | return JSON.parse(JSON.stringify(val)); 11 | else if (Array.isArray(val))// If array 12 | return toRaw(val); 13 | else if (typeof val === 'object') { // If object 14 | const result: any = {}; 15 | for (const key in val) 16 | result[key] = toRaw(val[key]); 17 | 18 | return result; 19 | } 20 | else 21 | return toRaw(val); 22 | } 23 | -------------------------------------------------------------------------------- /src/renderer/scss/_data-types.scss: -------------------------------------------------------------------------------- 1 | @mixin type-colors($types) { 2 | @each $type, $color in $types { 3 | .type-#{$type} { 4 | color: $color; 5 | } 6 | } 7 | } 8 | 9 | @include type-colors( 10 | ( 11 | "char": $string-color, 12 | "varchar": $string-color, 13 | "nvarchar": $string-color, 14 | "longvarchar": $string-color, 15 | "text": $string-color, 16 | "tinytext": $string-color, 17 | "mediumtext": $string-color, 18 | "longtext": $string-color, 19 | "string": $string-color, 20 | "json": $string-color, 21 | "name": $string-color, 22 | "character": $string-color, 23 | "character_varying": $string-color, 24 | "cidr": $string-color, 25 | "inet": $string-color, 26 | "macaddr": $string-color, 27 | "macaddr8": $string-color, 28 | "uuid": $string-color, 29 | "regproc": $string-color, 30 | "blob-text": $string-color, 31 | 32 | "int": $number-color, 33 | "tinyint": $number-color, 34 | "smallint": $number-color, 35 | "mediumint": $number-color, 36 | "float": $number-color, 37 | "double": $number-color, 38 | "decimal": $number-color, 39 | "bigint": $number-color, 40 | "newdecimal": $number-color, 41 | "integer": $number-color, 42 | "integer_unsigned": $number-color, 43 | "numeric": $number-color, 44 | "smallserial": $number-color, 45 | "serial": $number-color, 46 | "bigserial": $number-color, 47 | "real": $number-color, 48 | "double_precision": $number-color, 49 | "oid": $number-color, 50 | "xid": $number-color, 51 | 52 | "money": $number-color, 53 | "number": $number-color, 54 | "datetime": $date-color, 55 | "date": $date-color, 56 | "time": $date-color, 57 | "time_with_time_zone": $date-color, 58 | "year": $date-color, 59 | "timestamp": $date-color, 60 | "timestamp_without_time_zone": $date-color, 61 | "timestamp_with_time_zone": $date-color, 62 | 63 | "bit": $bit-color, 64 | "bit_varying": $bit-color, 65 | 66 | "binary": $blob-color, 67 | "char-binary": $blob-color, 68 | "varbinary": $blob-color, 69 | "blob": $blob-color, 70 | "tinyblob": $blob-color, 71 | "mediumblob": $blob-color, 72 | "medium_blob": $blob-color, 73 | "longblob": $blob-color, 74 | "long_blob": $blob-color, 75 | "bytea": $blob-color, 76 | 77 | "enum": $enum-color, 78 | "set": $enum-color, 79 | "bool": $enum-color, 80 | "boolean": $enum-color, 81 | 82 | "interval": $array-color, 83 | "array": $array-color, 84 | "anyarray": $array-color, 85 | "tsvector": $array-color, 86 | "tsquery": $array-color, 87 | "pg_node_tree": $array-color, 88 | "point": $array-color, 89 | "linestring": $array-color, 90 | "polygon": $array-color, 91 | "geometry": $array-color, 92 | "multipoint": $array-color, 93 | "multilinestring": $array-color, 94 | "multipolygon": $array-color, 95 | "geomcollection": $array-color, 96 | "geometrycollection": $array-color, 97 | "aclitem": $array-color, 98 | 99 | "unknown": $unknown-color, 100 | ) 101 | ); 102 | 103 | .is-null { 104 | color: $unknown-color; 105 | 106 | &::after { 107 | content: "NULL"; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/renderer/scss/_db-icons.scss: -------------------------------------------------------------------------------- 1 | .dbi { 2 | display: inline-block; 3 | width: 42px; 4 | height: 42px; 5 | background-size: cover; 6 | position: relative; 7 | 8 | &.dbi-mysql { 9 | background-image: url("../images/svg/mysql.svg"); 10 | } 11 | 12 | &.dbi-maria { 13 | background-image: url("../images/svg/mariadb.svg"); 14 | } 15 | 16 | &.dbi-mssql { 17 | background-image: url("../images/svg/mssql.svg"); 18 | } 19 | 20 | &.dbi-pg { 21 | background-image: url("../images/svg/pg.svg"); 22 | } 23 | 24 | &.dbi-sqlite { 25 | background-image: url("../images/svg/sqlite.svg"); 26 | } 27 | 28 | &.dbi-firebird { 29 | background-image: url("../images/svg/firebird.svg"); 30 | } 31 | 32 | &.dbi-oracledb { 33 | background-image: url("../images/svg/oracledb.svg"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/renderer/scss/_editor-icons.scss: -------------------------------------------------------------------------------- 1 | /* stylelint-disable selector-class-pattern */ 2 | 3 | /* Only used in ext-language_tools.js */ 4 | .editor-icon { 5 | display: inline-block; 6 | width: 17px; 7 | height: 17px; 8 | background-size: cover; 9 | position: relative; 10 | top: 2px; 11 | margin-right: 1px; 12 | opacity: 0.7; 13 | 14 | &.editor-icon-table { 15 | background-image: url("../images/svg/table.svg"); 16 | } 17 | 18 | &.editor-icon-rhombus-split-outline { 19 | background-image: url("../images/svg/rhombus-split-outline.svg"); 20 | } 21 | 22 | &.editor-icon-table-eye { 23 | background-image: url("../images/svg/table-eye.svg"); 24 | } 25 | 26 | &.editor-icon-table-cog { 27 | background-image: url("../images/svg/table-cog.svg"); 28 | } 29 | 30 | &.editor-icon-sync-circle { 31 | background-image: url("../images/svg/sync-circle.svg"); 32 | } 33 | 34 | &.editor-icon-arrow-right-bold-box { 35 | background-image: url("../images/svg/arrow-right-bold-box.svg"); 36 | } 37 | 38 | &.editor-icon-calendar-clock { 39 | background-image: url("../images/svg/calendar-clock.svg"); 40 | } 41 | 42 | &.editor-icon-cube { 43 | background-image: url("../images/svg/cube.svg"); 44 | } 45 | 46 | &.editor-icon-code-braces { 47 | background-image: url("../images/svg/code-braces.svg"); 48 | } 49 | 50 | &.editor-icon-alphabetical-variant { 51 | background-image: url("../images/svg/alphabetical-variant.svg"); 52 | } 53 | 54 | &.editor-icon-circle { 55 | background-image: url("../images/svg/circle.svg"); 56 | } 57 | 58 | &::before { 59 | line-height: 1; 60 | } 61 | } 62 | 63 | .ace_dark { 64 | .editor-icon { 65 | filter: invert(100%); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/renderer/scss/_fake-tables.scss: -------------------------------------------------------------------------------- 1 | .table { 2 | border-collapse: collapse; 3 | width: 100%; 4 | border-spacing: 0; 5 | display: table; 6 | table-layout: fixed; 7 | 8 | .tbody { 9 | display: table-row-group; 10 | } 11 | 12 | .tr { 13 | display: table-row; 14 | } 15 | 16 | /* Scollable tables */ 17 | &.table-scroll { 18 | display: block; 19 | overflow-x: auto; 20 | padding-bottom: 0.75rem; 21 | white-space: nowrap; 22 | } 23 | 24 | .thead { 25 | display: table-header-group; 26 | } 27 | 28 | .td, 29 | .th { 30 | padding: $unit-3 $unit-2; 31 | display: table-cell; 32 | border-radius: 3px; 33 | } 34 | 35 | .th { 36 | border-bottom-width: $border-width-lg; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/renderer/scss/_table-keys.scss: -------------------------------------------------------------------------------- 1 | /* stylelint-disable selector-class-pattern */ 2 | .column-key { 3 | &.key-pri, 4 | &.key-PRIMARY { 5 | color: goldenrod; 6 | } 7 | 8 | &.key-uni, 9 | &.key-UNIQUE { 10 | color: deepskyblue; 11 | } 12 | 13 | &.key-mul, 14 | &.key-INDEX, 15 | &.key-KEY { 16 | color: limegreen; 17 | } 18 | 19 | &.key-fk { 20 | color: chocolate; 21 | } 22 | 23 | &.key-FULLTEXT { 24 | color: mediumvioletred; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/renderer/scss/_transitions.scss: -------------------------------------------------------------------------------- 1 | .fade-slide-down-enter-active, 2 | .fade-slide-down-leave-active { 3 | transition: opacity 0.15s ease, transform 0.15s ease; 4 | opacity: 1; 5 | transform: translateY(0); 6 | } 7 | 8 | .fade-slide-down-enter-from, 9 | .fade-slide-down-leave-to { 10 | opacity: 0; 11 | transform: translateY(-1.8rem); 12 | } 13 | 14 | .slide-fade-enter-active { 15 | transition: all 0.3s ease; 16 | } 17 | 18 | .slide-fade-leave-active { 19 | transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1); 20 | } 21 | 22 | .slide-fade-enter-from, 23 | .slide-fade-leave-to { 24 | transform: translateX(10px); 25 | opacity: 0; 26 | } 27 | 28 | .fade-enter-active, 29 | .fade-leave-active { 30 | transition: opacity 0.5s; 31 | } 32 | 33 | .fade-enter-from, 34 | .fade-leave-to { 35 | opacity: 0; 36 | } 37 | 38 | .jump-down-enter-active { 39 | animation: jump-down-in 0.2s; 40 | } 41 | 42 | .jump-down-leave-active { 43 | animation: jump-down-in 0.2s reverse; 44 | } 45 | 46 | .flip-list-move { 47 | transition: transform 0.5s; 48 | } 49 | 50 | .no-move { 51 | transition: transform 0s; 52 | } 53 | 54 | .pulse { 55 | animation-name: pulse; 56 | animation-duration: 2s; 57 | animation-iteration-count: infinite; 58 | } 59 | 60 | @keyframes jump-down-in { 61 | 0% { 62 | transform: scale(0); 63 | } 64 | 65 | 100% { 66 | transform: scale(1); 67 | } 68 | } 69 | 70 | @keyframes pulse { 71 | 0% { 72 | opacity: 0; 73 | } 74 | 75 | 50% { 76 | opacity: 1; 77 | } 78 | 79 | 100% { 80 | opacity: 0; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/renderer/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | /* Colors */ 2 | $body-bg: #f3f3f3; 3 | $body-bg-dark: #1d1d1d; 4 | $body-font-color-dark: #fff; 5 | $bg-color-dark: #1d1d1d; 6 | $bg-color-light-dark: #3f3f3f; 7 | $bg-color-gray: #272727; 8 | $bg-color-light-gray: #f1f1f1; 9 | $light-color: #fdfdfd; 10 | $primary-color: #e36929; 11 | $success-color: #32b643; 12 | $error-color: #de3b28; 13 | $warning-color: #e0a40c; 14 | 15 | $string-color: seagreen; 16 | $number-color: cornflowerblue; 17 | $date-color: coral; 18 | $bit-color: lightskyblue; 19 | $blob-color: darkorchid; 20 | $array-color: yellowgreen; 21 | $enum-color: goldenrod; 22 | $unknown-color: gray; 23 | 24 | /* Sizes */ 25 | $border-radius: 0.3rem; 26 | $titlebar-height: 1.5rem; 27 | $settingbar-width: 3.5rem; 28 | $explorebar-width: 14rem; 29 | $footer-height: 1.5rem; 30 | 31 | @function get-excluding-size() { 32 | @return $footer-height + $titlebar-height; 33 | } 34 | 35 | /* stylelint-disable-next-line function-no-unknown */ 36 | $excluding-size: get-excluding-size(); 37 | -------------------------------------------------------------------------------- /src/renderer/stores/application.ts: -------------------------------------------------------------------------------- 1 | import { Ace } from 'ace-builds'; 2 | import * as Store from 'electron-store'; 3 | import { defineStore, storeToRefs } from 'pinia'; 4 | 5 | import { useScratchpadStore } from './scratchpad'; 6 | 7 | const persistentStore = new Store({ name: 'settings' }); 8 | export type UpdateStatus = 'noupdate' | 'available' | 'checking' | 'nocheck' | 'downloading' | 'downloaded' | 'disabled' | 'link'; 9 | 10 | export const useApplicationStore = defineStore('application', { 11 | state: () => ({ 12 | appName: 'Antares - SQL Client', 13 | appVersion: process.env.PACKAGE_VERSION || '0', 14 | cachedVersion: persistentStore.get('cached_version', '0') as string, 15 | isLoading: false, 16 | isNewModal: false, 17 | isSettingModal: false, 18 | isScratchpad: false, 19 | selectedSettingTab: 'general', 20 | updateStatus: 'noupdate' as UpdateStatus, 21 | downloadProgress: 0, 22 | baseCompleter: [] as Ace.Completer[] // Needed to reset ace editor, due global-only ace completer 23 | }), 24 | getters: { 25 | getBaseCompleter: state => state.baseCompleter, 26 | getDownloadProgress: state => Number(state.downloadProgress.toFixed(1)) 27 | }, 28 | actions: { 29 | checkVersionUpdate () { 30 | if (this.appVersion !== this.cachedVersion) { 31 | this.showSettingModal('changelog'); 32 | this.cachedVersion = this.appVersion; 33 | persistentStore.set('cached_version', this.cachedVersion); 34 | } 35 | }, 36 | setLoadingStatus (payload: boolean) { 37 | this.isLoading = payload; 38 | }, 39 | setBaseCompleters (payload: Ace.Completer[]) { 40 | this.baseCompleter = payload; 41 | }, 42 | // Modals 43 | showNewConnModal () { 44 | this.isNewModal = true; 45 | }, 46 | hideNewConnModal () { 47 | this.isNewModal = false; 48 | }, 49 | showSettingModal (tab: string) { 50 | this.selectedSettingTab = tab; 51 | this.isSettingModal = true; 52 | }, 53 | hideSettingModal () { 54 | this.isSettingModal = false; 55 | }, 56 | showScratchpad (tag?: string) { 57 | this.isScratchpad = true; 58 | if (!tag) tag = 'all'; 59 | const { selectedTag } = storeToRefs(useScratchpadStore()); 60 | selectedTag.value = tag; 61 | }, 62 | hideScratchpad () { 63 | this.isScratchpad = false; 64 | } 65 | } 66 | }); 67 | -------------------------------------------------------------------------------- /src/renderer/stores/console.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | const logsSize = 1000; 4 | 5 | export type LogType = 'query' | 'debug' 6 | export interface QueryLog { 7 | cUid: string; 8 | sql: string; 9 | date: Date; 10 | } 11 | 12 | export interface DebugLog { 13 | level: 'log' | 'info' | 'warn' | 'error' | string; 14 | process: 'renderer' | 'main' | 'worker'; 15 | message: string; 16 | date: Date; 17 | } 18 | 19 | export const useConsoleStore = defineStore('console', { 20 | state: () => ({ 21 | isConsoleOpen: false, 22 | queryLogs: [] as QueryLog[], 23 | debugLogs: [] as DebugLog[], 24 | selectedTab: 'query' as LogType, 25 | consoleHeight: 0 26 | }), 27 | getters: { 28 | getLogsByWorkspace: state => (uid: string) => state.queryLogs.filter(r => r.cUid === uid) 29 | }, 30 | actions: { 31 | putLog (type: LogType, record: QueryLog | DebugLog) { 32 | if (type === 'query') { 33 | this.queryLogs.push(record); 34 | 35 | if (this.queryLogs.length > logsSize) 36 | this.queryLogs = this.queryLogs.slice(0, logsSize); 37 | } 38 | else if (type === 'debug') { 39 | this.debugLogs.push(record); 40 | 41 | if (this.debugLogs.length > logsSize) 42 | this.debugLogs = this.debugLogs.slice(0, logsSize); 43 | } 44 | }, 45 | openConsole () { 46 | this.isConsoleOpen = true; 47 | this.consoleHeight = 250; 48 | }, 49 | closeConsole () { 50 | this.isConsoleOpen = false; 51 | this.consoleHeight = 0; 52 | }, 53 | resizeConsole (height: number) { 54 | if (height < 30) 55 | this.closeConsole(); 56 | else 57 | this.consoleHeight = height; 58 | }, 59 | toggleConsole () { 60 | if (this.isConsoleOpen) 61 | this.closeConsole(); 62 | else 63 | this.openConsole(); 64 | } 65 | } 66 | }); 67 | -------------------------------------------------------------------------------- /src/renderer/stores/history.ts: -------------------------------------------------------------------------------- 1 | import { uidGen } from 'common/libs/uidGen'; 2 | import * as Store from 'electron-store'; 3 | import { defineStore } from 'pinia'; 4 | const persistentStore = new Store({ name: 'history' }); 5 | const historySize = 1000; 6 | 7 | export interface HistoryRecord { 8 | uid: string; 9 | sql: string; 10 | date: Date; 11 | schema?: string; 12 | } 13 | 14 | export const useHistoryStore = defineStore('history', { 15 | state: () => ({ 16 | history: persistentStore.get('history', {}) as Record, 17 | favorites: persistentStore.get('favorites', {}) 18 | }), 19 | getters: { 20 | getHistoryByWorkspace: state => (uid: string) => state.history[uid] 21 | }, 22 | actions: { 23 | saveHistory (args: { uid: string; query: string; schema: string; tabUid: string }) { 24 | if (this.getHistoryByWorkspace(args.uid) && 25 | this.getHistoryByWorkspace(args.uid).length && 26 | this.getHistoryByWorkspace(args.uid)[0].sql === args.query 27 | ) return; 28 | 29 | if (!(args.uid in this.history)) 30 | this.history[args.uid] = []; 31 | 32 | this.history[args.uid] = [ 33 | { 34 | uid: uidGen('H'), 35 | sql: args.query, 36 | date: new Date(), 37 | schema: args.schema 38 | }, 39 | ...this.history[args.uid] 40 | ]; 41 | 42 | if (this.history[args.uid].length > historySize) 43 | this.history[args.uid] = this.history[args.uid].slice(0, historySize); 44 | 45 | persistentStore.set('history', this.history); 46 | }, 47 | deleteQueryFromHistory (query: Partial & { workspace: string}) { 48 | this.history[query.workspace] = (this.history[query.workspace] as HistoryRecord[]).filter(q => q.uid !== query.uid); 49 | persistentStore.set('history', this.history); 50 | } 51 | } 52 | }); 53 | -------------------------------------------------------------------------------- /src/renderer/stores/notifications.ts: -------------------------------------------------------------------------------- 1 | import { uidGen } from 'common/libs/uidGen'; 2 | import { defineStore } from 'pinia'; 3 | 4 | import { useConsoleStore } from './console'; 5 | 6 | export interface Notification { 7 | uid: string; 8 | status: string; 9 | message: string; 10 | } 11 | 12 | export const useNotificationsStore = defineStore('notifications', { 13 | state: () => ({ 14 | notifications: [] as Notification[] 15 | }), 16 | actions: { 17 | addNotification (payload: { status: string; message: string }) { 18 | const notification: Notification = { uid: uidGen('N'), ...payload }; 19 | this.notifications.unshift(notification); 20 | 21 | useConsoleStore().putLog('debug', { 22 | level: notification.status, 23 | process: 'renderer', 24 | message: notification.message, 25 | date: new Date() 26 | }); 27 | }, 28 | removeNotification (uid: string) { 29 | this.notifications = (this.notifications as Notification[]).filter(item => item.uid !== uid); 30 | } 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /src/renderer/stores/schemaExport.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | export const useSchemaExportStore = defineStore('schemaExport', { 4 | state: () => ({ 5 | isExportModal: false, 6 | selectedTable: undefined as undefined | string, 7 | selectedSchema: undefined as undefined | string 8 | }), 9 | actions: { 10 | showExportModal (schema?: string, table?: string) { 11 | this.selectedTable = table; 12 | this.selectedSchema = schema; 13 | this.isExportModal = true; 14 | }, 15 | hideExportModal () { 16 | this.isExportModal = false; 17 | this.selectedTable = undefined; 18 | this.selectedSchema = undefined; 19 | } 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /src/renderer/stores/scratchpad.ts: -------------------------------------------------------------------------------- 1 | import * as Store from 'electron-store'; 2 | import { defineStore } from 'pinia'; 3 | 4 | export type TagCode = 'all' | 'note' | 'todo' | 'query' 5 | 6 | export interface ConnectionNote { 7 | uid: string; 8 | cUid: string | null; 9 | title?: string; 10 | isArchived: boolean; 11 | type: TagCode; 12 | note: string; 13 | date: Date; 14 | } 15 | 16 | const persistentStore = new Store({ name: 'notes' }); 17 | 18 | // Migrate old scratchpad on new notes TODO: remove in future releases 19 | const oldNotes = persistentStore.get('notes') as string; 20 | if (oldNotes) { 21 | const newNotes = persistentStore.get('connectionNotes', []) as ConnectionNote[]; 22 | newNotes.unshift({ 23 | uid: 'N:LEGACY', 24 | cUid: null, 25 | isArchived: false, 26 | type: 'note', 27 | note: oldNotes, 28 | date: new Date() 29 | }); 30 | 31 | persistentStore.delete('notes'); 32 | 33 | persistentStore.set('connectionNotes', newNotes); 34 | } 35 | 36 | export const useScratchpadStore = defineStore('scratchpad', { 37 | state: () => ({ 38 | selectedTag: 'all', 39 | /** Connection specific notes */ 40 | connectionNotes: persistentStore.get('connectionNotes', []) as ConnectionNote[] 41 | }), 42 | actions: { 43 | changeNotes (notes: ConnectionNote[]) { 44 | this.connectionNotes = notes; 45 | persistentStore.set('connectionNotes', this.connectionNotes); 46 | }, 47 | addNote (note: ConnectionNote) { 48 | this.connectionNotes = [ 49 | note, 50 | ...this.connectionNotes 51 | ]; 52 | persistentStore.set('connectionNotes', this.connectionNotes); 53 | }, 54 | editNote (note: ConnectionNote) { 55 | this.connectionNotes = (this.connectionNotes as ConnectionNote[]).map(n => { 56 | if (n.uid === note.uid) 57 | n = note; 58 | 59 | return n; 60 | }); 61 | persistentStore.set('connectionNotes', this.connectionNotes); 62 | } 63 | } 64 | }); 65 | -------------------------------------------------------------------------------- /src/renderer/untyped.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | /* eslint-disable @typescript-eslint/ban-types */ 3 | declare module '@/App.vue'; 4 | declare module 'v-mask'; 5 | declare module 'json2php'; 6 | declare module '*/encoding_charset.js'; 7 | declare module 'vuedraggable' {// <- to export as default 8 | const draggableComponent: import('vue').DefineComponent<{ 9 | list: { 10 | type: ArrayConstructor; 11 | required: boolean; 12 | default: any; 13 | }; 14 | modelValue: { 15 | type: ArrayConstructor; 16 | required: boolean; 17 | default: any; 18 | }; 19 | itemKey: { 20 | type: (FunctionConstructor | StringConstructor)[]; 21 | required: boolean; 22 | }; 23 | clone: { 24 | type: FunctionConstructor; 25 | default: (original: any) => any; 26 | }; 27 | tag: { 28 | type: StringConstructor; 29 | default: string; 30 | }; 31 | move: { 32 | type: FunctionConstructor; 33 | default: any; 34 | }; 35 | componentData: { 36 | type: ObjectConstructor; 37 | required: boolean; 38 | default: any; 39 | }; 40 | }, unknown, { 41 | error: boolean; 42 | }, { 43 | realList(): any; 44 | getKey(): any; 45 | }, { 46 | getUnderlyingVm(domElement: any): any; 47 | getUnderlyingPotencialDraggableComponent(htmElement: any): any; 48 | emitChanges(evt: any): void; 49 | alterList(onList: any): void; 50 | spliceList(): void; 51 | updatePosition(oldIndex: any, newIndex: any): void; 52 | getRelatedContextFromMoveEvent({ to, related }: { 53 | to: any; 54 | related: any; 55 | }): any; 56 | getVmIndexFromDomIndex(domIndex: any): any; 57 | onDragStart(evt: any): void; 58 | onDragAdd(evt: any): void; 59 | onDragRemove(evt: any): void; 60 | onDragUpdate(evt: any): void; 61 | computeFutureIndex(relatedContext: any, evt: any): any; 62 | onDragMove(evt: any, originalEvent: any): any; 63 | onDragEnd(): void; 64 | }, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, any[], any, import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, Readonly<{ 65 | move: Function; 66 | tag: string; 67 | clone: Function; 68 | list: unknown[]; 69 | modelValue: unknown[]; 70 | componentData: Record; 71 | } & { 72 | itemKey?: string | Function; 73 | }>, { 74 | move: Function; 75 | tag: string; 76 | clone: Function; 77 | list: unknown[]; 78 | modelValue: unknown[]; 79 | componentData: Record; 80 | }>; 81 | export = draggableComponent; 82 | } 83 | 84 | declare const SvgIcon: import('vue').DefineComponent<{ 85 | type: { 86 | type: StringConstructor; 87 | default: string; 88 | }; 89 | path: { 90 | type: StringConstructor; 91 | default: string; 92 | }; 93 | size: { 94 | type: NumberConstructor; 95 | optional: boolean; 96 | }; 97 | viewbox: { 98 | type: StringConstructor; 99 | optional: boolean; 100 | }; 101 | flip: { 102 | type: StringConstructor; 103 | optional: boolean; 104 | }; 105 | rotate: { 106 | type: NumberConstructor; 107 | optional: boolean; 108 | }; 109 | }>; 110 | 111 | declare module '@jamescoyle/vue-icon' { 112 | export default SvgIcon; 113 | } 114 | -------------------------------------------------------------------------------- /tests/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | import { _electron as electron, Page } from 'playwright'; 3 | import { ElectronApplication } from 'playwright-core'; 4 | 5 | let appWindow: Page; 6 | let electronApp: ElectronApplication; 7 | 8 | test.beforeAll(async () => { 9 | electronApp = await electron.launch({ args: ['dist/main.js'] }); 10 | appWindow = await electronApp.firstWindow(); 11 | await appWindow.waitForEvent('load'); 12 | }); 13 | 14 | test('launch app', async () => { 15 | const isPackaged = await electronApp.evaluate(async ({ app }) => { 16 | return app.isPackaged; 17 | }); 18 | 19 | expect(isPackaged, 'expect is unpacked').toBe(false); 20 | }); 21 | 22 | test('main window elements visibility', async () => { 23 | const visibleSelectors = [ 24 | // '#titlebar', 25 | '#window-content', 26 | '#settingbar', 27 | '#footer' 28 | ]; 29 | setTimeout(async () => { 30 | for (const selector of visibleSelectors) 31 | expect(await appWindow.isVisible(selector), `expect ${selector} visible`).toBe(true); 32 | }, 3000); 33 | }); 34 | 35 | // test('SQLite connection', async () => {// FIXME: not working on GitHub Actions 36 | // await appWindow.selectOption('#connection-client', 'sqlite');// Select connection client 37 | // await appWindow.click('#connection-test');// Press test button 38 | // await new Promise(resolve => setTimeout(resolve, 50)); // Small toast wait 39 | // await appWindow.isVisible('.toast-primary');// If success toast 40 | // await appWindow.click('#connection-save');// Save connection 41 | // await appWindow.isVisible('.settingbar-top-elements .settingbar-element .dbi-sqlite');// If new connection in settingbar 42 | // await appWindow.click('#connection-connect');// Connect 43 | 44 | // // TODO: continue test chain 45 | // }); 46 | 47 | test.afterAll(async () => { 48 | // await new Promise(resolve => setTimeout(resolve, 10000)); 49 | await electronApp.close(); 50 | }); 51 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "./tests/**/*", 4 | "./src/main/**/*", 5 | "./src/renderer/**/*", 6 | "./src/common/**/*", 7 | ], 8 | "exclude": ["./src/renderer/libs/ext-language_tools.js"], 9 | "compilerOptions": { 10 | "baseUrl": "./", 11 | "target": "es2021", 12 | "allowJs": true, 13 | "module": "CommonJS", 14 | "noImplicitAny": true, 15 | "jsx": "preserve", 16 | "types": [ 17 | "node" 18 | ], 19 | "sourceMap": true, 20 | "allowSyntheticDefaultImports": true, 21 | "resolveJsonModule": true, 22 | "removeComments": true, 23 | "paths": { 24 | "common/*": ["./src/common/*"], 25 | "@/*": ["./src/renderer/*"], 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /webpack.main.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const ProgressPlugin = require('progress-webpack-plugin'); 3 | 4 | const { dependencies, devDependencies } = require('./package.json'); 5 | 6 | const externals = Object.keys(dependencies).concat(Object.keys(devDependencies)); 7 | const isDevMode = process.env.NODE_ENV === 'development'; 8 | const whiteListedModules = []; 9 | 10 | module.exports = { // Main 11 | name: 'main', 12 | mode: process.env.NODE_ENV, 13 | devtool: isDevMode ? 'eval-source-map' : false, 14 | entry: { 15 | main: path.join(__dirname, './src/main/main.ts') 16 | }, 17 | target: 'electron-main', 18 | output: { 19 | libraryTarget: 'commonjs2', 20 | path: path.join(__dirname, 'dist'), 21 | filename: '[name].js', 22 | assetModuleFilename: (pathData) => { 23 | const { filename } = pathData; 24 | 25 | if (filename.endsWith('.ts')) 26 | return '[name].js'; 27 | else 28 | return '[name][ext]'; 29 | } 30 | }, 31 | node: { 32 | global: true, 33 | __dirname: isDevMode, 34 | __filename: isDevMode 35 | }, 36 | externals: externals.filter((d) => !whiteListedModules.includes(d)), 37 | resolve: { 38 | extensions: ['.js', '.json', '.ts'], 39 | alias: { 40 | src: path.join(__dirname, 'src/'), 41 | common: path.resolve(__dirname, 'src/common') 42 | }, 43 | fallback: { 44 | 'pg-native': false, 45 | 'cpu-features': false, 46 | cardinal: false 47 | } 48 | }, 49 | plugins: [ 50 | new ProgressPlugin(true) 51 | ], 52 | module: { 53 | rules: [ 54 | { 55 | test: /\.node$/, 56 | loader: 'node-loader', 57 | options: { 58 | name: '[path][name].[ext]' 59 | } 60 | }, 61 | { 62 | test: /\.ts$/, 63 | exclude: /node_modules/, 64 | loader: 'ts-loader' 65 | }, 66 | { 67 | test: /\.js$/, 68 | exclude: /node_modules/, 69 | loader: 'babel-loader' 70 | }, 71 | { 72 | test: /\.(png|jpg|gif)$/, 73 | use: [{ 74 | loader: 'file-loader' 75 | }] 76 | } 77 | ] 78 | } 79 | }; 80 | -------------------------------------------------------------------------------- /webpack.workers.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const ProgressPlugin = require('progress-webpack-plugin'); 4 | 5 | const { dependencies, devDependencies, version } = require('./package.json'); 6 | 7 | const externals = Object.keys(dependencies).concat(Object.keys(devDependencies)); 8 | const isDevMode = process.env.NODE_ENV === 'development'; 9 | const whiteListedModules = []; 10 | 11 | const config = { 12 | name: 'workers', 13 | mode: process.env.NODE_ENV, 14 | devtool: isDevMode ? 'eval-source-map' : false, 15 | entry: { 16 | exporter: path.join(__dirname, './src/main/workers/exporter.ts'), 17 | importer: path.join(__dirname, './src/main/workers/importer.ts') 18 | }, 19 | target: 'node', 20 | output: { 21 | libraryTarget: 'commonjs2', 22 | path: path.join(__dirname, 'dist'), 23 | filename: '[name].js' 24 | }, 25 | node: { 26 | global: true, 27 | __dirname: isDevMode, 28 | __filename: isDevMode 29 | }, 30 | externals: externals.filter((d) => !whiteListedModules.includes(d)), 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.ts$/, 35 | exclude: /node_modules/, 36 | loader: 'ts-loader' 37 | }, 38 | { 39 | test: /\.js$/, 40 | use: 'babel-loader', 41 | exclude: /node_modules/ 42 | }, 43 | { 44 | test: /\.node$/, 45 | use: 'node-loader' 46 | } 47 | ] 48 | }, 49 | resolve: { 50 | extensions: ['.js', '.json', '.ts'], 51 | alias: { 52 | src: path.join(__dirname, 'src/'), 53 | common: path.resolve(__dirname, 'src/common') 54 | }, 55 | fallback: { 56 | 'pg-native': false, 57 | 'cpu-features': false, 58 | cardinal: false 59 | } 60 | }, 61 | plugins: [ 62 | new ProgressPlugin(true), 63 | new webpack.DefinePlugin({ 64 | 'process.env': { 65 | PACKAGE_VERSION: `"${version}"`, 66 | DISTRIBUTION: `"${process.env.DISTRIBUTION || 'none'}"` 67 | } 68 | }) 69 | ] 70 | }; 71 | 72 | /** 73 | * Adjust rendererConfig for production settings 74 | */ 75 | if (isDevMode) { 76 | // any dev only config 77 | config.plugins.push(new webpack.HotModuleReplacementPlugin()); 78 | } 79 | else { 80 | config.plugins.push( 81 | new webpack.LoaderOptionsPlugin({ 82 | minimize: true 83 | }) 84 | ); 85 | } 86 | 87 | module.exports = config; 88 | --------------------------------------------------------------------------------