Compare commits

...

99 Commits

Author SHA1 Message Date
Fabio Di Stasio 49f1a8ef2e chore(release): 0.7.25 2024-06-19 08:57:50 +02:00
Fabio Di Stasio 121aa21a6d Merge branch 'beta' of https://github.com/antares-sql/antares 2024-06-19 08:57:07 +02:00
Fabio Di Stasio 3a47607a5f Merge branch 'master' of https://github.com/antares-sql/antares 2024-06-17 09:13:55 +02:00
Fabio Di Stasio 7494ff6fcf ci: changes on create-artifact-macos.yml 2024-06-17 09:13:31 +02:00
Fabio Di Stasio 838491bfd4 chore(release): 0.7.25-beta.2 2024-06-16 13:40:02 +02:00
Fabio Di Stasio 0b9898f3e7 feat(PostgreSQL): support to materialized views, closes #804 2024-06-14 18:05:29 +02:00
Fabio Di Stasio a973ec3c60 perf(UI): views grouped in folders 2024-06-13 18:07:05 +02:00
Fabio Di Stasio d0c50f17ca ci: temporary disabled auto test e2e 2024-06-10 08:52:00 +02:00
Fabio Di Stasio b4cdd58973 chore(release): 0.7.25-beta.1 2024-06-08 17:29:47 +02:00
Fabio Di Stasio 9947479fdc Merge branch 'beta' of https://github.com/antares-sql/antares into develop 2024-06-07 16:12:10 +02:00
Fabio Di Stasio 4a1697d633 fix: issue switching table after using a filter, fixes#691 2024-06-05 18:34:43 +02:00
Fabio Di Stasio b7dfd5cb8c build(deps): update various dependencies 2024-05-26 17:28:42 +02:00
Fabio Di Stasio 0ec9d3cfc1
Merge pull request #807 from antares-sql/all-contributors/add-mangas
docs: add mangas as a contributor for code
2024-05-26 17:20:52 +02:00
allcontributors[bot] 4f615b26cf
docs: update .all-contributorsrc [skip ci] 2024-05-26 15:19:12 +00:00
allcontributors[bot] 86a1e05197
docs: update README.md [skip ci] 2024-05-26 15:19:11 +00:00
Fabio Di Stasio 3fa9873d20
Merge pull request #805 from mangas/upgrade-electron
chore: upgrade electron
2024-05-26 17:18:21 +02:00
Fabio Di Stasio 37a160a03f build(deps): update @electron/remote 2024-05-26 17:15:11 +02:00
Fabio Di Stasio 2385a8207c chore(release): 0.7.25-beta.0 2024-05-26 15:57:02 +02:00
Filipe Azevedo 243984e697 chore: upgrade electron 2024-05-17 12:46:58 +01:00
Fabio Di Stasio d1bb50b2bb fix(PostgreSQL): unable to search for databases, fixes #798 2024-05-08 08:56:23 +02:00
Fabio Di Stasio 8501fa2e81 Merge branch 'develop' of https://github.com/antares-sql/antares into develop 2024-05-07 18:16:30 +02:00
Fabio Di Stasio 25123e34ef fix: missing resizebars on mouse over 2024-05-07 18:15:32 +02:00
Fabio Di Stasio bd4502ee47
Merge pull request #800 from antares-sql/all-contributors/add-penguinlab
docs: add penguinlab as a contributor for translation
2024-05-07 16:31:40 +02:00
Fabio Di Stasio b2f9d475a2
Merge pull request #799 from penguinlab/master
feat: update japanese translation
2024-05-07 16:30:37 +02:00
allcontributors[bot] 93de974b09
docs: update .all-contributorsrc [skip ci] 2024-05-07 14:29:50 +00:00
allcontributors[bot] 949bf4cbcb
docs: update README.md [skip ci] 2024-05-07 14:29:49 +00:00
penguinlab bb3c87b2cf feat: update japanese translation 2024-05-07 18:18:45 +09:00
Fabio Di Stasio 40bf9a040a
Merge pull request #797 from jimcat8/cn_trans
Update zh-CN.ts file and update translation
2024-05-05 20:56:29 +02:00
tianci 978b55fdb1
Update again 2024-05-05 18:11:22 +08:00
tianci 098d4e96d6
Update zh-CN.ts file and update translation 2024-05-05 18:07:02 +08:00
Fabio Di Stasio 957cb9e1a5 chore(release): 0.7.24 2024-05-03 14:21:14 +02:00
Fabio Di Stasio 09c274a724 fix: missing accent color change 2024-05-02 18:00:07 +02:00
Fabio Di Stasio 9bcd874e80 chore(release): 0.7.24-beta.1 2024-04-30 18:09:52 +02:00
Fabio Di Stasio ece2ee05cc perf(UI): improvements on light theme 2024-04-30 18:08:07 +02:00
Fabio Di Stasio 058fc2fc0b feat: accent color based on folder color, closes #762 2024-04-30 18:07:08 +02:00
Fabio Di Stasio 33bbc0e7e6 fix(PostgreSQL): better handle connection errors, should fix #794 2024-04-30 18:06:11 +02:00
Fabio Di Stasio 23c59b4d4e fix(PostgreSQL): issue with similar tabs on differend databases 2024-04-18 18:22:29 +02:00
Fabio Di Stasio 6600197b82 perf(UI): hide "insert row" button in read-only mode, closes #695 2024-04-14 16:23:56 +02:00
Fabio Di Stasio 33203aeb04 refactor(UI): change query tab buttons order 2024-04-12 18:03:17 +02:00
Fabio Di Stasio f4f385589f chore(release): 0.7.24-beta.0 2024-04-12 08:44:08 +02:00
Fabio Di Stasio 0565ae1204 fix(translation): missing translation for "Open notes" shortcut 2024-04-08 18:33:37 +02:00
Fabio Di Stasio 258fbc81f7 Merge branch 'master' of https://github.com/antares-sql/antares into develop 2024-04-08 18:30:23 +02:00
Fabio Di Stasio 8d8650fbe7 feat: unsaved file reminder closing file tabs 2024-04-08 18:29:05 +02:00
Fabio Di Stasio af2812f2b0
Merge pull request #788 from antares-sql/all-contributors/add-bagusindrayana
docs: add bagusindrayana as a contributor for code
2024-04-08 12:49:46 +02:00
allcontributors[bot] d163cbfac8
docs: update .all-contributorsrc [skip ci] 2024-04-08 10:49:32 +00:00
allcontributors[bot] 4537d96f3e
docs: update README.md [skip ci] 2024-04-08 10:49:31 +00:00
Fabio Di Stasio 099a71a189
Merge pull request #785 from bagusindrayana/feat-open-edit-save-file
Feat open, edit, and save file in query tab
2024-04-08 12:49:01 +02:00
Fabio Di Stasio e7efb9c616 refactor(UI): change to query tab icons to avoid ambiguity with new features 2024-04-08 09:52:46 +02:00
Fabio Di Stasio a752dcb6a9 chore(release): 0.7.23 2024-04-07 16:54:05 +02:00
bagusindrayana c1e58eb695 feat: open,save, and save as file in query tab 2024-04-06 15:34:42 +08:00
bagusindrayana f7204dc0ae feat: add translation for open,save, and save as file 2024-04-06 15:34:18 +08:00
bagusindrayana 6b56c60b68 feat: add shortcut open,save, and save as file 2024-04-06 15:33:01 +08:00
Fabio Di Stasio 1875e895ae chore(release): 0.7.23-beta.1 2024-04-02 09:10:17 +02:00
Fabio Di Stasio 2064294119 feat: add the page reference in the export file name, closes #772 2024-03-25 09:08:30 +01:00
Fabio Di Stasio 62e3115860 feat: move connections out of folder from context menu, related to #773 2024-03-24 11:10:00 +01:00
Fabio Di Stasio 9aef287a98 feat: move connections to folders from context menu, related to #773 2024-03-23 18:45:38 +01:00
Fabio Di Stasio 65ec4c5da6 fix: bad format of timestamp fields on CSV export, fixes 776 2024-03-23 16:33:19 +01:00
Fabio Di Stasio e19118982b chore(release): 0.7.23-beta.0 2024-03-21 23:09:09 +01:00
Fabio Di Stasio 11f130d91c
Merge pull request #778 from dyaskur/fix_shortcut_on_macos
fix: shortcut not working on mac os
2024-03-14 09:05:07 +01:00
Yaskur 0bb5cedda6 fix: shortcut not working on mac os 2024-03-13 15:48:59 +07:00
Fabio Di Stasio de9dac3e8a fix: query result sort not working with aliased tables, fixes #765 2024-03-10 16:04:24 +01:00
Fabio Di Stasio dd5b41716a fix: CSV export does not escape strings when needed, fixes #770 2024-03-09 15:42:36 +01:00
Fabio Di Stasio 86acb390ac build: add husky and commitlint 2024-03-09 15:05:55 +01:00
Fabio Di Stasio 2884ec3dd6 chore(release): 0.7.22 2024-02-26 18:20:31 +01:00
Fabio Di Stasio b542a09c01 Merge branch 'beta' of https://github.com/antares-sql/antares 2024-02-26 18:19:35 +01:00
Fabio Di Stasio 6d94a04b67 Merge branch 'develop' of https://github.com/antares-sql/antares into beta 2024-02-26 18:19:14 +01:00
Fabio Di Stasio 8500fc40a1 refactor: improved note tab selection 2024-02-26 18:17:15 +01:00
Fabio Di Stasio 586f901bae fix: delete record modal pressing del when editing a field, fixes #767 2024-02-23 18:08:02 +01:00
Fabio Di Stasio 04e4d21e20 chore(release): 0.7.22-beta.2 2024-02-18 14:49:58 +01:00
Fabio Di Stasio fd3dd03eb2 chore: moved electron in devDependencies 2024-02-18 14:47:56 +01:00
Fabio Di Stasio d3f71e65ce feat(MySQL): option to enable single connection mode 2024-02-18 14:37:45 +01:00
Fabio Di Stasio 90b9b87b1d chore(deps): update various dependencies 2024-02-18 13:44:22 +01:00
Fabio Di Stasio e9b42c3edb chore(release): 0.7.22-beta.1 2024-02-12 18:31:01 +01:00
Fabio Di Stasio 259d051a21 fix: some issues related to previous commit 2024-02-12 18:30:07 +01:00
Fabio Di Stasio 876d5ea481 perf(MySQL): improvements in connection handling 2024-02-11 16:38:06 +01:00
Fabio Di Stasio 6d002efaf5
Update FUNDING.yml 2024-02-09 12:07:16 +01:00
Fabio Di Stasio 58be1abf5f
Update README.md 2024-02-09 09:27:16 +01:00
Fabio Di Stasio da56905572 refactor: improved SET field edit 2024-02-07 17:37:19 +01:00
Fabio Di Stasio d698f2798a fix: unable to edit tables containing SET fields, fixes #755 2024-02-06 18:16:26 +01:00
Fabio Di Stasio 9a41511c42
Merge pull request #758 from 64knl/feat/translation-spelling
feat: update dutch translation + fix spelling mistake
2024-02-06 16:45:29 +01:00
Rene 30ada13663
feat: update dutch translation + fix spelling mistake 2024-02-06 15:38:42 +01:00
Fabio Di Stasio 14eeaccb07 chore(release): 0.7.22-beta.0 2024-02-04 14:40:24 +01:00
Fabio Di Stasio 1a0c5da2f1 feat(UI): resizable textarea in new/edito note, closes #747 2024-02-04 14:38:15 +01:00
Fabio Di Stasio bb36e98beb perf(UI): improved notes, fixes #746 2024-01-20 10:11:49 +01:00
Fabio Di Stasio 8928510fb5 refactor: use Record to type objects 2024-01-19 18:03:20 +01:00
Fabio Di Stasio eb5d3f14f1 chore(release): 0.7.21 2024-01-13 16:31:14 +01:00
Fabio Di Stasio 33c127b090 Merge branch 'master' of https://github.com/antares-sql/antares 2024-01-13 16:30:23 +01:00
Fabio Di Stasio 4e98dc21d8 Merge branch 'beta' of https://github.com/antares-sql/antares 2024-01-13 16:30:21 +01:00
Fabio Di Stasio 20b27343cd feat(SQLite): enable schema reloat button on sidebar 2024-01-13 16:28:55 +01:00
Fabio Di Stasio 3b9228a723 fix(SQLite): unable to change integer fields length to 0, fixes #732 2024-01-13 16:28:31 +01:00
Fabio Di Stasio ab0f91b448 chore: remove Twitter links 2024-01-11 14:09:34 +01:00
Fabio286 0b6307c738 chore(release): 0.7.21-beta.1 2024-01-06 19:04:36 +01:00
Fabio286 dbf38fd99c ci: update create-generated-sources.yml 2024-01-06 18:53:47 +01:00
Fabio286 169fcb13da build(deps): downgrade better-sqlite3 2024-01-06 18:17:58 +01:00
Fabio Di Stasio 97ece32988 ci: action to generate generated-sources.json 2024-01-05 11:14:58 +01:00
Fabio Di Stasio c946c3fcda ci: update node version 2024-01-05 11:14:05 +01:00
Fabio Di Stasio cdd2a11f8e fix(PostgreSQL): unhandled error on connection lost, fixes #740 2023-12-29 14:42:12 +01:00
Fabio Di Stasio 23946ff2ce fix(PostgreSQL): exception deleting a table with one or less tabs open 2023-12-28 10:44:11 +01:00
Fabio Di Stasio 0f8d2cb4ef fix(PostgreSQL): error adding MONEY fields to a table 2023-12-28 10:13:28 +01:00
116 changed files with 6117 additions and 17284 deletions

View File

@ -266,6 +266,33 @@
"contributions": [ "contributions": [
"translation" "translation"
] ]
},
{
"login": "bagusindrayana",
"name": "Bagus Indrayana",
"avatar_url": "https://avatars.githubusercontent.com/u/36830534?v=4",
"profile": "https://github.com/bagusindrayana",
"contributions": [
"code"
]
},
{
"login": "penguinlab",
"name": "Naoki Ishikawa",
"avatar_url": "https://avatars.githubusercontent.com/u/10959317?v=4",
"profile": "https://github.com/penguinlab",
"contributions": [
"translation"
]
},
{
"login": "mangas",
"name": "Filipe Azevedo",
"avatar_url": "https://avatars.githubusercontent.com/u/1640325?v=4",
"profile": "https://fazevedo.dev",
"contributions": [
"code"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

2
.github/FUNDING.yml vendored
View File

@ -1,6 +1,6 @@
# These are supported funding model platforms # These are supported funding model platforms
github: [fabio286] github: [antares-sql,fabio286]
patreon: #fabio286 patreon: #fabio286
open_collective: # Replace with a single Open Collective username open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username ko_fi: # Replace with a single Ko-fi username

View File

@ -15,7 +15,7 @@ jobs:
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 20
- name: Install dependencies - name: Install dependencies
run: npm i run: npm i

View File

@ -18,6 +18,7 @@ jobs:
- name: npm install & build - name: npm install & build
run: | run: |
npm install npm install
npm install "dmg-license" --save-optional
npm run build npm run build
- name: Upload Artifact - name: Upload Artifact
@ -45,12 +46,13 @@ jobs:
- name: npm install & build - name: npm install & build
run: | run: |
npm install npm install
npm install "dmg-license" --save-optional
npm run build npm run build
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: macos-build-beta name: macos-build-develop
retention-days: 3 retention-days: 3
path: | path: |
build build

View File

@ -0,0 +1,50 @@
name: Create generated-rources.json
on:
workflow_dispatch: {}
jobs:
build:
runs-on: ubuntu-latest
steps:
# Install flatpak-node-generator
- name: Install Python
uses: actions/setup-python@v5
with:
python-version: '3.8'
- name: Install pipx
uses: CfirTsabari/actions-pipx@v1
- name: Install flatpak-node-generator
run: |
cd ../
git clone https://github.com/flatpak/flatpak-builder-tools.git
cd flatpak-builder-tools/node
pipx install .
# Install Antares
- name: Check out Git repository
uses: actions/checkout@v3
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18
# - name: Delete old package-lock.json
# run: rm package-lock.json
- name: Install dependencies
run: npm i --lockfile-version 2
- name: Generate generated-sources.json
run: flatpak-node-generator npm -r package-lock.json --electron-node-headers
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: generated-sources
retention-days: 3
path: |
generated-sources.json

View File

@ -1,9 +1,10 @@
name: Test end-to-end name: Test end-to-end
on: on:
push: workflow_dispatch: {}
branches: # push:
- develop # branches:
# - develop
jobs: jobs:
release: release:
@ -20,7 +21,7 @@ jobs:
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 20
- name: Install dependencies - name: Install dependencies
run: npm i run: npm i

1
.husky/commit-msg Normal file
View File

@ -0,0 +1 @@
npx --no -- commitlint --edit $1

1
.husky/pre-commit Normal file
View File

@ -0,0 +1 @@
npm run lint

View File

@ -5,14 +5,14 @@
], ],
"fix": true, "fix": true,
"formatter": "verbose", "formatter": "verbose",
"customSyntax": "postcss-html",
"plugins": [ "plugins": [
"stylelint-scss" "stylelint-scss"
], ],
"rules": { "rules": {
"at-rule-no-unknown": null, "at-rule-no-unknown": null,
"no-descending-specificity": null, "no-descending-specificity": null,
"font-family-no-missing-generic-family-keyword": null, "font-family-no-missing-generic-family-keyword": null
"declaration-colon-newline-after": "always-multi-line"
}, },
"syntax": "scss" "syntax": "scss"
} }

9
.vscode/launch.json vendored
View File

@ -17,15 +17,6 @@
"sourceMaps": true, "sourceMaps": true,
"type": "chrome", "type": "chrome",
"webRoot": "${workspaceFolder}" "webRoot": "${workspaceFolder}"
},
{
"name": "Electron: Worker",
"cwd": "${workspaceFolder}",
"port": 9224,
"request": "attach",
"sourceMaps": true,
"type": "node",
"timeout": 1000000
} }
], ],
"compounds": [ "compounds": [

View File

@ -2,6 +2,171 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.7.25](https://github.com/antares-sql/antares/compare/v0.7.25-beta.2...v0.7.25) (2024-06-19)
### [0.7.25-beta.2](https://github.com/antares-sql/antares/compare/v0.7.25-beta.1...v0.7.25-beta.2) (2024-06-16)
### Features
* **PostgreSQL:** support to materialized views, closes [#804](https://github.com/antares-sql/antares/issues/804) ([0b9898f](https://github.com/antares-sql/antares/commit/0b9898f3e714d2cb24d100f55dd3858a644de162))
### Improvements
* **UI:** views grouped in folders ([a973ec3](https://github.com/antares-sql/antares/commit/a973ec3c60398cb16685a4f991c43ec4ee74c986))
### [0.7.25-beta.1](https://github.com/antares-sql/antares/compare/v0.7.25-beta.0...v0.7.25-beta.1) (2024-06-08)
### Bug Fixes
* issue switching table after using a filter, fixes[#691](https://github.com/antares-sql/antares/issues/691) ([4a1697d](https://github.com/antares-sql/antares/commit/4a1697d63351b9990efff5804b95d92ac2fc9783))
### [0.7.25-beta.0](https://github.com/antares-sql/antares/compare/v0.7.24...v0.7.25-beta.0) (2024-05-26)
### Features
* update japanese translation ([bb3c87b](https://github.com/antares-sql/antares/commit/bb3c87b2cf6fa38e3cfb68317c02aa350aae7887))
### Bug Fixes
* missing resizebars on mouse over ([25123e3](https://github.com/antares-sql/antares/commit/25123e34ef860d8bf019c496097e68e0101c9ab9))
* **PostgreSQL:** unable to search for databases, fixes [#798](https://github.com/antares-sql/antares/issues/798) ([d1bb50b](https://github.com/antares-sql/antares/commit/d1bb50b2bb48d3445080990c28fdc656cf27a6d3))
### [0.7.24](https://github.com/antares-sql/antares/compare/v0.7.24-beta.1...v0.7.24) (2024-05-03)
### Bug Fixes
* missing accent color change ([09c274a](https://github.com/antares-sql/antares/commit/09c274a724b5020efc650aaf7eecb2404343a6fc))
### [0.7.24-beta.1](https://github.com/antares-sql/antares/compare/v0.7.24-beta.0...v0.7.24-beta.1) (2024-04-30)
### Features
* accent color based on folder color, closes [#762](https://github.com/antares-sql/antares/issues/762) ([058fc2f](https://github.com/antares-sql/antares/commit/058fc2fc0b34cde5aa19233a4a999ef3624dae71))
### Bug Fixes
* **PostgreSQL:** better handle connection errors, should fix [#794](https://github.com/antares-sql/antares/issues/794) ([33bbc0e](https://github.com/antares-sql/antares/commit/33bbc0e7e6be370c944e979a34ab2cb19562d1e3))
* **PostgreSQL:** issue with similar tabs on differend databases ([23c59b4](https://github.com/antares-sql/antares/commit/23c59b4d4e8f250acad75f54d157c7c162e1c4f8))
### Improvements
* **UI:** hide "insert row" button in read-only mode, closes [#695](https://github.com/antares-sql/antares/issues/695) ([6600197](https://github.com/antares-sql/antares/commit/6600197b8286ced4c79378883594d21e69a83d8c))
* **UI:** improvements on light theme ([ece2ee0](https://github.com/antares-sql/antares/commit/ece2ee05cc90a58c1926e882e3ccf4f057f02d68))
### [0.7.24-beta.0](https://github.com/antares-sql/antares/compare/v0.7.23...v0.7.24-beta.0) (2024-04-12)
### Features
* add shortcut open,save, and save as file ([6b56c60](https://github.com/antares-sql/antares/commit/6b56c60b68647bc7182548a137cccc3413e3fbd5))
* add translation for open,save, and save as file ([f7204dc](https://github.com/antares-sql/antares/commit/f7204dc0ae721534eaefbde097d1c26c1d72ad41))
* open,save, and save as file in query tab ([c1e58eb](https://github.com/antares-sql/antares/commit/c1e58eb695de78fbf1d2b26c608692f0962373df))
* unsaved file reminder closing file tabs ([8d8650f](https://github.com/antares-sql/antares/commit/8d8650fbe76c79fd66be857d049b3baaa9ab1f9f))
### Bug Fixes
* **translation:** missing translation for "Open notes" shortcut ([0565ae1](https://github.com/antares-sql/antares/commit/0565ae12042901b9d67fe3e0ea269562ec444994))
### [0.7.23](https://github.com/antares-sql/antares/compare/v0.7.23-beta.1...v0.7.23) (2024-04-07)
### [0.7.23-beta.1](https://github.com/antares-sql/antares/compare/v0.7.23-beta.0...v0.7.23-beta.1) (2024-04-02)
### Features
* add the page reference in the export file name, closes [#772](https://github.com/antares-sql/antares/issues/772) ([2064294](https://github.com/antares-sql/antares/commit/2064294119ed9dfab2a9968dfb5b35d52e2ae03b))
* move connections out of folder from context menu, related to [#773](https://github.com/antares-sql/antares/issues/773) ([62e3115](https://github.com/antares-sql/antares/commit/62e311586073ae7ee4896305198c7168f637c1af))
* move connections to folders from context menu, related to [#773](https://github.com/antares-sql/antares/issues/773) ([9aef287](https://github.com/antares-sql/antares/commit/9aef287a983754158cdbdc9b2a72db9ab82f76c8))
### Bug Fixes
* bad format of timestamp fields on CSV export, fixes 776 ([65ec4c5](https://github.com/antares-sql/antares/commit/65ec4c5da6187a7ab2dfff59326cd12bfa788c3b))
### [0.7.23-beta.0](https://github.com/antares-sql/antares/compare/v0.7.22...v0.7.23-beta.0) (2024-03-21)
### Bug Fixes
* CSV export does not escape strings when needed, fixes [#770](https://github.com/antares-sql/antares/issues/770) ([dd5b417](https://github.com/antares-sql/antares/commit/dd5b41716a10cf9500f2c611b232f5b5b0756a68))
* query result sort not working with aliased tables, fixes [#765](https://github.com/antares-sql/antares/issues/765) ([de9dac3](https://github.com/antares-sql/antares/commit/de9dac3e8abf3b3261f8c54c88cf2386a5be2207))
* shortcut not working on mac os ([0bb5ced](https://github.com/antares-sql/antares/commit/0bb5cedda6a67ccbeea8c127b799f533395101a2))
### [0.7.22](https://github.com/antares-sql/antares/compare/v0.7.22-beta.2...v0.7.22) (2024-02-26)
### Bug Fixes
* delete record modal pressing del when editing a field, fixes [#767](https://github.com/antares-sql/antares/issues/767) ([586f901](https://github.com/antares-sql/antares/commit/586f901bae9a80c0e53ac1d804cbae3f05e26d8e))
### [0.7.22-beta.2](https://github.com/antares-sql/antares/compare/v0.7.22-beta.1...v0.7.22-beta.2) (2024-02-18)
### Features
* **MySQL:** option to enable single connection mode ([d3f71e6](https://github.com/antares-sql/antares/commit/d3f71e65cef88838f03f95a4b34e197fb61878f8))
### [0.7.22-beta.1](https://github.com/antares-sql/antares/compare/v0.7.22-beta.0...v0.7.22-beta.1) (2024-02-12)
### Features
* update dutch translation + fix spelling mistake ([30ada13](https://github.com/antares-sql/antares/commit/30ada13663e88f89beb3dd7291010837059585d5))
### Bug Fixes
* some issues related to previous commit ([259d051](https://github.com/antares-sql/antares/commit/259d051a21e334496d3a52b662f1855ba9a9046d))
* unable to edit tables containing SET fields, fixes [#755](https://github.com/antares-sql/antares/issues/755) ([d698f27](https://github.com/antares-sql/antares/commit/d698f2798a2423f86e6d786dd3ab80439b372a08))
### Improvements
* **MySQL:** improvements in connection handling ([876d5ea](https://github.com/antares-sql/antares/commit/876d5ea48185334e9e2fc981c4282a9c42d22b10))
### [0.7.22-beta.0](https://github.com/antares-sql/antares/compare/v0.7.21...v0.7.22-beta.0) (2024-02-04)
### Features
* **UI:** resizable textarea in new/edito note, closes [#747](https://github.com/antares-sql/antares/issues/747) ([1a0c5da](https://github.com/antares-sql/antares/commit/1a0c5da2f14b99d6f5581b2bf6e916d67d097245))
### Improvements
* **UI:** improved notes, fixes [#746](https://github.com/antares-sql/antares/issues/746) ([bb36e98](https://github.com/antares-sql/antares/commit/bb36e98bebc5e1e55735e98d272428df2ab682e8))
### [0.7.21](https://github.com/antares-sql/antares/compare/v0.7.21-beta.1...v0.7.21) (2024-01-13)
### Features
* **SQLite:** enable schema reloat button on sidebar ([20b2734](https://github.com/antares-sql/antares/commit/20b27343cd95998bd83403b7556ea35fcad9fa1b))
### Bug Fixes
* **SQLite:** unable to change integer fields length to 0, fixes [#732](https://github.com/antares-sql/antares/issues/732) ([3b9228a](https://github.com/antares-sql/antares/commit/3b9228a7230dcd9f47f5794a83b60d28207bdce1))
### [0.7.21-beta.1](https://github.com/antares-sql/antares/compare/v0.7.21-beta.0...v0.7.21-beta.1) (2024-01-06)
### Bug Fixes
* **PostgreSQL:** error adding MONEY fields to a table ([0f8d2cb](https://github.com/antares-sql/antares/commit/0f8d2cb4ef5c327f96f788179be0b309689b4ce8))
* **PostgreSQL:** exception deleting a table with one or less tabs open ([23946ff](https://github.com/antares-sql/antares/commit/23946ff2cef6d63e1529e2c8c4357d7fdedc3284))
* **PostgreSQL:** unhandled error on connection lost, fixes [#740](https://github.com/antares-sql/antares/issues/740) ([cdd2a11](https://github.com/antares-sql/antares/commit/cdd2a11f8e33d6607337989723774d60c7c1a030))
### [0.7.21-beta.0](https://github.com/antares-sql/antares/compare/v0.7.20...v0.7.21-beta.0) (2023-12-25) ### [0.7.21-beta.0](https://github.com/antares-sql/antares/compare/v0.7.20...v0.7.21-beta.0) (2023-12-25)

View File

@ -7,7 +7,7 @@
# Antares SQL Client # Antares SQL Client
![GitHub package.json version](https://img.shields.io/github/package-json/v/fabio286/antares) ![GitHub](https://img.shields.io/github/license/fabio286/antares) ![Test e2e](https://github.com/antares-sql/antares/actions/workflows/test-e2e-win.yml/badge.svg?branch=develop) ![Mastodon Follow](https://img.shields.io/mastodon/follow/%20110860460902482117?domain=https%3A%2F%2Ffosstodon.org&style=social) [![Twitter Follow](https://img.shields.io/twitter/follow/AntaresSQL?style=social)](https://twitter.com/AntaresSQL) [![Plant a Tree](https://raw.githubusercontent.com/Fabio286/treedom-badge/master/svg/plant-a-tree.svg)](https://www.treedom.net/en/user/fabio-di-stasio/event/antares-for-the-planet) ![GitHub package.json version](https://img.shields.io/github/package-json/v/fabio286/antares) ![GitHub](https://img.shields.io/github/license/fabio286/antares) ![Test e2e](https://github.com/antares-sql/antares/actions/workflows/test-e2e-win.yml/badge.svg?branch=develop) ![Mastodon Follow](https://img.shields.io/mastodon/follow/%20110860460902482117?domain=https%3A%2F%2Ffosstodon.org&style=social) [![Plant a Tree](https://raw.githubusercontent.com/Fabio286/treedom-badge/master/svg/plant-a-tree.svg)](https://www.treedom.net/en/user/fabio-di-stasio/event/antares-for-the-planet)
Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers. Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers.
Our target is to support as many databases as possible, and all major operating systems, including the ARM versions. Our target is to support as many databases as possible, and all major operating systems, including the ARM versions.
@ -17,7 +17,7 @@ However, there are all the features necessary to have a pleasant database manage
We are actively working on it, hoping to provide new cool features, improvements and fixes as soon as possible. We are actively working on it, hoping to provide new cool features, improvements and fixes as soon as possible.
🔗 If you are curious to try Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases/latest). 🔗 If you are curious to try Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases/latest).
👁 To stay tuned for new releases follow Antares SQL on [Mastodon](https://fosstodon.org/@AntaresSQL) or [Twitter](https://twitter.com/AntaresSQL). 👁 To stay tuned for new releases follow Antares SQL on [Mastodon](https://fosstodon.org/@AntaresSQL).
🌟 Don't forget to **leave a star** if you appreciate this project. 🌟 Don't forget to **leave a star** if you appreciate this project.
🗳️ Polls: 🗳️ Polls:
@ -71,17 +71,6 @@ On macOS you can run `.dmg` distribution following [this guide](https://support.
[<img height='56' alt='Download on Flathub' src='https://dl.flathub.org/assets/badges/flathub-badge-en.svg'/>](https://flathub.org/apps/it.fabiodistasio.AntaresSQL) [![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/antares) [![Get it from AUR](https://raw.githubusercontent.com/antares-sql/antares/master/docs/aur-badge.svg)](https://aur.archlinux.org/packages/antares-sql-bin) [<img src="https://developer.microsoft.com/store/badges/images/English_get-it-from-MS.png" style="height: 56px">](https://www.microsoft.com/p/antares-sql-client/9nhtb9sq51r1?cid=storebadge&ocid=badge&rtc=1&activetab=pivot:overviewtab) [<img height='56' alt='Download on Flathub' src='https://dl.flathub.org/assets/badges/flathub-badge-en.svg'/>](https://flathub.org/apps/it.fabiodistasio.AntaresSQL) [![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/antares) [![Get it from AUR](https://raw.githubusercontent.com/antares-sql/antares/master/docs/aur-badge.svg)](https://aur.archlinux.org/packages/antares-sql-bin) [<img src="https://developer.microsoft.com/store/badges/images/English_get-it-from-MS.png" style="height: 56px">](https://www.microsoft.com/p/antares-sql-client/9nhtb9sq51r1?cid=storebadge&ocid=badge&rtc=1&activetab=pivot:overviewtab)
🚀 **[Other Downloads](https://github.com/antares-sql/antares/releases/latest)** 🚀 **[Other Downloads](https://github.com/antares-sql/antares/releases/latest)**
## Coming soon
This is a roadmap with major features will come in near future.
- Database tools.
- Users management (add/edit/delete).
- More context menu shortcuts.
- More keyboard shortcuts.
- Support for other databases.
- Apple Silicon distribution
## Currently supported ## Currently supported
### Databases ### Databases
@ -90,6 +79,7 @@ This is a roadmap with major features will come in near future.
- [x] PostgreSQL - [x] PostgreSQL
- [x] SQLite - [x] SQLite
- [x] Firebird SQL - [x] Firebird SQL
- [ ] DuckDB
- [ ] SQL Server - [ ] SQL Server
- [ ] More... - [ ] More...
@ -111,7 +101,7 @@ This is a roadmap with major features will come in near future.
- 🌍 [Translate Antares](https://github.com/Fabio286/antares/wiki/Translate-Antares) - 🌍 [Translate Antares](https://github.com/Fabio286/antares/wiki/Translate-Antares)
- 📖 [Contributors Guide](https://github.com/Fabio286/antares/wiki/Contributors-Guide) - 📖 [Contributors Guide](https://github.com/Fabio286/antares/wiki/Contributors-Guide)
- 🚧 [Project Board](https://github.com/antares-sql/antares/projects/1) - 🚧 [Project Board](https://github.com/orgs/antares-sql/projects/3/views/2)
## Contributors ✨ ## Contributors ✨
@ -158,6 +148,11 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Lawondyss"><img src="https://avatars.githubusercontent.com/u/272130?v=4?s=100" width="100px;" alt="Ladislav Vondráček"/><br /><sub><b>Ladislav Vondráček</b></sub></a><br /><a href="#translation-Lawondyss" title="Translation">🌍</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/Lawondyss"><img src="https://avatars.githubusercontent.com/u/272130?v=4?s=100" width="100px;" alt="Ladislav Vondráček"/><br /><sub><b>Ladislav Vondráček</b></sub></a><br /><a href="#translation-Lawondyss" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zvlad"><img src="https://avatars.githubusercontent.com/u/9055134?v=4?s=100" width="100px;" alt="Vladyslav"/><br /><sub><b>Vladyslav</b></sub></a><br /><a href="#translation-zvlad" title="Translation">🌍</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/zvlad"><img src="https://avatars.githubusercontent.com/u/9055134?v=4?s=100" width="100px;" alt="Vladyslav"/><br /><sub><b>Vladyslav</b></sub></a><br /><a href="#translation-zvlad" title="Translation">🌍</a></td>
</tr> </tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bagusindrayana"><img src="https://avatars.githubusercontent.com/u/36830534?v=4?s=100" width="100px;" alt="Bagus Indrayana"/><br /><sub><b>Bagus Indrayana</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=bagusindrayana" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/penguinlab"><img src="https://avatars.githubusercontent.com/u/10959317?v=4?s=100" width="100px;" alt="Naoki Ishikawa"/><br /><sub><b>Naoki Ishikawa</b></sub></a><br /><a href="#translation-penguinlab" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://fazevedo.dev"><img src="https://avatars.githubusercontent.com/u/1640325?v=4?s=100" width="100px;" alt="Filipe Azevedo"/><br /><sub><b>Filipe Azevedo</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=mangas" title="Code">💻</a></td>
</tr>
</tbody> </tbody>
</table> </table>

33
commitlint.config.js Normal file
View File

@ -0,0 +1,33 @@
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
// TODO Add Scope Enum Here
// 'scope-enum': [2, 'always', ['yourscope', 'yourscope']],
'type-enum': [
2,
'always',
[
'feat',
'fix',
'docs',
'chore',
'style',
'refactor',
'build',
'ci',
'test',
'revert',
'perf'
]
],
'subject-case': [
2,
'never',
[
'upper-case',
'pascal-case',
'start-case'
]
]
}
};

20136
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{ {
"name": "antares", "name": "antares",
"productName": "Antares", "productName": "Antares",
"version": "0.7.21-beta.0", "version": "0.7.25",
"description": "A modern, fast and productivity driven SQL client with a focus in UX.", "description": "A modern, fast and productivity driven SQL client with a focus in UX.",
"license": "MIT", "license": "MIT",
"repository": "https://github.com/antares-sql/antares.git", "repository": "https://github.com/antares-sql/antares.git",
@ -25,7 +25,8 @@
"lint": "eslint . --ext .js,.ts,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"", "lint": "eslint . --ext .js,.ts,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
"lint:fix": "eslint . --ext .js,.ts,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix", "lint:fix": "eslint . --ext .js,.ts,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
"contributors:add": "all-contributors add", "contributors:add": "all-contributors add",
"contributors:generate": "all-contributors generate" "contributors:generate": "all-contributors generate",
"prepare": "husky"
}, },
"author": "Fabio Di Stasio <info@fabiodistasio.it>", "author": "Fabio Di Stasio <info@fabiodistasio.it>",
"main": "./dist/main.js", "main": "./dist/main.js",
@ -118,45 +119,70 @@
} }
}, },
"dependencies": { "dependencies": {
"@electron/remote": "~2.0.1", "@electron/remote": "~2.1.2",
"@fabio286/ssh2-promise": "~1.0.4-b", "@fabio286/ssh2-promise": "~1.0.4-b",
"@faker-js/faker": "~6.1.2", "@faker-js/faker": "~6.1.2",
"@jamescoyle/vue-icon": "~0.1.2", "@jamescoyle/vue-icon": "~0.1.2",
"@mdi/js": "~7.2.96", "@mdi/js": "~7.2.96",
"@turf/helpers": "~6.5.0", "@turf/helpers": "~6.5.0",
"@vue/compiler-sfc": "~3.2.33",
"@vueuse/core": "~10.4.1", "@vueuse/core": "~10.4.1",
"ace-builds": "~1.24.1", "ace-builds": "~1.34.1",
"better-sqlite3": "~9.1.1", "babel-loader": "~8.2.3",
"better-sqlite3": "~10.0.0",
"chalk": "~4.1.2",
"cpu-features": "^0.0.10",
"cross-env": "~7.0.2",
"css-loader": "~6.5.0",
"electron-log": "~5.0.1", "electron-log": "~5.0.1",
"electron-store": "~8.1.0", "electron-store": "~8.1.0",
"electron-updater": "~4.6.5", "electron-updater": "~4.6.5",
"electron-window-state": "~5.0.3", "electron-window-state": "~5.0.3",
"encoding": "~0.1.13", "encoding": "~0.1.13",
"file-loader": "~6.2.0",
"floating-vue": "~2.0.0-beta.20", "floating-vue": "~2.0.0-beta.20",
"html-webpack-plugin": "~5.5.0",
"json2php": "~0.0.7", "json2php": "~0.0.7",
"leaflet": "~1.7.1", "leaflet": "~1.7.1",
"marked": "~4.0.19", "marked": "~12.0.0",
"moment": "~2.29.4", "mini-css-extract-plugin": "~2.4.5",
"mysql2": "~3.5.2", "moment": "~2.30.1",
"node-firebird": "~1.1.4", "mysql2": "~3.9.7",
"pg": "~8.11.1", "node-firebird": "~1.1.8",
"node-loader": "~2.0.0",
"pg": "~8.11.5",
"pg-connection-string": "~2.5.0", "pg-connection-string": "~2.5.0",
"pg-query-stream": "~4.2.3", "pg-query-stream": "~4.2.3",
"pgsql-ast-parser": "~7.2.1", "pgsql-ast-parser": "~7.2.1",
"pinia": "~2.1.6", "pinia": "~2.1.7",
"postcss-html": "~1.5.0",
"progress-webpack-plugin": "~1.0.12",
"rimraf": "~3.0.2",
"sass": "~1.42.1",
"sass-loader": "~12.3.0",
"source-map-support": "~0.5.20", "source-map-support": "~0.5.20",
"spectre.css": "~0.5.9", "spectre.css": "~0.5.9",
"sql-formatter": "~13.0.0", "sql-formatter": "~13.0.0",
"sql-highlight": "~4.4.0", "sql-highlight": "~4.4.0",
"style-loader": "~3.3.1",
"tree-kill": "~1.2.2",
"ts-loader": "~9.2.8",
"typescript": "~4.6.3",
"unzip-crx-3": "~0.2.0",
"v-mask": "~2.3.0", "v-mask": "~2.3.0",
"vue": "~3.3.4", "vue": "~3.4.27",
"vue-i18n": "~9.2.2", "vue-i18n": "~9.13.1",
"vuedraggable": "~4.1.0" "vue-loader": "~16.8.3",
"vuedraggable": "~4.1.0",
"webpack": "^5.91.0",
"webpack-cli": "~4.9.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "~7.15.7", "@babel/eslint-parser": "~7.15.7",
"@babel/preset-env": "~7.15.8", "@babel/preset-env": "~7.15.8",
"@babel/preset-typescript": "~7.16.7", "@babel/preset-typescript": "~7.16.7",
"@commitlint/cli": "~19.0.3",
"@commitlint/config-conventional": "~19.0.3",
"@playwright/test": "~1.28.1", "@playwright/test": "~1.28.1",
"@types/better-sqlite3": "~7.5.0", "@types/better-sqlite3": "~7.5.0",
"@types/leaflet": "~1.7.9", "@types/leaflet": "~1.7.9",
@ -166,14 +192,9 @@
"@types/ssh2": "~1.11.6", "@types/ssh2": "~1.11.6",
"@typescript-eslint/eslint-plugin": "~5.18.0", "@typescript-eslint/eslint-plugin": "~5.18.0",
"@typescript-eslint/parser": "~5.18.0", "@typescript-eslint/parser": "~5.18.0",
"@vue/compiler-sfc": "~3.2.33",
"all-contributors-cli": "~6.20.0", "all-contributors-cli": "~6.20.0",
"babel-loader": "~8.2.3", "electron": "~30.0.8",
"chalk": "~4.1.2", "electron-builder": "~24.13.3",
"cross-env": "~7.0.2",
"css-loader": "~6.5.0",
"electron": "~22.3.27",
"electron-builder": "~24.6.4",
"eslint": "~7.32.0", "eslint": "~7.32.0",
"eslint-config-standard": "~16.0.3", "eslint-config-standard": "~16.0.3",
"eslint-plugin-import": "~2.24.2", "eslint-plugin-import": "~2.24.2",
@ -181,32 +202,16 @@
"eslint-plugin-promise": "~5.2.0", "eslint-plugin-promise": "~5.2.0",
"eslint-plugin-simple-import-sort": "~10.0.0", "eslint-plugin-simple-import-sort": "~10.0.0",
"eslint-plugin-vue": "~8.0.3", "eslint-plugin-vue": "~8.0.3",
"file-loader": "~6.2.0", "husky": "~9.0.11",
"html-webpack-plugin": "~5.5.0",
"mini-css-extract-plugin": "~2.4.5",
"node-loader": "~2.0.0",
"playwright": "~1.28.1", "playwright": "~1.28.1",
"playwright-core": "~1.28.1", "playwright-core": "~1.28.1",
"postcss-html": "~1.5.0",
"progress-webpack-plugin": "~1.0.12",
"rimraf": "~3.0.2",
"sass": "~1.42.1",
"sass-loader": "~12.3.0",
"standard-version": "~9.3.1", "standard-version": "~9.3.1",
"style-loader": "~3.3.1",
"stylelint": "^15.11.0", "stylelint": "^15.11.0",
"stylelint-config-recommended-vue": "~1.5.0", "stylelint-config-recommended-vue": "~1.5.0",
"stylelint-config-standard": "~34.0.0", "stylelint-config-standard": "~34.0.0",
"stylelint-scss": "~5.3.0", "stylelint-scss": "~5.3.0",
"tree-kill": "~1.2.2",
"ts-loader": "~9.2.8",
"ts-node": "~10.9.1", "ts-node": "~10.9.1",
"typescript": "~4.6.3",
"unzip-crx-3": "~0.2.0",
"vue-eslint-parser": "~8.3.0", "vue-eslint-parser": "~8.3.0",
"vue-loader": "~16.8.3",
"webpack": "~5.72.0",
"webpack-cli": "~4.9.1",
"webpack-dev-server": "~4.11.1", "webpack-dev-server": "~4.11.1",
"xvfb-maybe": "~0.2.1" "xvfb-maybe": "~0.2.1"
} }

View File

@ -19,6 +19,7 @@ export const defaults: Customizations = {
sshConnection: false, sshConnection: false,
fileConnection: false, fileConnection: false,
cancelQueries: false, cancelQueries: false,
singleConnectionMode: false,
// Tools // Tools
processesList: false, processesList: false,
usersManagement: false, usersManagement: false,

View File

@ -29,6 +29,7 @@ export const customizations: Customizations = {
sslConnection: true, sslConnection: true,
sshConnection: true, sshConnection: true,
cancelQueries: true, cancelQueries: true,
singleConnectionMode: true,
// Tools // Tools
processesList: true, processesList: true,
// Structure // Structure

View File

@ -31,6 +31,7 @@ export const customizations: Customizations = {
schemas: true, schemas: true,
tables: true, tables: true,
views: true, views: true,
materializedViews: true,
triggers: true, triggers: true,
triggerFunctions: true, triggerFunctions: true,
routines: true, routines: true,
@ -42,6 +43,7 @@ export const customizations: Customizations = {
tableDuplicate: true, tableDuplicate: true,
tableDdl: true, tableDdl: true,
viewAdd: true, viewAdd: true,
materializedViewAdd: true,
triggerAdd: true, triggerAdd: true,
triggerFunctionAdd: true, triggerFunctionAdd: true,
routineAdd: true, routineAdd: true,
@ -52,6 +54,7 @@ export const customizations: Customizations = {
databaseEdit: false, databaseEdit: false,
tableSettings: true, tableSettings: true,
viewSettings: true, viewSettings: true,
materializedViewSettings: true,
triggerSettings: true, triggerSettings: true,
triggerFunctionSettings: true, triggerFunctionSettings: true,
routineSettings: true, routineSettings: true,

View File

@ -66,7 +66,7 @@ export default [
group: 'monetary', group: 'monetary',
types: [ types: [
{ {
name: 'money', name: 'MONEY',
length: false, length: false,
unsigned: true unsigned: true
} }

View File

@ -52,6 +52,7 @@ export interface ConnectionParams {
password: string; password: string;
ask: boolean; ask: boolean;
readonly: boolean; readonly: boolean;
singleConnectionMode: boolean;
ssl: boolean; ssl: boolean;
cert?: string; cert?: string;
key?: string; key?: string;
@ -363,7 +364,7 @@ export interface QueryBuilderObject {
offset: number; offset: number;
join: string[]; join: string[];
update: string[]; update: string[];
insert: {[key: string]: string | boolean | number }[]; insert: Record<string, string | boolean | number>[];
delete: boolean; delete: boolean;
} }

View File

@ -19,6 +19,7 @@ export interface Customizations {
sshConnection?: boolean; sshConnection?: boolean;
fileConnection?: boolean; fileConnection?: boolean;
cancelQueries?: boolean; cancelQueries?: boolean;
singleConnectionMode?: boolean;
// Tools // Tools
processesList?: boolean; processesList?: boolean;
usersManagement?: boolean; usersManagement?: boolean;
@ -27,6 +28,7 @@ export interface Customizations {
schemas?: boolean; schemas?: boolean;
tables?: boolean; tables?: boolean;
views?: boolean; views?: boolean;
materializedViews?: boolean;
triggers?: boolean; triggers?: boolean;
triggerFunctions?: boolean; triggerFunctions?: boolean;
routines?: boolean; routines?: boolean;
@ -44,6 +46,8 @@ export interface Customizations {
tableDdl?: boolean; tableDdl?: boolean;
viewAdd?: boolean; viewAdd?: boolean;
viewSettings?: boolean; viewSettings?: boolean;
materializedViewAdd?: boolean;
materializedViewSettings?: boolean;
triggerAdd?: boolean; triggerAdd?: boolean;
triggerFunctionAdd?: boolean; triggerFunctionAdd?: boolean;
routineAdd?: boolean; routineAdd?: boolean;

View File

@ -13,7 +13,7 @@ export interface ExportOptions {
includeContent: boolean; includeContent: boolean;
includeDropStatement: boolean; includeDropStatement: boolean;
}[]; }[];
includes: {[key: string]: boolean}; includes: Record<string, boolean>;
outputFormat: 'sql' | 'sql.zip'; outputFormat: 'sql' | 'sql.zip';
outputFile: string; outputFile: string;
sqlInsertAfter: number; sqlInsertAfter: number;

View File

@ -18,7 +18,7 @@ export interface TableDeleteParams {
primary?: string; primary?: string;
field: string; field: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
rows: {[key: string]: any}; rows: Record<string, any>;
} }
export type TableFilterOperator = '=' | '!=' | '>' | '<' | '>=' | '<=' | 'IN' | 'NOT IN' | 'LIKE' | 'NOT LIKE' | 'RLIKE' | 'NOT RLIKE' | 'BETWEEN' | 'IS NULL' | 'IS NOT NULL' export type TableFilterOperator = '=' | '!=' | '>' | '<' | '>=' | '<=' | 'IN' | 'NOT IN' | 'LIKE' | 'NOT LIKE' | 'RLIKE' | 'NOT RLIKE' | 'BETWEEN' | 'IS NULL' | 'IS NOT NULL'
@ -35,7 +35,7 @@ export interface InsertRowsParams {
uid: string; uid: string;
schema: string; schema: string;
table: string; table: string;
row: {[key: string]: { row: Record<string, {
group: string; group: string;
method: string; method: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -43,9 +43,8 @@ export interface InsertRowsParams {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
value: any; value: any;
length: number; length: number;
}; }>;
};
repeat: number; repeat: number;
fields: {[key: string]: string}; fields: Record<string, string>;
locale: UsableLocale; locale: UsableLocale;
} }

View File

@ -41,7 +41,7 @@ export const escapeAndQuote = (val: string, client: ClientCode) => {
const { stringsWrapper: sw } = customizations[client]; const { stringsWrapper: sw } = customizations[client];
// eslint-disable-next-line no-control-regex // eslint-disable-next-line no-control-regex
const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g; const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g;
const CHARS_ESCAPE_MAP: {[key: string]: string} = { const CHARS_ESCAPE_MAP: Record<string, string> = {
'\0': '\\0', '\0': '\\0',
'\b': '\\b', '\b': '\\b',
'\t': '\\t', '\t': '\\t',
@ -153,9 +153,9 @@ export const valueToSqlString = (args: {
}; };
export const jsonToSqlInsert = (args: { export const jsonToSqlInsert = (args: {
json: { [key: string]: any}[]; json: Record<string, any>[];
client: ClientCode; client: ClientCode;
fields: { [key: string]: {type: string; datePrecision: number}}; fields: Record<string, {type: string; datePrecision: number}>;
table: string; table: string;
options?: {sqlInsertAfter: number; sqlInsertDivider: 'bytes' | 'rows'}; options?: {sqlInsertAfter: number; sqlInsertDivider: 'bytes' | 'rows'};
}) => { }) => {

View File

@ -1,4 +1,4 @@
export const shortcutEvents: { [key: string]: { l18n: string; l18nParam?: string | number; context?: 'tab' }} = { export const shortcutEvents: Record<string, { l18n: string; l18nParam?: string | number; context?: 'tab' }> = {
'run-or-reload': { l18n: 'application.runOrReload', context: 'tab' }, 'run-or-reload': { l18n: 'application.runOrReload', context: 'tab' },
'open-new-tab': { l18n: 'application.openNewTab', context: 'tab' }, 'open-new-tab': { l18n: 'application.openNewTab', context: 'tab' },
'close-tab': { l18n: 'application.closeTab', context: 'tab' }, 'close-tab': { l18n: 'application.closeTab', context: 'tab' },
@ -6,6 +6,9 @@ export const shortcutEvents: { [key: string]: { l18n: string; l18nParam?: string
'kill-query': { l18n: 'database.killQuery', context: 'tab' }, 'kill-query': { l18n: 'database.killQuery', context: 'tab' },
'query-history': { l18n: 'database.queryHistory', context: 'tab' }, 'query-history': { l18n: 'database.queryHistory', context: 'tab' },
'clear-query': { l18n: 'database.clearQuery', context: 'tab' }, 'clear-query': { l18n: 'database.clearQuery', context: 'tab' },
// 'save-file': { l18n: 'application.saveFile', context: 'tab' },
'open-file': { l18n: 'application.openFile', context: 'tab' },
'save-file-as': { l18n: 'application.saveFileAs', context: 'tab' },
'next-tab': { l18n: 'application.nextTab' }, 'next-tab': { l18n: 'application.nextTab' },
'prev-tab': { l18n: 'application.previousTab' }, 'prev-tab': { l18n: 'application.previousTab' },
'open-all-connections': { l18n: 'application.openAllConnections' }, 'open-all-connections': { l18n: 'application.openAllConnections' },
@ -16,7 +19,7 @@ export const shortcutEvents: { [key: string]: { l18n: string; l18nParam?: string
'save-content': { l18n: 'application.saveContent' }, 'save-content': { l18n: 'application.saveContent' },
'create-connection': { l18n: 'connection.createNewConnection' }, 'create-connection': { l18n: 'connection.createNewConnection' },
'open-settings': { l18n: 'application.openSettings' }, 'open-settings': { l18n: 'application.openSettings' },
'open-scratchpad': { l18n: 'application.openScratchpad' } 'open-scratchpad': { l18n: 'application.openNotes' }
}; };
interface ShortcutRecord { interface ShortcutRecord {
@ -119,6 +122,21 @@ const shortcuts: ShortcutRecord[] = [
event: 'toggle-console', event: 'toggle-console',
keys: ['CommandOrControl+`'], keys: ['CommandOrControl+`'],
os: ['darwin', 'linux', 'win32'] os: ['darwin', 'linux', 'win32']
},
// {
// event: 'save-file',
// keys: ['CommandOrControl+S'],
// os: ['darwin', 'linux', 'win32']
// },
{
event: 'open-file',
keys: ['CommandOrControl+O'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'save-file-as',
keys: ['Shift+CommandOrControl+S'],
os: ['darwin', 'linux', 'win32']
} }
]; ];

View File

@ -1,5 +1,6 @@
import { app, dialog, ipcMain, safeStorage } from 'electron'; import { app, dialog, ipcMain, safeStorage } from 'electron';
import * as Store from 'electron-store'; import * as Store from 'electron-store';
import * as fs from 'fs';
import { validateSender } from '../libs/misc/validateSender'; import { validateSender } from '../libs/misc/validateSender';
import { ShortcutRegister } from '../libs/ShortcutRegister'; import { ShortcutRegister } from '../libs/ShortcutRegister';
@ -52,6 +53,11 @@ export default () => {
return dialog.showOpenDialog(options); return dialog.showOpenDialog(options);
}); });
ipcMain.handle('show-save-dialog', (event, options) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
return dialog.showSaveDialog(options);
});
ipcMain.handle('get-download-dir-path', (event) => { ipcMain.handle('get-download-dir-path', (event) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
return app.getPath('downloads'); return app.getPath('downloads');
@ -80,4 +86,26 @@ export default () => {
const shortCutRegister = ShortcutRegister.getInstance(); const shortCutRegister = ShortcutRegister.getInstance();
shortCutRegister.unregister(); shortCutRegister.unregister();
}); });
ipcMain.handle('read-file', (event, filePath) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
try {
const content = fs.readFileSync(filePath, 'utf-8');
return content;
}
catch (error) {
return { status: 'error', response: error.toString() };
}
});
ipcMain.handle('write-file', (event, filePath, content) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
try {
fs.writeFileSync(filePath, content, 'utf-8');
return { status: 'success' };
}
catch (error) {
return { status: 'error', response: error.toString() };
}
});
}; };

View File

@ -6,7 +6,7 @@ import { SslOptions } from 'mysql2';
import { ClientsFactory } from '../libs/ClientsFactory'; import { ClientsFactory } from '../libs/ClientsFactory';
import { validateSender } from '../libs/misc/validateSender'; import { validateSender } from '../libs/misc/validateSender';
export default (connections: {[key: string]: antares.Client}) => { export default (connections: Record<string, antares.Client>) => {
ipcMain.handle('test-connection', async (event, conn: antares.ConnectionParams) => { ipcMain.handle('test-connection', async (event, conn: antares.ConnectionParams) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
@ -146,7 +146,7 @@ export default (connections: {[key: string]: antares.Client}) => {
uid: conn.uid, uid: conn.uid,
client: conn.client, client: conn.client,
params, params,
poolSize: 5 poolSize: conn.singleConnectionMode ? 0 : 5
}); });
await connection.connect(); await connection.connect();

View File

@ -3,7 +3,7 @@ import { ipcMain } from 'electron';
import { validateSender } from '../libs/misc/validateSender'; import { validateSender } from '../libs/misc/validateSender';
export default (connections: {[key: string]: antares.Client}) => { export default (connections: Record<string, antares.Client>) => {
ipcMain.handle('get-databases', async (event, uid) => { ipcMain.handle('get-databases', async (event, uid) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };

View File

@ -3,7 +3,7 @@ import { ipcMain } from 'electron';
import { validateSender } from '../libs/misc/validateSender'; import { validateSender } from '../libs/misc/validateSender';
export default (connections: {[key: string]: antares.Client}) => { export default (connections: Record<string, antares.Client>) => {
ipcMain.handle('get-function-informations', async (event, params) => { ipcMain.handle('get-function-informations', async (event, params) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };

View File

@ -13,7 +13,7 @@ import updates from './updates';
import users from './users'; import users from './users';
import views from './views'; import views from './views';
const connections: {[key: string]: antares.Client} = {}; const connections: Record<string, antares.Client> = {};
export default () => { export default () => {
connection(connections); connection(connections);

View File

@ -3,7 +3,7 @@ import { ipcMain } from 'electron';
import { validateSender } from '../libs/misc/validateSender'; import { validateSender } from '../libs/misc/validateSender';
export default (connections: {[key: string]: antares.Client}) => { export default (connections: Record<string, antares.Client>) => {
ipcMain.handle('get-routine-informations', async (event, params) => { ipcMain.handle('get-routine-informations', async (event, params) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };

View File

@ -3,7 +3,7 @@ import { ipcMain } from 'electron';
import { validateSender } from '../libs/misc/validateSender'; import { validateSender } from '../libs/misc/validateSender';
export default (connections: {[key: string]: antares.Client}) => { export default (connections: Record<string, antares.Client>) => {
ipcMain.handle('get-scheduler-informations', async (event, params) => { ipcMain.handle('get-scheduler-informations', async (event, params) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };

View File

@ -6,7 +6,7 @@ import { Worker } from 'worker_threads';
import { validateSender } from '../libs/misc/validateSender'; import { validateSender } from '../libs/misc/validateSender';
export default (connections: {[key: string]: antares.Client}) => { export default (connections: Record<string, antares.Client>) => {
let exporter: Worker = null; let exporter: Worker = null;
let importer: Worker = null; let importer: Worker = null;

View File

@ -10,7 +10,7 @@ import * as moment from 'moment';
import { validateSender } from '../libs/misc/validateSender'; import { validateSender } from '../libs/misc/validateSender';
export default (connections: {[key: string]: antares.Client}) => { export default (connections: Record<string, antares.Client>) => {
ipcMain.handle('get-table-columns', async (event, params) => { ipcMain.handle('get-table-columns', async (event, params) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
@ -249,7 +249,7 @@ export default (connections: {[key: string]: antares.Client}) => {
if (params.primary) { if (params.primary) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const idString = params.rows.map((row: {[key: string]: any}) => { const idString = params.rows.map((row: Record<string, any>) => {
const fieldName = Object.keys(row)[0].includes('.') ? `${params.table}.${params.primary}` : params.primary; const fieldName = Object.keys(row)[0].includes('.') ? `${params.table}.${params.primary}` : params.primary;
return typeof row[fieldName] === 'string' return typeof row[fieldName] === 'string'
@ -304,10 +304,10 @@ export default (connections: {[key: string]: antares.Client}) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
try { // TODO: move to client classes try { // TODO: move to client classes
const rows: {[key: string]: string | number | boolean | Date | Buffer}[] = []; const rows: Record<string, string | number | boolean | Date | Buffer>[] = [];
for (let i = 0; i < +params.repeat; i++) { for (let i = 0; i < +params.repeat; i++) {
const insertObj: {[key: string]: string | number | boolean | Date | Buffer} = {}; const insertObj: Record<string, string | number | boolean | Date | Buffer> = {};
for (const key in params.row) { for (const key in params.row) {
const type = params.fields[key]; const type = params.fields[key];
@ -367,7 +367,7 @@ export default (connections: {[key: string]: antares.Client}) => {
insertObj[key] = escapedParam; insertObj[key] = escapedParam;
} }
else { // Faker value else { // Faker value
const parsedParams: {[key: string]: string | number | boolean | Date | Buffer} = {}; const parsedParams: Record<string, string | number | boolean | Date | Buffer> = {};
let fakeValue; let fakeValue;
if (params.locale) if (params.locale)
@ -437,12 +437,12 @@ export default (connections: {[key: string]: antares.Client}) => {
if (description) if (description)
query.select(`LEFT(${description}, 20) AS foreign_description`); query.select(`LEFT(${description}, 20) AS foreign_description`);
const results = await query.run<{[key: string]: string}>(); const results = await query.run<Record<string, string>>();
const parsedResults: {[key: string]: string}[] = []; const parsedResults: Record<string, string>[] = [];
for (const row of results.rows) { for (const row of results.rows) {
const remappedRow: {[key: string]: string} = {}; const remappedRow: Record<string, string> = {};
for (const key in row) for (const key in row)
remappedRow[key.toLowerCase()] = row[key];// Thanks Firebird -.- remappedRow[key.toLowerCase()] = row[key];// Thanks Firebird -.-

View File

@ -3,7 +3,7 @@ import { ipcMain } from 'electron';
import { validateSender } from '../libs/misc/validateSender'; import { validateSender } from '../libs/misc/validateSender';
export default (connections: {[key: string]: antares.Client}) => { export default (connections: Record<string, antares.Client>) => {
ipcMain.handle('get-trigger-informations', async (event, params) => { ipcMain.handle('get-trigger-informations', async (event, params) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };

View File

@ -3,7 +3,7 @@ import { ipcMain } from 'electron';
import { validateSender } from '../libs/misc/validateSender'; import { validateSender } from '../libs/misc/validateSender';
export default (connections: {[key: string]: antares.Client}) => { export default (connections: Record<string, antares.Client>) => {
ipcMain.handle('get-users', async (event, uid) => { ipcMain.handle('get-users', async (event, uid) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };

View File

@ -3,7 +3,7 @@ import { ipcMain } from 'electron';
import { validateSender } from '../libs/misc/validateSender'; import { validateSender } from '../libs/misc/validateSender';
export default (connections: {[key: string]: antares.Client}) => { export default (connections: Record<string, antares.Client>) => {
ipcMain.handle('get-view-informations', async (event, params) => { ipcMain.handle('get-view-informations', async (event, params) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
@ -51,4 +51,52 @@ export default (connections: {[key: string]: antares.Client}) => {
return { status: 'error', response: err.toString() }; return { status: 'error', response: err.toString() };
} }
}); });
ipcMain.handle('get-materialized-view-informations', async (event, params) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
try {
const result = await connections[params.uid].getMaterializedViewInformations(params);
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('drop-materialized-view', async (event, params) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
try {
await connections[params.uid].dropMaterializedView(params);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('alter-materialized-view', async (event, params) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
try {
await connections[params.uid].alterView(params);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('create-materialized-view', async (event, params) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
try {
await connections[params.uid].createMaterializedView(params);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
}; };

View File

@ -70,23 +70,21 @@ export class ShortcutRegister {
} }
private setLocalShortcuts () { private setLocalShortcuts () {
const isMenuVisible = process.platform === 'darwin';
const submenu = [];
for (const shortcut of this.shortcuts) { for (const shortcut of this.shortcuts) {
if (shortcut.os.includes(process.platform)) { if (shortcut.os.includes(process.platform)) {
for (const key of shortcut.keys) { for (const key of shortcut.keys) {
try { try {
this._menu.append(new MenuItem({ submenu.push({
label: '.', label: String(shortcut.event),
visible: false,
submenu: [{
label: String(key),
accelerator: key, accelerator: key,
visible: false, visible: isMenuVisible,
click: () => { click: () => {
this._mainWindow.webContents.send(shortcut.event); this._mainWindow.webContents.send(shortcut.event);
if (isDevelopment) console.log('LOCAL EVENT:', shortcut); if (isDevelopment) console.log('LOCAL EVENT:', shortcut);
} }
}] });
}));
} }
catch (error) { catch (error) {
if (isDevelopment) console.log(error); if (isDevelopment) console.log(error);
@ -96,6 +94,11 @@ export class ShortcutRegister {
} }
} }
} }
this._menu.append(new MenuItem({
label: 'Shortcut',
visible: isMenuVisible,
submenu
}));
} }
private setGlobalShortcuts () { private setGlobalShortcuts () {

View File

@ -136,7 +136,7 @@ export abstract class BaseClient {
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
insert (arr: {[key: string]: any}[]) { insert (arr: Record<string, any>[]) {
this._query.insert = [...this._query.insert, ...arr]; this._query.insert = [...this._query.insert, ...arr];
return this; return this;
} }
@ -234,6 +234,18 @@ export abstract class BaseClient {
throw new Error('Method "getVariables" not implemented'); throw new Error('Method "getVariables" not implemented');
} }
getMaterializedViewInformations (...args: any) {
throw new Error('Method "getMaterializedViewInformations" not implemented');
}
dropMaterializedView (...args: any) {
throw new Error('Method "dropMaterializedView" not implemented');
}
createMaterializedView (...args: any) {
throw new Error('Method "createMaterializedView" not implemented');
}
getEventInformations (...args: any) { getEventInformations (...args: any) {
throw new Error('Method "getEventInformations" not implemented'); throw new Error('Method "getEventInformations" not implemented');
} }

View File

@ -13,7 +13,7 @@ export class FirebirdSQLClient extends BaseClient {
protected _connection?: firebird.Database | firebird.ConnectionPool; protected _connection?: firebird.Database | firebird.ConnectionPool;
_params: firebird.Options; _params: firebird.Options;
private _types: {[key: number]: string} ={ private _types: Record<number, string> ={
452: 'CHAR', // Array of char 452: 'CHAR', // Array of char
448: 'VARCHAR', 448: 'VARCHAR',
500: 'SMALLINT', 500: 'SMALLINT',

View File

@ -12,10 +12,11 @@ export class MySQLClient extends BaseClient {
private _connectionsToCommit: Map<string, mysql.Connection | mysql.PoolConnection>; private _connectionsToCommit: Map<string, mysql.Connection | mysql.PoolConnection>;
private _keepaliveTimer: NodeJS.Timer; private _keepaliveTimer: NodeJS.Timer;
private _keepaliveMs: number; private _keepaliveMs: number;
private sqlMode?: string[];
_connection?: mysql.Connection | mysql.Pool; _connection?: mysql.Connection | mysql.Pool;
_params: mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean}; _params: mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean};
private types: {[key: number]: string} = { private types: Record<number, string> = {
0: 'DECIMAL', 0: 'DECIMAL',
1: 'TINYINT', 1: 'TINYINT',
2: 'SMALLINT', 2: 'SMALLINT',
@ -58,6 +59,10 @@ export class MySQLClient extends BaseClient {
this._keepaliveMs = 10*60*1000; this._keepaliveMs = 10*60*1000;
} }
private get isPool () {
return 'getConnection' in this._connection;
}
private _getType (field: mysql.FieldPacket & { columnType?: number; columnLength?: number }) { private _getType (field: mysql.FieldPacket & { columnType?: number; columnLength?: number }) {
let name = this.types[field.columnType]; let name = this.types[field.columnType];
let length = field.columnLength; let length = field.columnLength;
@ -181,9 +186,32 @@ export class MySQLClient extends BaseClient {
async connect () { async connect () {
if (!this._poolSize) if (!this._poolSize)
this._connection = await this.getConnection(); this._connection = await this.getSingleConnection();
else else
this._connection = await this.getConnectionPool(); this._connection = await this.getConnectionPool();
// ANSI_QUOTES check
const [response] = await this._connection.query<mysql.RowDataPacket[]>('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\'');
this.sqlMode = response[0]?.Value?.split(',');
const hasAnsiQuotes = this.sqlMode.includes('ANSI') || this.sqlMode.includes('ANSI_QUOTES');
if (hasAnsiQuotes)
await this._connection.query(`SET SESSION sql_mode = '${this.sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`);
if (this._params.readonly)
await this._connection.query('SET SESSION TRANSACTION READ ONLY');
if (this._poolSize) {
const hasAnsiQuotes = this.sqlMode.includes('ANSI') || this.sqlMode.includes('ANSI_QUOTES');
this._connection.on('connection', conn => {
if (this._params.readonly)
conn.query('SET SESSION TRANSACTION READ ONLY');
if (hasAnsiQuotes)
conn.query(`SET SESSION sql_mode = '${this.sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`);
});
}
} }
destroy () { destroy () {
@ -196,7 +224,7 @@ export class MySQLClient extends BaseClient {
} }
} }
async getConnection () { async getSingleConnection () {
const dbConfig = await this.getDbConfig(); const dbConfig = await this.getDbConfig();
const connection = await mysql.createConnection({ const connection = await mysql.createConnection({
...dbConfig, ...dbConfig,
@ -208,17 +236,6 @@ export class MySQLClient extends BaseClient {
} }
}); });
// ANSI_QUOTES check
const [response] = await connection.query<mysql.RowDataPacket[]>('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\'');
const sqlMode: string[] = response[0]?.Value?.split(',');
const hasAnsiQuotes = sqlMode.includes('ANSI') || sqlMode.includes('ANSI_QUOTES');
if (this._params.readonly)
await connection.query('SET SESSION TRANSACTION READ ONLY');
if (hasAnsiQuotes)
await connection.query(`SET SESSION sql_mode = '${sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`);
return connection; return connection;
} }
@ -227,6 +244,7 @@ export class MySQLClient extends BaseClient {
const connection = mysql.createPool({ const connection = mysql.createPool({
...dbConfig, ...dbConfig,
connectionLimit: this._poolSize, connectionLimit: this._poolSize,
enableKeepAlive: true,
typeCast: (field, next) => { typeCast: (field, next) => {
if (field.type === 'DATETIME') if (field.type === 'DATETIME')
return field.string(); return field.string();
@ -235,25 +253,6 @@ export class MySQLClient extends BaseClient {
} }
}); });
// ANSI_QUOTES check
const [res] = await connection.query<mysql.RowDataPacket[]>('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\'');
const sqlMode: string[] = res[0]?.Value?.split(',');
const hasAnsiQuotes = sqlMode.includes('ANSI') || sqlMode.includes('ANSI_QUOTES');
if (hasAnsiQuotes)
await connection.query(`SET SESSION sql_mode = '${sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`);
if (this._params.readonly)
await connection.query('SET SESSION TRANSACTION READ ONLY');
connection.on('connection', conn => {
if (this._params.readonly)
conn.query('SET SESSION TRANSACTION READ ONLY');
if (hasAnsiQuotes)
conn.query(`SET SESSION sql_mode = '${sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`);
});
this._keepaliveTimer = setInterval(async () => { this._keepaliveTimer = setInterval(async () => {
await this.keepAlive(); await this.keepAlive();
}, this._keepaliveMs); }, this._keepaliveMs);
@ -261,6 +260,43 @@ export class MySQLClient extends BaseClient {
return connection; return connection;
} }
async getConnection (args?: antares.QueryParams, retry?: boolean): Promise<mysql.Pool | mysql.PoolConnection | mysql.Connection> {
let connection;
try {
if (args && !args.autocommit && args.tabUid) { // autocommit OFF
if (this._connectionsToCommit.has(args.tabUid))
connection = this._connectionsToCommit.get(args.tabUid);
else {
connection = await this.getSingleConnection();
await connection.query('SET SESSION autocommit=0');
this._connectionsToCommit.set(args.tabUid, connection);
}
}
else// autocommit ON
connection = this.isPool ? await (this._connection as mysql.Pool).getConnection() : this._connection;
if (args && args.tabUid && this.isPool) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this._runningConnections.set(args.tabUid, (connection as any).connection.connectionId);
}
if (args && args.schema)
await connection.query(`USE \`${args.schema}\``);
return connection;
}
catch (error) {
if (error.code === 'ECONNRESET' && !retry) {
this.destroy();
await this.connect();
return this.getConnection(args, true);
}
else
throw new Error(error.message);
}
}
private async keepAlive () { private async keepAlive () {
try { try {
const connection = await (this._connection as mysql.Pool).getConnection(); const connection = await (this._connection as mysql.Pool).getConnection();
@ -582,7 +618,7 @@ export class MySQLClient extends BaseClient {
} }
}) })
.filter(Boolean) .filter(Boolean)
.reduce((acc: {[key: string]: { name: string; type: string; length: string; default: string}}, curr) => { .reduce((acc: Record<string, { name: string; type: string; length: string; default: string}>, curr) => {
acc[curr.name] = curr; acc[curr.name] = curr;
return acc; return acc;
}, {}); }, {});
@ -591,7 +627,7 @@ export class MySQLClient extends BaseClient {
return rows.map((field) => { return rows.map((field) => {
const numLengthMatch = field.COLUMN_TYPE.match(/int\(([^)]+)\)/); const numLengthMatch = field.COLUMN_TYPE.match(/int\(([^)]+)\)/);
const numLength = numLengthMatch ? +numLengthMatch.pop() : field.NUMERIC_PRECISION || null; const numLength = numLengthMatch ? +numLengthMatch.pop() : field.NUMERIC_PRECISION || null;
const enumValues = /(enum)/.test(field.COLUMN_TYPE) const enumValues = /(enum|set)/.test(field.COLUMN_TYPE)
? field.COLUMN_TYPE.match(/\(([^)]+)\)/)[0].slice(1, -1) ? field.COLUMN_TYPE.match(/\(([^)]+)\)/)[0].slice(1, -1)
: null; : null;
@ -1648,28 +1684,7 @@ export class MySQLClient extends BaseClient {
.map(q => q.trim()) .map(q => q.trim())
: [sql]; : [sql];
let connection: mysql.Connection | mysql.Pool | mysql.PoolConnection; const connection = await this.getConnection(args);
const isPool = 'getConnection' in this._connection;
if (!args.autocommit && args.tabUid) { // autocommit OFF
if (this._connectionsToCommit.has(args.tabUid))
connection = this._connectionsToCommit.get(args.tabUid);
else {
connection = await this.getConnection();
await connection.query('SET SESSION autocommit=0');
this._connectionsToCommit.set(args.tabUid, connection);
}
}
else// autocommit ON
connection = isPool ? await (this._connection as mysql.Pool).getConnection() : this._connection;
if (args.tabUid && isPool) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this._runningConnections.set(args.tabUid, (connection as any).connection.connectionId);
}
if (args.schema)
await connection.query(`USE \`${args.schema}\``);
for (const query of queries) { for (const query of queries) {
if (!query) continue; if (!query) continue;
@ -1729,7 +1744,7 @@ export class MySQLClient extends BaseClient {
}); });
} }
catch (err) { catch (err) {
if (isPool && args.autocommit) { if (this.isPool && args.autocommit) {
(connection as mysql.PoolConnection).release(); (connection as mysql.PoolConnection).release();
this._runningConnections.delete(args.tabUid); this._runningConnections.delete(args.tabUid);
} }
@ -1741,7 +1756,7 @@ export class MySQLClient extends BaseClient {
keysArr = keysArr ? [...keysArr, ...response] : response; keysArr = keysArr ? [...keysArr, ...response] : response;
} }
catch (err) { catch (err) {
if (isPool && args.autocommit) { if (this.isPool && args.autocommit) {
(connection as mysql.PoolConnection).release(); (connection as mysql.PoolConnection).release();
this._runningConnections.delete(args.tabUid); this._runningConnections.delete(args.tabUid);
} }
@ -1759,7 +1774,7 @@ export class MySQLClient extends BaseClient {
keys: keysArr keys: keysArr
}); });
}).catch((err) => { }).catch((err) => {
if (isPool && args.autocommit) { if (this.isPool && args.autocommit) {
(connection as mysql.PoolConnection).release(); (connection as mysql.PoolConnection).release();
this._runningConnections.delete(args.tabUid); this._runningConnections.delete(args.tabUid);
} }
@ -1776,7 +1791,7 @@ export class MySQLClient extends BaseClient {
}); });
} }
if (isPool && args.autocommit) { if (this.isPool && args.autocommit) {
(connection as mysql.PoolConnection).release(); (connection as mysql.PoolConnection).release();
this._runningConnections.delete(args.tabUid); this._runningConnections.delete(args.tabUid);
} }

View File

@ -88,8 +88,8 @@ export class PostgreSQLClient extends BaseClient {
private _keepaliveTimer: NodeJS.Timer; private _keepaliveTimer: NodeJS.Timer;
private _keepaliveMs: number; private _keepaliveMs: number;
protected _connection?: pg.Client | pg.Pool; protected _connection?: pg.Client | pg.Pool;
private types: {[key: string]: string} = {}; private types: Record<string, string> = {};
private _arrayTypes: {[key: string]: string} = { private _arrayTypes: Record<string, string> = {
_int2: 'SMALLINT', _int2: 'SMALLINT',
_int4: 'INTEGER', _int4: 'INTEGER',
_int8: 'BIGINT', _int8: 'BIGINT',
@ -210,6 +210,10 @@ export class PostgreSQLClient extends BaseClient {
if (this._params.readonly) if (this._params.readonly)
await connection.query('SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY'); await connection.query('SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY');
connection.on('error', err => { // Intercepts errors and converts to rejections
Promise.reject(err);
});
return connection; return connection;
} }
@ -232,6 +236,10 @@ export class PostgreSQLClient extends BaseClient {
await this.keepAlive(); await this.keepAlive();
}, this._keepaliveMs); }, this._keepaliveMs);
connection.on('error', err => { // Intercepts errors and converts to rejections
Promise.reject(err);
});
return connection; return connection;
} }
@ -327,6 +335,19 @@ export class PostgreSQLClient extends BaseClient {
ORDER BY table_name ORDER BY table_name
`); `);
let { rows: matViews } = await this.raw<antares.QueryResult<ShowTableResult>>(`
SELECT schemaname AS schema_name,
matviewname AS table_name,
matviewowner AS owner,
ispopulated AS is_populated,
definition,
'materializedview' AS table_type
FROM pg_matviews
WHERE schemaname = '${db.database}'
ORDER BY schema_name,
table_name;
`);
if (tables.length) { if (tables.length) {
tables = tables.map(table => { tables = tables.map(table => {
table.Db = db.database; table.Db = db.database;
@ -335,6 +356,14 @@ export class PostgreSQLClient extends BaseClient {
tablesArr.push(...tables); tablesArr.push(...tables);
} }
if (matViews.length) {
matViews = matViews.map(view => {
view.Db = db.database;
return view;
});
tablesArr.push(...matViews);
}
let { rows: triggers } = await this.raw<antares.QueryResult<ShowTriggersResult>>(` let { rows: triggers } = await this.raw<antares.QueryResult<ShowTriggersResult>>(`
SELECT SELECT
pg_class.relname AS table_name, pg_class.relname AS table_name,
@ -370,7 +399,11 @@ export class PostgreSQLClient extends BaseClient {
return { return {
name: table.table_name, name: table.table_name,
type: table.table_type === 'VIEW' ? 'view' : 'table', type: table.table_type === 'VIEW'
? 'view'
: table.table_type === 'materializedview'
? 'materializedview'
: 'table',
rows: table.reltuples, rows: table.reltuples,
size: tableSize, size: tableSize,
collation: table.Collation, collation: table.Collation,
@ -652,7 +685,7 @@ export class PostgreSQLClient extends BaseClient {
let createSql = ''; let createSql = '';
const sequences = []; const sequences = [];
const columnsSql = []; const columnsSql = [];
const arrayTypes: {[key: string]: string} = { const arrayTypes: Record<string, string> = {
_int2: 'smallint', _int2: 'smallint',
_int4: 'integer', _int4: 'integer',
_int8: 'bigint', _int8: 'bigint',
@ -1048,11 +1081,32 @@ export class PostgreSQLClient extends BaseClient {
})[0]; })[0];
} }
async getMaterializedViewInformations ({ schema, view }: { schema: string; view: string }) {
const sql = `SELECT "definition" FROM "pg_matviews" WHERE "matviewname"='${view}' AND "schemaname"='${schema}'`;
const results = await this.raw(sql);
return results.rows.map(row => {
return {
algorithm: '',
definer: '',
security: '',
updateOption: '',
sql: row.definition,
name: view
};
})[0];
}
async dropView (params: { schema: string; view: string }) { async dropView (params: { schema: string; view: string }) {
const sql = `DROP VIEW "${params.schema}"."${params.view}"`; const sql = `DROP VIEW "${params.schema}"."${params.view}"`;
return await this.raw(sql); return await this.raw(sql);
} }
async dropMaterializedView (params: { schema: string; view: string }) {
const sql = `DROP MATERIALIZED VIEW "${params.schema}"."${params.view}"`;
return await this.raw(sql);
}
async alterView ({ view }: { view: antares.AlterViewParams }) { async alterView ({ view }: { view: antares.AlterViewParams }) {
let sql = `CREATE OR REPLACE VIEW "${view.schema}"."${view.oldName}" AS ${view.sql}`; let sql = `CREATE OR REPLACE VIEW "${view.schema}"."${view.oldName}" AS ${view.sql}`;
@ -1062,11 +1116,25 @@ export class PostgreSQLClient extends BaseClient {
return await this.raw(sql); return await this.raw(sql);
} }
async alterMaterializedView ({ view }: { view: antares.AlterViewParams }) {
let sql = `CREATE OR REPLACE MATERIALIZED VIEW "${view.schema}"."${view.oldName}" AS ${view.sql}`;
if (view.name !== view.oldName)
sql += `; ALTER VIEW "${view.schema}"."${view.oldName}" RENAME TO "${view.name}"`;
return await this.raw(sql);
}
async createView (params: antares.CreateViewParams) { async createView (params: antares.CreateViewParams) {
const sql = `CREATE VIEW "${params.schema}"."${params.name}" AS ${params.sql}`; const sql = `CREATE VIEW "${params.schema}"."${params.name}" AS ${params.sql}`;
return await this.raw(sql); return await this.raw(sql);
} }
async createMaterializedView (params: antares.CreateViewParams) {
const sql = `CREATE MATERIALIZED VIEW "${params.schema}"."${params.name}" AS ${params.sql}`;
return await this.raw(sql);
}
async getTriggerInformations ({ schema, trigger }: { schema: string; trigger: string }) { async getTriggerInformations ({ schema, trigger }: { schema: string; trigger: string }) {
const [table, triggerName] = trigger.split('.'); const [table, triggerName] = trigger.split('.');

View File

@ -166,7 +166,7 @@ export class SQLiteClient extends BaseClient {
type: type.trim(), type: type.trim(),
schema: schema, schema: schema,
table: table, table: table,
numPrecision: [...NUMBER, ...FLOAT].includes(type) ? length : null, numLength: [...NUMBER, ...FLOAT].includes(type) ? length : null,
datePrecision: null, datePrecision: null,
charLength: ![...NUMBER, ...FLOAT].includes(type) ? length : null, charLength: ![...NUMBER, ...FLOAT].includes(type) ? length : null,
nullable: !field.notnull, nullable: !field.notnull,
@ -658,7 +658,7 @@ export class SQLiteClient extends BaseClient {
let queryAllResult: any[]; let queryAllResult: any[];
let affectedRows; let affectedRows;
let fields; let fields;
const detectedTypes: {[key: string]: string} = {}; const detectedTypes: Record<string, string> = {};
try { try {
const stmt = connection.prepare(query); const stmt = connection.prepare(query);

View File

@ -354,7 +354,7 @@ CREATE TABLE \`${view.Name}\`(
escapeAndQuote (val: string) { escapeAndQuote (val: string) {
// eslint-disable-next-line no-control-regex // eslint-disable-next-line no-control-regex
const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g; const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g;
const CHARS_ESCAPE_MAP: {[key: string]: string} = { const CHARS_ESCAPE_MAP: Record<string, string> = {
'\0': '\\0', '\0': '\\0',
'\b': '\\b', '\b': '\\b',
'\t': '\\t', '\t': '\\t',

View File

@ -59,7 +59,7 @@ SET row_security = off;\n\n\n`;
let createSql = ''; let createSql = '';
const sequences = []; const sequences = [];
const columnsSql = []; const columnsSql = [];
const arrayTypes: {[key: string]: string} = { const arrayTypes: Record<string, string> = {
_int2: 'smallint', _int2: 'smallint',
_int4: 'integer', _int4: 'integer',
_int8: 'bigint', _int8: 'bigint',
@ -440,7 +440,7 @@ SET row_security = off;\n\n\n`;
escapeAndQuote (val: string) { escapeAndQuote (val: string) {
// eslint-disable-next-line no-control-regex // eslint-disable-next-line no-control-regex
const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g; const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g;
const CHARS_ESCAPE_MAP: {[key: string]: string} = { const CHARS_ESCAPE_MAP: Record<string, string> = {
'\0': '\\0', '\0': '\\0',
'\b': '\\b', '\b': '\\b',
'\t': '\\t', '\t': '\\t',

View File

@ -127,8 +127,8 @@ app.on('ready', async () => {
if (isWindows) if (isWindows)
mainWindow.show(); mainWindow.show();
// if (isDevelopment) if (isDevelopment && !isWindows)// Because on Windows you can open devtools from title-bar
// mainWindow.webContents.openDevTools(); mainWindow.webContents.openDevTools();
process.on('uncaughtException', error => { process.on('uncaughtException', error => {
mainWindow.webContents.send('unhandled-exception', error); mainWindow.webContents.send('unhandled-exception', error);

View File

@ -56,8 +56,8 @@ const { t } = useI18n();
const props = defineProps({ const props = defineProps({
size: { size: {
type: String as PropType<'small' | 'medium' | '400' | 'large'>, type: String as PropType<'small' | 'medium' | '400' | 'large' | 'resize'>,
validator: (prop: string) => ['small', 'medium', '400', 'large'].includes(prop), validator: (prop: string) => ['small', 'medium', '400', 'large', 'resize'].includes(prop),
default: 'small' default: 'small'
}, },
hideFooter: { hideFooter: {
@ -88,6 +88,8 @@ const modalSizeClass = computed(() => {
return 'modal-sm'; return 'modal-sm';
if (props.size === '400') if (props.size === '400')
return 'modal-400'; return 'modal-400';
if (props.size === 'resize')
return 'modal-resize';
else if (props.size === 'large') else if (props.size === 'large')
return 'modal-lg'; return 'modal-lg';
else return ''; else return '';
@ -120,6 +122,12 @@ onBeforeUnmount(() => {
max-width: 400px; max-width: 400px;
} }
.modal-resize .modal-container {
max-width: 95vw;
max-height: 95vh;
width: auto;
}
.modal.modal-sm .modal-container { .modal.modal-sm .modal-container {
padding: 0; padding: 0;
} }

View File

@ -99,7 +99,7 @@ onMounted(() => {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background: $primary-color; background: var(--primary-color);
border-radius: 50%; border-radius: 50%;
box-shadow: 0 0 5px 1px darken($body-font-color-dark, 40%); box-shadow: 0 0 5px 1px darken($body-font-color-dark, 40%);
} }

View File

@ -4,7 +4,11 @@
:id="`editor-${id}`" :id="`editor-${id}`"
class="editor" class="editor"
:class="editorClass" :class="editorClass"
:style="{height: `${height}px`}" :style="{
height: `${height}px`,
width: width ? `${width}px` : null,
resize: resizable ? 'both' : 'none'
}"
/> />
</div> </div>
</template> </template>
@ -17,7 +21,7 @@ import 'ace-builds/webpack-resolver';
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { onMounted, watch } from 'vue'; import { PropType, onMounted, watch } from 'vue';
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
@ -25,10 +29,12 @@ const props = defineProps({
modelValue: String, modelValue: String,
mode: { type: String, default: 'text' }, mode: { type: String, default: 'text' },
editorClass: { type: String, default: '' }, editorClass: { type: String, default: '' },
resizable: { type: Boolean, default: false },
autoFocus: { type: Boolean, default: false }, autoFocus: { type: Boolean, default: false },
readOnly: { type: Boolean, default: false }, readOnly: { type: Boolean, default: false },
showLineNumbers: { type: Boolean, default: true }, showLineNumbers: { type: Boolean, default: true },
height: { type: Number, default: 200 } height: { type: Number, default: 200 },
width: { type: [Number, Boolean] as PropType<number|false>, default: false }
}); });
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
@ -134,6 +140,8 @@ onMounted(() => {
.editor-wrapper { .editor-wrapper {
.editor { .editor {
width: 100%; width: 100%;
height: 100%;
max-width: 90vw;
} }
} }
</style> </style>

View File

@ -113,7 +113,7 @@ const selectedGroup: Ref<string> = ref('manual');
const selectedMethod: Ref<string> = ref(''); const selectedMethod: Ref<string> = ref('');
const selectedValue: Ref<string> = ref(''); const selectedValue: Ref<string> = ref('');
const debounceTimeout: Ref<NodeJS.Timeout> = ref(null); const debounceTimeout: Ref<NodeJS.Timeout> = ref(null);
const methodParams: Ref<{[key: string]: string}> = ref({}); const methodParams: Ref<Record<string, string>> = ref({});
const enumArray: Ref<string[]> = ref(null); const enumArray: Ref<string[]> = ref(null);
const fakerGroups = computed(() => { const fakerGroups = computed(() => {

View File

@ -360,7 +360,7 @@ onBeforeUnmount(() => {
outline: none; outline: none;
&:focus { &:focus {
box-shadow: 0 0 3px 0.1rem rgba($primary-color, 80%); box-shadow: 0 0 3px 0.1rem rgba(var(--primary-color), 80%);
} }
&:hover { &:hover {

View File

@ -73,7 +73,7 @@ const props = defineProps({
const emit = defineEmits(['confirm', 'close']); const emit = defineEmits(['confirm', 'close']);
const firstInput: Ref<HTMLInputElement[]> = ref(null); const firstInput: Ref<HTMLInputElement[]> = ref(null);
const values: Ref<{[key: string]: string}> = ref({}); const values: Ref<Record<string, string>> = ref({});
const inParameters = computed(() => { const inParameters = computed(() => {
return props.localRoutine.parameters.filter(param => param.context === 'IN'); return props.localRoutine.parameters.filter(param => param.context === 'IN');

View File

@ -204,7 +204,7 @@ onBeforeUnmount(() => {
cursor: pointer; cursor: pointer;
&.selected { &.selected {
outline: 2px solid $primary-color; outline: 2px solid var(--primary-color);
border-radius: 8px; border-radius: 8px;
} }
} }

View File

@ -327,7 +327,7 @@ const tables: Ref<{
}[]> = ref([]); }[]> = ref([]);
const options: Ref<Partial<ExportOptions>> = ref({ const options: Ref<Partial<ExportOptions>> = ref({
schema: selectedSchema.value, schema: selectedSchema.value,
includes: {} as {[key: string]: boolean}, includes: {} as Record<string, boolean>,
outputFormat: 'sql' as 'sql' | 'sql.zip', outputFormat: 'sql' as 'sql' | 'sql.zip',
sqlInsertAfter: 250, sqlInsertAfter: 250,
sqlInsertDivider: 'bytes' as 'bytes' | 'rows' sqlInsertDivider: 'bytes' as 'bytes' | 'rows'

View File

@ -142,7 +142,7 @@ const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { trapRef } = useFocusTrap({ disableAutofocus: true }); const { trapRef } = useFocusTrap({ disableAutofocus: true });
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const localRow: Ref<{[key: string]: any}> = ref({}); const localRow: Ref<Record<string, any>> = ref({});
const fieldsToExclude = ref([]); const fieldsToExclude = ref([]);
const nInserts = ref(1); const nInserts = ref(1);
const isInserting = ref(false); const isInserting = ref(false);
@ -225,7 +225,7 @@ const insertRows = async () => {
delete rowToInsert[key]; delete rowToInsert[key];
}); });
const fieldTypes: {[key: string]: string} = {}; const fieldTypes: Record<string, string> = {};
props.fields.forEach(field => { props.fields.forEach(field => {
fieldTypes[field.name] = field.type; fieldTypes[field.name] = field.type;
}); });
@ -290,7 +290,7 @@ onMounted(() => {
} }
}, 50); }, 50);
const rowObj: {[key: string]: unknown} = {}; const rowObj: Record<string, unknown> = {};
if (!props.rowToDuplicate) { if (!props.rowToDuplicate) {
// Set default values // Set default values

View File

@ -115,7 +115,7 @@
<BaseIcon icon-name="mdiHistory" :size="48" /> <BaseIcon icon-name="mdiHistory" :size="48" />
</div> </div>
<p class="empty-title h5"> <p class="empty-title h5">
{{ t('database.thereIsNoQueriesYet') }} {{ t('database.thereAreNoQueriesYet') }}
</p> </p>
</div> </div>
</div> </div>
@ -287,7 +287,7 @@ onBeforeUnmount(() => {
max-width: 100%; max-width: 100%;
display: inline-block; display: inline-block;
font-size: 100%; font-size: 100%;
// color: $primary-color; // color: var(--primary-color);
opacity: 0.8; opacity: 0.8;
font-weight: 600; font-weight: 600;
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<ConfirmModal <ConfirmModal
size="medium" size="resize"
:disable-autofocus="true" :disable-autofocus="true"
:close-on-confirm="!!localNote.note.length" :close-on-confirm="!!localNote.note.length"
:confirm-text="t('general.save')" :confirm-text="t('general.save')"
@ -52,6 +52,10 @@
v-model="localNote.note" v-model="localNote.note"
:mode="editorMode" :mode="editorMode"
:show-line-numbers="false" :show-line-numbers="false"
:auto-focus="true"
:height="400"
:width="640"
:resizable="true"
/> />
</div> </div>
</form> </form>

View File

@ -1,6 +1,6 @@
<template> <template>
<ConfirmModal <ConfirmModal
size="medium" size="resize"
:disable-autofocus="true" :disable-autofocus="true"
:close-on-confirm="!!newNote.note.length" :close-on-confirm="!!newNote.note.length"
:confirm-text="t('general.save')" :confirm-text="t('general.save')"
@ -52,6 +52,10 @@
v-model="newNote.note" v-model="newNote.note"
:mode="editorMode" :mode="editorMode"
:show-line-numbers="false" :show-line-numbers="false"
:auto-focus="true"
:height="400"
:width="640"
:resizable="true"
/> />
</div> </div>
</form> </form>

View File

@ -67,7 +67,7 @@ const props = defineProps({
const emit = defineEmits(['select-row', 'contextmenu', 'stop-refresh']); const emit = defineEmits(['select-row', 'contextmenu', 'stop-refresh']);
const isInlineEditor: Ref<{[key: string]: boolean}> = ref({}); const isInlineEditor: Ref<Record<string, boolean>> = ref({});
const isInfoModal = ref(false); const isInfoModal = ref(false);
const editorMode = ref('sql'); const editorMode = ref('sql');

View File

@ -409,14 +409,6 @@
class="d-inline mr-1" class="d-inline mr-1"
:size="16" :size="16"
/> Mastodon</a> <a /> Mastodon</a> <a
class="c-hand"
:style="'align-items: center; display: inline-flex;'"
@click="openOutside('https://twitter.com/AntaresSQL')"
><BaseIcon
icon-name="mdiTwitter"
class="d-inline mr-1"
:size="16"
/> Twitter</a> <a
class="c-hand" class="c-hand"
:style="'align-items: center; display: inline-flex;'" :style="'align-items: center; display: inline-flex;'"
@click="openOutside('https://antares-sql.app/')" @click="openOutside('https://antares-sql.app/')"
@ -711,7 +703,7 @@ onBeforeUnmount(() => {
&.selected { &.selected {
img { img {
box-shadow: 0 0 0 3px $primary-color; box-shadow: 0 0 0 3px var(--primary-color);
} }
} }
@ -739,7 +731,7 @@ onBeforeUnmount(() => {
.badge-update::after { .badge-update::after {
bottom: initial; bottom: initial;
background: $primary-color; background: var(--primary-color);
} }
.form-label { .form-label {

View File

@ -409,7 +409,7 @@ defineExpose({ editor });
position: absolute; position: absolute;
left: 3px; left: 3px;
top: 2px; top: 2px;
color: $primary-color; color: var(--primary-color);
display: inline-block; display: inline-block;
font: normal normal normal 24px/1 "Material Design Icons", sans-serif; font: normal normal normal 24px/1 "Material Design Icons", sans-serif;
font-size: inherit; font-size: inherit;

View File

@ -14,7 +14,7 @@
<div class="tile-icon"> <div class="tile-icon">
<BaseIcon <BaseIcon
:icon-name="note.type === 'query' :icon-name="note.type === 'query'
? 'mdiStarOutline' ? 'mdiHeartOutline'
: note.type === 'todo' : note.type === 'todo'
? note.isArchived ? note.isArchived
? 'mdiCheckboxMarkedOutline' ? 'mdiCheckboxMarkedOutline'
@ -256,9 +256,9 @@ const highlightWord = (string: string) => {
code, pre { code, pre {
max-width: 100%; max-width: 100%;
width: 100%;
display: inline-block; display: inline-block;
font-size: 100%; font-size: 100%;
// color: $primary-color;
opacity: 0.8; opacity: 0.8;
font-weight: 600; font-weight: 600;
white-space: break-spaces; white-space: break-spaces;
@ -342,6 +342,8 @@ const highlightWord = (string: string) => {
<style lang="scss"> <style lang="scss">
.tile-paragraph { .tile-paragraph {
white-space: initial; white-space: initial;
word-break: break-word;
user-select: text;
h1, h2, h3, h4, h5, h6, p, li { h1, h2, h3, h4, h5, h6, p, li {
margin: 0; margin: 0;

View File

@ -15,6 +15,56 @@
:size="18" :size="18"
/> {{ t('connection.disconnect') }}</span> /> {{ t('connection.disconnect') }}</span>
</div> </div>
<div v-if="!contextConnection.isFolder" class="context-element">
<span class="d-flex">
<BaseIcon
class="text-light mt-1 mr-1"
icon-name="mdiFolderMove"
:size="18"
/> {{ t('general.moveTo') }}</span>
<BaseIcon
class="text-light ml-1"
icon-name="mdiChevronRight"
:size="18"
/>
<div class="context-submenu">
<div class="context-element" @click.stop="moveToFolder(null)">
<span class="d-flex">
<BaseIcon
class="text-light mt-1 mr-1"
icon-name="mdiFolderPlus"
:size="18"
/> {{ t('application.newFolder') }}</span>
</div>
<div
v-for="folder in filteredFolders"
:key="folder.uid"
class="context-element"
@click.stop="moveToFolder(folder.uid)"
>
<span class="d-flex">
<BaseIcon
class="text-light mt-1 mr-1"
icon-name="mdiFolder"
:size="18"
:style="`color: ${folder.color}!important`"
/> {{ folder.name || t('general.folder') }}</span>
</div>
<div
v-if="isInFolder"
class="context-element"
@click="outOfFolder"
>
<span class="d-flex">
<BaseIcon
class="text-light mt-1 mr-1"
icon-name="mdiFolderOff"
:size="18"
/> {{ t('application.outOfFolder') }}</span>
</div>
</div>
</div>
<div class="context-element" @click.stop="showAppearanceModal"> <div class="context-element" @click.stop="showAppearanceModal">
<span class="d-flex"> <span class="d-flex">
<BaseIcon <BaseIcon
@ -79,6 +129,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
import { storeToRefs } from 'pinia';
import { computed, Prop, ref } from 'vue'; import { computed, Prop, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@ -98,9 +149,14 @@ const {
getConnectionByUid, getConnectionByUid,
getConnectionName, getConnectionName,
addConnection, addConnection,
deleteConnection deleteConnection,
addFolder,
addToFolder,
removeFromFolders
} = connectionsStore; } = connectionsStore;
const { getFolders: folders } = storeToRefs(connectionsStore);
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { const {
@ -121,6 +177,8 @@ const isConnectionEdit = ref(false);
const connectionName = computed(() => props.contextConnection.name || getConnectionName(props.contextConnection.uid) || t('general.folder', 1)); const connectionName = computed(() => props.contextConnection.name || getConnectionName(props.contextConnection.uid) || t('general.folder', 1));
const isConnected = computed(() => getWorkspace(props.contextConnection.uid)?.connectionStatus === 'connected'); const isConnected = computed(() => getWorkspace(props.contextConnection.uid)?.connectionStatus === 'connected');
const filteredFolders = computed(() => folders.value.filter(f => !f.connections.includes(props.contextConnection.uid)));
const isInFolder = computed(() => folders.value.some(f => f.connections.includes(props.contextConnection.uid)));
const confirmDeleteConnection = () => { const confirmDeleteConnection = () => {
if (isConnected.value) if (isConnected.value)
@ -129,6 +187,27 @@ const confirmDeleteConnection = () => {
closeContext(); closeContext();
}; };
const moveToFolder = (folderUid?: string) => {
if (!folderUid) {
addFolder({
connections: [props.contextConnection.uid]
});
}
else {
addToFolder({
folder: folderUid,
connection: props.contextConnection.uid
});
}
closeContext();
};
const outOfFolder = () => {
removeFromFolders(props.contextConnection.uid);
closeContext();
};
const duplicateConnection = () => { const duplicateConnection = () => {
let connectionCopy = getConnectionByUid(props.contextConnection.uid); let connectionCopy = getConnectionByUid(props.contextConnection.uid);
connectionCopy = { connectionCopy = {

View File

@ -1,8 +1,8 @@
<template> <template>
<div <div
id="footer" id="footer"
:class="[lightColors.includes(footerColor) ? 'text-dark' : 'text-light']" :class="[lightColors.includes(accentColor) ? 'text-dark' : 'text-light']"
:style="`background-color: ${footerColor};`" :style="`background-color: ${accentColor};`"
> >
<div class="footer-left-elements"> <div class="footer-left-elements">
<ul class="footer-elements"> <ul class="footer-elements">
@ -85,10 +85,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { shell } from 'electron'; import { shell } from 'electron';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { computed, ComputedRef } from 'vue'; import { computed, ComputedRef, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import BaseIcon from '@/components/BaseIcon.vue'; import BaseIcon from '@/components/BaseIcon.vue';
import { hexToRGBA } from '@/libs/hexToRgba';
import { useApplicationStore } from '@/stores/application'; import { useApplicationStore } from '@/stores/application';
import { useConnectionsStore } from '@/stores/connections'; import { useConnectionsStore } from '@/stores/connections';
import { useConsoleStore } from '@/stores/console'; import { useConsoleStore } from '@/stores/console';
@ -117,7 +118,11 @@ const { getWorkspace } = workspacesStore;
const { getConnectionFolder, getConnectionByUid } = connectionsStore; const { getConnectionFolder, getConnectionByUid } = connectionsStore;
const workspace = computed(() => getWorkspace(workspaceUid.value)); const workspace = computed(() => getWorkspace(workspaceUid.value));
const footerColor = computed(() => getConnectionFolder(workspaceUid.value)?.color || '#E36929'); const accentColor = computed(() => {
if (getConnectionFolder(workspaceUid.value)?.color)
return getConnectionFolder(workspaceUid.value).color;
return '#E36929';
});
const connectionInfos = computed(() => getConnectionByUid(workspaceUid.value)); const connectionInfos = computed(() => getConnectionByUid(workspaceUid.value));
const version: ComputedRef<DatabaseInfos> = computed(() => { const version: ComputedRef<DatabaseInfos> = computed(() => {
return getWorkspace(workspaceUid.value) ? workspace.value.version : null; return getWorkspace(workspaceUid.value) ? workspace.value.version : null;
@ -129,7 +134,17 @@ const versionString = computed(() => {
return ''; return '';
}); });
watch(accentColor, () => {
changeAccentColor();
});
const openOutside = (link: string) => shell.openExternal(link); const openOutside = (link: string) => shell.openExternal(link);
const changeAccentColor = () => {
document.querySelector<HTMLBodyElement>(':root').style.setProperty('--primary-color', accentColor.value);
document.querySelector<HTMLBodyElement>(':root').style.setProperty('--primary-color-shadow', hexToRGBA(accentColor.value, 0.2));
};
changeAccentColor();
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -32,7 +32,7 @@ const { removeNotification } = notificationsStore;
const { notifications } = storeToRefs(notificationsStore); const { notifications } = storeToRefs(notificationsStore);
const { notificationsTimeout } = storeToRefs(settingsStore); const { notificationsTimeout } = storeToRefs(settingsStore);
const timeouts: Ref<{[key: string]: NodeJS.Timeout}> = ref({}); const timeouts: Ref<Record<string, NodeJS.Timeout>> = ref({});
const latestNotifications = computed(() => notifications.value.slice(0, 10)); const latestNotifications = computed(() => notifications.value.slice(0, 10));

View File

@ -186,7 +186,7 @@ if (!connectionsArr.value.length)
.settingbar-top-elements { .settingbar-top-elements {
overflow-x: hidden; overflow-x: hidden;
overflow-y: overlay; overflow-y: overlay;
// max-height: calc((100vh - 3.5rem) - #{$excluding-size}); width: 100%;
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 3px; width: 3px;
@ -233,6 +233,7 @@ if (!connectionsArr.value.length)
border-radius: 0; border-radius: 0;
padding: 0; padding: 0;
position: relative; position: relative;
border: none;
&:hover { &:hover {
opacity: 1; opacity: 1;

View File

@ -42,11 +42,11 @@
> >
<BaseIcon <BaseIcon
class="mt-1 mr-1" class="mt-1 mr-1"
icon-name="mdiCodeTags" :icon-name="element.filePath ? 'mdiFileCodeOutline' : 'mdiCodeTags'"
:size="18" :size="18"
/> />
<span> <span>
<span>{{ cutText(element.content || 'Query', 20, true) }} #{{ element.index }}</span> <span>{{ cutText(element.elementName || element.content || 'Query', 20, true) }} #{{ element.index }}</span>
<span <span
class="btn btn-clear" class="btn btn-clear"
:title="t('general.close')" :title="t('general.close')"
@ -63,7 +63,7 @@
> >
<BaseIcon <BaseIcon
class="mt-1 mr-1" class="mt-1 mr-1"
:icon-name="element.elementType === 'view' ? 'mdiTableEye' : 'mdiTable'" :icon-name="['view', 'materializedview'].includes(element.elementType) ? 'mdiTableEye' : 'mdiTable'"
:size="18" :size="18"
/> />
<span :title="`${t('general.data').toUpperCase()}: ${t(`database.${element.elementType}`)}`"> <span :title="`${t('general.data').toUpperCase()}: ${t(`database.${element.elementType}`)}`">
@ -80,7 +80,7 @@
<a v-else-if="element.type === 'data'" class="tab-link"> <a v-else-if="element.type === 'data'" class="tab-link">
<BaseIcon <BaseIcon
class="mt-1 mr-1" class="mt-1 mr-1"
:icon-name="element.elementType === 'view' ? 'mdiTableEye' : 'mdiTable'" :icon-name="['view', 'materializedview'].includes(element.elementType) ? 'mdiTableEye' : 'mdiTable'"
:size="18" :size="18"
/> />
<span :title="`${t('general.data').toUpperCase()}: ${t(`database.${element.elementType}`)}`"> <span :title="`${t('general.data').toUpperCase()}: ${t(`database.${element.elementType}`)}`">
@ -157,6 +157,27 @@
</span> </span>
</a> </a>
<a
v-else-if="element.type === 'materialized-view-props'"
class="tab-link"
:class="{'badge': element.isChanged}"
>
<BaseIcon
class="mr-1"
icon-name="mdiWrenchCog"
:size="18"
/>
<span :title="`${t('application.settings').toUpperCase()}: ${t(`database.view`)}`">
{{ cutText(element.elementName, 20, true) }}
<span
class="btn btn-clear"
:title="t('general.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
</span>
</a>
<a <a
v-else-if="element.type === 'new-view'" v-else-if="element.type === 'new-view'"
class="tab-link" class="tab-link"
@ -178,6 +199,27 @@
</span> </span>
</a> </a>
<a
v-else-if="element.type === 'new-materialized-view'"
class="tab-link"
:class="{'badge': element.isChanged}"
>
<BaseIcon
class="mr-1"
icon-name="mdiShapeSquarePlus"
:size="18"
/>
<span :title="`${t('general.new').toUpperCase()}: ${t(`database.${element.elementType}`)}`">
{{ t('database.newMaterializedView') }}
<span
class="btn btn-clear"
:title="t('general.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
</span>
</a>
<a <a
v-else-if="element.type === 'new-trigger'" v-else-if="element.type === 'new-trigger'"
class="tab-link" class="tab-link"
@ -446,6 +488,14 @@
:is-selected="selectedTab === tab.uid && isSelected" :is-selected="selectedTab === tab.uid && isSelected"
:schema="tab.schema" :schema="tab.schema"
/> />
<WorkspaceTabNewMaterializedView
v-else-if="tab.type === 'new-materialized-view'"
:tab-uid="tab.uid"
:tab="tab"
:connection="connection"
:is-selected="selectedTab === tab.uid && isSelected"
:schema="tab.schema"
/>
<WorkspaceTabPropsView <WorkspaceTabPropsView
v-else-if="tab.type === 'view-props'" v-else-if="tab.type === 'view-props'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
@ -454,6 +504,14 @@
:view="tab.elementName" :view="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
<WorkspaceTabPropsMaterializedView
v-else-if="tab.type === 'materialized-view-props'"
:tab-uid="tab.uid"
:is-selected="selectedTab === tab.uid && isSelected"
:connection="connection"
:view="tab.elementName"
:schema="tab.schema"
/>
<WorkspaceTabNewTrigger <WorkspaceTabNewTrigger
v-else-if="tab.type === 'new-trigger'" v-else-if="tab.type === 'new-trigger'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
@ -596,6 +654,9 @@ import Connection from '@/ipc-api/Connection';
import { useConsoleStore } from '@/stores/console'; import { useConsoleStore } from '@/stores/console';
import { useWorkspacesStore, WorkspaceTab } from '@/stores/workspaces'; import { useWorkspacesStore, WorkspaceTab } from '@/stores/workspaces';
import WorkspaceTabNewMaterializedView from './WorkspaceTabNewMaterializedView.vue';
import WorkspaceTabPropsMaterializedView from './WorkspaceTabPropsMaterializedView.vue';
const { t } = useI18n(); const { t } = useI18n();
const { cutText } = useFilters(); const { cutText } = useFilters();
@ -638,7 +699,6 @@ const draggableTabs = computed<WorkspaceTab[]>({
get () { get () {
if (workspace.value.customizations.database) if (workspace.value.customizations.database)
return workspace.value.tabs.filter(tab => tab.type === 'query' || tab.database === workspace.value.database); return workspace.value.tabs.filter(tab => tab.type === 'query' || tab.database === workspace.value.database);
else else
return workspace.value.tabs; return workspace.value.tabs;
}, },
@ -689,13 +749,14 @@ const openAsPermanentTab = (tab: WorkspaceTab) => {
const permanentTabs = { const permanentTabs = {
table: 'data', table: 'data',
view: 'data', view: 'data',
materializedView: 'data',
trigger: 'trigger-props', trigger: 'trigger-props',
triggerFunction: 'trigger-function-props', triggerFunction: 'trigger-function-props',
function: 'function-props', function: 'function-props',
routine: 'routine-props', routine: 'routine-props',
procedure: 'routine-props', procedure: 'routine-props',
scheduler: 'scheduler-props' scheduler: 'scheduler-props'
} as {[key: string]: string}; } as Record<string, string>;
newTab({ newTab({
uid: props.connection.uid, uid: props.connection.uid,

View File

@ -163,22 +163,30 @@
> >
</div> </div>
</div> </div>
<div v-if="clientCustomizations.readOnlyMode" class="form-group columns"> <div v-if="clientCustomizations.readOnlyMode" class="form-group columns mb-0">
<div class="column col-5 col-sm-12" /> <div class="column col-5 col-sm-12" />
<div class="column col-7 col-sm-12"> <div class="column col-7 col-sm-12">
<label class="form-checkbox form-inline"> <label class="form-checkbox form-inline my-0">
<input v-model="connection.readonly" type="checkbox"><i class="form-icon" /> {{ t('connection.readOnlyMode') }} <input v-model="connection.readonly" type="checkbox"><i class="form-icon" /> {{ t('connection.readOnlyMode') }}
</label> </label>
</div> </div>
</div> </div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns"> <div v-if="!clientCustomizations.fileConnection" class="form-group columns mb-0">
<div class="column col-5 col-sm-12" /> <div class="column col-5 col-sm-12" />
<div class="column col-7 col-sm-12"> <div class="column col-7 col-sm-12">
<label class="form-checkbox form-inline"> <label class="form-checkbox form-inline my-0">
<input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ t('connection.askCredentials') }} <input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ t('connection.askCredentials') }}
</label> </label>
</div> </div>
</div> </div>
<div v-if="clientCustomizations.singleConnectionMode" class="form-group columns mb-0">
<div class="column col-5 col-sm-12" />
<div class="column col-7 col-sm-12">
<label class="form-checkbox form-inline my-0">
<input v-model="connection.singleConnectionMode" type="checkbox"><i class="form-icon" /> {{ t('connection.singleConnection') }}
</label>
</div>
</div>
</fieldset> </fieldset>
</form> </form>
</div> </div>
@ -572,11 +580,11 @@ const pathSelection = (event: Event & {target: {files: {path: string}[]}}, name:
const { files } = event.target; const { files } = event.target;
if (!files.length) return; if (!files.length) return;
(connection.value as unknown as {[key: string]: string})[name] = files[0].path as string; (connection.value as unknown as Record<string, string>)[name] = files[0].path as string;
}; };
const pathClear = (name: keyof ConnectionParams) => { const pathClear = (name: keyof ConnectionParams) => {
(connection.value as unknown as {[key: string]: string})[name] = ''; (connection.value as unknown as Record<string, string>)[name] = '';
}; };
setDefaults(); setDefaults();

View File

@ -165,22 +165,30 @@
> >
</div> </div>
</div> </div>
<div v-if="clientCustomizations.readOnlyMode" class="form-group columns"> <div v-if="clientCustomizations.readOnlyMode" class="form-group columns mb-0">
<div class="column col-5 col-sm-12" /> <div class="column col-5 col-sm-12" />
<div class="column col-7 col-sm-12"> <div class="column col-7 col-sm-12">
<label class="form-checkbox form-inline"> <label class="form-checkbox form-inline my-0">
<input v-model="localConnection.readonly" type="checkbox"><i class="form-icon" /> {{ t('connection.readOnlyMode') }} <input v-model="localConnection.readonly" type="checkbox"><i class="form-icon" /> {{ t('connection.readOnlyMode') }}
</label> </label>
</div> </div>
</div> </div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns"> <div v-if="!clientCustomizations.fileConnection" class="form-group columns mb-0">
<div class="column col-5 col-sm-12" /> <div class="column col-5 col-sm-12" />
<div class="column col-7 col-sm-12"> <div class="column col-7 col-sm-12">
<label class="form-checkbox form-inline"> <label class="form-checkbox form-inline my-0">
<input v-model="localConnection.ask" type="checkbox"><i class="form-icon" /> {{ t('connection.askCredentials') }} <input v-model="localConnection.ask" type="checkbox"><i class="form-icon" /> {{ t('connection.askCredentials') }}
</label> </label>
</div> </div>
</div> </div>
<div v-if="clientCustomizations.singleConnectionMode" class="form-group columns mb-0">
<div class="column col-5 col-sm-12" />
<div class="column col-7 col-sm-12">
<label class="form-checkbox form-inline my-0">
<input v-model="localConnection.singleConnectionMode" type="checkbox"><i class="form-icon" /> {{ t('connection.singleConnection') }}
</label>
</div>
</div>
</fieldset> </fieldset>
</form> </form>
</div> </div>
@ -569,11 +577,11 @@ const pathSelection = (event: Event & {target: {files: {path: string}[]}}, name:
const { files } = event.target; const { files } = event.target;
if (!files.length) return; if (!files.length) return;
(localConnection.value as unknown as {[key: string]: string})[name] = files[0].path; (localConnection.value as unknown as Record<string, string>)[name] = files[0].path;
}; };
const pathClear = (name: keyof ConnectionParams) => { const pathClear = (name: keyof ConnectionParams) => {
(localConnection.value as unknown as {[key: string]: string})[name] = ''; (localConnection.value as unknown as Record<string, string>)[name] = '';
}; };
localConnection.value = JSON.parse(JSON.stringify(props.connection)); localConnection.value = JSON.parse(JSON.stringify(props.connection));

View File

@ -19,6 +19,8 @@
v-model="selectedDatabase" v-model="selectedDatabase"
:options="databases" :options="databases"
class="form-select select-sm text-bold my-0" class="form-select select-sm text-bold my-0"
@keypress.stop=""
@keydown.stop=""
/> />
</div> </div>
<span v-else class="workspace-explorebar-title">{{ connectionName }}</span> <span v-else class="workspace-explorebar-title">{{ connectionName }}</span>
@ -34,7 +36,6 @@
</div> </div>
<div :title="t('general.refresh')"> <div :title="t('general.refresh')">
<BaseIcon <BaseIcon
v-if="customizations.schemas"
icon-name="mdiRefresh" icon-name="mdiRefresh"
:size="18" :size="18"
class="c-hand mr-2" class="c-hand mr-2"
@ -110,6 +111,7 @@
@close-context="closeDatabaseContext" @close-context="closeDatabaseContext"
@open-create-table-tab="openCreateElementTab('table')" @open-create-table-tab="openCreateElementTab('table')"
@open-create-view-tab="openCreateElementTab('view')" @open-create-view-tab="openCreateElementTab('view')"
@open-create-materialized-view-tab="openCreateElementTab('materialized-view')"
@open-create-trigger-tab="openCreateElementTab('trigger')" @open-create-trigger-tab="openCreateElementTab('trigger')"
@open-create-routine-tab="openCreateElementTab('routine')" @open-create-routine-tab="openCreateElementTab('routine')"
@open-create-function-tab="openCreateElementTab('function')" @open-create-function-tab="openCreateElementTab('function')"
@ -140,10 +142,12 @@
:selected-misc="selectedMisc" :selected-misc="selectedMisc"
:selected-schema="selectedSchema" :selected-schema="selectedSchema"
:context-event="miscContextEvent" :context-event="miscContextEvent"
@open-create-view-tab="openCreateElementTab('view')"
@open-create-materializedview-tab="openCreateElementTab('materialized-view')"
@open-create-trigger-tab="openCreateElementTab('trigger')" @open-create-trigger-tab="openCreateElementTab('trigger')"
@open-create-trigger-function-tab="openCreateElementTab('trigger-function')"
@open-create-routine-tab="openCreateElementTab('routine')" @open-create-routine-tab="openCreateElementTab('routine')"
@open-create-function-tab="openCreateElementTab('function')" @open-create-function-tab="openCreateElementTab('function')"
@open-create-trigger-function-tab="openCreateElementTab('trigger-function')"
@open-create-scheduler-tab="openCreateElementTab('scheduler')" @open-create-scheduler-tab="openCreateElementTab('scheduler')"
@close-context="closeMiscFolderContext" @close-context="closeMiscFolderContext"
@reload="refresh" @reload="refresh"
@ -502,7 +506,7 @@ const toggleSearchMethod = () => {
transition: background 0.2s; transition: background 0.2s;
&:hover { &:hover {
background: rgba($primary-color, 50%); background: var(--primary-color-dark);
} }
} }

View File

@ -3,6 +3,30 @@
:context-event="props.contextEvent" :context-event="props.contextEvent"
@close-context="closeContext" @close-context="closeContext"
> >
<div
v-if="props.selectedMisc === 'view'"
class="context-element"
@click="emit('open-create-view-tab')"
>
<span class="d-flex">
<BaseIcon
class="text-light mt-1 mr-1"
icon-name="mdiTableCog"
:size="18"
/> {{ t('database.createNewView') }}</span>
</div>
<div
v-if="props.selectedMisc === 'materializedview'"
class="context-element"
@click="emit('open-create-materializedview-tab')"
>
<span class="d-flex">
<BaseIcon
class="text-light mt-1 mr-1"
icon-name="mdiTableCog"
:size="18"
/> {{ t('database.createNewMaterializedView') }}</span>
</div>
<div <div
v-if="props.selectedMisc === 'trigger'" v-if="props.selectedMisc === 'trigger'"
class="context-element" class="context-element"
@ -81,6 +105,8 @@ const props = defineProps({
}); });
const emit = defineEmits([ const emit = defineEmits([
'open-create-view-tab',
'open-create-materializedview-tab',
'open-create-trigger-tab', 'open-create-trigger-tab',
'open-create-routine-tab', 'open-create-routine-tab',
'open-create-function-tab', 'open-create-function-tab',

View File

@ -67,6 +67,104 @@
</ul> </ul>
</div> </div>
<div v-if="filteredViews.length" class="database-misc">
<details class="accordion">
<summary
class="accordion-header misc-name"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.trigger}"
@contextmenu.prevent="showMiscFolderContext($event, 'view')"
>
<BaseIcon
class="misc-icon mr-1"
icon-name="mdiFolderEye"
:size="18"
/>
<BaseIcon
class="misc-icon open-folder mr-1"
icon-name="mdiFolderOpen"
:size="18"
/>
{{ t('database.view', 2) }}
</summary>
<div class="accordion-body">
<div>
<ul class="menu menu-nav pt-0">
<li
v-for="view of filteredViews"
:key="view.name"
class="menu-item"
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.view === view.name}"
@mousedown.left="selectTable({schema: database.name, table: view})"
@dblclick="openDataTab({schema: database.name, table: view})"
@contextmenu.prevent="showTableContext($event, view)"
>
<a class="table-name">
<div v-if="checkLoadingStatus(view.name, 'table')" class="icon loading mr-1" />
<BaseIcon
v-else
class="table-icon mr-1"
icon-name="mdiTableEye"
:size="18"
:style="`min-width: 18px`"
/>
<span v-html="highlightWord(view.name)" />
</a>
</li>
</ul>
</div>
</div>
</details>
</div>
<div v-if="filteredMatViews.length && customizations.materializedViews" class="database-misc">
<details class="accordion">
<summary
class="accordion-header misc-name"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.trigger}"
@contextmenu.prevent="showMiscFolderContext($event, 'materializedview')"
>
<BaseIcon
class="misc-icon mr-1"
icon-name="mdiFolderEye"
:size="18"
/>
<BaseIcon
class="misc-icon open-folder mr-1"
icon-name="mdiFolderOpen"
:size="18"
/>
{{ t('database.materializedview', 2) }}
</summary>
<div class="accordion-body">
<div>
<ul class="menu menu-nav pt-0">
<li
v-for="view of filteredMatViews"
:key="view.name"
class="menu-item"
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.view === view.name}"
@mousedown.left="selectTable({schema: database.name, table: view})"
@dblclick="openDataTab({schema: database.name, table: view})"
@contextmenu.prevent="showTableContext($event, view)"
>
<a class="table-name">
<div v-if="checkLoadingStatus(view.name, 'table')" class="icon loading mr-1" />
<BaseIcon
v-else
class="table-icon mr-1"
icon-name="mdiTableEye"
:size="18"
:style="`min-width: 18px`"
/>
<span v-html="highlightWord(view.name)" />
</a>
</li>
</ul>
</div>
</div>
</details>
</div>
<div v-if="filteredTriggers.length && customizations.triggers" class="database-misc"> <div v-if="filteredTriggers.length && customizations.triggers" class="database-misc">
<details class="accordion"> <details class="accordion">
<summary <summary
@ -380,11 +478,25 @@ const searchTerm = computed(() => {
const filteredTables = computed(() => { const filteredTables = computed(() => {
if (props.searchMethod === 'elements') if (props.searchMethod === 'elements')
return props.database.tables.filter(table => table.name.search(searchTerm.value) >= 0); return props.database.tables.filter(table => table.name.search(searchTerm.value) >= 0 && table.type === 'table');
else else
return props.database.tables; return props.database.tables;
}); });
const filteredViews = computed(() => {
if (props.searchMethod === 'elements')
return props.database.tables.filter(table => table.name.search(searchTerm.value) >= 0 && table.type === 'view');
else
return props.database.tables.filter(table => table.type === 'view');
});
const filteredMatViews = computed(() => {
if (props.searchMethod === 'elements')
return props.database.tables.filter(table => table.name.search(searchTerm.value) >= 0 && table.type === 'materializedview');
else
return props.database.tables.filter(table => table.type === 'materializedview');
});
const filteredTriggers = computed(() => { const filteredTriggers = computed(() => {
if (props.searchMethod === 'elements') if (props.searchMethod === 'elements')
return props.database.triggers.filter(trigger => trigger.name.search(searchTerm.value) >= 0); return props.database.triggers.filter(trigger => trigger.name.search(searchTerm.value) >= 0);
@ -513,7 +625,13 @@ const selectMisc = ({ schema, misc, type }: { schema: string; misc: { name: stri
}; };
const openDataTab = ({ schema, table }: { schema: string; table: TableInfos }) => { const openDataTab = ({ schema, table }: { schema: string; table: TableInfos }) => {
newTab({ uid: props.connection.uid, elementName: table.name, schema: props.database.name, type: 'data', elementType: table.type }); newTab({
uid: props.connection.uid,
elementName: table.name,
schema: props.database.name,
type: 'data',
elementType: table.type
});
setBreadcrumbs({ schema, [table.type]: table.name }); setBreadcrumbs({ schema, [table.type]: table.name });
}; };

View File

@ -40,6 +40,18 @@
:size="18" :size="18"
/> {{ t('database.view') }}</span> /> {{ t('database.view') }}</span>
</div> </div>
<div
v-if="workspace.customizations.materializedViewAdd"
class="context-element"
@click="openCreateMaterializedViewTab"
>
<span class="d-flex">
<BaseIcon
class="text-light mt-1 mr-1"
icon-name="mdiTableEye"
:size="18"
/> {{ t('database.materializedview') }}</span>
</div>
<div <div
v-if="workspace.customizations.triggerAdd" v-if="workspace.customizations.triggerAdd"
class="context-element" class="context-element"
@ -221,6 +233,7 @@ const props = defineProps({
const emit = defineEmits([ const emit = defineEmits([
'open-create-table-tab', 'open-create-table-tab',
'open-create-view-tab', 'open-create-view-tab',
'open-create-materialized-view-tab',
'open-create-trigger-tab', 'open-create-trigger-tab',
'open-create-routine-tab', 'open-create-routine-tab',
'open-create-function-tab', 'open-create-function-tab',
@ -257,6 +270,10 @@ const openCreateViewTab = () => {
emit('open-create-view-tab'); emit('open-create-view-tab');
}; };
const openCreateMaterializedViewTab = () => {
emit('open-create-materialized-view-tab');
};
const openCreateTriggerTab = () => { const openCreateTriggerTab = () => {
emit('open-create-trigger-tab'); emit('open-create-trigger-tab');
}; };

View File

@ -47,6 +47,18 @@
:size="18" :size="18"
/> {{ t('application.settings') }}</span> /> {{ t('application.settings') }}</span>
</div> </div>
<div
v-if="selectedTable && selectedTable.type === 'materializedview' && customizations.materializedViewSettings"
class="context-element"
@click="openMaterializedViewSettingTab"
>
<span class="d-flex">
<BaseIcon
class="text-light mt-1 mr-1"
icon-name="mdiWrenchCog"
:size="18"
/> {{ t('application.settings') }}</span>
</div>
<div <div
v-if="selectedTable && selectedTable.type === 'table' && customizations.tableDuplicate" v-if="selectedTable && selectedTable.type === 'table' && customizations.tableDuplicate"
class="context-element" class="context-element"
@ -238,6 +250,23 @@ const openViewSettingTab = () => {
closeContext(); closeContext();
}; };
const openMaterializedViewSettingTab = () => {
newTab({
uid: selectedWorkspace.value,
elementType: 'table',
elementName: props.selectedTable.name,
schema: props.selectedSchema,
type: 'materialized-view-props'
});
changeBreadcrumbs({
schema: props.selectedSchema,
view: props.selectedTable.name
});
closeContext();
};
const duplicateTable = () => { const duplicateTable = () => {
emit('duplicate-table', { schema: props.selectedSchema, table: props.selectedTable }); emit('duplicate-table', { schema: props.selectedSchema, table: props.selectedTable });
}; };

View File

@ -145,7 +145,7 @@ onMounted(() => {
transition: background 0.2s; transition: background 0.2s;
&:hover { &:hover {
background: rgba($primary-color, 50%); background: var(--primary-color-dark);
} }
} }

View File

@ -0,0 +1,293 @@
<template>
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
<div class="workspace-query-runner column col-12">
<div class="workspace-query-runner-footer">
<div class="workspace-query-buttons">
<button
class="btn btn-primary btn-sm"
:disabled="!isChanged"
:class="{'loading':isSaving}"
@click="saveChanges"
>
<BaseIcon
class="mr-1"
icon-name="mdiContentSave"
:size="24"
/>
<span>{{ t('general.save') }}</span>
</button>
<button
:disabled="!isChanged"
class="btn btn-link btn-sm mr-0"
:title="t('database.clearChanges')"
@click="clearChanges"
>
<BaseIcon
class="mr-1"
icon-name="mdiDeleteSweep"
:size="24"
/>
<span>{{ t('general.clear') }}</span>
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="t('database.schema')">
<BaseIcon
class="mt-1 mr-1"
icon-name="mdiDatabase"
:size="18"
/><b>{{ schema }}</b>
</div>
</div>
</div>
</div>
<div class="container">
<div class="columns">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ t('general.name') }}</label>
<input
ref="firstInput"
v-model="localView.name"
class="form-input"
type="text"
>
</div>
</div>
<div class="column col-auto">
<div v-if="workspace.customizations.definer" class="form-group">
<label class="form-label">{{ t('database.definer') }}</label>
<BaseSelect
v-model="localView.definer"
:options="users"
:option-label="(user: any) => user.value === '' ? t('database.currentUser') : `${user.name}@${user.host}`"
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select"
/>
</div>
</div>
<div class="column col-auto mr-2">
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
<label class="form-label">{{ t('database.sqlSecurity') }}</label>
<BaseSelect
v-model="localView.security"
:options="['DEFINER', 'INVOKER']"
class="form-select"
/>
</div>
</div>
<div class="column col-auto mr-2">
<div v-if="workspace.customizations.viewAlgorithm" class="form-group">
<label class="form-label">{{ t('database.algorithm') }}</label>
<BaseSelect
v-model="localView.algorithm"
:options="['UNDEFINED', 'MERGE', 'TEMPTABLE']"
class="form-select"
/>
</div>
</div>
<div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2">
<div class="form-group">
<label class="form-label">{{ t('database.updateOption') }}</label>
<BaseSelect
v-model="localView.updateOption"
:option-track-by="(user: any) => user.value"
:options="[{label: 'None', value: ''}, {label: 'CASCADED', value: 'CASCADED'}, {label: 'LOCAL', value: 'LOCAL'}]"
class="form-select"
/>
</div>
</div>
</div>
</div>
<div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ t('database.selectStatement') }}</label>
<QueryEditor
v-show="isSelected"
ref="queryEditor"
v-model="localView.sql"
:workspace="workspace"
:schema="schema"
:height="editorHeight"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { Ace } from 'ace-builds';
import { ipcRenderer } from 'electron';
import { storeToRefs } from 'pinia';
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import BaseIcon from '@/components/BaseIcon.vue';
import BaseLoader from '@/components/BaseLoader.vue';
import BaseSelect from '@/components/BaseSelect.vue';
import QueryEditor from '@/components/QueryEditor.vue';
import Views from '@/ipc-api/Views';
import { useConsoleStore } from '@/stores/console';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
const { t } = useI18n();
const props = defineProps({
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
});
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { consoleHeight } = storeToRefs(useConsoleStore());
const {
getWorkspace,
refreshStructure,
setUnsavedChanges,
changeBreadcrumbs,
newTab,
removeTab
} = workspacesStore;
const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
const firstInput: Ref<HTMLInputElement> = ref(null);
const isLoading = ref(false);
const isSaving = ref(false);
const originalView = ref(null);
const localView = ref(null);
const editorHeight = ref(300);
const workspace = computed(() => getWorkspace(props.connection.uid));
const isChanged = computed(() => JSON.stringify(originalView.value) !== JSON.stringify(localView.value));
const isDefinerInUsers = computed(() => originalView.value ? workspace.value.users.some(user => originalView.value.definer === `\`${user.name}\`@\`${user.host}\``) : true);
const users = computed(() => {
const users = [{ value: '' }, ...workspace.value.users];
if (!isDefinerInUsers.value) {
const [name, host] = originalView.value.definer.replaceAll('`', '').split('@');
users.unshift({ name, host });
}
return users;
});
const saveChanges = async () => {
if (isSaving.value) return;
isSaving.value = true;
const params = {
uid: props.connection.uid,
schema: props.schema,
...localView.value
};
try {
const { status, response } = await Views.createMaterializedView(params);
if (status === 'success') {
await refreshStructure(props.connection.uid);
newTab({
uid: props.connection.uid,
schema: props.schema,
elementName: localView.value.name,
elementType: 'materializedview',
type: 'materialized-view-props'
});
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
changeBreadcrumbs({ schema: props.schema, view: localView.value.name });
}
else
addNotification({ status: 'error', message: response });
}
catch (err) {
addNotification({ status: 'error', message: err.stack });
}
isSaving.value = false;
};
const clearChanges = () => {
localView.value = JSON.parse(JSON.stringify(originalView.value));
queryEditor.value.editor.session.setValue(localView.value.sql);
};
const resizeQueryEditor = () => {
if (queryEditor.value) {
let sizeToSubtract = 0;
const footer = document.getElementById('footer');
if (footer) sizeToSubtract += footer.offsetHeight;
sizeToSubtract += consoleHeight.value;
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - sizeToSubtract;
editorHeight.value = size;
queryEditor.value.editor.resize();
}
};
const saveContentListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
if (props.isSelected && !hasModalOpen && isChanged.value)
saveChanges();
};
watch(() => props.isSelected, (val) => {
if (val) {
changeBreadcrumbs({ schema: props.schema, view: localView.value.name });
setTimeout(() => {
resizeQueryEditor();
}, 50);
}
});
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
});
watch(consoleHeight, () => {
resizeQueryEditor();
});
originalView.value = {
algorithm: 'UNDEFINED',
definer: '',
security: 'DEFINER',
updateOption: '',
sql: '',
name: ''
};
localView.value = JSON.parse(JSON.stringify(originalView.value));
setTimeout(() => {
resizeQueryEditor();
}, 50);
onMounted(() => {
if (props.isSelected)
changeBreadcrumbs({ schema: props.schema });
ipcRenderer.on('save-content', saveContentListener);
setTimeout(() => {
firstInput.value.focus();
}, 100);
window.addEventListener('resize', resizeQueryEditor);
});
onUnmounted(() => {
window.removeEventListener('resize', resizeQueryEditor);
});
onBeforeUnmount(() => {
ipcRenderer.removeListener('save-content', saveContentListener);
});
</script>

View File

@ -0,0 +1,316 @@
<template>
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
<div class="workspace-query-runner column col-12">
<div class="workspace-query-runner-footer">
<div class="workspace-query-buttons">
<button
class="btn btn-primary btn-sm"
:disabled="!isChanged"
:class="{'loading':isSaving}"
@click="saveChanges"
>
<BaseIcon
class="mr-1"
icon-name="mdiContentSave"
:size="24"
/>
<span>{{ t('general.save') }}</span>
</button>
<button
:disabled="!isChanged"
class="btn btn-link btn-sm mr-0"
:title="t('database.clearChanges')"
@click="clearChanges"
>
<BaseIcon
class="mr-1"
icon-name="mdiDeleteSweep"
:size="24"
/>
<span>{{ t('general.clear') }}</span>
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="t('database.schema')">
<BaseIcon
class="mt-1 mr-1"
icon-name="mdiDatabase"
:size="18"
/><b>{{ schema }}</b>
</div>
</div>
</div>
</div>
<div class="container">
<div class="columns">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ t('general.name') }}</label>
<input
v-model="localView.name"
class="form-input"
type="text"
>
</div>
</div>
<div class="column col-auto">
<div v-if="workspace.customizations.definer" class="form-group">
<label class="form-label">{{ t('database.definer') }}</label>
<BaseSelect
v-model="localView.definer"
:options="users"
:option-label="(user: any) => user.value === '' ? t('database.currentUser') : `${user.name}@${user.host}`"
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select"
/>
</div>
</div>
<div class="column col-auto mr-2">
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
<label class="form-label">{{ t('database.sqlSecurity') }}</label>
<BaseSelect
v-model="localView.security"
:options="['DEFINER', 'INVOKER']"
class="form-select"
/>
</div>
</div>
<div class="column col-auto mr-2">
<div v-if="workspace.customizations.viewAlgorithm" class="form-group">
<label class="form-label">{{ t('database.algorithm') }}</label>
<BaseSelect
v-model="localView.algorithm"
:options="['UNDEFINED', 'MERGE', 'TEMPTABLE']"
class="form-select"
/>
</div>
</div>
<div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2">
<div class="form-group">
<label class="form-label">{{ t('database.updateOption') }}</label>
<BaseSelect
v-model="localView.updateOption"
:option-track-by="(user: any) => user.value"
:options="[{label: 'None', value: ''}, {label: 'CASCADED', value: 'CASCADED'}, {label: 'LOCAL', value: 'LOCAL'}]"
class="form-select"
/>
</div>
</div>
</div>
</div>
<div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ t('database.selectStatement') }}</label>
<QueryEditor
v-show="isSelected"
ref="queryEditor"
v-model="localView.sql"
:workspace="workspace"
:schema="schema"
:height="editorHeight"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { Ace } from 'ace-builds';
import { ipcRenderer } from 'electron';
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import BaseIcon from '@/components/BaseIcon.vue';
import BaseLoader from '@/components/BaseLoader.vue';
import BaseSelect from '@/components/BaseSelect.vue';
import QueryEditor from '@/components/QueryEditor.vue';
import Views from '@/ipc-api/Views';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
const { t } = useI18n();
const props = defineProps({
tabUid: String,
connection: Object,
isSelected: Boolean,
schema: String,
view: String
});
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const {
getWorkspace,
refreshStructure,
renameTabs,
changeBreadcrumbs,
setUnsavedChanges
} = workspacesStore;
const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
const isLoading = ref(false);
const isSaving = ref(false);
const originalView = ref(null);
const localView = ref(null);
const editorHeight = ref(300);
const lastView = ref(null);
const sqlProxy = ref('');
const workspace = computed(() => getWorkspace(props.connection.uid));
const isChanged = computed(() => JSON.stringify(originalView.value) !== JSON.stringify(localView.value));
const isDefinerInUsers = computed(() => originalView.value ? workspace.value.users.some(user => originalView.value.definer === `\`${user.name}\`@\`${user.host}\``) : true);
const users = computed(() => {
const users = [{ value: '' }, ...workspace.value.users];
if (!isDefinerInUsers.value) {
const [name, host] = originalView.value.definer.replaceAll('`', '').split('@');
users.unshift({ name, host });
}
return users;
});
const getViewData = async () => {
if (!props.view) return;
isLoading.value = true;
localView.value = { sql: '' };
lastView.value = props.view;
const params = {
uid: props.connection.uid,
schema: props.schema,
view: props.view
};
try {
const { status, response } = await Views.getMaterializedViewInformations(params);
if (status === 'success') {
originalView.value = response;
localView.value = JSON.parse(JSON.stringify(originalView.value));
sqlProxy.value = localView.value.sql;
}
else
addNotification({ status: 'error', message: response });
}
catch (err) {
addNotification({ status: 'error', message: err.stack });
}
resizeQueryEditor();
isLoading.value = false;
};
const saveChanges = async () => {
if (isSaving.value) return;
isSaving.value = true;
const params = {
uid: props.connection.uid,
view: {
...localView.value,
schema: props.schema,
oldName: originalView.value.name
}
};
try {
const { status, response } = await Views.alterMaterializedView(params);
if (status === 'success') {
const oldName = originalView.value.name;
await refreshStructure(props.connection.uid);
if (oldName !== localView.value.name) {
renameTabs({
uid: props.connection.uid,
schema: props.schema,
elementName: oldName,
elementNewName: localView.value.name,
elementType: 'materializedview'
});
changeBreadcrumbs({ schema: props.schema, view: localView.value.name });
}
else
getViewData();
}
else
addNotification({ status: 'error', message: response });
}
catch (err) {
addNotification({ status: 'error', message: err.stack });
}
isSaving.value = false;
};
const clearChanges = () => {
localView.value = JSON.parse(JSON.stringify(originalView.value));
queryEditor.value.editor.session.setValue(localView.value.sql);
};
const resizeQueryEditor = () => {
if (queryEditor.value) {
const footer = document.getElementById('footer');
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
editorHeight.value = size;
queryEditor.value.editor.resize();
}
};
const saveContentListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
if (props.isSelected && !hasModalOpen && isChanged.value)
saveChanges();
};
watch(() => props.schema, async () => {
if (props.isSelected) {
await getViewData();
queryEditor.value.editor.session.setValue(localView.value.sql);
lastView.value = props.view;
}
});
watch(() => props.view, async () => {
if (props.isSelected) {
await getViewData();
queryEditor.value.editor.session.setValue(localView.value.sql);
lastView.value = props.view;
}
});
watch(() => props.isSelected, (val) => {
if (val) {
changeBreadcrumbs({ schema: props.schema, view: localView.value.name });
setTimeout(() => {
resizeQueryEditor();
}, 50);
}
});
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
});
(async () => {
await getViewData();
queryEditor.value.editor.session.setValue(localView.value.sql);
})();
onMounted(() => {
window.addEventListener('resize', resizeQueryEditor);
ipcRenderer.on('save-content', saveContentListener);
});
onUnmounted(() => {
window.removeEventListener('resize', resizeQueryEditor);
});
onBeforeUnmount(() => {
ipcRenderer.removeListener('save-content', saveContentListener);
});
</script>

View File

@ -382,7 +382,7 @@ const getFieldsData = async () => {
if (status === 'success') { if (status === 'success') {
const indexesObj = response const indexesObj = response
.filter((index: TableIndex) => index.type !== 'FOREIGN KEY') .filter((index: TableIndex) => index.type !== 'FOREIGN KEY')
.reduce((acc: {[key: string]: TableIndex[]}, curr: TableIndex) => { .reduce((acc: Record<string, TableIndex[]>, curr: TableIndex) => {
acc[curr.name] = acc[curr.name] || []; acc[curr.name] = acc[curr.name] || [];
acc[curr.name].push(curr); acc[curr.name].push(curr);
return acc; return acc;

View File

@ -101,7 +101,13 @@ const props = defineProps({
selectedField: Object selectedField: Object
}); });
const emit = defineEmits(['close-context', 'duplicate-selected', 'delete-selected', 'add-new-index', 'add-to-index']); const emit = defineEmits([
'close-context',
'duplicate-selected',
'delete-selected',
'add-new-index',
'add-to-index'
]);
const hasPrimary = computed(() => props.indexes.some(index => index.type === 'PRIMARY')); const hasPrimary = computed(() => props.indexes.some(index => index.type === 'PRIMARY'));

View File

@ -150,7 +150,13 @@ const props = defineProps({
mode: String mode: String
}); });
const emit = defineEmits(['add-new-index', 'add-to-index', 'rename-field', 'duplicate-field', 'remove-field']); const emit = defineEmits([
'add-new-index',
'add-to-index',
'rename-field',
'duplicate-field',
'remove-field'
]);
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const consoleStore = useConsoleStore(); const consoleStore = useConsoleStore();

View File

@ -258,7 +258,7 @@ const indexesPanel: Ref<HTMLDivElement> = ref(null);
const foreignProxy = ref([]); const foreignProxy = ref([]);
const selectedForeignID = ref(''); const selectedForeignID = ref('');
const modalInnerHeight = ref(400); const modalInnerHeight = ref(400);
const refFields = ref({} as {[key: string]: TableField[]}); const refFields = ref({} as Record<string, TableField[]>);
const foreignActions = computed(() => props.workspace.customizations.foreignActions); const foreignActions = computed(() => props.workspace.customizations.foreignActions);
const selectedForeignObj = computed(() => foreignProxy.value.find(foreign => foreign._antares_id === selectedForeignID.value)); const selectedForeignObj = computed(() => foreignProxy.value.find(foreign => foreign._antares_id === selectedForeignID.value));

View File

@ -19,7 +19,10 @@
<div ref="resizer" class="query-area-resizer" /> <div ref="resizer" class="query-area-resizer" />
<div ref="queryAreaFooter" class="workspace-query-runner-footer"> <div ref="queryAreaFooter" class="workspace-query-runner-footer">
<div class="workspace-query-buttons"> <div class="workspace-query-buttons">
<div @mouseenter="setCancelButtonVisibility(true)" @mouseleave="setCancelButtonVisibility(false)"> <div
@mouseenter="setCancelButtonVisibility(true)"
@mouseleave="setCancelButtonVisibility(false)"
>
<button <button
v-if="showCancel && isQuering" v-if="showCancel && isQuering"
class="btn btn-primary btn-sm cancellable" class="btn btn-primary btn-sm cancellable"
@ -94,6 +97,48 @@
> >
<BaseIcon icon-name="mdiBrush" :size="24" /> <BaseIcon icon-name="mdiBrush" :size="24" />
</button> </button>
<div class="btn-group">
<button
class="btn btn-dark btn-sm mr-0"
:disabled="!filePath || lastSavedQuery === query"
:title="t('application.saveFile')"
@click="saveFile()"
>
<BaseIcon icon-name="mdiContentSaveCheckOutline" :size="24" />
</button>
<button
class="btn btn-dark btn-sm mr-0"
:title="t('application.saveFileAs')"
@click="saveFileAs()"
>
<BaseIcon icon-name="mdiContentSavePlusOutline" :size="24" />
</button>
<button
class="btn btn-dark btn-sm"
:title="t('application.openFile')"
@click="openFile()"
>
<BaseIcon icon-name="mdiFolderOpenOutline" :size="24" />
</button>
</div>
<div class="btn-group">
<button
class="btn btn-dark btn-sm mr-0"
:disabled="isQuering || (isQuerySaved || query.length < 5)"
:title="t('application.saveAsNote')"
@click="saveQuery()"
>
<BaseIcon icon-name="mdiHeartPlusOutline" :size="24" />
</button>
<button
class="btn btn-dark btn-sm"
:disabled="isQuering"
:title="t('database.savedQueries')"
@click="openSavedModal()"
>
<BaseIcon icon-name="mdiNotebookHeartOutline" :size="24" />
</button>
</div>
<button <button
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:disabled="isQuering" :disabled="isQuering"
@ -102,24 +147,6 @@
> >
<BaseIcon icon-name="mdiHistory" :size="24" /> <BaseIcon icon-name="mdiHistory" :size="24" />
</button> </button>
<div class="btn-group">
<button
class="btn btn-dark btn-sm mr-0"
:disabled="isQuering || (isQuerySaved || query.length < 5)"
:title="t('general.save')"
@click="saveQuery()"
>
<BaseIcon icon-name="mdiContentSaveOutline" :size="24" />
</button>
<button
class="btn btn-dark btn-sm"
:disabled="isQuering"
:title="t('database.savedQueries')"
@click="openSavedModal()"
>
<BaseIcon icon-name="mdiStarOutline" :size="24" />
</button>
</div>
<div class="dropdown table-dropdown pr-2"> <div class="dropdown table-dropdown pr-2">
<button <button
:disabled="!hasResults || isQuering" :disabled="!hasResults || isQuering"
@ -251,7 +278,7 @@ import { uidGen } from 'common/libs/uidGen';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { format } from 'sql-formatter'; import { format } from 'sql-formatter';
import { Component, computed, onBeforeUnmount, onMounted, Prop, Ref, ref, watch } from 'vue'; import { Component, computed, onBeforeUnmount, onMounted, Prop, Ref, ref, toRaw, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import BaseIcon from '@/components/BaseIcon.vue'; import BaseIcon from '@/components/BaseIcon.vue';
@ -262,6 +289,7 @@ import QueryEditor from '@/components/QueryEditor.vue';
import WorkspaceTabQueryEmptyState from '@/components/WorkspaceTabQueryEmptyState.vue'; import WorkspaceTabQueryEmptyState from '@/components/WorkspaceTabQueryEmptyState.vue';
import WorkspaceTabQueryTable from '@/components/WorkspaceTabQueryTable.vue'; import WorkspaceTabQueryTable from '@/components/WorkspaceTabQueryTable.vue';
import { useResultTables } from '@/composables/useResultTables'; import { useResultTables } from '@/composables/useResultTables';
import Application from '@/ipc-api/Application';
import Schema from '@/ipc-api/Schema'; import Schema from '@/ipc-api/Schema';
import { useApplicationStore } from '@/stores/application'; import { useApplicationStore } from '@/stores/application';
import { useConsoleStore } from '@/stores/console'; import { useConsoleStore } from '@/stores/console';
@ -302,14 +330,18 @@ const {
getWorkspace, getWorkspace,
changeBreadcrumbs, changeBreadcrumbs,
updateTabContent, updateTabContent,
setUnsavedChanges setUnsavedChanges,
newTab
} = workspacesStore; } = workspacesStore;
const queryEditor: Ref<Component & { editor: Ace.Editor; $el: HTMLElement }> = ref(null); const queryEditor: Ref<Component & { editor: Ace.Editor; $el: HTMLElement }> = ref(null);
const queryAreaFooter: Ref<HTMLDivElement> = ref(null); const queryAreaFooter: Ref<HTMLDivElement> = ref(null);
const resizer: Ref<HTMLDivElement> = ref(null); const resizer: Ref<HTMLDivElement> = ref(null);
const queryName = ref('');
const query = ref(''); const query = ref('');
const filePath = ref('');
const lastQuery = ref(''); const lastQuery = ref('');
const lastSavedQuery = ref('');
const isCancelling = ref(false); const isCancelling = ref(false);
const showCancel = ref(false); const showCancel = ref(false);
const autocommit = ref(true); const autocommit = ref(true);
@ -333,17 +365,41 @@ const databaseSchemas = computed(() => {
}); });
const hasResults = computed(() => results.value.length && results.value[0].rows); const hasResults = computed(() => results.value.length && results.value[0].rows);
const hasAffected = computed(() => affectedCount.value || (!resultsCount.value && affectedCount.value !== null)); const hasAffected = computed(() => affectedCount.value || (!resultsCount.value && affectedCount.value !== null));
const isChanged = computed(() => {
return filePath.value && lastSavedQuery.value !== query.value;
});
watch(query, (val) => { watch(query, (val) => {
clearTimeout(debounceTimeout.value); clearTimeout(debounceTimeout.value);
debounceTimeout.value = setTimeout(() => { debounceTimeout.value = setTimeout(() => {
updateTabContent({ updateTabContent({
elementName: queryName.value,
filePath: filePath.value,
uid: props.connection.uid, uid: props.connection.uid,
tab: props.tab.uid, tab: props.tab.uid,
type: 'query', type: 'query',
schema: selectedSchema.value, schema: selectedSchema.value,
content: val content: val
});
isQuerySaved.value = false;
}, 200);
});
watch(queryName, (val) => {
clearTimeout(debounceTimeout.value);
debounceTimeout.value = setTimeout(() => {
updateTabContent({
elementName: val,
filePath: filePath.value,
uid: props.connection.uid,
tab: props.tab.uid,
type: 'query',
schema: selectedSchema.value,
content: query.value
}); });
isQuerySaved.value = false; isQuerySaved.value = false;
@ -377,6 +433,10 @@ watch(() => props.tab.content, () => {
queryEditor.value.editor.session.setValue(query.value); queryEditor.value.editor.session.setValue(query.value);
}); });
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
});
const runQuery = async (query: string) => { const runQuery = async (query: string) => {
if (!query || isQuering.value) return; if (!query || isQuering.value) return;
isQuering.value = true; isQuering.value = true;
@ -529,7 +589,8 @@ const saveQuery = () => {
type: 'query', type: 'query',
date: new Date(), date: new Date(),
note: query.value, note: query.value,
isArchived: false isArchived: false,
title: queryName.value
}); });
isQuerySaved.value = true; isQuerySaved.value = true;
}; };
@ -596,6 +657,8 @@ const rollbackTab = async () => {
defineExpose({ resizeResults }); defineExpose({ resizeResults });
query.value = props.tab.content as string; query.value = props.tab.content as string;
queryName.value = props.tab.elementName as string;
filePath.value = props.tab.filePath as string;
selectedSchema.value = props.tab.schema || breadcrumbsSchema.value; selectedSchema.value = props.tab.schema || breadcrumbsSchema.value;
window.addEventListener('resize', onWindowResize); window.addEventListener('resize', onWindowResize);
@ -630,6 +693,73 @@ const historyListener = () => {
openHistoryModal(); openHistoryModal();
}; };
const openFileListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
if (props.isSelected && !hasModalOpen)
openFile();
};
const saveFileAsListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
if (props.isSelected && !hasModalOpen)
saveFileAs();
};
const saveContentListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
if (props.isSelected && !hasModalOpen && filePath)
saveFile();
};
const openFile = async () => {
const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: 'SQL', extensions: ['sql', 'txt'] }] });
if (result && !result.canceled) {
const file = result.filePaths[0];
const content = await Application.readFile(file);
const fileName = file.split('/').pop().split('\\').pop();
if (props.tab.filePath && props.tab.filePath !== file) {
newTab({
uid: props.connection.uid,
type: 'query',
filePath: file,
content: '',
schema: selectedSchema.value,
elementName: fileName
});
}
else {
filePath.value = file;
queryName.value = fileName;
query.value = content;
lastSavedQuery.value = content;
}
}
};
const saveFileAs = async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result: any = await Application.showSaveDialog({ filters: [{ name: 'SQL', extensions: ['sql'] }], defaultPath: `${queryName.value || 'query'}.sql` });
if (result && !result.canceled) {
await Application.writeFile(result.filePath, query.value);
addNotification({ status: 'success', message: t('general.actionSuccessful', { action: t('application.saveFile') }) });
queryName.value = result.filePath.split('/').pop().split('\\').pop();
filePath.value = result.filePath;
lastSavedQuery.value = toRaw(query.value);
}
};
const saveFile = async () => {
await Application.writeFile(filePath.value, query.value);
addNotification({ status: 'success', message: t('general.actionSuccessful', { action: t('application.saveFile') }) });
lastSavedQuery.value = toRaw(query.value);
};
const loadFileContent = async (file: string) => {
const content = await Application.readFile(file);
query.value = content;
lastSavedQuery.value = content;
};
onMounted(() => { onMounted(() => {
const localResizer = resizer.value; const localResizer = resizer.value;
@ -638,6 +768,9 @@ onMounted(() => {
ipcRenderer.on('kill-query', killQueryListener); ipcRenderer.on('kill-query', killQueryListener);
ipcRenderer.on('clear-query', clearQueryListener); ipcRenderer.on('clear-query', clearQueryListener);
ipcRenderer.on('query-history', historyListener); ipcRenderer.on('query-history', historyListener);
ipcRenderer.on('open-file', openFileListener);
ipcRenderer.on('save-file-as', saveFileAsListener);
ipcRenderer.on('save-content', saveContentListener);
localResizer.addEventListener('mousedown', (e: MouseEvent) => { localResizer.addEventListener('mousedown', (e: MouseEvent) => {
e.preventDefault(); e.preventDefault();
@ -648,6 +781,9 @@ onMounted(() => {
if (props.tab.autorun) if (props.tab.autorun)
runQuery(query.value); runQuery(query.value);
if (props.tab.filePath)
loadFileContent(props.tab.filePath);
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
@ -663,6 +799,9 @@ onBeforeUnmount(() => {
ipcRenderer.removeListener('kill-query', killQueryListener); ipcRenderer.removeListener('kill-query', killQueryListener);
ipcRenderer.removeListener('clear-query', clearQueryListener); ipcRenderer.removeListener('clear-query', clearQueryListener);
ipcRenderer.removeListener('query-history', historyListener); ipcRenderer.removeListener('query-history', historyListener);
ipcRenderer.removeListener('open-file', openFileListener);
ipcRenderer.removeListener('save-file-as', saveFileAsListener);
ipcRenderer.removeListener('save-content', saveContentListener);
}); });
</script> </script>
@ -682,7 +821,7 @@ onBeforeUnmount(() => {
transition: background 0.2s; transition: background 0.2s;
&:hover { &:hover {
background: rgba($primary-color, 50%); background: var(--primary-color-dark);
} }
} }
@ -721,4 +860,4 @@ onBeforeUnmount(() => {
min-height: 200px; min-height: 200px;
} }
} }
</style> </style>filePathsfilePathsfilePaths

View File

@ -31,7 +31,7 @@
:class="{ 'active': resultsetIndex === index }" :class="{ 'active': resultsetIndex === index }"
@click="selectResultset(index)" @click="selectResultset(index)"
> >
<a>{{ result.fields ? result.fields[0]?.table : '' }} ({{ result.rows.length }})</a> <a>{{ result.fields ? result.fields[0]?.tableAlias ?? result.fields[0]?.table : `${t('general.results')} #${index}` }} ({{ result.rows.length }})</a>
</li> </li>
</ul> </ul>
<div ref="table" class="table table-hover"> <div ref="table" class="table table-hover">
@ -44,7 +44,7 @@
:title="`${field.type} ${fieldLength(field) ? `(${fieldLength(field)})` : ''}`" :title="`${field.type} ${fieldLength(field) ? `(${fieldLength(field)})` : ''}`"
> >
<div ref="columnResize" class="column-resizable"> <div ref="columnResize" class="column-resizable">
<div class="table-column-title" @click="sort(field.name)"> <div class="table-column-title" @click="sort(field)">
<div v-if="field.key" :title="keyName(field.key)"> <div v-if="field.key" :title="keyName(field.key)">
<BaseIcon <BaseIcon
icon-name="mdiKey" icon-name="mdiKey"
@ -56,8 +56,8 @@
</div> </div>
<span>{{ field.alias || field.name }}</span> <span>{{ field.alias || field.name }}</span>
<BaseIcon <BaseIcon
v-if="isSortable && currentSort === field.name || currentSort === `${field.table}.${field.name}`" v-if="isSortable && currentSort[resultsetIndex]?.field === field.name || currentSort[resultsetIndex]?.field === `${field.tableAlias || field.table}.${field.name}`"
:icon-name="currentSortDir === 'asc' ? 'mdiSortAscending' : 'mdiSortDescending'" :icon-name="currentSort[resultsetIndex].dir === 'asc' ? 'mdiSortAscending' : 'mdiSortDescending'"
:size="18" :size="18"
class="sort-icon ml-1" class="sort-icon ml-1"
/> />
@ -292,6 +292,10 @@ const props = defineProps({
results: Array as Prop<QueryResult[]>, results: Array as Prop<QueryResult[]>,
connUid: String, connUid: String,
mode: String as Prop<'table' | 'query'>, mode: String as Prop<'table' | 'query'>,
page: {
type: Number,
required: false
},
isSelected: Boolean, isSelected: Boolean,
elementType: { type: String, default: 'table' } elementType: { type: String, default: 'table' }
}); });
@ -314,8 +318,7 @@ const hasFocus = ref(false);
const contextEvent = ref(null); const contextEvent = ref(null);
const selectedCell = ref(null); const selectedCell = ref(null);
const selectedRows = ref([]); const selectedRows = ref([]);
const currentSort = ref(''); const currentSort: Ref<{field: string; dir: 'asc' | 'desc'}[]> = ref([]);
const currentSortDir = ref('asc');
const resultsetIndex = ref(0); const resultsetIndex = ref(0);
const scrollElement = ref(null); const scrollElement = ref(null);
const rowHeight = ref(23); const rowHeight = ref(23);
@ -358,14 +361,16 @@ const isHardSort = computed(() => {
}); });
const sortedResults = computed(() => { const sortedResults = computed(() => {
if (currentSort.value && !isHardSort.value) { if (currentSort.value[resultsetIndex.value] && !isHardSort.value) {
const sortObj = currentSort.value[resultsetIndex.value];
return [...localResults.value].sort((a: any, b: any) => { return [...localResults.value].sort((a: any, b: any) => {
let modifier = 1; let modifier = 1;
let valA = typeof a[currentSort.value] === 'string' ? a[currentSort.value].toLowerCase() : a[currentSort.value]; let valA = typeof a[sortObj.field] === 'string' ? a[sortObj.field].toLowerCase() : a[sortObj.field];
if (!isNaN(valA)) valA = Number(valA); if (!isNaN(valA)) valA = Number(valA);
let valB = typeof b[currentSort.value] === 'string' ? b[currentSort.value].toLowerCase() : b[currentSort.value]; let valB = typeof b[sortObj.field] === 'string' ? b[sortObj.field].toLowerCase() : b[sortObj.field];
if (!isNaN(valB)) valB = Number(valB); if (!isNaN(valB)) valB = Number(valB);
if (currentSortDir.value === 'desc') modifier = -1; if (sortObj.dir === 'desc') modifier = -1;
if (valA < valB) return -1 * modifier; if (valA < valB) return -1 * modifier;
if (valA > valB) return 1 * modifier; if (valA > valB) return 1 * modifier;
return 0; return 0;
@ -784,32 +789,42 @@ const contextMenu = (event: MouseEvent, cell: any) => {
isContext.value = true; isContext.value = true;
}; };
const sort = (field: string) => { const sort = (field: TableField) => {
if (!isSortable.value) return; if (!isSortable.value) return;
selectedRows.value = []; selectedRows.value = [];
let fieldName = field.name;
const hasTableInFieldname = Object.keys(localResults.value[0]).find(k => k !== '_antares_id').includes('.');
if (props.mode === 'query') if (props.mode === 'query' && hasTableInFieldname)
field = `${getTable(resultsetIndex.value)}.${field}`; fieldName = `${field.tableAlias || field.table}.${field.name}`;
if (field === currentSort.value) { if (fieldName === currentSort.value[resultsetIndex.value]?.field) {
if (currentSortDir.value === 'asc') if (currentSort.value[resultsetIndex.value].dir === 'asc')
currentSortDir.value = 'desc'; currentSort.value[resultsetIndex.value].dir = 'desc';
else else
resetSort(); resetSort();
} }
else { else {
currentSortDir.value = 'asc'; currentSort.value[resultsetIndex.value] = {
currentSort.value = field; field: fieldName,
dir: 'asc'
};
} }
if (isHardSort.value) if (isHardSort.value) {
emit('hard-sort', { field: currentSort.value, dir: currentSortDir.value }); emit('hard-sort', {
field: currentSort.value[resultsetIndex.value].field,
dir: currentSort.value[resultsetIndex.value].dir
});
}
}; };
const resetSort = () => { const resetSort = () => {
currentSort.value = ''; currentSort.value[resultsetIndex.value] = {
currentSortDir.value = 'asc'; field: null,
dir: 'asc'
};
}; };
const selectResultset = (index: number) => { const selectResultset = (index: number) => {
@ -857,6 +872,7 @@ const downloadTable = (format: 'csv' | 'json' | 'sql' | 'php', table: string, po
}, },
client: workspaceClient.value, client: workspaceClient.value,
table, table,
page: props.page,
sqlOptions: popup ? { ...sqlExportOptions.value } : null, sqlOptions: popup ? { ...sqlExportOptions.value } : null,
csvOptions: popup ? { ...csvExportOptions.value } : null csvOptions: popup ? { ...csvExportOptions.value } : null
}); });

View File

@ -43,6 +43,7 @@
autofocus autofocus
class="editable-field form-input input-sm px-1" class="editable-field form-input input-sm px-1"
@blur="editOFF" @blur="editOFF"
@keyup.delete.stop
> >
<BaseSelect <BaseSelect
v-else-if="inputProps.type === 'boolean'" v-else-if="inputProps.type === 'boolean'"
@ -50,6 +51,7 @@
:options="['true', 'false']" :options="['true', 'false']"
class="form-select small-select editable-field" class="form-select small-select editable-field"
@blur="editOFF" @blur="editOFF"
@keyup.delete.stop
/> />
<BaseSelect <BaseSelect
v-else-if="enumArray" v-else-if="enumArray"
@ -58,6 +60,7 @@
class="form-select small-select editable-field" class="form-select small-select editable-field"
dropdown-class="small-select" dropdown-class="small-select"
@blur="editOFF" @blur="editOFF"
@keyup.delete.stop
/> />
<input <input
v-else v-else
@ -67,6 +70,7 @@
autofocus autofocus
class="editable-field form-input input-sm px-1" class="editable-field form-input input-sm px-1"
@blur="editOFF" @blur="editOFF"
@keyup.delete.stop
> >
</template> </template>
</template> </template>
@ -382,7 +386,7 @@ const isBaseSelectField = computed(() => {
}); });
const enumArray = computed(() => { const enumArray = computed(() => {
if (props.fields[editingField.value] && props.fields[editingField.value].enumValues) if (props.fields[editingField.value] && props.fields[editingField.value].enumValues && props.fields[editingField.value].type !== 'SET')
return props.fields[editingField.value].enumValues.replaceAll('\'', '').split(','); return props.fields[editingField.value].enumValues.replaceAll('\'', '').split(',');
return false; return false;
}); });

View File

@ -91,7 +91,7 @@
<BaseIcon icon-name="mdiMagnify" :size="24" /> <BaseIcon icon-name="mdiMagnify" :size="24" />
</button> </button>
<button <button
v-if="isTable" v-if="isTable && !connection.readonly"
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:disabled="isQuering" :disabled="isQuering"
@click="showFakerModal()" @click="showFakerModal()"
@ -202,6 +202,7 @@
v-if="results" v-if="results"
ref="queryTable" ref="queryTable"
:results="results" :results="results"
:page="page"
:tab-uid="tabUid" :tab-uid="tabUid"
:conn-uid="connection.uid" :conn-uid="connection.uid"
:is-selected="isSelected" :is-selected="isSelected"
@ -304,7 +305,7 @@ const customizations = computed(() => {
}); });
const isTable = computed(() => { const isTable = computed(() => {
return !workspace.value.breadcrumbs.view; return props.elementType === 'table';
}); });
const fields = computed(() => { const fields = computed(() => {
@ -440,6 +441,25 @@ const resizeScroller = () => {
const updateFilters = (clausoles: TableFilterClausole[]) => { const updateFilters = (clausoles: TableFilterClausole[]) => {
filters.value = clausoles; filters.value = clausoles;
results.value = []; results.value = [];
const permanentTabs = {
table: 'data',
view: 'data',
trigger: 'trigger-props',
triggerFunction: 'trigger-function-props',
function: 'function-props',
routine: 'routine-props',
procedure: 'routine-props',
scheduler: 'scheduler-props'
} as Record<string, string>;
newTab({
uid: props.connection.uid,
schema: props.schema,
elementName: props.table,
type: permanentTabs[props.elementType],
elementType: props.elementType
});
getTableData(); getTableData();
}; };
@ -479,8 +499,8 @@ const openTableSettingTab = () => {
uid: workspace.value.uid, uid: workspace.value.uid,
elementName: props.table, elementName: props.table,
schema: props.schema, schema: props.schema,
type: isTable.value ? 'table-props' : 'view-props', type: isTable.value ? 'table-props' : props.elementType === 'view' ? 'view-props' : 'materialized-view-props',
elementType: isTable.value ? 'table' : 'view' elementType: props.elementType
}); });
changeBreadcrumbs({ changeBreadcrumbs({

View File

@ -28,7 +28,7 @@ export function useFilters () {
return `(${num})`; return `(${num})`;
}; };
const parseKeys = (keys: {[key: number]: string}[]) => { const parseKeys = (keys: Record<number, string>[]) => {
const isMacOS = process.platform === 'darwin'; const isMacOS = process.platform === 'darwin';
return (keys as string[]).map(k => ( return (keys as string[]).map(k => (
k.split('+') k.split('+')

View File

@ -232,7 +232,7 @@ export const caES = {
newFunction: 'Nova funció', newFunction: 'Nova funció',
newScheduler: 'Nou planificador', newScheduler: 'Nou planificador',
newTriggerFunction: 'Nova funció de disparador', newTriggerFunction: 'Nova funció de disparador',
thereIsNoQueriesYet: 'Encara no hi ha consultes', thereAreNoQueriesYet: 'Encara no hi ha consultes',
searchForQueries: 'Cerca consultes', searchForQueries: 'Cerca consultes',
killProcess: 'Mata procés', killProcess: 'Mata procés',
exportSchema: 'Exporta esquema', exportSchema: 'Exporta esquema',

View File

@ -233,7 +233,7 @@ export const csCZ = {
newFunction: 'Nová funkce', newFunction: 'Nová funkce',
newScheduler: 'Nový scheduler', newScheduler: 'Nový scheduler',
newTriggerFunction: 'Nová trigger funkce', newTriggerFunction: 'Nová trigger funkce',
thereIsNoQueriesYet: 'Nejsou tu žádné dotazy', thereAreNoQueriesYet: 'Nejsou tu žádné dotazy',
searchForQueries: 'Hledání dotazů', searchForQueries: 'Hledání dotazů',
killProcess: 'Zabít proces', killProcess: 'Zabít proces',
exportSchema: 'Exportovat schéma', exportSchema: 'Exportovat schéma',

View File

@ -79,7 +79,8 @@ export const enUS = {
search: 'Search', search: 'Search',
title: 'Title', title: 'Title',
archive: 'Archive', // verb archive: 'Archive', // verb
undo: 'Undo' undo: 'Undo',
moveTo: 'Move to'
}, },
connection: { // Database connection connection: { // Database connection
connection: 'Connection', connection: 'Connection',
@ -116,7 +117,8 @@ export const enUS = {
readOnlyMode: 'Read-only mode', readOnlyMode: 'Read-only mode',
allConnections: 'All connections', allConnections: 'All connections',
searchForConnections: 'Search for connections', searchForConnections: 'Search for connections',
keepAliveInterval: 'Keep alive interval' keepAliveInterval: 'Keep alive interval',
singleConnection: 'Single connection'
}, },
database: { // Database related terms database: { // Database related terms
schema: 'Schema', schema: 'Schema',
@ -138,6 +140,7 @@ export const enUS = {
total: 'Total', total: 'Total',
table: 'Table | Tables', table: 'Table | Tables',
view: 'View | Views', view: 'View | Views',
materializedview: 'Materialized view | Materialized views',
definer: 'Definer', definer: 'Definer',
algorithm: 'Algorithm', algorithm: 'Algorithm',
trigger: 'Trigger | Triggers', trigger: 'Trigger | Triggers',
@ -214,6 +217,7 @@ export const enUS = {
updateOption: 'Update option', updateOption: 'Update option',
deleteView: 'Delete view', deleteView: 'Delete view',
createNewView: 'Create new view', createNewView: 'Create new view',
createNewMaterializedView: 'Create new materialized view',
deleteTrigger: 'Delete trigger', deleteTrigger: 'Delete trigger',
createNewTrigger: 'Create new trigger', createNewTrigger: 'Create new trigger',
currentUser: 'Current user', currentUser: 'Current user',
@ -246,12 +250,13 @@ export const enUS = {
thereAreNoTableFields: 'There are no table fields', thereAreNoTableFields: 'There are no table fields',
newTable: 'New table', newTable: 'New table',
newView: 'New view', newView: 'New view',
newMaterializedView: 'New materialized view',
newTrigger: 'New trigger', newTrigger: 'New trigger',
newRoutine: 'New routine', newRoutine: 'New routine',
newFunction: 'New function', newFunction: 'New function',
newScheduler: 'New scheduler', newScheduler: 'New scheduler',
newTriggerFunction: 'New trigger function', newTriggerFunction: 'New trigger function',
thereIsNoQueriesYet: 'There is no queries yet', thereAreNoQueriesYet: 'There are no queries yet',
searchForQueries: 'Search for queries', searchForQueries: 'Search for queries',
killProcess: 'Kill process', killProcess: 'Kill process',
exportSchema: 'Export schema', exportSchema: 'Export schema',
@ -331,7 +336,7 @@ export const enUS = {
wrapLongLines: 'Wrap long lines', wrapLongLines: 'Wrap long lines',
markdownSupported: 'Markdown supported', markdownSupported: 'Markdown supported',
plantATree: 'Plant a Tree', plantATree: 'Plant a Tree',
dataTabPageSize: 'DATA tab page size', dataTabPageSize: 'Results per page',
noOpenTabs: 'There are no open tabs, navigate on the left bar or:', noOpenTabs: 'There are no open tabs, navigate on the left bar or:',
restorePreviousSession: 'Restore previous session', restorePreviousSession: 'Restore previous session',
closeTab: 'Close tab', closeTab: 'Close tab',
@ -362,6 +367,8 @@ export const enUS = {
editFolder: 'Edit folder', editFolder: 'Edit folder',
folderName: 'Folder name', folderName: 'Folder name',
deleteFolder: 'Delete folder', deleteFolder: 'Delete folder',
newFolder: 'New folder',
outOfFolder: 'Out of folder',
editConnectionAppearance: 'Edit connection appearance', editConnectionAppearance: 'Edit connection appearance',
defaultCopyType: 'Default copy type', defaultCopyType: 'Default copy type',
showTableSize: 'Show table size in sidebar', showTableSize: 'Show table size in sidebar',
@ -393,9 +400,15 @@ export const enUS = {
thereAreNoNotesYet: 'There are no notes yet', thereAreNoNotesYet: 'There are no notes yet',
addNote: 'Add note', addNote: 'Add note',
editNote: 'Edit note', editNote: 'Edit note',
saveAsNote: 'Save as note',
showArchivedNotes: 'Show archived notes', showArchivedNotes: 'Show archived notes',
hideArchivedNotes: 'Hide archived notes', hideArchivedNotes: 'Hide archived notes',
tag: 'Tag' // Note tag tag: 'Tag', // Note tag,
saveFile: 'Save file',
saveFileAs: 'Save file as',
openFile: 'Open file',
openNotes: 'Open notes'
}, },
faker: { // Faker.js methods, used in random generated content faker: { // Faker.js methods, used in random generated content
address: 'Address', address: 'Address',

View File

@ -229,7 +229,7 @@ export const frFR = {
newFunction: 'Nouvelle function', newFunction: 'Nouvelle function',
newScheduler: 'Nouveau déclencheur', newScheduler: 'Nouveau déclencheur',
newTriggerFunction: 'Nouvelle fonction de déclencheur', newTriggerFunction: 'Nouvelle fonction de déclencheur',
thereIsNoQueriesYet: 'Il n\'y a pas encore de requête', thereAreNoQueriesYet: 'Il n\'y a pas encore de requête',
searchForQueries: 'Rechercher des requêtes', searchForQueries: 'Rechercher des requêtes',
killProcess: 'Terminer un processus', killProcess: 'Terminer un processus',
exportSchema: 'Exporter un schéma', exportSchema: 'Exporter un schéma',

View File

@ -227,7 +227,7 @@ export const idID = {
newFunction: 'Fungsi baru', newFunction: 'Fungsi baru',
newScheduler: 'Penjadwal baru', newScheduler: 'Penjadwal baru',
newTriggerFunction: 'Fungsi pemicu baru', newTriggerFunction: 'Fungsi pemicu baru',
thereIsNoQueriesYet: 'Belum ada kueri', thereAreNoQueriesYet: 'Belum ada kueri',
searchForQueries: 'Telusuri kueri', searchForQueries: 'Telusuri kueri',
killProcess: 'Membunuh proses', killProcess: 'Membunuh proses',
exportSchema: 'Skema ekspor', exportSchema: 'Skema ekspor',

View File

@ -228,7 +228,7 @@ export const itIT = {
newFunction: 'Nuova funzione', newFunction: 'Nuova funzione',
newScheduler: 'Nuovo scheduler', newScheduler: 'Nuovo scheduler',
newTriggerFunction: 'Nuova funzione di trigger', newTriggerFunction: 'Nuova funzione di trigger',
thereIsNoQueriesYet: 'Non ci sono ancora query', thereAreNoQueriesYet: 'Non ci sono ancora query',
searchForQueries: 'Cerca query', searchForQueries: 'Cerca query',
killProcess: 'Uccidi processo', killProcess: 'Uccidi processo',
exportSchema: 'Esporta schema', exportSchema: 'Esporta schema',

View File

@ -4,106 +4,145 @@ export const jaJP = {
save: '保存', save: '保存',
close: '閉じる', close: '閉じる',
delete: '削除', delete: '削除',
confirm: '確', confirm: '確',
cancel: 'キャンセル', cancel: 'キャンセル',
send: '送信', send: '送信',
refresh: 'リフレッシュ', refresh: 'リフレッシュ',
autoRefresh: 'オートリフレシュ', autoRefresh: '自動リフレッシュ',
version: 'バージョン', version: 'バージョン',
donate: '寄付する', donate: '寄付',
run: '実行', run: '実行',
results: '結果', results: '結果',
size: 'サイズ', size: 'サイズ',
mimeType: 'マイムタイプ', mimeType: 'MIME タイプ',
download: 'ダウンロード', download: 'ダウンロード',
add: '追加', add: '追加',
data: 'データ', data: 'データ',
properties: 'プロパティ', properties: 'プロパティ',
insert: '挿入', name: '名前',
name: '名称',
clear: 'クリア', clear: 'クリア',
seconds: '秒数',
options: 'オプション', options: 'オプション',
insert: '挿入',
discard: '破棄', discard: '破棄',
stay: 'ステイ', stay: '留まる',
author: '作者', author: '作者',
upload: 'アップロード', upload: 'アップロード',
browse: '閲覧', browse: '参照',
content: 'コンテンツ', content: '内容',
cut: 'カット', cut: '切り取り',
copy: 'コピー', copy: 'コピー',
paste: '貼り付け', paste: '貼り付け',
duplicate: '複製',
tools: 'ツール', tools: 'ツール',
format: 'フォーマット', seconds: '秒',
all: 'すべて', all: 'すべて',
duplicate: 'デュプリケート', new: '新規',
history: '履歴',
select: '選択', select: '選択',
deleteConfirm: 'のキャンセルを確認しますか?', change: '変更',
include: '含める',
includes: '含める',
completed: '完了しました',
aborted: '中断しました',
disabled: '無効',
enable: '有効化',
disable: '無効化',
contributors: 'コントリビューター',
pin: '固定',
unpin: '固定を解除',
folder: 'フォルダー | フォルダー',
none: 'なし',
singleQuote: 'シングルクォート',
doubleQuote: 'ダブルクォート',
deleteConfirm: '次の要素を抹消します。確定しますか?',
uploadFile: 'ファイルのアップロード', uploadFile: 'ファイルのアップロード',
format: 'フォーマット',
history: '履歴',
filter: 'フィルタ',
manualValue: 'マニュアル値', manualValue: 'マニュアル値',
selectAll: 'すべてを選択する', selectAll: 'すべて選択',
pageNumber: 'ページ番号' pageNumber: 'ページ番号',
directoryPath: 'ディレクトリパス',
actionSuccessful: '{action} 成功',
outputFormat: '出力フォーマット',
singleFile: '単一の {ext} ファイル',
zipCompressedFile: 'ZIP 圧縮済の {ext} ファイル',
copyName: '名前をコピー',
search: '検索',
title: 'タイトル',
archive: 'アーカイブ',
undo: '元に戻す',
moveTo: '移動'
}, },
connection: { connection: {
connection: '接続',
connectionName: '接続名', connectionName: '接続名',
client: 'クライアント',
hostName: 'ホスト名', hostName: 'ホスト名',
client: 'クライアント',
port: 'ポート', port: 'ポート',
user: 'ユーザー名', user: 'ユーザー名',
password: 'パスワード', password: 'パスワード',
credentials: '認証情報', credentials: '認証情報',
connect: '接続', connect: '接続',
connected: '接続中', connected: '接続',
disconnect: '接続解除', disconnect: '切断',
disconnected: '接続解除', disconnected: '切断済',
ssl: 'SSL', ssl: 'SSL',
enableSsl: 'SSL を有効化',
privateKey: '秘密鍵', privateKey: '秘密鍵',
certificate: '証明書', certificate: '証明書',
caCertificate: 'CA 証明書', caCertificate: 'CA 証明書',
ciphers: '暗号', ciphers: '暗号化アルゴリズム',
untrustedConnection: '信頼できない接続',
passphrase: 'パスフレーズ',
sshTunnel: 'SSH トンネル', sshTunnel: 'SSH トンネル',
addConnection: '接続の追加', enableSsh: 'SSH を有効化',
createConnection: '接続の作成', connectionString: '接続文字列',
createNewConnection: '新しい接続の作成', addConnection: '接続を追加',
askCredentials: '認証情報の入力', createConnection: '接続を作成',
testConnection: '接続のテスト', createNewConnection: '新規接続の作成',
editConnection: '接続の編集', askCredentials: '認証情報を接続時に尋ねる',
deleteConnection: '接続の削除', testConnection: '接続をテスト',
editConnection: '接続を編集',
deleteConnection: '接続を削除',
connectionSuccessfullyMade: '接続に成功しました。', connectionSuccessfullyMade: '接続に成功しました。',
enableSsl: 'SSL 対応', readOnlyMode: '読み取り専用モード',
enableSsh: 'SSH を有効にする' allConnections: 'すべての接続',
searchForConnections: '接続を検索',
keepAliveInterval: 'Keep alive 間隔',
singleConnection: '単一接続'
}, },
database: { database: {
schema: 'スキーマ', schema: 'スキーマ',
type: 'タイプ', type: '型',
insert: '挿入',
indexes: 'インデックス',
foreignKeys: '外部キー', foreignKeys: '外部キー',
length: '長さ', length: '長さ',
unsigned: '符号なし', unsigned: '符号なし',
default: 'デフォルト', default: 'デフォルト',
comment: 'コメント', comment: 'コメント',
collation: '照合',
key: 'キー | キー', key: 'キー | キー',
order: '順序', order: '順序',
expression: '表現', expression: '',
autoIncrement: 'オートインクリメント', autoIncrement: '自動インクリメント',
engine: 'エンジン', engine: 'エンジン',
field: 'フィールド | フィールド', field: 'フィールド | フィールド',
approximately: '約', approximately: '約',
total: '合計', total: '合計',
table: 'テーブル', table: 'テーブル',
view: 'ビュー', view: 'ビュー',
indexes: 'インデックス',
definer: 'デファイナー', definer: 'デファイナー',
algorithm: 'アルゴリズム', algorithm: 'アルゴリズム',
trigger: 'トリガー | トリガー', trigger: 'トリガー | トリガー',
storedRoutine: 'ストアド・ルーチン | ストアド・ルーチン', storedRoutine: 'ストアド・ルーチン | ストアド・ルーチン',
scheduler: 'スケジューラー | スケジューラー', scheduler: 'スケジューラー | スケジューラー',
event: 'イベント', event: 'イベント',
parameters: 'パラメータ', parameters: 'パラメータ',
function: '関数 | 関数', function: '関数 | 関数',
deterministic: '決定論的',
context: 'コンテキスト', context: 'コンテキスト',
export: 'エクスポート', export: 'エクスポート',
import: 'インポート',
returns: '戻り値', returns: '戻り値',
timing: 'タイミング', timing: 'タイミング',
state: '状態', state: '状態',
@ -115,128 +154,249 @@ export const jaJP = {
database: 'データベース', database: 'データベース',
array: '配列', array: '配列',
structure: '構造', structure: '構造',
row: 'ロウ | ロウ', row: '行 | 行',
cell: 'セル | セル', cell: 'セル | セル',
triggerFunction: 'トリガー関数 | トリガー関数', triggerFunction: 'トリガー関数 | トリガー関数',
routine: 'ルーチン', routine: 'ルーチン',
unableEditFieldWithoutPrimary: '主キーのないフィールドを結果セットで編集できない', drop: 'ドロップ',
editCell: 'セルの編集', commit: 'コミット',
deleteRows: '行の削除 | {count} 行の削除', rollback: 'ロールバック',
confirmToDeleteRows: '1つの行を削除することを確認しますか | {count} 行を削除することを確認しますか?', ddl: 'DDL',
addNewRow: '新しい行の追加', collation: '照合',
numberOfInserts: 'インサート数', resultsTable: '結果テーブル',
unableEditFieldWithoutPrimary: '結果セットでは主キーのないフィールドを編集できません',
editCell: 'セルを編集',
deleteRows: '行を削除 | {count} 行を削除',
confirmToDeleteRows: '行を削除します。確定しますか? | {count} 行を削除します。確定しますか?',
addNewRow: '行を新規追加',
numberOfInserts: '挿入レコード数',
affectedRows: '影響を受ける行', affectedRows: '影響を受ける行',
createNewDatabase: '新規データベースの作成', createNewDatabase: '新規データベースの作成',
databaseName: 'データベース名', databaseName: 'データベース名',
serverDefault: 'サーバーのデフォルト', serverDefault: 'サーバーのデフォルト',
deleteDatabase: 'データベースの削除', deleteDatabase: 'データベース削除',
editDatabase: 'データベースの編集', editDatabase: 'データベース編集',
clearChanges: '変更の消去', clearChanges: '変更をクリア',
addNewField: '新しいフィールドの追加', addNewField: '新規フィールドを追加',
manageIndexes: 'インデックスの管理', manageIndexes: 'インデックスの管理',
manageForeignKeys: '外部キーの管理', manageForeignKeys: '外部キーの管理',
allowNull: 'NULL を許可する', allowNull: 'NULL を許可',
zeroFill: 'ゼロフィル', zeroFill: 'ゼロ埋め',
customValue: 'カスタム値', customValue: 'カスタム値',
onUpdate: '更新時', onUpdate: 'ON UPDATE',
deleteField: 'フィールドの削除', deleteField: 'フィールドを削除',
createNewIndex: '新しいインデックスの作成', createNewIndex: '新規インデックスの作成',
addToIndex: 'インデックスへの追加', addToIndex: 'インデックスに追加',
createNewTable: '新しいテーブルの作成', createNewTable: '新規テーブルの作成',
emptyTable: '空のテーブル', emptyTable: 'テーブルを空にする',
deleteTable: 'テーブルの削除', duplicateTable: 'テーブルを複製',
emptyConfirm: '空にすることを確認しますか?', deleteTable: 'テーブルを削除',
thereAreNoIndexes: 'インデックスがありません', exportTable: 'テーブルをエクスポート',
emptyConfirm: 'テーブルを空にします。確定しますか?',
thereAreNoIndexes: 'インデックスがありません。',
thereAreNoForeign: '外部キーがありません。', thereAreNoForeign: '外部キーがありません。',
createNewForeign: '新しい外部キーの作成', createNewForeign: '新外部キーの作成',
referenceTable: '参照テーブル', referenceTable: '参照テーブル',
referenceField: '参照フィールド', referenceField: '参照フィールド',
foreignFields: '外部フィールド', foreignFields: '外部フィールド',
invalidDefault: '無効なデフォルト', invalidDefault: '無効なデフォルト',
onDelete: '削除時', onDelete: 'ON DELETE',
selectStatement: '選択文', selectStatement: 'SELECT 文',
triggerStatement: 'トリガー文', triggerStatement: 'トリガー文',
sqlSecurity: 'SQL セキュリティ', sqlSecurity: 'SQL セキュリティ',
updateOption: '更新オプション', updateOption: '更新オプション',
deleteView: 'ビューの削除', deleteView: 'ビューの削除',
createNewView: '新規ビューの作成', createNewView: '新規ビューの作成',
deleteTrigger: 'トリガー削除', deleteTrigger: 'トリガー削除',
createNewTrigger: '新しいトリガの作成', createNewTrigger: '新規トリガーの作成',
currentUser: '現在のユーザー', currentUser: '現在のユーザー',
routineBody: 'ルーチン本体', routineBody: 'ルーチン本体',
dataAccess: 'データアクセス', dataAccess: 'データアクセス',
thereAreNoParameters: 'パラメータありません', thereAreNoParameters: 'パラメーターがありません',
createNewParameter: '新しいパラメータの作成', createNewParameter: '新パラメータの作成',
createNewRoutine: 'ストアド・ルーチンの新規作成', createNewRoutine: '新規ストアド・ルーチンの作成',
deleteRoutine: 'ストアド・ルーチンの削除', deleteRoutine: 'ストアド・ルーチンの削除',
functionBody: '関数本体', functionBody: '関数本体',
createNewFunction: '新しい関数の作成', createNewFunction: '新関数の作成',
deleteFunction: '関数削除', deleteFunction: '関数削除',
schedulerBody: 'スケジューラ本体', schedulerBody: 'スケジューラ本体',
createNewScheduler: 'スケジューラの新規作成', createNewScheduler: '新規スケジューラの作成',
deleteScheduler: 'スケジューラ削除', deleteScheduler: 'スケジューラ削除',
preserveOnCompletion: '完了時に保存する', preserveOnCompletion: '完了時に保存する',
tableFiller: 'テーブルフィラー', tableFiller: 'テーブルフィラー',
fakeDataLanguage: 'フェイクデータの言語', fakeDataLanguage: 'フェイクデータの言語',
queryDuration: '問い合わせ期間', queryDuration: 'クエリ実行時間',
setNull: 'NULL の設定', setNull: 'NULL の設定',
processesList: 'プロセス一覧', processesList: 'プロセス一覧',
processInfo: 'プロセス情報', processInfo: 'プロセス情報',
manageUsers: 'ユーザーの管理', manageUsers: 'ユーザーの管理',
createNewSchema: '新しいスキーマの作成', createNewSchema: '新スキーマの作成',
schemaName: 'スキーマ名', schemaName: 'スキーマ名',
editSchema: 'スキーマの編集', editSchema: 'スキーマを編集',
deleteSchema: 'スキーマの削除', deleteSchema: 'スキーマを削除',
duplicateTable: 'テーブルを複製する',
noSchema: 'スキーマなし', noSchema: 'スキーマなし',
runQuery: 'クエリ実行', runQuery: 'クエリ実行',
thereAreNoTableFields: 'テーブルのフィールドがありません', thereAreNoTableFields: 'テーブルにフィールドがありません。',
newTable: '新しいテーブル', newTable: '新テーブル',
newView: '新しいビュー', newView: '新ビュー',
newTrigger: '新しいトリガー', newTrigger: '新トリガー',
newRoutine: '新しいルーチン', newRoutine: '新ルーチン',
newFunction: '新しい関数', newFunction: '新関数',
newScheduler: '新規スケジューラ', newScheduler: '新規スケジューラ',
newTriggerFunction: '新しいトリガー機能', newTriggerFunction: '新トリガー機能',
thereIsNoQueriesYet: 'まだ問い合わせはありません', thereAreNoQueriesYet: 'クエリはまだありません。',
searchForQueries: 'クエリの検索', searchForQueries: 'クエリの検索',
killProcess: 'プロセスの停止' killProcess: 'プロセスの停止',
exportSchema: 'スキーマをエクスポート',
importSchema: 'スキーマをインポート',
newInsertStmtEvery: 'それぞれに新しい INSERT 文',
processingTableExport: '{table} を処理中',
fetchingTableExport: '{table} のデータを取得中',
writingTableExport: '{table} のデータを書き込み中',
checkAllTables: 'すべてのテーブルを選択',
uncheckAllTables: 'すべてのテーブルの選択を解除',
killQuery: 'クエリを終了',
insertRow: '行を挿入 | 行を挿入',
commitMode: 'コミットモード',
autoCommit: '自動コミット',
manualCommit: '手動コミット',
importQueryErrors: '警告: {n} 個のエラーが発生しました。 | 警告: {n} 個のエラーが発生しました。',
executedQueries: '{n} 個のクエリを実行しました。 | {n} 個のクエリを実行しました。',
disableFKChecks: '外部キーのチェックを無効化',
formatQuery: 'クエリをフォーマット',
queryHistory: 'クエリ履歴',
clearQuery: 'クエリをクリア',
fillCell: 'セルを埋める',
executeSelectedQuery: '選択されたクエリを実行',
noResultsPresent: '結果がありません。',
sqlExportOptions: 'SQL エクスポートオプション',
targetTable: '対象テーブル',
switchDatabase: 'データベースを切り替え',
searchForElements: '要素を検索',
searchForSchemas: 'スキーマを検索',
savedQueries: '保存済のクエリ'
}, },
application: { application: {
settings: '設定', settings: '設定',
console: 'コンソール',
general: '一般', general: '一般',
themes: 'テーマ', themes: 'テーマ',
update: '更新情報', update: '更新',
about: 'About',
language: '言語', language: '言語',
shortcuts: 'ショートカット',
key: 'キー | キー', // キーボードのキー
event: 'イベント',
light: 'ライト', light: 'ライト',
dark: 'ダーク', dark: 'ダーク',
autoCompletion: 'オートコンプリート', autoCompletion: 'オートコンプリート',
application: 'アプリケーション', application: 'アプリケーション',
editor: 'エディター', editor: 'エディター',
scratchpad: 'スクラッチパッド',
changelog: '変更履歴', changelog: '変更履歴',
madeWithJS: '💛 と JavaScript で作られています。', small: '小',
checkForUpdates: '更新情報の確認', medium: '中',
noUpdatesAvailable: 'アップデートがありません', large: '大',
checkingForUpdate: 'アップデートを確認中', appearance: '外観',
checkFailure: 'チェックに失敗しました、後で試してください', color: '色',
updateAvailable: 'アップデートが利用可能です', label: 'ラベル',
downloadingUpdate: 'アップデートのダウンロード', icon: 'アイコン',
updateDownloaded: 'アップデートのダウンロード', fileName: 'ファイル名',
restartToInstall: 'Antares を再起動してインストールしてください', choseFile: 'ファイルを選択',
data: 'データ',
password: 'パスワード',
required: '必須',
madeWithJS: '💛 と JavaScript で作られています!',
checkForUpdates: '更新を確認',
noUpdatesAvailable: '更新はありません。',
checkingForUpdate: '更新を確認中',
checkFailure: 'チェックに失敗しました、後で試してください。',
updateAvailable: '更新が利用可能です。',
downloadingUpdate: '更新をダウンロード中',
updateDownloaded: '更新をダウンロード済',
restartToInstall: 'Antares を再起動してインストールしてください。',
includeBetaUpdates: 'ベータ版アップデートを含む',
notificationsTimeout: '通知のタイムアウト', notificationsTimeout: '通知のタイムアウト',
openNewTab: '新しいタブを開く', openNewTab: '新しいタブを開く',
unsavedChanges: '保存されていない変更', unsavedChanges: '保存されていない変更',
discardUnsavedChanges: '保存されていない変更があります。このタブを閉じると、これらの変更は破棄されます。', discardUnsavedChanges: '保存されていない変更があります。このタブを閉じると、これらの変更は破棄されます。',
applicationTheme: 'アプリケーションテーマ', applicationTheme: 'アプリケーションテーマ',
editorTheme: 'エディターテーマ', editorTheme: 'エディターテーマ',
wrapLongLines: '長い行の折り返し', wrapLongLines: '長い行を折り返す',
includeBetaUpdates: 'ベータ版アップデートを含む', markdownSupported: 'Markdown をサポートしています。',
markdownSupported: 'マークダウン対応', plantATree: '木を植える',
dataTabPageSize: 'DATA タブのページサイズ', dataTabPageSize: 'DATA タブのページサイズ',
noOpenTabs: '開いているタブがありません。左のバーでナビゲートするか', noOpenTabs: '開いているタブがありません。',
restorePreviousSession: '前のセッションに戻す', restorePreviousSession: '前のセッションに戻す',
searchForElements: '要素の検索' closeTab: 'タブを閉じる',
goToDownloadPage: 'ダウンロードページへ移動',
disableBlur: 'ぼかしを無効化',
missingOrIncompleteTranslation: '翻訳が不足しているか、または不完全ですか?',
findOutHowToContribute: 'コントリビュートの方法を調べる',
reportABug: 'バグを報告',
nextTab: '次のタブ',
previousTab: '前のタブ',
selectTabNumber: 'タブ番号を選択 {param}',
toggleConsole: 'コンソールを切り替え',
addShortcut: 'ショートカットを追加',
editShortcut: 'ショートカットを編集',
deleteShortcut: 'ショートカットを削除',
restoreDefaults: 'デフォルトに戻す',
restoreDefaultsQuestion: 'デフォルト値に戻します。確定しますか?',
registerAShortcut: 'ショートカットを登録',
invalidShortcutMessage: '無効な組み合わせです、続けて入力してください。',
shortcutAlreadyExists: 'ショートカットが既に存在します。',
saveContent: '内容を保存',
openAllConnections: 'すべての接続を開く',
openSettings: '設定を開く',
runOrReload: '実行またはリロード',
openFilter: 'フィルタを開く',
nextResultsPage: '次の結果ページ',
previousResultsPage: '前の結果ページ',
editFolder: 'フォルダーを編集',
folderName: 'フォルダー名',
deleteFolder: 'フォルダーを削除',
newFolder: '新規フォルダー',
outOfFolder: 'フォルダーの外',
editConnectionAppearance: '接続の外観を編集',
defaultCopyType: 'デフォルトのコピータイプ',
showTableSize: 'サイドバーにテーブルのサイズを表示',
showTableSizeDescription: 'MySQL/MariaDB のみ。このオプションを有効にすると、多数のテーブルを持つスキーマのパフォーマンスに影響を与える可能性があります。',
switchSearchMethod: '検索方法を切り替え',
phpArray: 'PHP 配列',
closeAllTabs: 'すべてのタブを閉じる',
closeOtherTabs: '他のタブを閉じる',
closeTabsToLeft: '左のタブを閉じる',
closeTabsToRight: '右のタブを閉じる',
csvFieldDelimiter: 'フィールドの区切り文字',
csvLinesTerminator: '行の終端',
csvStringDelimiter: '文字列の区切り文字',
csvIncludeHeader: 'ヘッダを含める',
csvExportOptions: 'CSV エクスポートオプション',
exportData: 'データをエクスポート',
exportDataExplanation: 'Anteras に保存された接続をエクスポートします。エクスポートされたファイルを暗号化するためのパスワードが要求されます。',
importData: 'データをインポート',
importDataExplanation: '接続を含む .antares ファイルをインポートします。エクスポート時に定義したパスワードを入力する必要があります。',
includeConnectionPasswords: '接続パスワードを含める',
includeFolders: 'フォルダーを含める',
encryptionPassword: '暗号化パスワード',
encryptionPasswordError: '暗号化パスワードは8文字以上でなければなりません。',
ignoreDuplicates: '重複を無視',
wrongImportPassword: 'インポートパスワードが誤っています。',
wrongFileFormat: 'ファイルフォーマットが誤っています。',
dataImportSuccess: 'データのインポートに成功しました。',
note: 'ノート | ノート',
thereAreNoNotesYet: 'ノートはまだありません。',
addNote: 'ノートを追加',
editNote: 'ノートを編集',
saveAsNote: 'ノートとして保存',
showArchivedNotes: 'アーカイブ済のノートを表示',
hideArchivedNotes: 'アーカイブ済のノートを非表示',
tag: 'タグ', // ノートのタグ
saveFile: 'ファイルを保存',
saveFileAs: 'ファイルを別名で保存',
openFile: 'ファイルを開く',
openNotes: 'ノートを開く'
}, },
faker: { faker: {
address: '住所', address: '住所',
@ -245,42 +405,42 @@ export const jaJP = {
database: 'データベース', database: 'データベース',
date: '日付', date: '日付',
finance: 'ファイナンス', finance: 'ファイナンス',
// git: 'ギット', git: 'Git',
hacker: 'ハッカー', hacker: 'ハッカー',
internet: 'インターネット', internet: 'インターネット',
// lorem: 'ローレム', lorem: 'Lorem',
name: '名前', name: '名前',
music: '音楽', music: '音楽',
phone: '電話', phone: '電話',
random: 'ランダム', random: 'ランダム',
system: 'システム', system: 'システム',
time: '時間', time: '時間',
vehicle: '車', vehicle: '車',
zipCode: '郵便番号', zipCode: '郵便番号',
zipCodeByState: '都道府県別郵便番号', zipCodeByState: '都道府県別郵便番号',
city: '都市名', city: '都市名',
cityPrefix: '市のプレフィックス', cityPrefix: '市のプレフィックス',
citySuffix: '市の接尾辞', citySuffix: '都市のサフィックス',
streetName: '通りの名前', streetName: '通りの名前',
streetAddress: 'ストリートアドレス', streetAddress: '通りの住所',
streetSuffix: '通りの接尾辞', streetSuffix: '通りのサフィックス',
streetPrefix: 'ストリートプレフィックス', streetPrefix: '通りのプレフィックス',
secondaryAddress: '副住所', secondaryAddress: '副住所',
county: '郡', county: '郡',
country: '国名', country: '国名',
countryCode: '国コード', countryCode: '国コード',
state: '州', state: '州',
stateAbbr: '州の略', stateAbbr: '州の略',
latitude: '緯度', latitude: '緯度',
longitude: '経度', longitude: '経度',
direction: '方向', direction: '方向',
cardinalDirection: '枢機卿の方向', cardinalDirection: '4方位',
ordinalDirection: '序列方向', ordinalDirection: '8方位',
nearbyGPSCoordinate: '近くのGPS座標', nearbyGPSCoordinate: '近くのGPS座標',
timeZone: 'タイムゾーン', timeZone: 'タイムゾーン',
color: '色', color: '色',
department: '部門', department: '部門',
productName: '品名', productName: '品名',
price: '価格', price: '価格',
productAdjective: '製品の形容詞', productAdjective: '製品の形容詞',
productMaterial: '製品の素材', productMaterial: '製品の素材',
@ -288,29 +448,30 @@ export const jaJP = {
productDescription: '製品の説明', productDescription: '製品の説明',
suffixes: 'サフィックス', suffixes: 'サフィックス',
companyName: '会社名', companyName: '会社名',
companySuffix: '会社のサフィックス', companySuffix: '会社のサフィックス',
catchPhrase: 'キャッチフレーズ', catchPhrase: 'キャッチフレーズ',
// bs: 'BS', bs: 'BS',
catchPhraseAdjective: 'キャッチフレーズ形容詞', catchPhraseAdjective: 'キャッチフレーズ形容詞',
catchPhraseDescriptor: 'キャッチフレーズの説明文', catchPhraseDescriptor: 'キャッチフレーズの説明文',
catchPhraseNoun: 'キャッチフレーズの名詞', catchPhraseNoun: 'キャッチフレーズの名詞',
bsAdjective: 'BS 形容詞', bsAdjective: 'BS 形容詞',
bsBuzz: 'BS の話題', bsBuzz: 'BS の話題',
bsNoun: 'BS の名詞', bsNoun: 'BS の名詞',
column: 'ラム', column: 'ラム',
type: 'タイプ', type: 'タイプ',
collation: '照合', collation: '照合',
engine: 'エンジン', engine: 'エンジン',
past: '過去', past: '過去',
now: '現在',
future: '未来', future: '未来',
between: '間', between: '間',
recent: '最近', recent: '最近',
soon: 'すぐ', soon: 'まもなく',
month: '月', month: '月',
weekday: '曜日', weekday: '曜日',
account: 'アカウント', account: '口座',
accountName: '口座名', accountName: '口座名',
routingNumber: 'ルーティング番号', routingNumber: 'ルーティングナンバー',
mask: 'マスク', mask: 'マスク',
amount: '金額', amount: '金額',
transactionType: '取引の種類', transactionType: '取引の種類',
@ -318,19 +479,19 @@ export const jaJP = {
currencyName: '通貨名', currencyName: '通貨名',
currencySymbol: '通貨記号', currencySymbol: '通貨記号',
bitcoinAddress: 'Bitcoin アドレス', bitcoinAddress: 'Bitcoin アドレス',
litecoinAddress: 'ライトコインのアドレス', litecoinAddress: 'Litecoin アドレス',
creditCardNumber: 'クレジットカード番号', creditCardNumber: 'クレジットカード番号',
creditCardCVV: 'クレジットカードの CVV', creditCardCVV: 'クレジットカードの CVV',
ethereumAddress: 'イーサリアムのアドレス', ethereumAddress: 'Ethereum アドレス',
iban: 'アイバン', iban: 'IBAN',
bic: 'ビック', bic: 'BIC',
transactionDescription: '取引内容', transactionDescription: '取引の説明',
branch: 'ブランチ', branch: 'ブランチ',
commitEntry: 'コミットエントリ', commitEntry: 'コミットエントリ',
commitMessage: 'コミットメッセージ', commitMessage: 'コミットメッセージ',
commitSha: 'コミット SHA', commitSha: 'コミット SHA',
shortSha: 'ショート SHA', shortSha: 'ショート SHA',
abbreviation: '省略形', abbreviation: '略称',
adjective: '形容詞', adjective: '形容詞',
noun: '名詞', noun: '名詞',
verb: '動詞', verb: '動詞',
@ -345,23 +506,23 @@ export const jaJP = {
domainName: 'ドメイン名', domainName: 'ドメイン名',
domainSuffix: 'ドメインのサフィックス', domainSuffix: 'ドメインのサフィックス',
domainWord: 'ドメイン名', domainWord: 'ドメイン名',
ip: 'Ip', ip: 'IP',
ipv6: 'Ipv6', ipv6: 'IPv6',
userAgent: 'ユーザーエージェント', userAgent: 'User-Agent',
// mac: 'Mac', mac: 'MAC',
password: 'パスワード', password: 'パスワード',
word: 'ワード', word: '単語',
words: '単語', words: '単語',
sentence: '文章', sentence: '文章',
slug: 'スラッグ', slug: 'Slug',
sentences: 'センテンス', sentences: '文章',
paragraph: 'パラグラフ', paragraph: '段落',
paragraphs: 'パラグラフ', paragraphs: '段落',
text: 'テキスト', text: 'テキスト',
lines: '行', lines: '行',
genre: 'ジャンル', genre: 'ジャンル',
firstName: 'ファーストネーム', firstName: '',
lastName: '苗字', lastName: '',
middleName: 'ミドルネーム', middleName: 'ミドルネーム',
findName: 'フルネーム', findName: 'フルネーム',
jobTitle: '役職名', jobTitle: '役職名',
@ -371,35 +532,35 @@ export const jaJP = {
title: '役職名', title: '役職名',
jobDescriptor: '職務記述書', jobDescriptor: '職務記述書',
jobArea: '職務領域', jobArea: '職務領域',
jobType: '仕事の種類', jobType: '職種',
phoneNumber: '電話番号', phoneNumber: '電話番号',
phoneNumberFormat: '電話番号のフォーマット', phoneNumberFormat: '電話番号のフォーマット',
phoneFormats: '電話番号のフォーマット', phoneFormats: '電話番号のフォーマット',
// number: '番号', number: '数字',
// float: 'フロート', float: '浮動小数点数',
arrayElement: '配列要素', arrayElement: '配列要素',
arrayElements: '配列要素', arrayElements: '配列要素',
objectElement: 'オブジェクトの要素', objectElement: 'オブジェクトの要素',
// uuid: 'Uuid', uuid: 'UUID',
// boolean: 'ブール', boolean: 'ブール値',
image: '画像', image: '画像',
locale: 'ロケール', locale: 'ロケール',
alpha: '英字', alpha: '英字',
alphaNumeric: '英数字', alphaNumeric: '英数字',
hexaDecimal: '16進', hexaDecimal: '16進',
fileName: 'ファイル名', fileName: 'ファイル名',
commonFileName: '一般的なファイル名', commonFileName: '一般的なファイル名',
mimeType: 'Mimeタイプ', mimeType: 'MIME タイプ',
commonFileType: '共通のファイルタイプ', commonFileType: '共通のファイルタイプ',
commonFileExt: '共通のファイル拡張子', commonFileExt: '共通のファイル拡張子',
fileType: 'ファイルタイプ', fileType: 'ファイルタイプ',
fileExt: 'ファイル拡張子', fileExt: 'ファイル拡張子',
directoryPath: 'ディレクトリパス', directoryPath: 'ディレクトリパス',
filePath: 'ファイルパス', filePath: 'ファイルパス',
// semver: 'セムバー', semver: 'セマンティックバージョニング',
manufacturer: 'メーカー', manufacturer: 'メーカー',
model: 'モデル', model: 'モデル',
fuel: '燃料' fuel: '燃料',
// vin: 'Vin' vin: '車両識別番号'
} }
}; };

View File

@ -228,7 +228,7 @@ export const koKR = {
newFunction: '새 함수', newFunction: '새 함수',
newScheduler: '새 스케줄러', newScheduler: '새 스케줄러',
newTriggerFunction: '새 트리거 함수', newTriggerFunction: '새 트리거 함수',
thereIsNoQueriesYet: '아직 쿼리가 없습니다', thereAreNoQueriesYet: '아직 쿼리가 없습니다',
searchForQueries: '쿼리 검색', searchForQueries: '쿼리 검색',
killProcess: '프로세스 종료', killProcess: '프로세스 종료',
exportSchema: '스키마 내보내기', exportSchema: '스키마 내보내기',

View File

@ -100,7 +100,7 @@ export const nlNL = {
readOnlyMode: 'Alleen lezen modus', readOnlyMode: 'Alleen lezen modus',
untrustedConnection: 'Niet vertrouwde verbinding', untrustedConnection: 'Niet vertrouwde verbinding',
allConnections: 'Alle verbindingen', allConnections: 'Alle verbindingen',
searchForConnections: 'Search for connections' searchForConnections: 'Zoek naar verbindingen'
}, },
database: { database: {
schema: 'Schema', schema: 'Schema',
@ -233,8 +233,8 @@ export const nlNL = {
newFunction: 'Nieuwe function', newFunction: 'Nieuwe function',
newScheduler: 'Nieuwe scheduler', newScheduler: 'Nieuwe scheduler',
newTriggerFunction: 'Nieuwe trigger function', newTriggerFunction: 'Nieuwe trigger function',
thereIsNoQueriesYet: 'There is no queries yet', thereAreNoQueriesYet: 'Er zijn nog geen queries',
searchForQueries: 'Search for queries', searchForQueries: 'Zoek naar queries',
killProcess: 'Sluit proces af', killProcess: 'Sluit proces af',
exportSchema: 'Exporteer schema', exportSchema: 'Exporteer schema',
importSchema: 'Importeer schema', importSchema: 'Importeer schema',

View File

@ -231,7 +231,7 @@ export const ptBR = {
newFunction: 'Nova função', newFunction: 'Nova função',
newScheduler: 'Novo agendento', newScheduler: 'Novo agendento',
newTriggerFunction: 'Novo gatinho de função', newTriggerFunction: 'Novo gatinho de função',
thereIsNoQueriesYet: 'Nenhuma consulta ainda', thereAreNoQueriesYet: 'Nenhuma consulta ainda',
searchForQueries: 'Pesquisar por consultas', searchForQueries: 'Pesquisar por consultas',
killProcess: 'Matar processo', killProcess: 'Matar processo',
exportSchema: 'Exportar banco de dados', exportSchema: 'Exportar banco de dados',

View File

@ -229,7 +229,7 @@ export const ruRU = {
newFunction: 'Новая функция', newFunction: 'Новая функция',
newScheduler: 'Новый планировщик', newScheduler: 'Новый планировщик',
newTriggerFunction: 'Новая функция триггера', newTriggerFunction: 'Новая функция триггера',
thereIsNoQueriesYet: 'Запросы пока отсутствуют', thereAreNoQueriesYet: 'Запросы пока отсутствуют',
searchForQueries: 'Поиск по запросам', searchForQueries: 'Поиск по запросам',
killProcess: 'Убить процесс', killProcess: 'Убить процесс',
exportSchema: 'Экспорт схемы', exportSchema: 'Экспорт схемы',

View File

@ -1,4 +1,4 @@
export const localesNames: {[key: string]: string} = { export const localesNames: Record<string, string> = {
'en-US': 'English', 'en-US': 'English',
'it-IT': 'Italiano', 'it-IT': 'Italiano',
'ar-SA': 'العربية', 'ar-SA': 'العربية',

View File

@ -238,7 +238,7 @@ export const ukUA = {
newFunction: 'Нова функція', newFunction: 'Нова функція',
newScheduler: 'Новий планувальник', newScheduler: 'Новий планувальник',
newTriggerFunction: 'Нова функція тригера', newTriggerFunction: 'Нова функція тригера',
thereIsNoQueriesYet: 'Поки що немає запитів', thereAreNoQueriesYet: 'Поки що немає запитів',
searchForQueries: 'Пошук запитів', searchForQueries: 'Пошук запитів',
killProcess: 'Завершити процес', killProcess: 'Завершити процес',
exportSchema: 'Експорт схеми', exportSchema: 'Експорт схеми',

Some files were not shown because too many files have changed in this diff Show More