1
1
mirror of https://github.com/Fabio286/antares.git synced 2025-06-05 21:59:22 +02:00

Compare commits

...

222 Commits

Author SHA1 Message Date
d702ce3fa9 chore(release): 0.7.16-beta.1 2023-08-18 12:22:56 +02:00
63544e95da Merge branch 'feat/export-connections' of https://github.com/antares-sql/antares into develop 2023-08-18 12:12:05 +02:00
6df214558f feat: ability to import connections 2023-08-18 12:11:08 +02:00
f5b86e59e7 feat: ability to export connections 2023-08-17 13:48:55 +02:00
27f0068415 Merge pull request #652 from Lawondyss/develop
Add CZ translation and titles for buttons
2023-08-14 12:16:44 +02:00
Ladislav Vondráček
52e42fa1b5 fix: add some missing titles of buttons 2023-08-14 11:50:38 +02:00
Ladislav Vondráček
5b9b539bc7 chore(i18n): add czech translation 2023-08-14 11:47:41 +02:00
832aa75ebe fix(SQLite): improved view body parsing 2023-08-14 10:44:36 +02:00
aba67f3872 refactor(translation): refactor ca-ES to new format 2023-08-14 09:16:40 +02:00
5a1644f023 Merge branches 'develop' and 'master' of https://github.com/antares-sql/antares into develop 2023-08-14 09:07:55 +02:00
e372712556 Merge pull request #651 from antares-sql/all-contributors/add-markusand
docs: add markusand as a contributor for translation
2023-08-14 09:06:55 +02:00
d4727a7d20 Merge pull request #649 from markusand/master
feat: Add support for catalan language
2023-08-14 09:06:42 +02:00
allcontributors[bot]
f2c2f33afa docs: update .all-contributorsrc [skip ci] 2023-08-14 07:06:40 +00:00
allcontributors[bot]
6dc4bdd0c0 docs: update README.md [skip ci] 2023-08-14 07:06:39 +00:00
Marc Vilella
9c6f1a9ea5 feat: Add catalan language 2023-08-13 19:20:26 +02:00
dd264f802e ci: update snapcraft.yaml 2023-08-11 18:12:38 +02:00
fed0e10702 Merge branch 'master' of https://github.com/antares-sql/antares into beta 2023-08-11 17:34:50 +02:00
e7ce4ef6ed Merge branch 'master' of https://github.com/antares-sql/antares 2023-08-11 17:34:30 +02:00
e03f2eef49 ci: update snapcraft.yaml 2023-08-11 17:34:27 +02:00
3a77f0818d chore(release): 0.7.16-beta.0 2023-08-11 16:50:20 +02:00
6cd2f89bbf refactor: ts improvements 2023-08-11 16:42:34 +02:00
16ec82eb7e Merge branch 'develop' of https://github.com/antares-sql/antares into beta 2023-08-11 16:30:16 +02:00
84b2255bf4 feat: ability to export a table dump from table context menu 2023-08-11 11:49:49 +02:00
2e39d810b5 feat: ability to change sql dump file name 2023-08-11 09:52:25 +02:00
56918d89c7 Merge branch 'master' of https://github.com/antares-sql/antares into develop 2023-08-09 18:45:05 +02:00
083198af8f chore: update README.md 2023-08-09 18:44:31 +02:00
ad7e459c68 feat: support to links in changelog 2023-08-08 18:05:27 +02:00
8c4c93cb07 chore(translation): add translation helper script 2023-08-04 12:28:03 +02:00
eee85d24b7 ci: update snapcraft.yml 2023-08-03 18:36:27 +02:00
92fe029906 refactor: minor translation improvements 2023-08-03 18:36:09 +02:00
1dd2147b68 refactor(translation): complete refactor of translations structure 2023-08-03 18:28:50 +02:00
5a54e7ac33 Merge branch 'master' of https://github.com/antares-sql/antares into beta 2023-08-02 09:21:24 +02:00
c19bac2373 Merge branch 'master' of https://github.com/antares-sql/antares into develop 2023-08-02 09:20:29 +02:00
7b1cb4ff86 fix(MySQL): missing error details on mysql importer with some exceptione 2023-08-01 10:04:00 +02:00
f120af25f4 build(deps): update mysql2 2023-08-01 10:02:35 +02:00
83b3ca563a chore(release): 0.7.15 2023-07-31 13:04:53 +02:00
0fab3bc43c ci: arrangements for beta channel 2023-07-31 13:04:30 +02:00
473c4636cf ci: arrangements for beta channel 2023-07-31 10:55:05 +02:00
2e11a0c032 chore: update dependabot branch target 2023-07-26 17:57:14 +02:00
193042b92d chore: update dependabot branch target 2023-07-26 17:42:16 +02:00
51b14195a8 fix(MySQL): error insertinf new fields with some MySQL configurations, fixes #360 2023-07-24 09:09:11 +02:00
f831fcd442 fix: display content of BLOB fields if not binary, fixes #628 2023-07-21 18:17:31 +02:00
3104847b92 Merge pull request #630 from SmileYzn/master
Update pt-BR.ts 🇧🇷
2023-07-21 08:44:11 +02:00
Cleverson
bc0b029369 Update pt-BR.ts 2023-07-20 16:55:23 -03:00
efee7f3a0e Merge branch 'master' of https://github.com/antares-sql/antares 2023-07-17 18:00:43 +02:00
7c820b1827 fix(MySQL): unable to set more than one value in SET fields 2023-07-17 18:00:40 +02:00
38ec703705 Merge pull request #625 from antares-sql/all-contributors/add-zxp19821005
docs: add zxp19821005 as a contributor for platform
2023-07-17 09:34:48 +02:00
allcontributors[bot]
2afb66a2e6 docs: update .all-contributorsrc [skip ci] 2023-07-17 07:34:13 +00:00
allcontributors[bot]
5a5af3af5e docs: update README.md [skip ci] 2023-07-17 07:34:12 +00:00
8f0e5407ae Merge branch 'master' of https://github.com/antares-sql/antares 2023-07-16 11:33:30 +02:00
8bed7c2f34 chore: update README.md AUR link 2023-07-16 11:33:28 +02:00
f4a2f43ea4 Merge pull request #621 from brdtheo/i18n-french-translation
chore(i18n): update french translation
2023-07-16 11:16:41 +02:00
Théo Billardey
6063b3f697 chore(i18n): update french translation 2023-07-15 21:57:18 +02:00
2f5fa0f2e4 feat: export file name with hour, minutes and seconds, closes #610 2023-07-12 15:20:44 +02:00
703a515462 chore(release): 0.7.14 2023-07-07 15:22:53 +02:00
9d8e9a5e1f perf(PostgreSQL): minor improvement to schema selection 2023-07-07 15:22:16 +02:00
1c73503138 fix: unable to insert new rows 2023-07-07 15:21:48 +02:00
32bbc45329 feat: button to open view settings tab 2023-07-07 09:04:40 +02:00
9d90dc362e chore(release): 0.7.13 2023-07-06 22:02:55 +02:00
e808b86c52 fix(PostgreSQL): unable to connect to database, fixes #614 2023-07-06 22:01:41 +02:00
6e01f0f2e7 fix: unable to copy as sql inserts with BIT fileds, fixes #613 2023-07-06 12:06:32 +02:00
38bfea279c feat: button to open table settings tab, closes #608 2023-07-05 18:27:30 +02:00
0044522390 Merge pull request #611 from 64knl/feat/update-connection-icons
feat: update connection icons
2023-07-05 17:42:49 +02:00
Rene
462ede8dc7 feat: update connection
icons to better match other icons
2023-07-05 15:50:17 +02:00
60dd4df5ec chore(release): 0.7.12 2023-07-03 09:04:25 +02:00
fa006798cf Merge pull request #602 from antares-sql/all-contributors/add-64knl
docs: add 64knl as a contributor for translation
2023-06-30 08:07:37 +02:00
c5abc3d6b2 Merge pull request #599 from 64knl/feat/locale-dutch
feat: Add Dutch translation
2023-06-30 08:07:15 +02:00
allcontributors[bot]
a8dc30c9dd docs: update .all-contributorsrc [skip ci] 2023-06-30 06:07:02 +00:00
allcontributors[bot]
2cfed3e79b docs: update README.md [skip ci] 2023-06-30 06:07:01 +00:00
Rene
001983c5a2 fix: update source string to fix spelling 2023-06-24 17:13:12 +02:00
470f7455c0 Merge pull request #598 from antares-sql/all-contributors/add-64knl
docs: add 64knl as a contributor for code
2023-06-24 13:18:21 +02:00
allcontributors[bot]
70e00a7ee6 docs: update .all-contributorsrc [skip ci] 2023-06-24 11:18:08 +00:00
allcontributors[bot]
b76247e304 docs: update README.md [skip ci] 2023-06-24 11:18:07 +00:00
f58c30ff17 Merge pull request #597 from 64knl/feat/sort-available-languages
feat: Sort available languages
2023-06-24 13:16:46 +02:00
5bdbebfbc3 Merge pull request #596 from 64knl/fix/spelling-appearance
fix: spelling corrections
2023-06-24 13:16:27 +02:00
Rene
03c4a1c797 wip: more translations 2023-06-22 19:06:02 +02:00
Rene
e18604b3ce wip: translation 2023-06-22 18:59:23 +02:00
Rene
3ff8d2571b feat: Sort available languages alphabetically 2023-06-22 18:27:41 +02:00
Rene
f22f8d2317 wip: translation 2023-06-22 18:22:44 +02:00
Rene
8a4a099e37 fix: rename components and variables in SettingBarContext 2023-06-22 18:18:13 +02:00
Rene
702ffb81ef style: formatting 2023-06-22 17:40:23 +02:00
Rene
93e16fdda2 fix: spelling corrections
- Updated appearence -> appearance across files
- Fixed some English spelling mistakes
2023-06-22 17:36:53 +02:00
Rene
575c8ea8ca feat: more translations 2023-06-22 15:06:53 +02:00
Rene
4ff1d107b8 feat: add Dutch language support 2023-06-22 14:43:15 +02:00
3ad1e51f42 feat: format options of csv export, closes #196 2023-06-14 19:47:09 +02:00
e07e7b736e fix(PostgreSQL): unable to export as sql inserts 2023-06-14 09:38:54 +02:00
89815bf5e7 feat(PostgreSQL): ability to switch the database, closes #432 2023-06-13 18:10:52 +02:00
9d00f58998 chore: update README.md 2023-06-12 21:44:06 +02:00
f5d001846a Merge pull request #554 from antares-sql/dependabot/npm_and_yarn/sql-formatter-12.2.0
build(deps): bump sql-formatter from 12.0.3 to 12.2.0
2023-06-12 15:07:19 +02:00
0a6907b549 Merge branch 'master' into dependabot/npm_and_yarn/sql-formatter-12.2.0 2023-06-12 15:01:48 +02:00
322f92b734 Merge pull request #591 from m1khal3v/patch-3
Update ru-RU.ts
2023-06-07 03:57:34 +02:00
Anton Mikhalev
038e4494f5 Update ru-RU.ts 2023-06-06 23:41:22 +03:00
56b71ec0d1 chore(release): 0.7.11 2023-06-04 10:43:42 +02:00
787014c38d feat: context menu to close tabs, closes #392 2023-06-03 19:13:20 +02:00
e60726c741 fix: disable filter during table content loading, fixes #588 2023-06-01 18:00:16 +02:00
904670781d fix(MySQL): unable to get users list with some db settings 2023-06-01 11:47:31 +02:00
22bdaac18b fix: weird behavior in some text editors 2023-06-01 11:42:19 +02:00
446199827b fix: unique keys not recognized in table settings on some MariaDB versions 2023-05-30 18:33:37 +02:00
53a71d55c5 chore(release): 0.7.10 2023-05-28 13:32:38 +02:00
03638c0553 feat: copy rows as PHP array 2023-05-28 13:31:44 +02:00
8968179c11 feat: export table content or query results as PHP array, closes #575 2023-05-28 13:20:28 +02:00
2c0b4ffe1f fix: disable shorctut to show Ace editor settings, fixes #585 2023-05-26 18:17:23 +02:00
f454b4bb1c feat: DDL query in table settings for MySQL and PostgreSQL, closes #581 2023-05-25 18:51:56 +02:00
56698725cb Merge pull request #583 from m1khal3v/patch-2
Update ru-RU.ts
2023-05-21 00:40:16 +02:00
Anton Mikhalev
1896013267 Update ru-RU.ts 2023-05-19 14:14:35 +03:00
17eeb6d38e feat: keepalive on mysql/postgre connections, should fix #577 2023-05-14 18:48:21 +02:00
5e83b4466d Merge pull request #574 from antares-sql/all-contributors/add-m1khal3v
docs: add m1khal3v as a contributor for translation
2023-05-05 09:13:09 +02:00
allcontributors[bot]
45d1934f96 docs: update .all-contributorsrc [skip ci] 2023-05-05 07:12:57 +00:00
allcontributors[bot]
7821e25bdb docs: update README.md [skip ci] 2023-05-05 07:12:56 +00:00
ae8d558989 Merge pull request #572 from m1khal3v/patch-1
Update ru-RU.ts
2023-05-05 09:12:12 +02:00
Anton Mikhalev
b348c83501 Update ru-RU.ts 2023-05-04 16:11:10 +03:00
Anton Mikhalev
f58a12ebd5 Update ru-RU.ts 2023-05-04 16:01:17 +03:00
Anton Mikhalev
b806deeed0 Update ru-RU.ts 2023-05-04 14:36:47 +03:00
Anton Mikhalev
4e1be838bd Update ru-RU.ts 2023-05-04 14:19:38 +03:00
Anton Mikhalev
bb7ec76ced Update ru-RU.ts 2023-05-04 14:05:12 +03:00
Anton Mikhalev
e2b843cd18 Update ru-RU.ts 2023-05-04 14:01:52 +03:00
8c9713e805 ci: action to create linux arm64 artifacts 2023-05-02 08:57:14 +02:00
786de6a7ba ci: disable arm64 linux target 2023-05-01 13:41:01 +02:00
1bf54a69fd ci: disable arm deb target 2023-05-01 13:11:41 +02:00
3ddfd6bb44 chore(release): 0.7.9 2023-05-01 10:45:41 +02:00
c48266c336 feat: option to choose the target table of an SQL INSERT exportation, closes #556 2023-04-28 18:46:24 +02:00
96e1ceb1d2 perf(translation): update italian translation 2023-04-27 18:12:19 +02:00
19859f45f4 feat: no table results message 2023-04-27 18:11:36 +02:00
74c136f833 fix: unable to delete rows with null values and no primary key 2023-04-27 14:14:04 +02:00
af91d96db6 fix: sidebar height out of visible area 2023-04-26 18:52:32 +02:00
0cd55fbfe9 fix: vertical scrollbar does not reset after performing a search, fixes #567 2023-04-21 00:47:31 +02:00
55aee163b6 Merge pull request #562 from antares-sql/all-contributors/add-555cider
docs: add 555cider as a contributor for translation
2023-04-17 09:22:30 +02:00
allcontributors[bot]
eb172022fa docs: update .all-contributorsrc [skip ci] 2023-04-17 07:22:12 +00:00
allcontributors[bot]
0d5cac27ed docs: update README.md [skip ci] 2023-04-17 07:22:11 +00:00
baef4ea4d1 feat(translation): ko-KR translation, closes #561 2023-04-17 09:21:01 +02:00
6b3b22a01a chore(release): 0.7.8 2023-04-12 08:57:17 +02:00
ebf7780c3c refactor: fix ts error 2023-04-08 13:12:29 +02:00
0f24c80e5a feat(MySQL): option to export from results SQL INSERTS in chunks, closes #501 2023-04-08 13:03:46 +02:00
afa61a9bc2 fix: unable to export BLOB values from table content o query result 2023-04-08 09:39:28 +02:00
8be9f932e7 feat: filter schemas in sidebar, closes #555 2023-04-07 18:06:41 +02:00
d802b32597 fix: triggers not exported if related table not included 2023-04-04 11:55:39 +02:00
dependabot[bot]
12f3e03b45 build(deps): bump sql-formatter from 12.0.3 to 12.2.0
Bumps [sql-formatter](https://github.com/sql-formatter-org/sql-formatter) from 12.0.3 to 12.2.0.
- [Release notes](https://github.com/sql-formatter-org/sql-formatter/releases)
- [Changelog](https://github.com/sql-formatter-org/sql-formatter/blob/master/.release-it.json)
- [Commits](https://github.com/sql-formatter-org/sql-formatter/compare/v12.0.3...v12.2.0)

---
updated-dependencies:
- dependency-name: sql-formatter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-01 20:01:44 +00:00
28f0419af4 refactor: remove viewMenu on macOS 2023-03-30 18:07:24 +02:00
52108d7613 fix(MySQL): missing scale for FLOAT type 2023-03-28 17:27:47 +02:00
df6625af49 chore(release): 0.7.7 2023-03-10 18:12:26 +01:00
59846e6ff4 fix(MySQL): missing table information in table setting tab 2023-03-09 09:07:57 +01:00
63fece2a1b fix(Linux): remove app menu shown pressing ALT key 2023-03-06 18:29:53 +01:00
9ef475ec3f chore: package.json changes 2023-03-06 09:38:51 +01:00
3546c57e39 fix: unable to set string fields default values starting with 0 2023-03-02 18:09:16 +01:00
8730be02af ci: minor change 2023-03-02 09:49:59 +01:00
b925ff9c01 fix(Linux): remove app menu shown pressing ALT key 2023-03-02 09:07:10 +01:00
2c46269cf2 fix: hide table size tooltip if disabled 2023-03-02 09:06:28 +01:00
23d8467154 chore(release): 0.7.6 2023-02-27 10:44:53 +01:00
d1297a0085 fix: error with import/export tools, fixes #541 2023-02-27 10:43:51 +01:00
6bd8667f43 Merge pull request #540 from jimcat8/cn_main
Supplement the key-value pair and translate its value.
2023-02-27 10:07:46 +01:00
tianci li
c9093a36b3 Word error 2023-02-27 12:14:20 +08:00
tianci li
b6dbfe1564 Supplement the key-value pair and translate its value. 2023-02-27 11:59:29 +08:00
06b0090480 chore(release): 0.7.5 2023-02-26 17:29:31 +01:00
629ce63329 fix: single quotes not properly escaped for random generated content 2023-02-26 17:02:41 +01:00
313e7407eb feat(MySQL): option, disabled by default, to enable table size indicators on sidebar 2023-02-23 08:51:33 +01:00
4458177688 fix(MariaDB): exception with event_scheduler DISABLED with MariaDB 10, fixes #535 2023-02-21 18:07:29 +01:00
ea7865a086 Merge pull request #533 from antares-sql/all-contributors/add-jimcat8
docs: add jimcat8 as a contributor for translation
2023-02-19 10:43:34 +01:00
allcontributors[bot]
ca900f3dcf docs: update .all-contributorsrc [skip ci] 2023-02-19 09:43:23 +00:00
allcontributors[bot]
4f6f28c6e6 docs: update README.md [skip ci] 2023-02-19 09:43:22 +00:00
8caac0f21d Merge pull request #532 from jimcat8/cn_main
Updated zh-CN translation text
2023-02-19 10:43:15 +01:00
tianci li
cacfc1c2eb review 2023-02-19 14:21:42 +08:00
tianci li
2f30c0d42f Updated zh-CN translation text 2023-02-19 14:07:49 +08:00
b1fbc43ab2 fix: unable to import after a failed import, fixes #515 2023-02-13 18:06:55 +01:00
fe9817bf31 chore(release): 0.7.4 2023-02-10 09:26:50 +01:00
ddb7ead083 fix: tables sort in sidebar 2023-02-10 09:26:17 +01:00
9d8c21244b chore(release): 0.7.3 2023-02-09 11:50:16 +01:00
d934ae1e6c fix(SQLite): triggers disappear after editing related table, fixes #523 2023-02-04 13:51:02 +01:00
68f8d48064 chore: minor ts changes 2023-02-04 12:10:09 +01:00
e7e491340a feat(SQLite): added support to INTEGER UNSIGNED 2023-02-04 11:38:30 +01:00
e8447e5655 fix: select of table type stuck when editing an unknown type 2023-02-04 11:34:53 +01:00
6decba316c fix: longtext edit modal opens when it shouldn't, fixes #524 2023-02-04 09:36:29 +01:00
2b5e1e7b39 fix(SQLite): error with integer timestamp fields 2023-02-04 09:07:41 +01:00
a124f04661 Merge pull request #529 from antares-sql/dependabot/npm_and_yarn/mdi/font-7.1.96
build(deps): bump @mdi/font from 7.0.96 to 7.1.96
2023-02-02 16:45:38 +01:00
dependabot[bot]
3ee4ab4d84 build(deps): bump @mdi/font from 7.0.96 to 7.1.96
Bumps [@mdi/font](https://github.com/Templarian/MaterialDesign-Webfont) from 7.0.96 to 7.1.96.
- [Release notes](https://github.com/Templarian/MaterialDesign-Webfont/releases)
- [Commits](https://github.com/Templarian/MaterialDesign-Webfont/compare/v7.0.96...v7.1.96)

---
updated-dependencies:
- dependency-name: "@mdi/font"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-01 19:05:41 +00:00
174aa3c508 chore(release): 0.7.2 2023-01-22 17:16:04 +01:00
729edd40a6 build: update electron 2023-01-22 15:07:47 +01:00
c7ab3b77a2 fix: allow comments in queies, fixes #519 2023-01-19 14:32:37 +01:00
5624b3b2d7 Merge pull request #520 from dyaskur/master
add copy shortcut and default copy type setting
2023-01-16 18:16:13 +01:00
4c16d8c61f refactor: results table setting section 2023-01-16 18:15:49 +01:00
Dyas🍌 Yaskur🍎
9aca89477f feat: add copy shortcut and default copy type setting 2023-01-16 04:49:44 +07:00
0e80e823d0 fix(SQLite): exception saving tables without INT fields length 2023-01-09 10:02:28 +01:00
ff4bc6c39b feat: connection info icons in footer 2023-01-05 17:21:22 +01:00
b4e1e9ac26 Merge branch 'master' of https://github.com/antares-sql/antares 2023-01-02 09:21:03 +01:00
f177c7f1f1 style: minor refactor 2023-01-02 09:21:01 +01:00
6a67c27915 Merge pull request #406 from antares-sql/dependabot/npm_and_yarn/electron-store-8.1.0
build(deps): bump electron-store from 8.0.2 to 8.1.0
2023-01-02 09:19:15 +01:00
374cedba2b Merge pull request #510 from antares-sql/dependabot/npm_and_yarn/pinia-2.0.28
build(deps): bump pinia from 2.0.27 to 2.0.28
2023-01-02 09:18:55 +01:00
dependabot[bot]
de8097c297 build(deps): bump pinia from 2.0.27 to 2.0.28
Bumps [pinia](https://github.com/vuejs/pinia) from 2.0.27 to 2.0.28.
- [Release notes](https://github.com/vuejs/pinia/releases)
- [Commits](https://github.com/vuejs/pinia/compare/pinia@2.0.27...pinia@2.0.28)

---
updated-dependencies:
- dependency-name: pinia
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-01 19:02:46 +00:00
6fa430adf6 fix: ssh connection closed after idle time, fixes #425 2022-12-29 13:04:20 +01:00
0e7b93c2df chore(release): 0.7.1 2022-12-23 09:25:50 +01:00
843c15e428 fix(MySQL): not every connection gets read-only option 2022-12-23 09:24:05 +01:00
2982b6cb96 ci: change macos build target to macos-11 2022-12-22 22:22:17 +01:00
c08946e932 fix: context submenu outside view when near the edge, fixes #506 2022-12-22 11:27:13 +01:00
55c1604e7f refactor(UI): hide selected connection bar when folder preview 2022-12-21 13:16:56 +01:00
d64fbbad0f Merge pull request #505 from antares-sql/all-contributors/add-dyaskur
docs: add dyaskur as a contributor for code
2022-12-20 21:30:13 +01:00
allcontributors[bot]
c88d734bc0 docs: update .all-contributorsrc [skip ci] 2022-12-20 20:29:48 +00:00
allcontributors[bot]
99d94ea92c docs: update README.md [skip ci] 2022-12-20 20:29:47 +00:00
e4f620c5a1 Merge pull request #500 from dyaskur/add_copy_as_table_rows
feat: Copy rows as html table, so we can paste it to spreadsheet
2022-12-20 21:28:54 +01:00
99f9a9e188 refactor: replace string "Table" with localization 2022-12-20 21:26:33 +01:00
Dyas🍌 Yaskur🍎
2236c8fe75 set copy plain text delimiter to tab 2022-12-19 19:07:31 +07:00
b9a097e2f5 Merge pull request #502 from antares-sql/all-contributors/add-dyaskur
docs: add dyaskur as a contributor for translation
2022-12-19 09:00:17 +01:00
allcontributors[bot]
966446afd6 docs: update .all-contributorsrc [skip ci] 2022-12-19 07:59:59 +00:00
allcontributors[bot]
b681adc632 docs: update README.md [skip ci] 2022-12-19 07:59:58 +00:00
15d0158993 Merge pull request #498 from dyaskur/indonesian-language-translation
Add new language: Bahasa Indonesia
2022-12-19 08:58:25 +01:00
e76d324810 chore: minor change in e2e tests 2022-12-19 08:54:20 +01:00
Dyas🍌 Yaskur🍎
897795ddbb fix: bahasa indonesia typos 2022-12-19 07:34:50 +07:00
Dyas🍌 Yaskur🍎
c32f463ea5 feat: Copy rows as html table, so we can paste it to spreadsheet 2022-12-19 05:33:50 +07:00
Dyas🍌 Yaskur🍎
25e1ba4384 Add new language: Bahasa Indonesia 2022-12-19 04:36:10 +07:00
dependabot[bot]
a71ae05c6f build(deps): bump electron-store from 8.0.2 to 8.1.0
Bumps [electron-store](https://github.com/sindresorhus/electron-store) from 8.0.2 to 8.1.0.
- [Release notes](https://github.com/sindresorhus/electron-store/releases)
- [Commits](https://github.com/sindresorhus/electron-store/compare/v8.0.2...v8.1.0)

---
updated-dependencies:
- dependency-name: electron-store
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-18 15:01:12 +00:00
1bd26ceaa6 feat: option to disable selected query execution, closes #477 2022-12-18 15:59:58 +01:00
56a0361ed2 Merge pull request #493 from antares-sql/all-contributors/add-brdtheo
docs: add brdtheo as a contributor for translation
2022-12-12 09:14:39 +01:00
allcontributors[bot]
c0dd3e0941 docs: update .all-contributorsrc [skip ci] 2022-12-12 08:14:23 +00:00
allcontributors[bot]
33ab5d7491 docs: update README.md [skip ci] 2022-12-12 08:14:22 +00:00
72e6a23fd6 Merge pull request #491 from brdtheo/i18n-fix-french-translation
Fix french translation
2022-12-12 09:11:08 +01:00
brdtheo
fd129a2ad1 fix(i18n): add missing keys for french translation 2022-12-07 22:45:44 +01:00
2c63cbc4e8 chore: update dependencies 2022-12-06 12:56:36 +01:00
c9a33936a0 ci: test target os change 2022-12-03 17:53:16 +01:00
a6bdf69a28 fix: connection default icon not change after client change 2022-12-03 17:53:00 +01:00
dd971d70e0 fix(UI): white background dragging connections inside folder on Linux 2022-12-02 09:38:40 +01:00
669d7e8d4d fix: white background dragging connections or tabs on Linux, fixes #486 2022-12-01 15:38:19 +01:00
02f204a01d Merge pull request #485 from SmileYzn/patch-1
Update pt-BR.ts
2022-11-30 17:50:00 +01:00
Cleverson
023d7aa92d Update pt-BR.ts 2022-11-30 13:45:23 -03:00
4ca40c07d6 ci: ubuntu 20.04 as linux actions target 2022-11-30 15:57:48 +01:00
149 changed files with 11444 additions and 20568 deletions

View File

@@ -174,8 +174,83 @@
"contributions": [
"translation"
]
},
{
"login": "brdtheo",
"name": "Théo Billardey",
"avatar_url": "https://avatars.githubusercontent.com/u/48206778?v=4",
"profile": "https://codepen.io/theo-billardey",
"contributions": [
"translation"
]
},
{
"login": "dyaskur",
"name": "Muhammad Dyas Yaskur",
"avatar_url": "https://avatars.githubusercontent.com/u/9539970?v=4",
"profile": "http://yaskur.net",
"contributions": [
"translation",
"code"
]
},
{
"login": "jimcat8",
"name": "tianci li",
"avatar_url": "https://avatars.githubusercontent.com/u/86754294?v=4",
"profile": "https://github.com/jimcat8",
"contributions": [
"translation"
]
},
{
"login": "555cider",
"name": "555cider",
"avatar_url": "https://avatars.githubusercontent.com/u/73565447?v=4",
"profile": "https://github.com/555cider",
"contributions": [
"translation"
]
},
{
"login": "m1khal3v",
"name": "Anton Mikhalev",
"avatar_url": "https://avatars.githubusercontent.com/u/41085561?v=4",
"profile": "https://github.com/m1khal3v",
"contributions": [
"translation"
]
},
{
"login": "64knl",
"name": "René",
"avatar_url": "https://avatars.githubusercontent.com/u/3864423?v=4",
"profile": "https://64k.nl/",
"contributions": [
"code",
"translation"
]
},
{
"login": "zxp19821005",
"name": "Woodenman",
"avatar_url": "https://avatars.githubusercontent.com/u/4915850?v=4",
"profile": "https://github.com/zxp19821005",
"contributions": [
"platform"
]
},
{
"login": "markusand",
"name": "Marc Vilella",
"avatar_url": "https://avatars.githubusercontent.com/u/12972543?v=4",
"profile": "https://github.com/markusand",
"contributions": [
"translation"
]
}
],
"contributorsPerLine": 7,
"skipCi": true
"skipCi": true,
"commitType": "docs"
}

View File

@@ -11,3 +11,4 @@ updates:
directory: "/"
schedule:
interval: "monthly"
target-branch: "develop"

42
.github/workflows/build-beta.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: Build & release [BETA]
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+'
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-11, ubuntu-latest, windows-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v3
with:
ref: beta
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: npm i
- name: "Build"
run: npm run build
- name: Release
uses: ncipollo/release-action@v1
with:
artifacts: "build/*.AppImage,build/*.yml,build/*.deb,build/*.dmg,build/*.blockmap,build/*.zip,build/*.exe"
allowUpdates: true
draft: true
generateReleaseNotes: true

View File

@@ -1,9 +1,12 @@
name: Build & release
name: Build & release [STABLE]
on:
push:
tags:
- "v*"
- 'v[0-9]+.[0-9]+.[0-9]+'
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
jobs:
release:
@@ -11,11 +14,19 @@ jobs:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
os: [macos-11, ubuntu-latest, windows-latest]
steps:
- name: Exit if not on master branch
if: contains(env.BRANCH_NAME, 'master') == false
run: |
echo "Wrong environment ${{env.BRANCH_NAME}}"
exit 0
- name: Check out Git repository
uses: actions/checkout@v3
with:
ref: master
- name: Install Node.js
uses: actions/setup-node@v3

View File

@@ -0,0 +1,32 @@
name: Create artifact [LINUX ARM64]
on:
workflow_dispatch: {}
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v3
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: npm i
- name: "Build"
run: npm run build -- --arm64 --linux deb AppImage
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: linux-build
retention-days: 3
path: |
build
!build/*-unpacked
!build/.icon-ico

View File

@@ -5,7 +5,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
steps:
- name: Check out Git repository
uses: actions/checkout@v3
@@ -15,10 +15,11 @@ jobs:
with:
node-version: 16
- name: npm install & build
run: |
npm install
npm run build
- name: Install dependencies
run: npm i
- name: "Build"
run: npm run build
- name: Upload Artifact
uses: actions/upload-artifact@v3

View File

@@ -1,6 +1,9 @@
name: Test end-to-end [LINUX]
name: Test end-to-end [WINDOWS]
on: push
on:
push:
branches:
- master
jobs:
release:
@@ -8,7 +11,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
os: [windows-latest]
steps:
- name: Check out Git repository

View File

@@ -2,6 +2,253 @@
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.16-beta.1](https://github.com/antares-sql/antares/compare/v0.7.16-beta.0...v0.7.16-beta.1) (2023-08-18)
### Features
* ability to export connections ([f5b86e5](https://github.com/antares-sql/antares/commit/f5b86e59e7e41bbbe1555ec8a85ec1f849e8271d))
* ability to import connections ([6df2145](https://github.com/antares-sql/antares/commit/6df214558f8186dd5fbf101505465f4c07c8ad5f))
* Add catalan language ([9c6f1a9](https://github.com/antares-sql/antares/commit/9c6f1a9ea52b4791082bdbae91b764c486684c3b))
### Bug Fixes
* add some missing titles of buttons ([52e42fa](https://github.com/antares-sql/antares/commit/52e42fa1b529512154c0680124c5e425964a5b1a))
* **SQLite:** improved view body parsing ([832aa75](https://github.com/antares-sql/antares/commit/832aa75ebe46776b9c365dde2dd13db3746adcd5))
### [0.7.16-beta.0](https://github.com/antares-sql/antares/compare/v0.7.15...v0.7.16-beta.0) (2023-08-11)
### Features
* ability to change sql dump file name ([2e39d81](https://github.com/antares-sql/antares/commit/2e39d810b519401d0410cbb76520c07c595329fc))
* ability to export a table dump from table context menu ([84b2255](https://github.com/antares-sql/antares/commit/84b2255bf405431070511890b2bca770fc4cc2bc))
* support to links in changelog ([ad7e459](https://github.com/antares-sql/antares/commit/ad7e459c689d70ca664b9a3091d3dcab3f26ce4d))
### Bug Fixes
* **MySQL:** missing error details on mysql importer with some exceptione ([7b1cb4f](https://github.com/antares-sql/antares/commit/7b1cb4ff86dddb5fb4b4d960b47ea17f38a24aa6))
### [0.7.15](https://github.com/antares-sql/antares/compare/v0.7.14...v0.7.15) (2023-07-31)
### Features
* export file name with hour, minutes and seconds, closes [#610](https://github.com/antares-sql/antares/issues/610) ([2f5fa0f](https://github.com/antares-sql/antares/commit/2f5fa0f2e416e88753c9dc2bf32edefba8c4e072))
### Bug Fixes
* display content of BLOB fields if not binary, fixes [#628](https://github.com/antares-sql/antares/issues/628) ([f831fcd](https://github.com/antares-sql/antares/commit/f831fcd442f16a03f0ea83ff5bf77a4a79906eb6))
* **MySQL:** error insertinf new fields with some MySQL configurations, fixes [#360](https://github.com/antares-sql/antares/issues/360) ([51b1419](https://github.com/antares-sql/antares/commit/51b14195a8a6c7e7f1b12bd4b86b7164a56b2aa5))
* **MySQL:** unable to set more than one value in SET fields ([7c820b1](https://github.com/antares-sql/antares/commit/7c820b18276e4ed5e381d91110acd032862a5be9))
### [0.7.14](https://github.com/antares-sql/antares/compare/v0.7.13...v0.7.14) (2023-07-07)
### Features
* button to open view settings tab ([32bbc45](https://github.com/antares-sql/antares/commit/32bbc453294736970ddbcadd3cc45597277f5bda))
### Bug Fixes
* unable to insert new rows ([1c73503](https://github.com/antares-sql/antares/commit/1c735031380c48aa8b08ed5ce3df80008dc72af8))
### Improvements
* **PostgreSQL:** minor improvement to schema selection ([9d8e9a5](https://github.com/antares-sql/antares/commit/9d8e9a5e1fda190ee7b3d4fd59e6178dd5ec1651))
### [0.7.13](https://github.com/antares-sql/antares/compare/v0.7.12...v0.7.13) (2023-07-06)
### Features
* button to open table settings tab, closes [#608](https://github.com/antares-sql/antares/issues/608) ([38bfea2](https://github.com/antares-sql/antares/commit/38bfea279ce93366dfd2021d0e91622a5a88878e))
* update connection ([462ede8](https://github.com/antares-sql/antares/commit/462ede8dc701aaf9c6089b3ec41eea0f31babdf9))
### Bug Fixes
* **PostgreSQL:** unable to connect to database, fixes [#614](https://github.com/antares-sql/antares/issues/614) ([e808b86](https://github.com/antares-sql/antares/commit/e808b86c52b8488e0c079a9f0ddce225338af4c0))
* unable to copy as sql inserts with BIT fileds, fixes [#613](https://github.com/antares-sql/antares/issues/613) ([6e01f0f](https://github.com/antares-sql/antares/commit/6e01f0f2e7194284341f89a44839d16398358f9b))
### [0.7.12](https://github.com/antares-sql/antares/compare/v0.7.11...v0.7.12) (2023-07-03)
### Features
* add Dutch language support ([4ff1d10](https://github.com/antares-sql/antares/commit/4ff1d107b8837887ceb3ffecaed46b9bdb908127))
* format options of csv export, closes [#196](https://github.com/antares-sql/antares/issues/196) ([3ad1e51](https://github.com/antares-sql/antares/commit/3ad1e51f42f3cc37ad66bdab64e9a8a0eadea74b))
* more translations ([575c8ea](https://github.com/antares-sql/antares/commit/575c8ea8cabc1fb315bad1693df6cd4ccd869838))
* **PostgreSQL:** ability to switch the database, closes [#432](https://github.com/antares-sql/antares/issues/432) ([89815bf](https://github.com/antares-sql/antares/commit/89815bf5e7c4062950b7418b31b45552ae0e1276))
* Sort available languages alphabetically ([3ff8d25](https://github.com/antares-sql/antares/commit/3ff8d2571be306a8a72e5ad2b9f963b285e8db26))
### Bug Fixes
* **PostgreSQL:** unable to export as sql inserts ([e07e7b7](https://github.com/antares-sql/antares/commit/e07e7b736ef29368262ec4b17d9e1f54ef3ec390))
* rename components and variables in SettingBarContext ([8a4a099](https://github.com/antares-sql/antares/commit/8a4a099e37c9bbc1365f322f6276f32153e24379))
* spelling corrections ([93e16fd](https://github.com/antares-sql/antares/commit/93e16fdda29601f3e898b1a9470bd8546ea95df6))
* update source string to fix spelling ([001983c](https://github.com/antares-sql/antares/commit/001983c5a2ebe17c943a9be3a58cc587f5cd09e3))
### [0.7.11](https://github.com/antares-sql/antares/compare/v0.7.10...v0.7.11) (2023-06-04)
### Features
* context menu to close tabs, closes [#392](https://github.com/antares-sql/antares/issues/392) ([787014c](https://github.com/antares-sql/antares/commit/787014c38df743315c04962871a3801805a93c55))
### Bug Fixes
* disable filter during table content loading, fixes [#588](https://github.com/antares-sql/antares/issues/588) ([e60726c](https://github.com/antares-sql/antares/commit/e60726c741276b7da0d54c0986d2a45ed9979bc9))
* **MySQL:** unable to get users list with some db settings ([9046707](https://github.com/antares-sql/antares/commit/904670781d47b1ac0dcfd982215ba1786f8c8145))
* unique keys not recognized in table settings on some MariaDB versions ([4461998](https://github.com/antares-sql/antares/commit/446199827be4b07382453739f42d46fa0201d04c))
* weird behavior in some text editors ([22bdaac](https://github.com/antares-sql/antares/commit/22bdaac18b1c46a57802cbbd3ad339ee075ec70b))
### [0.7.10](https://github.com/antares-sql/antares/compare/v0.7.9...v0.7.10) (2023-05-28)
### Features
* copy rows as PHP array ([03638c0](https://github.com/antares-sql/antares/commit/03638c05534e9ce2e594ce5945485587ed99e609))
* DDL query in table settings for MySQL and PostgreSQL, closes [#581](https://github.com/antares-sql/antares/issues/581) ([f454b4b](https://github.com/antares-sql/antares/commit/f454b4bb1ca79eec285b3b4039a2ef66802ff82a))
* export table content or query results as PHP array, closes [#575](https://github.com/antares-sql/antares/issues/575) ([8968179](https://github.com/antares-sql/antares/commit/8968179c11f4fe3e624873aac4685a5a33521024))
* keepalive on mysql/postgre connections, should fix [#577](https://github.com/antares-sql/antares/issues/577) ([17eeb6d](https://github.com/antares-sql/antares/commit/17eeb6d38e45b553e35e004b748569971743ca18))
### Bug Fixes
* disable shorctut to show Ace editor settings, fixes [#585](https://github.com/antares-sql/antares/issues/585) ([2c0b4ff](https://github.com/antares-sql/antares/commit/2c0b4ffe1f2e418f5e9120a40787788d8e7fd27e))
### [0.7.9](https://github.com/antares-sql/antares/compare/v0.7.8...v0.7.9) (2023-05-01)
### Features
* no table results message ([19859f4](https://github.com/antares-sql/antares/commit/19859f45f4457292b6ecfe79bdcfbdcc7722be06))
* option to choose the target table of an SQL INSERT exportation, closes [#556](https://github.com/antares-sql/antares/issues/556) ([c48266c](https://github.com/antares-sql/antares/commit/c48266c336d7c61abe2b56b5702e5bca83bb57b3))
* **translation:** ko-KR translation, closes [#561](https://github.com/antares-sql/antares/issues/561) ([baef4ea](https://github.com/antares-sql/antares/commit/baef4ea4d1747233a86b90fe5b60a0d6cfba1f1c))
### Bug Fixes
* sidebar height out of visible area ([af91d96](https://github.com/antares-sql/antares/commit/af91d96db6e79222e5dbc9b880a904a40332c09b))
* unable to delete rows with null values and no primary key ([74c136f](https://github.com/antares-sql/antares/commit/74c136f8334b6972ae55dd8ee0ade09ef8ae3282))
* vertical scrollbar does not reset after performing a search, fixes [#567](https://github.com/antares-sql/antares/issues/567) ([0cd55fb](https://github.com/antares-sql/antares/commit/0cd55fbfe9ff09589ae5993f16b0dd56a2ea1a5a))
### Improvements
* **translation:** update italian translation ([96e1ceb](https://github.com/antares-sql/antares/commit/96e1ceb1d2488390216553cd3fce2eec261f04eb))
### [0.7.8](https://github.com/antares-sql/antares/compare/v0.7.7...v0.7.8) (2023-04-12)
### Features
* filter schemas in sidebar, closes [#555](https://github.com/antares-sql/antares/issues/555) ([8be9f93](https://github.com/antares-sql/antares/commit/8be9f932e7a44b2067d8b57950d8faafc577123f))
* **MySQL:** option to export from results SQL INSERTS in chunks, closes [#501](https://github.com/antares-sql/antares/issues/501) ([0f24c80](https://github.com/antares-sql/antares/commit/0f24c80e5a2dc45875df6b67d3c097cf1cca458e))
### Bug Fixes
* **MySQL:** missing scale for FLOAT type ([52108d7](https://github.com/antares-sql/antares/commit/52108d76133d5fdffb56faa995d7ab7ee3e7c4bc))
* triggers not exported if related table not included ([d802b32](https://github.com/antares-sql/antares/commit/d802b32597e42ee90a2d691fe74245b3bc2517ee))
* unable to export BLOB values from table content o query result ([afa61a9](https://github.com/antares-sql/antares/commit/afa61a9bc2d698894096a6b5413c49f05b2fd5aa))
### [0.7.7](https://github.com/antares-sql/antares/compare/v0.7.6...v0.7.7) (2023-03-10)
### Bug Fixes
* hide table size tooltip if disabled ([2c46269](https://github.com/antares-sql/antares/commit/2c46269cf262248d3f5644b7c5b89d5d317a89a4))
* **Linux:** remove app menu shown pressing ALT key ([63fece2](https://github.com/antares-sql/antares/commit/63fece2a1b6b09f61ae416f7c3a7b51ee0a53d0e))
* **Linux:** remove app menu shown pressing ALT key ([b925ff9](https://github.com/antares-sql/antares/commit/b925ff9c01afcc727e5d70bafb079d468ed1eede))
* **MySQL:** missing table information in table setting tab ([59846e6](https://github.com/antares-sql/antares/commit/59846e6ff47591ec8dc22403c3be0e70e0f3ccfd))
* unable to set string fields default values starting with 0 ([3546c57](https://github.com/antares-sql/antares/commit/3546c57e398be7b2e226bb8e93e8fc0fb8bab40a))
### [0.7.6](https://github.com/antares-sql/antares/compare/v0.7.5...v0.7.6) (2023-02-27)
### Bug Fixes
* error with import/export tools, fixes [#541](https://github.com/antares-sql/antares/issues/541) ([d1297a0](https://github.com/antares-sql/antares/commit/d1297a0085fd54967817816efaeed5770a887bbf))
### [0.7.5](https://github.com/antares-sql/antares/compare/v0.7.4...v0.7.5) (2023-02-26)
### Features
* **MySQL:** option, disabled by default, to enable table size indicators on sidebar ([313e740](https://github.com/antares-sql/antares/commit/313e7407eb1afe5d19ac49ee4b308950b48cafa8))
### Bug Fixes
* **MariaDB:** exception with event_scheduler DISABLED with MariaDB 10, fixes [#535](https://github.com/antares-sql/antares/issues/535) ([4458177](https://github.com/antares-sql/antares/commit/445817768863616ea7340c8bf62472197b73bd6e))
* single quotes not properly escaped for random generated content ([629ce63](https://github.com/antares-sql/antares/commit/629ce633294c862266db9e27ffa5c154e8fc416c))
* unable to import after a failed import, fixes [#515](https://github.com/antares-sql/antares/issues/515) ([b1fbc43](https://github.com/antares-sql/antares/commit/b1fbc43ab2f39827cb85ac7d21ac889ffc2f4c64))
### [0.7.4](https://github.com/antares-sql/antares/compare/v0.7.3...v0.7.4) (2023-02-10)
### Bug Fixes
* tables sort in sidebar ([ddb7ead](https://github.com/antares-sql/antares/commit/ddb7ead0832708713ba4bb2717661b8b93a47e3f))
### [0.7.3](https://github.com/antares-sql/antares/compare/v0.7.2...v0.7.3) (2023-02-09)
### Features
* **SQLite:** added support to INTEGER UNSIGNED ([e7e4913](https://github.com/antares-sql/antares/commit/e7e491340a037b64d6d8e538376415779d54332e))
### Bug Fixes
* longtext edit modal opens when it shouldn't, fixes [#524](https://github.com/antares-sql/antares/issues/524) ([6decba3](https://github.com/antares-sql/antares/commit/6decba316ca46106520cb4dba44409ceb4a4af75))
* select of table type stuck when editing an unknown type ([e8447e5](https://github.com/antares-sql/antares/commit/e8447e56551871a200517bdaa747ae215ad83cf4))
* **SQLite:** error with integer timestamp fields ([2b5e1e7](https://github.com/antares-sql/antares/commit/2b5e1e7b39c25f536b6139a4d01b9f7f17069ea8))
* **SQLite:** triggers disappear after editing related table, fixes [#523](https://github.com/antares-sql/antares/issues/523) ([d934ae1](https://github.com/antares-sql/antares/commit/d934ae1e6c0747698b4973d9cad217379076a6cf))
### [0.7.2](https://github.com/antares-sql/antares/compare/v0.7.1...v0.7.2) (2023-01-22)
### Features
* add copy shortcut and default copy type setting ([9aca894](https://github.com/antares-sql/antares/commit/9aca89477f1fd7b7f55f1e5c290d495c46f61d8e))
* connection info icons in footer ([ff4bc6c](https://github.com/antares-sql/antares/commit/ff4bc6c39b05a827cebde84466814cf246908208))
### Bug Fixes
* allow comments in queies, fixes [#519](https://github.com/antares-sql/antares/issues/519) ([c7ab3b7](https://github.com/antares-sql/antares/commit/c7ab3b77a22e85cee6fb93064eaad5a8e8ad9fd2))
* **SQLite:** exception saving tables without INT fields length ([0e80e82](https://github.com/antares-sql/antares/commit/0e80e823d059dfe24995b5848d88cc84235e6275))
* ssh connection closed after idle time, fixes [#425](https://github.com/antares-sql/antares/issues/425) ([6fa430a](https://github.com/antares-sql/antares/commit/6fa430adf68013a9d0a093031f56dd741bdc0299))
### [0.7.1](https://github.com/antares-sql/antares/compare/v0.7.0...v0.7.1) (2022-12-23)
### Features
* Copy rows as html table, so we can paste it to spreadsheet ([c32f463](https://github.com/antares-sql/antares/commit/c32f463ea5ac3f54cba32929f77442f1e0ba934a))
* option to disable selected query execution, closes [#477](https://github.com/antares-sql/antares/issues/477) ([1bd26ce](https://github.com/antares-sql/antares/commit/1bd26ceaa68fe66f26c76b3b60fa6eeccea91729))
### Bug Fixes
* bahasa indonesia typos ([897795d](https://github.com/antares-sql/antares/commit/897795ddbb4ade2652b0471f18288b8b3aaf0eb9))
* connection default icon not change after client change ([a6bdf69](https://github.com/antares-sql/antares/commit/a6bdf69a281c8614c41274b6dc2f3563aa89c57e))
* context submenu outside view when near the edge, fixes [#506](https://github.com/antares-sql/antares/issues/506) ([c08946e](https://github.com/antares-sql/antares/commit/c08946e932884e5f0253df2545f98315ab7e5219))
* **i18n:** add missing keys for french translation ([fd129a2](https://github.com/antares-sql/antares/commit/fd129a2ad1c3401372c9172b38f4406254d134df))
* **MySQL:** not every connection gets read-only option ([843c15e](https://github.com/antares-sql/antares/commit/843c15e428c4a0412f19a93ab05d2fcbb60da09b))
* **UI:** white background dragging connections inside folder on Linux ([dd971d7](https://github.com/antares-sql/antares/commit/dd971d70e04faf0d5b239586b12e4a9a42407433))
* white background dragging connections or tabs on Linux, fixes [#486](https://github.com/antares-sql/antares/issues/486) ([669d7e8](https://github.com/antares-sql/antares/commit/669d7e8d4d062ed5bdafe1d5cde8ec51a2f68b26))
## [0.7.0](https://github.com/antares-sql/antares/compare/v0.6.0...v0.7.0) (2022-11-30)

View File

@@ -7,7 +7,8 @@
# 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) [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Ffabio286%2Fantares%2Fbadge&style=flat)](https://actions-badge.atrox.dev/fabio286/antares/goto) [![antares](https://snapcraft.io/antares/badge.svg)](https://snapcraft.io/antares) [![antares](https://snapcraft.io/antares/trending.svg?name=0)](https://snapcraft.io/antares) [![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) [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Ffabio286%2Fantares%2Fbadge&style=flat)](https://actions-badge.atrox.dev/fabio286/antares/goto) [![antares](https://snapcraft.io/antares/badge.svg)](https://snapcraft.io/antares) [![antares](https://snapcraft.io/antares/trending.svg?name=0)](https://snapcraft.io/antares)
![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)
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.
@@ -17,9 +18,12 @@ 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.
🔗 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](https://twitter.com/AntaresSQL) on Twitter.
👁 To stay tuned for new releases follow Antares SQL on [Mastodon](https://fosstodon.org/@AntaresSQL) or [Twitter](https://twitter.com/AntaresSQL).
🌟 Don't forget to **leave a star** if you appreciate this project.
🗳️ Poll: **[Which is the main OS you use Antares on?](https://github.com/antares-sql/antares/discussions/379)**
🗳️ Polls:
- **[Which is the main OS you use Antares on?](https://github.com/antares-sql/antares/discussions/379)**
- **[Which database do you use the most?](https://github.com/antares-sql/antares/discussions/594)**
## Current key features
@@ -63,7 +67,7 @@ On macOS you can run `.dmg` distribution following [this guide](https://support.
## Download
[![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/Fabio286/antares/3e00c4bae6e036300c752c1a40c5a038fea9c169/docs/aur-badge.svg)](https://aur.archlinux.org/packages/antares-sql/) [<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)
[![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/Fabio286/antares/3e00c4bae6e036300c752c1a40c5a038fea9c169/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/Fabio286/antares/releases/latest)**
## Coming soon
@@ -116,30 +120,42 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tbody>
<tr>
<td align="center"><a href="https://fabiodistasio.it/"><img src="https://avatars.githubusercontent.com/u/31471771?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fabio Di Stasio</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=Fabio286" title="Code">💻</a> <a href="#translation-Fabio286" title="Translation">🌍</a> <a href="https://github.com/antares-sql/antares/commits?author=Fabio286" title="Documentation">📖</a></td>
<td align="center"><a href="https://www.linkedin.com/in/giulioganci/"><img src="https://avatars.githubusercontent.com/u/4192159?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Giulio Ganci</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=toriphes" title="Code">💻</a></td>
<td align="center"><a href="https://christianratz.de/"><img src="https://avatars.githubusercontent.com/u/2630316?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Christian Ratz</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=digitalgopnik" title="Code">💻</a> <a href="#translation-digitalgopnik" title="Translation">🌍</a></td>
<td align="center"><a href="https://reverb6821.github.io/"><img src="https://avatars.githubusercontent.com/u/55198803?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Giuseppe Gigliotti</b></sub></a><br /><a href="#translation-reverb6821" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Mohd-PH"><img src="https://avatars.githubusercontent.com/u/9362157?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mohd-PH</b></sub></a><br /><a href="#translation-Mohd-PH" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/hongkfui"><img src="https://avatars.githubusercontent.com/u/37477191?v=4?s=100" width="100px;" alt=""/><br /><sub><b>hongkfui</b></sub></a><br /><a href="#translation-hongkfui" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/MrAnyx"><img src="https://avatars.githubusercontent.com/u/44176707?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Robin</b></sub></a><br /><a href="#translation-MrAnyx" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://fabiodistasio.it/"><img src="https://avatars.githubusercontent.com/u/31471771?v=4?s=100" width="100px;" alt="Fabio Di Stasio"/><br /><sub><b>Fabio Di Stasio</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=Fabio286" title="Code">💻</a> <a href="#translation-Fabio286" title="Translation">🌍</a> <a href="https://github.com/antares-sql/antares/commits?author=Fabio286" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/giulioganci/"><img src="https://avatars.githubusercontent.com/u/4192159?v=4?s=100" width="100px;" alt="Giulio Ganci"/><br /><sub><b>Giulio Ganci</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=toriphes" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://christianratz.de/"><img src="https://avatars.githubusercontent.com/u/2630316?v=4?s=100" width="100px;" alt="Christian Ratz"/><br /><sub><b>Christian Ratz</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=digitalgopnik" title="Code">💻</a> <a href="#translation-digitalgopnik" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://reverb6821.github.io/"><img src="https://avatars.githubusercontent.com/u/55198803?v=4?s=100" width="100px;" alt="Giuseppe Gigliotti"/><br /><sub><b>Giuseppe Gigliotti</b></sub></a><br /><a href="#translation-reverb6821" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Mohd-PH"><img src="https://avatars.githubusercontent.com/u/9362157?v=4?s=100" width="100px;" alt="Mohd-PH"/><br /><sub><b>Mohd-PH</b></sub></a><br /><a href="#translation-Mohd-PH" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hongkfui"><img src="https://avatars.githubusercontent.com/u/37477191?v=4?s=100" width="100px;" alt="hongkfui"/><br /><sub><b>hongkfui</b></sub></a><br /><a href="#translation-hongkfui" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MrAnyx"><img src="https://avatars.githubusercontent.com/u/44176707?v=4?s=100" width="100px;" alt="Robin"/><br /><sub><b>Robin</b></sub></a><br /><a href="#translation-MrAnyx" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/daeleduardo"><img src="https://avatars.githubusercontent.com/u/8599078?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Eduardo</b></sub></a><br /><a href="#translation-daeleduardo" title="Translation">🌍</a></td>
<td align="center"><a href="https://ngoquocdat.com/"><img src="https://avatars.githubusercontent.com/u/56961917?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ngô Quốc Đạt</b></sub></a><br /><a href="#translation-datlechin" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/IsamuSugi"><img src="https://avatars.githubusercontent.com/u/7746658?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Isamu Sugiura</b></sub></a><br /><a href="#translation-IsamuSugi" title="Translation">🌍</a></td>
<td align="center"><a href="http://rsacchetto.nexxontech.it/"><img src="https://avatars.githubusercontent.com/u/18429412?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Riccardo Sacchetto</b></sub></a><br /><a href="#platform-Occhioverde" title="Packaging/porting to new platform">📦</a></td>
<td align="center"><a href="https://kilianstallinger.com"><img src="https://avatars.githubusercontent.com/u/5290318?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kilian Stallinger</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=kilianstallz" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/wenj91"><img src="https://avatars.githubusercontent.com/u/12549338?v=4?s=100" width="100px;" alt=""/><br /><sub><b>文杰</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=wenj91" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/goYou"><img src="https://avatars.githubusercontent.com/u/62732795?v=4?s=100" width="100px;" alt=""/><br /><sub><b>goYou</b></sub></a><br /><a href="#translation-goYou" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/daeleduardo"><img src="https://avatars.githubusercontent.com/u/8599078?v=4?s=100" width="100px;" alt="Daniel Eduardo"/><br /><sub><b>Daniel Eduardo</b></sub></a><br /><a href="#translation-daeleduardo" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://ngoquocdat.com/"><img src="https://avatars.githubusercontent.com/u/56961917?v=4?s=100" width="100px;" alt="Ngô Quốc Đạt"/><br /><sub><b>Ngô Quốc Đạt</b></sub></a><br /><a href="#translation-datlechin" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/IsamuSugi"><img src="https://avatars.githubusercontent.com/u/7746658?v=4?s=100" width="100px;" alt="Isamu Sugiura"/><br /><sub><b>Isamu Sugiura</b></sub></a><br /><a href="#translation-IsamuSugi" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://rsacchetto.nexxontech.it/"><img src="https://avatars.githubusercontent.com/u/18429412?v=4?s=100" width="100px;" alt="Riccardo Sacchetto"/><br /><sub><b>Riccardo Sacchetto</b></sub></a><br /><a href="#platform-Occhioverde" title="Packaging/porting to new platform">📦</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://kilianstallinger.com"><img src="https://avatars.githubusercontent.com/u/5290318?v=4?s=100" width="100px;" alt="Kilian Stallinger"/><br /><sub><b>Kilian Stallinger</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=kilianstallz" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/wenj91"><img src="https://avatars.githubusercontent.com/u/12549338?v=4?s=100" width="100px;" alt="文杰"/><br /><sub><b>文杰</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=wenj91" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/goYou"><img src="https://avatars.githubusercontent.com/u/62732795?v=4?s=100" width="100px;" alt="goYou"/><br /><sub><b>goYou</b></sub></a><br /><a href="#translation-goYou" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/raliqala"><img src="https://avatars.githubusercontent.com/u/30502407?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Topollo</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=raliqala" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/SmileYzn"><img src="https://avatars.githubusercontent.com/u/5851851?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cleverson</b></sub></a><br /><a href="#translation-SmileYzn" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/fredatgithub"><img src="https://avatars.githubusercontent.com/u/6720055?v=4?s=100" width="100px;" alt=""/><br /><sub><b>fred</b></sub></a><br /><a href="#translation-fredatgithub" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/xak666"><img src="https://avatars.githubusercontent.com/u/38811437?v=4?s=100" width="100px;" alt=""/><br /><sub><b>xaka_xak</b></sub></a><br /><a href="#translation-xak666" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/raliqala"><img src="https://avatars.githubusercontent.com/u/30502407?v=4?s=100" width="100px;" alt="Topollo"/><br /><sub><b>Topollo</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=raliqala" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/SmileYzn"><img src="https://avatars.githubusercontent.com/u/5851851?v=4?s=100" width="100px;" alt="Cleverson"/><br /><sub><b>Cleverson</b></sub></a><br /><a href="#translation-SmileYzn" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fredatgithub"><img src="https://avatars.githubusercontent.com/u/6720055?v=4?s=100" width="100px;" alt="fred"/><br /><sub><b>fred</b></sub></a><br /><a href="#translation-fredatgithub" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xak666"><img src="https://avatars.githubusercontent.com/u/38811437?v=4?s=100" width="100px;" alt="xaka_xak"/><br /><sub><b>xaka_xak</b></sub></a><br /><a href="#translation-xak666" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://codepen.io/theo-billardey"><img src="https://avatars.githubusercontent.com/u/48206778?v=4?s=100" width="100px;" alt="Théo Billardey"/><br /><sub><b>Théo Billardey</b></sub></a><br /><a href="#translation-brdtheo" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://yaskur.net"><img src="https://avatars.githubusercontent.com/u/9539970?v=4?s=100" width="100px;" alt="Muhammad Dyas Yaskur"/><br /><sub><b>Muhammad Dyas Yaskur</b></sub></a><br /><a href="#translation-dyaskur" title="Translation">🌍</a> <a href="https://github.com/antares-sql/antares/commits?author=dyaskur" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jimcat8"><img src="https://avatars.githubusercontent.com/u/86754294?v=4?s=100" width="100px;" alt="tianci li"/><br /><sub><b>tianci li</b></sub></a><br /><a href="#translation-jimcat8" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/555cider"><img src="https://avatars.githubusercontent.com/u/73565447?v=4?s=100" width="100px;" alt="555cider"/><br /><sub><b>555cider</b></sub></a><br /><a href="#translation-555cider" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/m1khal3v"><img src="https://avatars.githubusercontent.com/u/41085561?v=4?s=100" width="100px;" alt="Anton Mikhalev"/><br /><sub><b>Anton Mikhalev</b></sub></a><br /><a href="#translation-m1khal3v" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://64k.nl/"><img src="https://avatars.githubusercontent.com/u/3864423?v=4?s=100" width="100px;" alt="René"/><br /><sub><b>René</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=64knl" title="Code">💻</a> <a href="#translation-64knl" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zxp19821005"><img src="https://avatars.githubusercontent.com/u/4915850?v=4?s=100" width="100px;" alt="Woodenman"/><br /><sub><b>Woodenman</b></sub></a><br /><a href="#platform-zxp19821005" title="Packaging/porting to new platform">📦</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/markusand"><img src="https://avatars.githubusercontent.com/u/12972543?v=4?s=100" width="100px;" alt="Marc Vilella"/><br /><sub><b>Marc Vilella</b></sub></a><br /><a href="#translation-markusand" title="Translation">🌍</a></td>
</tr>
</tbody>
</table>
<!-- markdownlint-restore -->

21381
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "antares",
"productName": "Antares",
"version": "0.7.0",
"version": "0.7.16-beta.1",
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
"license": "MIT",
"repository": "https://github.com/antares-sql/antares.git",
@@ -16,9 +16,10 @@
"build:appx": "npm run build -- --win appx",
"rebuild:electron": "rimraf ./dist && npm run postinstall && npm run devtools:install",
"release": "standard-version",
"release:pre": "npm run release -- --prerelease alpha",
"release:beta": "npm run release -- --prerelease beta",
"devtools:install": "node scripts/devtoolsInstaller",
"postinstall": "electron-builder install-app-deps",
"translation:check": "ts-node ./scripts/translationCheck.ts",
"test:e2e": "npm run compile && npm run test:e2e-dry",
"test:e2e-dry": "xvfb-maybe -- playwright test",
"lint": "eslint . --ext .js,.ts,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
@@ -66,16 +67,16 @@
"target": "deb",
"arch": [
"x64",
"armv7l",
"arm64"
"arm64",
"armv7l"
]
},
{
"target": "AppImage",
"arch": [
"x64",
"armv7l",
"arm64"
"arm64",
"armv7l"
]
}
],
@@ -121,33 +122,34 @@
"dependencies": {
"@electron/remote": "~2.0.1",
"@faker-js/faker": "~6.1.2",
"@mdi/font": "~7.0.96",
"@mdi/font": "~7.1.96",
"@turf/helpers": "~6.5.0",
"@vueuse/core": "~8.7.5",
"ace-builds": "~1.8.1",
"ace-builds": "~1.14.0",
"better-sqlite3": "~8.0.0",
"electron-log": "~4.4.1",
"electron-store": "~8.0.1",
"electron-store": "~8.1.0",
"electron-updater": "~4.6.5",
"electron-window-state": "~5.0.3",
"encoding": "~0.1.13",
"floating-vue": "~2.0.0-beta.20",
"json2php": "~0.0.7",
"leaflet": "~1.7.1",
"marked": "~4.0.19",
"moment": "~2.29.4",
"mysql2": "~2.3.2",
"node-firebird": "~1.1.3",
"pg": "~8.7.1",
"mysql2": "~3.5.2",
"node-firebird": "~1.1.4",
"pg": "~8.11.1",
"pg-connection-string": "~2.5.0",
"pg-query-stream": "~4.2.3",
"pgsql-ast-parser": "~7.2.1",
"pinia": "~2.0.23",
"pinia": "~2.0.28",
"source-map-support": "~0.5.20",
"spectre.css": "~0.5.9",
"sql-formatter": "~11.0.2",
"sql-formatter": "~12.2.0",
"ssh2-promise": "~1.0.2",
"v-mask": "~2.3.0",
"vue": "~3.2.40",
"vue": "~3.2.45",
"vue-i18n": "~9.2.2",
"vuedraggable": "~4.1.0"
},
@@ -155,12 +157,13 @@
"@babel/eslint-parser": "~7.15.7",
"@babel/preset-env": "~7.15.8",
"@babel/preset-typescript": "~7.16.7",
"@playwright/test": "~1.21.1",
"@playwright/test": "~1.28.1",
"@types/better-sqlite3": "~7.5.0",
"@types/leaflet": "~1.7.9",
"@types/marked": "~4.0.7",
"@types/node": "~17.0.23",
"@types/pg": "~8.6.5",
"@types/ssh2": "~1.11.6",
"@typescript-eslint/eslint-plugin": "~5.18.0",
"@typescript-eslint/parser": "~5.18.0",
"@vue/compiler-sfc": "~3.2.33",
@@ -169,8 +172,8 @@
"chalk": "~4.1.2",
"cross-env": "~7.0.2",
"css-loader": "~6.5.0",
"electron": "~19.1.2",
"electron-builder": "~23.0.3",
"electron": "~22.0.3",
"electron-builder": "~22.10.3",
"eslint": "~7.32.0",
"eslint-config-standard": "~16.0.3",
"eslint-plugin-import": "~2.24.2",
@@ -181,8 +184,8 @@
"html-webpack-plugin": "~5.5.0",
"mini-css-extract-plugin": "~2.4.5",
"node-loader": "~2.0.0",
"playwright": "~1.21.1",
"playwright-core": "~1.21.1",
"playwright": "~1.28.1",
"playwright-core": "~1.28.1",
"postcss-html": "~1.5.0",
"progress-webpack-plugin": "~1.0.12",
"rimraf": "~3.0.2",
@@ -196,13 +199,19 @@
"stylelint-scss": "~4.3.0",
"tree-kill": "~1.2.2",
"ts-loader": "~9.2.8",
"ts-node": "~10.9.1",
"typescript": "~4.6.3",
"unzip-crx-3": "~0.2.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.4.0",
"webpack-dev-server": "~4.11.1",
"xvfb-maybe": "~0.2.1"
},
"overrides": {
"ssh2-promise": {
"ssh2": "github:Fabio286/ssh2"
}
}
}

View File

@@ -0,0 +1,45 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { localesNames } from '../src/renderer/i18n/supported-locales';
import { enUS } from '../src/renderer/i18n/en-US';
const locale = process.argv[2];
let fullCount = 0;
let checkCount = 0;
if (!locale) {
console.log('Please specify locale code as first argument.');
process.exit();
}
if (!Object.keys(localesNames).includes(locale)) {
console.log(`Translation ${locale} not fount in supported locales.`);
process.exit();
}
console.log('Checking missing translations for:', locale);
const i18nFile = require(`../src/renderer/i18n/${locale}`)[locale.replace('-', '')];
for (const group in enUS) {
// @ts-ignore
fullCount += Object.keys(enUS[group]).length;
if (!Object.keys(i18nFile).includes(group)) {
console.log(`Group "\u001b[31m${group}\u001b[0m" missing!`);
continue;
}
// @ts-ignore
for (const term in enUS[group]) {
if (!Object.keys(i18nFile[group]).includes(term))
console.log(`Translation "\u001b[33m${group}.${term}\u001b[0m" missing!`);
// @ts-ignore
else if (i18nFile[group][term] === enUS[group][term]) {
console.log(`Term "\u001b[36m${group}.${term}\u001b[0m" not translated!`);
checkCount++;
}
else
checkCount++;
}
}
console.log(checkCount, 'of', fullCount, 'strings are present in', locale, `(\u001b[32m${(checkCount*100/fullCount).toFixed(1)}%\u001b[0m)`);

View File

@@ -4,7 +4,7 @@ summary: Open source SQL client made to be simple and complete.
description: |
Antares is an SQL client that aims to become an useful and complete tool, especially for developers.
The target is to support as many databases as possible, and all major operating systems, including the ARM versions.
base: core20
base: core22
grade: stable
confinement: strict
@@ -31,10 +31,10 @@ parts:
fi
# Get the latest releases json
echo "Get GitHub releases..."
wget --quiet https://api.github.com/repos/fabio286/antares/releases/latest -O releases.json
wget --quiet https://api.github.com/repos/fabio286/antares/releases -O releases.json
# Get the version from the tag_name and the download URL.
VERSION=$(jq . releases.json | grep tag_name | cut -d'"' -f4 | sed s'/release-//')
DEB_URL=$(cat releases.json | jq -r ".assets[] | select(.name | test(\"${FILTER}\")) | .browser_download_url")
VERSION=$(jq . releases.json | grep tag_name | head -1 | cut -d'"' -f4 | sed s'/release-//')
DEB_URL=$(cat releases.json | jq -r ".[0].assets[] | select(.name | test(\"${FILTER}\")) | .browser_download_url")
DEB=$(basename "${DEB_URL}")
echo "Downloading ${DEB_URL}..."
wget --quiet "${DEB_URL}" -O "${SNAPCRAFT_PART_INSTALL}/${DEB}"
@@ -79,7 +79,7 @@ parts:
- libdb5.3
- libdbus-1-3
- libexpat1
- libffi7
- libffi8
- libgcc-s1
- libgcrypt20
- libglib2.0-0
@@ -87,9 +87,9 @@ parts:
- libgnutls30
- libgpg-error0
- libgssapi-krb5-2
- libhogweed5
- libhogweed6
- libidn2-0
- libjson-c4
- libjson-c5
- libk5crypto3
- libkeyutils1
- libkrb5-3
@@ -97,12 +97,12 @@ parts:
- liblz4-1
- liblzma5
- libmount1
- libnettle7
- libnettle8
- libp11-kit0
- libpcre2-8-0
- libselinux1
- libsqlite3-0
- libssl1.1
- libssl3
- libstdc++6
- libsystemd0
- libtasn1-6
@@ -119,10 +119,10 @@ parts:
cleanup:
after: [antares]
plugin: nil
build-snaps: [gnome-3-38-2004]
build-snaps: [gnome-42-2204]
override-prime: |
set -eux
cd /snap/gnome-3-38-2004/current
cd /snap/gnome-42-2204/current
find . -type f,l -exec rm -f $SNAPCRAFT_PRIME/{} \;
mdns-lookup:
@@ -136,7 +136,7 @@ parts:
- libnss-mdns
override-prime: |
set -eux
sed -Ee 's/^\s*hosts:(\s+)files/hosts:\1files mdns4_minimal \[NOTFOUND=return\]/' /snap/core20/current/etc/nsswitch.conf > $SNAPCRAFT_STAGE/etc/nsswitch.conf
sed -Ee 's/^\s*hosts:(\s+)files/hosts:\1files mdns4_minimal \[NOTFOUND=return\]/' /snap/core22/current/etc/nsswitch.conf > $SNAPCRAFT_STAGE/etc/nsswitch.conf
snapcraftctl prime
prime:
- lib/$SNAPCRAFT_ARCH_TRIPLET/libnss_mdns4_minimal*
@@ -146,10 +146,7 @@ apps:
antares:
command: opt/Antares/antares --no-sandbox
desktop: usr/share/applications/antares.desktop
extensions: [gnome-3-38]
environment:
# Fallback to XWayland if running in a Wayland session.
DISABLE_WAYLAND: 1
extensions: [gnome]
plugs:
- browser-support
- cups-control

View File

@@ -31,11 +31,12 @@ export const defaults: Customizations = {
routines: false,
functions: false,
schedulers: false,
// Settings
// Misc
elementsWrapper: '',
stringsWrapper: '"',
tableAdd: false,
tableTruncateDisableFKCheck: false,
tableDdl: false,
viewAdd: false,
triggerAdd: false,
triggerFunctionAdd: false,

View File

@@ -44,6 +44,7 @@ export const customizations: Customizations = {
tableAdd: true,
tableTruncateDisableFKCheck: true,
tableDuplicate: true,
tableDdl: true,
viewAdd: true,
triggerAdd: true,
routineAdd: true,

View File

@@ -35,11 +35,12 @@ export const customizations: Customizations = {
triggerFunctions: true,
routines: true,
functions: true,
// Settings
// Misc
elementsWrapper: '"',
stringsWrapper: '\'',
tableAdd: true,
tableDuplicate: true,
tableDdl: true,
viewAdd: true,
triggerAdd: true,
triggerFunctionAdd: true,

View File

@@ -54,6 +54,7 @@ export default [
{
name: 'FLOAT',
length: true,
scale: true,
collation: false,
unsigned: false,
zerofill: false

View File

@@ -6,7 +6,7 @@ export default [
types: [
{
name: 'INT',
length: true,
length: 10,
collation: false,
unsigned: true,
zerofill: true
@@ -18,6 +18,13 @@ export default [
unsigned: true,
zerofill: true
},
{
name: 'INTEGER UNSIGNED',
length: true,
collation: false,
unsigned: true,
zerofill: true
},
{
name: 'BIGINT',
length: true,

View File

@@ -85,13 +85,11 @@ export interface TableInfos {
name: string;
type: string;
rows: number;
created: Date;
updated: Date;
engine: string;
comment: string;
size: number | false;
autoIncrement: number;
collation: string;
autoIncrement?: boolean;
}
export type TableOptions = Partial<TableInfos>;

View File

@@ -30,7 +30,7 @@ export interface Customizations {
routines?: boolean;
functions?: boolean;
schedulers?: boolean;
// Settings
// Misc
elementsWrapper: string;
stringsWrapper: string;
tableAdd?: boolean;
@@ -39,6 +39,7 @@ export interface Customizations {
tableArray?: boolean;
tableRealCount?: boolean;
tableTruncateDisableFKCheck?: boolean;
tableDdl?: boolean;
viewAdd?: boolean;
viewSettings?: boolean;
triggerAdd?: boolean;

View File

@@ -0,0 +1,31 @@
import * as crypto from 'crypto';
const algorithm = 'aes-256-gcm';
function encrypt (text: string, password: string) {
const iv = crypto.randomBytes(16);
const key = crypto.scryptSync(password, 'antares', 32);
const cipher = crypto.createCipheriv(algorithm, key, iv);
const encrypted = Buffer.concat([cipher.update(text), cipher.final()]);
const authTag = cipher.getAuthTag();
return {
iv: iv.toString('hex'),
authTag: authTag.toString('hex'),
content: encrypted.toString('hex')
};
}
function decrypt (hash: { iv: string; content: string; authTag: string }, password: string) {
const key = crypto.scryptSync(password, 'antares', 32);
const decipher = crypto.createDecipheriv(algorithm, key, Buffer.from(hash.iv, 'hex'));
decipher.setAuthTag(Buffer.from(hash.authTag, 'hex'));
const decrpyted = decipher.update(hash.content, 'hex', 'utf8') + decipher.final('utf8');
return decrpyted;
}
export {
encrypt,
decrypt
};

View File

@@ -72,7 +72,7 @@ export const escapeAndQuote = (val: string, client: ClientCode) => {
export const valueToSqlString = (args: {
val: any;
client: ClientCode;
field: {type: string; datePrecision: number};
field: {type: string; datePrecision?: number; precision?: number | false; isArray?: boolean};
}): string => {
let parsedValue;
const { val, client, field } = args;
@@ -94,7 +94,7 @@ export const valueToSqlString = (args: {
? escapeAndQuote(moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`), client)
: escapeAndQuote(val, client);
}
else if ('isArray' in field) {
else if ('isArray' in field && field.isArray) {
let localVal;
if (Array.isArray(val))
localVal = JSON.stringify(val).replaceAll('[', '{').replaceAll(']', '}');
@@ -105,12 +105,18 @@ export const valueToSqlString = (args: {
else if (TEXT_SEARCH.includes(field.type))
parsedValue = `'${val.replaceAll('\'', '\'\'')}'`;
else if (BIT.includes(field.type))
parsedValue = `b'${hexToBinary(Buffer.from(val).toString('hex') as undefined as HexChar[])}'`;
parsedValue = `b'${hexToBinary(Buffer.from(new Uint8Array(Object.values(val))).toString('hex') as undefined as HexChar[])}'`;
else if (BLOB.includes(field.type)) {
let buffer: Buffer;
if (val instanceof Uint8Array)
buffer = Buffer.from(val);
else
buffer = val;
if (['mysql', 'maria'].includes(client))
parsedValue = `X'${val.toString('hex').toUpperCase()}'`;
parsedValue = `X'${buffer.toString('hex').toUpperCase()}'`;
else if (client === 'pg')
parsedValue = `decode('${val.toString('hex').toUpperCase()}', 'hex')`;
parsedValue = `decode('${buffer.toString('hex').toUpperCase()}', 'hex')`;
}
else if (NUMBER.includes(field.type))
parsedValue = val;
@@ -146,17 +152,47 @@ export const valueToSqlString = (args: {
};
export const jsonToSqlInsert = (args: {
json: { [key: string]: any};
json: { [key: string]: any}[];
client: ClientCode;
fields: { [key: string]: {type: string; datePrecision: number}};
table: string;
options?: {sqlInsertAfter: number; sqlInsertDivider: 'bytes' | 'rows'};
}) => {
const { client, json, fields, table } = args;
const { client, json, fields, table, options } = args;
const sqlInsertAfter = options && options.sqlInsertAfter ? options.sqlInsertAfter : 1;
const sqlInsertDivider = options && options.sqlInsertDivider ? options.sqlInsertDivider : 'rows';
const { elementsWrapper: ew } = customizations[client];
const fieldNames = Object.keys(json).map(key => `${ew}${key}${ew}`);
const values = Object.keys(json).map(key => (
valueToSqlString({ val: json[key], client, field: fields[key] })
));
const fieldNames = Object.keys(json[0]).map(key => `${ew}${key}${ew}`);
let insertStmt = `INSERT INTO ${ew}${table}${ew} (${fieldNames.join(', ')}) VALUES `;
let insertsString = '';
let queryLength = 0;
let rowsWritten = 0;
return `INSERT INTO ${ew}${table}${ew} (${fieldNames.join(', ')}) VALUES (${values.join(', ')});`;
for (const row of json) {
const values = [];
values.push(Object.keys(row).map(key => (
valueToSqlString({ val: row[key], client, field: fields[key] })
)));
if (
(sqlInsertDivider === 'bytes' && queryLength >= sqlInsertAfter * 1024) ||
(sqlInsertDivider === 'rows' && rowsWritten === sqlInsertAfter)
) {
insertsString += insertStmt+';';
insertStmt = `\nINSERT INTO ${ew}${table}${ew} (${fieldNames.join(', ')}) VALUES `;
rowsWritten = 0;
}
rowsWritten++;
if (rowsWritten > 1) insertStmt += ',\n';
insertStmt += `(${values.join(',')})`;
queryLength = insertStmt.length;
}
if (rowsWritten > 0)
insertsString += insertStmt+';';
return insertsString;
};

View File

@@ -1,22 +1,22 @@
export const shortcutEvents: { [key: string]: { l18n: string; l18nParam?: string | number; context?: 'tab' }} = {
'run-or-reload': { l18n: 'message.runOrReload', context: 'tab' },
'open-new-tab': { l18n: 'message.openNewTab', context: 'tab' },
'close-tab': { l18n: 'message.closeTab', context: 'tab' },
'format-query': { l18n: 'message.formatQuery', context: 'tab' },
'kill-query': { l18n: 'message.killQuery', context: 'tab' },
'query-history': { l18n: 'message.queryHistory', context: 'tab' },
'clear-query': { l18n: 'message.clearQuery', context: 'tab' },
'next-tab': { l18n: 'message.nextTab' },
'prev-tab': { l18n: 'message.previousTab' },
'open-all-connections': { l18n: 'message.openAllConnections' },
'open-filter': { l18n: 'message.openFilter' },
'next-page': { l18n: 'message.nextResultsPage' },
'prev-page': { l18n: 'message.previousResultsPage' },
'toggle-console': { l18n: 'message.toggleConsole' },
'save-content': { l18n: 'message.saveContent' },
'create-connection': { l18n: 'message.createNewConnection' },
'open-settings': { l18n: 'message.openSettings' },
'open-scratchpad': { l18n: 'message.openScratchpad' }
'run-or-reload': { l18n: 'application.runOrReload', context: 'tab' },
'open-new-tab': { l18n: 'application.openNewTab', context: 'tab' },
'close-tab': { l18n: 'application.closeTab', context: 'tab' },
'format-query': { l18n: 'database.formatQuery', context: 'tab' },
'kill-query': { l18n: 'database.killQuery', context: 'tab' },
'query-history': { l18n: 'database.queryHistory', context: 'tab' },
'clear-query': { l18n: 'database.clearQuery', context: 'tab' },
'next-tab': { l18n: 'application.nextTab' },
'prev-tab': { l18n: 'application.previousTab' },
'open-all-connections': { l18n: 'application.openAllConnections' },
'open-filter': { l18n: 'application.openFilter' },
'next-page': { l18n: 'application.nextResultsPage' },
'prev-page': { l18n: 'application.previousResultsPage' },
'toggle-console': { l18n: 'application.toggleConsole' },
'save-content': { l18n: 'application.saveContent' },
'create-connection': { l18n: 'connection.createNewConnection' },
'open-settings': { l18n: 'application.openSettings' },
'open-scratchpad': { l18n: 'application.openScratchpad' }
};
interface ShortcutRecord {
@@ -124,7 +124,7 @@ const shortcuts: ShortcutRecord[] = [
for (let i = 1; i <= 9; i++) {
shortcutEvents[`select-tab-${i}`] = {
l18n: 'message.selectTabNumber',
l18n: 'application.selectTabNumber',
l18nParam: i
};

View File

@@ -0,0 +1,14 @@
import * as antares from 'common/interfaces/antares';
import { ipcMain } from 'electron';
export default (connections: {[key: string]: antares.Client}) => {
ipcMain.handle('get-databases', async (event, uid) => {
try {
const result = await connections[uid].getDatabases();
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
};

View File

@@ -9,6 +9,7 @@ import functions from './functions';
import schedulers from './schedulers';
import updates from './updates';
import application from './application';
import database from './database';
import schema from './schema';
import users from './users';
@@ -22,6 +23,7 @@ export default () => {
routines(connections);
functions(connections);
schedulers(connections);
database(connections);
schema(connections);
users(connections);
updates();

View File

@@ -62,7 +62,7 @@ export default (connections: {[key: string]: antares.Client}) => {
ipcMain.handle('get-structure', async (event, params) => {
try {
const structure = await connections[params.uid].getStructure(
const structure: unknown = await connections[params.uid].getStructure(
params.schemas
);
@@ -160,8 +160,7 @@ export default (connections: {[key: string]: antares.Client}) => {
details: true,
schema,
tabUid,
autocommit,
comments: false
autocommit
});
return { status: 'success', response: result };

View File

@@ -75,6 +75,17 @@ export default (connections: {[key: string]: antares.Client}) => {
}
});
ipcMain.handle('get-table-ddl', async (event, params) => {
try {
const result = await connections[params.uid].getTableDll(params);
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-key-usage', async (event, params) => {
try {
const result = await connections[params.uid].getKeyUsage(params);
@@ -247,6 +258,9 @@ export default (connections: {[key: string]: antares.Client}) => {
if (typeof row[key] === 'string')
row[key] = `'${row[key]}'`;
if (row[key] === null)
row[key] = 'IS NULL';
else
row[key] = `= ${row[key]}`;
}
@@ -352,7 +366,18 @@ export default (connections: {[key: string]: antares.Client}) => {
if (typeof fakeValue === 'string') {
if (params.row[key].length)
fakeValue = fakeValue.substring(0, params.row[key].length);
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
fakeValue = `'${sqlEscaper(fakeValue)}'`;
break;
case 'pg':
case 'sqlite':
case 'firebird':
fakeValue = `'${fakeValue.replaceAll('\'', '\'\'')}'`;
break;
}
}
else if ([...DATE, ...DATETIME].includes(type))
fakeValue = `'${moment(fakeValue).format('YYYY-MM-DD HH:mm:ss.SSSSSS')}'`;

View File

@@ -1,11 +1,20 @@
import { ipcMain } from 'electron';
import { autoUpdater } from 'electron-updater';
import * as Store from 'electron-store';
const persistentStore = new Store({ name: 'settings', clearInvalidConfig: true });
const isMacOS = process.platform === 'darwin';
const persistentStore = new Store({
name: 'settings',
clearInvalidConfig: true,
migrations: {
'0.7.15': store => {
store.set('allow_prerelease', false);
}
}
});
const isMacOS = process.platform === 'darwin';
let mainWindow: Electron.IpcMainEvent;
autoUpdater.allowPrerelease = persistentStore.get('allow_prerelease', true) as boolean;
autoUpdater.allowPrerelease = persistentStore.get('allow_prerelease', false) as boolean;
export default () => {
ipcMain.on('check-for-updates', event => {

View File

@@ -162,6 +162,10 @@ export abstract class AntaresCore {
throw new Error('Method "getDbConfig" not implemented');
}
getDatabases () {
throw new Error('Method "getDatabases" not implemented');
}
createSchema (...args: any) {
throw new Error('Method "createSchema" not implemented');
}
@@ -174,6 +178,10 @@ export abstract class AntaresCore {
throw new Error('Method "dropSchema" not implemented');
}
getTableDll (...args: any) {
throw new Error('Method "getTableDll" not implemented');
}
getDatabaseCollation (...args: any) {
throw new Error('Method "getDatabaseCollation" not implemented');
}

View File

@@ -75,7 +75,7 @@ export class ShortcutRegister {
for (const key of shortcut.keys) {
try {
this._menu.append(new MenuItem({
label: 'Shortcuts',
label: '.',
visible: false,
submenu: [{
label: String(key),

View File

@@ -1047,7 +1047,7 @@ export class FirebirdSQLClient extends AntaresCore {
connection = await this.getConnection();
const transaction = await new Promise<firebird.Transaction>((resolve, reject) => {
(connection as firebird.Database).transaction(firebird.ISOLATION_READ_COMMITED, (err, transaction) => {
(connection as firebird.Database).transaction(firebird.ISOLATION_READ_COMMITTED, (err, transaction) => {
if (err) reject(err);
else resolve(transaction);
});

View File

@@ -9,6 +9,8 @@ export class MySQLClient extends AntaresCore {
private _schema?: string;
private _runningConnections: Map<string, number>;
private _connectionsToCommit: Map<string, mysql.Connection | mysql.PoolConnection>;
private _keepaliveTimer: NodeJS.Timer;
private _keepaliveMs: number;
_connection?: mysql.Connection | mysql.Pool;
_params: mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean};
@@ -52,6 +54,7 @@ export class MySQLClient extends AntaresCore {
this._schema = null;
this._runningConnections = new Map();
this._connectionsToCommit = new Map();
this._keepaliveMs = 10*60*1000;
}
private _getType (field: mysql.FieldPacket & { columnType?: number; columnLength?: number }) {
@@ -147,7 +150,14 @@ export class MySQLClient extends AntaresCore {
if (this._params.ssh) {
try {
this._ssh = new SSH2Promise({ ...this._params.ssh });
if (this._params.ssh.password === '') delete this._params.ssh.password;
if (this._params.ssh.passphrase === '') delete this._params.ssh.passphrase;
this._ssh = new SSH2Promise({
...this._params.ssh,
keepaliveInterval: 30*60*1000,
debug: process.env.NODE_ENV !== 'production' ? (s) => console.log(s) : null
});
const tunnel = await this._ssh.addTunnel({
remoteAddr: this._params.host,
@@ -175,6 +185,8 @@ export class MySQLClient extends AntaresCore {
destroy () {
this._connection.end();
clearInterval(this._keepaliveTimer);
this._keepaliveTimer = undefined;
if (this._ssh) this._ssh.close();
}
@@ -225,6 +237,9 @@ export class MySQLClient extends AntaresCore {
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');
@@ -233,9 +248,19 @@ export class MySQLClient extends AntaresCore {
conn.query(`SET SESSION sql_mode = '${sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`);
});
this._keepaliveTimer = setInterval(async () => {
await this.keepAlive();
}, this._keepaliveMs);
return connection;
}
private async keepAlive () {
const connection = await (this._connection as mysql.Pool).getConnection();
await connection.ping();
connection.release();
}
use (schema: string) {
this._schema = schema;
return this.raw(`USE \`${schema}\``);
@@ -244,25 +269,15 @@ export class MySQLClient extends AntaresCore {
async getStructure (schemas: Set<string>) {
/* eslint-disable camelcase */
interface ShowTableResult {
Db?: string;
Name: string;
Engine: string;
Version: number;
Row_format: string;
Rows: number;
Avg_row_length: number;
Data_length: number;
Max_data_length: number;
Index_length: number;
Data_free: number;
Auto_increment: number;
Create_time: Date;
Update_time: Date;
Check_time?: number;
Collation: string;
Checksum?: number;
Create_options: string;
Comment: string;
TABLE_SCHEMA?: string;
TABLE_NAME: string;
TABLE_TYPE: string;
TABLE_ROWS: number;
ENGINE: string;
DATA_LENGTH: number;
INDEX_LENGTH: number;
TABLE_COLLATION: string;
TABLE_COMMENT: string;
}
interface ShowTriggersResult {
@@ -290,23 +305,70 @@ export class MySQLClient extends AntaresCore {
const { rows: functions } = await this.raw('SHOW FUNCTION STATUS');
const { rows: procedures } = await this.raw('SHOW PROCEDURE STATUS');
const { rows: schedulers } = await this.raw('SELECT *, EVENT_SCHEMA AS `Db`, EVENT_NAME AS `Name` FROM information_schema.`EVENTS`');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let schedulers: any[] = [];
try { // Avoid exception with event_scheduler DISABLED with MariaDB 10
const { rows } = await this.raw('SELECT *, EVENT_SCHEMA AS `Db`, EVENT_NAME AS `Name` FROM information_schema.`EVENTS`');
schedulers = rows;
}
catch (err) {
console.log(err);
}
const tablesArr: ShowTableResult[] = [];
const triggersArr: ShowTriggersResult[] = [];
let schemaSize = 0;
const Store = require('electron-store');
Store.initRenderer();
const settingsStore = new Store({ name: 'settings' });
for (const db of filteredDatabases) {
if (!schemas.has(db.Database)) continue;
let { rows: tables } = await this.raw<antares.QueryResult<ShowTableResult>>(`SHOW TABLE STATUS FROM \`${db.Database}\``);
const showTableSize = settingsStore.get('show_table_size', false);
if (showTableSize) {
let { rows: tables } = await this.raw<antares.QueryResult<ShowTableResult>>(`
SELECT
TABLE_NAME,
TABLE_TYPE,
ENGINE,
DATA_LENGTH,
INDEX_LENGTH,
TABLE_COMMENT,
TABLE_COLLATION,
CREATE_TIME,
UPDATE_TIME
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = "${db.Database}"
ORDER BY TABLE_NAME
`);
if (tables.length) {
tables = tables.map(table => {
table.Db = db.Database;
table.TABLE_SCHEMA = db.Database;
return table;
});
tablesArr.push(...tables);
}
}
else {
let { rows: tables } = await this.raw<antares.QueryResult<ShowTableResult>>(`SHOW FULL TABLES FROM \`${db.Database}\``);
if (tables.length) {
tables = tables.map(table => {
const [name, type] = Object.values(table);
table.TABLE_SCHEMA = db.Database;
table.TABLE_NAME = name;
table.TABLE_TYPE = type;
return table;
});
tablesArr.push(...tables);
}
}
let { rows: triggers } = await this.raw<antares.QueryResult<ShowTriggersResult>>(`SHOW TRIGGERS FROM \`${db.Database}\``);
if (triggers.length) {
@@ -321,9 +383,9 @@ export class MySQLClient extends AntaresCore {
return filteredDatabases.map(db => {
if (schemas.has(db.Database)) {
// TABLES
const remappedTables: antares.TableInfos[] = tablesArr.filter(table => table.Db === db.Database).map(table => {
const remappedTables: antares.TableInfos[] = tablesArr.filter(table => table.TABLE_SCHEMA === db.Database).map(table => {
let tableType;
switch (table.Comment) {
switch (table.TABLE_TYPE) {
case 'VIEW':
tableType = 'view';
break;
@@ -332,20 +394,17 @@ export class MySQLClient extends AntaresCore {
break;
}
const tableSize = Number(table.Data_length) + Number(table.Index_length);
const tableSize = Number(table.DATA_LENGTH) + Number(table.INDEX_LENGTH);
schemaSize += tableSize;
return {
name: table.Name,
name: table.TABLE_NAME,
type: tableType,
rows: table.Rows,
created: table.Create_time,
updated: table.Update_time,
engine: table.Engine,
comment: table.Comment,
rows: table.TABLE_ROWS,
engine: table.ENGINE,
comment: table.TABLE_COMMENT,
size: tableSize,
autoIncrement: table.Auto_increment,
collation: table.Collation
collation: table.TABLE_COLLATION
};
});
@@ -523,7 +582,7 @@ export class MySQLClient extends AntaresCore {
return rows.map((field) => {
const numLengthMatch = field.COLUMN_TYPE.match(/int\(([^)]+)\)/);
const numLength = numLengthMatch ? +numLengthMatch.pop() : field.NUMERIC_PRECISION || null;
const enumValues = /(enum|set)/.test(field.COLUMN_TYPE)
const enumValues = /(enum)/.test(field.COLUMN_TYPE)
? field.COLUMN_TYPE.match(/\(([^)]+)\)/)[0].slice(1, -1)
: null;
@@ -630,11 +689,11 @@ export class MySQLClient extends AntaresCore {
return rows.map(row => {
return {
unique: !row.Non_unique,
unique: !Number(row.Non_unique),
name: row.Key_name,
column: row.Column_name,
indexType: row.Index_type,
type: row.Key_name === 'PRIMARY' ? 'PRIMARY' : !row.Non_unique ? 'UNIQUE' : row.Index_type === 'FULLTEXT' ? 'FULLTEXT' : 'INDEX',
type: row.Key_name === 'PRIMARY' ? 'PRIMARY' : !Number(row.Non_unique) ? 'UNIQUE' : row.Index_type === 'FULLTEXT' ? 'FULLTEXT' : 'INDEX',
cardinality: row.Cardinality,
comment: row.Comment,
indexComment: row.Index_comment
@@ -642,6 +701,17 @@ export class MySQLClient extends AntaresCore {
});
}
async getTableDll ({ schema, table }: { schema: string; table: string }) {
const { rows } = await this.raw<antares.QueryResult<{
'Create Table'?: string;
Table: string;
}>>(`SHOW CREATE TABLE \`${schema}\`.\`${table}\``);
if (rows.length)
return rows[0]['Create Table'];
else return '';
}
async getKeyUsage ({ schema, table }: { schema: string; table: string }) {
interface KeyResult {
TABLE_SCHEMA: string;
@@ -694,7 +764,7 @@ export class MySQLClient extends AntaresCore {
}
async getUsers () {
const { rows } = await this.raw('SELECT `user`, `host`, authentication_string AS `password` FROM `mysql`.`user`');
const { rows } = await this.raw('SELECT `user` as \'user\', `host` as \'host\', authentication_string AS `password` FROM `mysql`.`user`');
return rows.map(row => {
return {
@@ -795,21 +865,23 @@ export class MySQLClient extends AntaresCore {
options
} = params;
let sql = `ALTER TABLE \`${schema}\`.\`${table}\` `;
const alterColumns: string[] = [];
const sql = `ALTER TABLE \`${schema}\`.\`${table}\` `;
const alterColumnsAdd: string[] = [];
const alterColumnsChange: string[] = [];
const alterColumnsDrop: string[] = [];
// OPTIONS
if ('comment' in options) alterColumns.push(`COMMENT='${options.comment}'`);
if ('engine' in options) alterColumns.push(`ENGINE=${options.engine}`);
if ('autoIncrement' in options) alterColumns.push(`AUTO_INCREMENT=${+options.autoIncrement}`);
if ('collation' in options) alterColumns.push(`COLLATE='${options.collation}'`);
if ('comment' in options) alterColumnsChange.push(`COMMENT='${options.comment}'`);
if ('engine' in options) alterColumnsChange.push(`ENGINE=${options.engine}`);
if ('autoIncrement' in options) alterColumnsChange.push(`AUTO_INCREMENT=${+options.autoIncrement}`);
if ('collation' in options) alterColumnsChange.push(`COLLATE='${options.collation}'`);
// ADD FIELDS
additions.forEach(addition => {
const typeInfo = this.getTypeInfo(addition.type);
const length = typeInfo.length ? addition.enumValues || addition.numLength || addition.charLength || addition.datePrecision : false;
alterColumns.push(`ADD COLUMN \`${addition.name}\`
alterColumnsAdd.push(`ADD COLUMN \`${addition.name}\`
${addition.type.toUpperCase()}${length ? `(${length}${addition.numScale ? `,${addition.numScale}` : ''})` : ''}
${addition.unsigned ? 'UNSIGNED' : ''}
${addition.zerofill ? 'ZEROFILL' : ''}
@@ -828,18 +900,18 @@ export class MySQLClient extends AntaresCore {
let type = addition.type;
if (type === 'PRIMARY')
alterColumns.push(`ADD PRIMARY KEY (${fields})`);
alterColumnsAdd.push(`ADD PRIMARY KEY (${fields})`);
else {
if (type === 'UNIQUE')
type = 'UNIQUE INDEX';
alterColumns.push(`ADD ${type} \`${addition.name}\` (${fields})`);
alterColumnsAdd.push(`ADD ${type} \`${addition.name}\` (${fields})`);
}
});
// ADD FOREIGN KEYS
foreignChanges.additions.forEach(addition => {
alterColumns.push(`ADD CONSTRAINT \`${addition.constraintName}\` FOREIGN KEY (\`${addition.field}\`) REFERENCES \`${addition.refTable}\` (\`${addition.refField}\`) ON UPDATE ${addition.onUpdate} ON DELETE ${addition.onDelete}`);
alterColumnsAdd.push(`ADD CONSTRAINT \`${addition.constraintName}\` FOREIGN KEY (\`${addition.field}\`) REFERENCES \`${addition.refTable}\` (\`${addition.refField}\`) ON UPDATE ${addition.onUpdate} ON DELETE ${addition.onDelete}`);
});
// CHANGE FIELDS
@@ -847,7 +919,7 @@ export class MySQLClient extends AntaresCore {
const typeInfo = this.getTypeInfo(change.type);
const length = typeInfo.length ? change.enumValues || change.numLength || change.charLength || change.datePrecision : false;
alterColumns.push(`CHANGE COLUMN \`${change.orgName}\` \`${change.name}\`
alterColumnsChange.push(`CHANGE COLUMN \`${change.orgName}\` \`${change.name}\`
${change.type.toUpperCase()}${length ? `(${length}${change.numScale ? `,${change.numScale}` : ''})` : ''}
${change.unsigned ? 'UNSIGNED' : ''}
${change.zerofill ? 'ZEROFILL' : ''}
@@ -863,53 +935,56 @@ export class MySQLClient extends AntaresCore {
// CHANGE INDEX
indexChanges.changes.forEach(change => {
if (change.oldType === 'PRIMARY')
alterColumns.push('DROP PRIMARY KEY');
alterColumnsChange.push('DROP PRIMARY KEY');
else
alterColumns.push(`DROP INDEX \`${change.oldName}\``);
alterColumnsChange.push(`DROP INDEX \`${change.oldName}\``);
const fields = change.fields.map(field => `\`${field}\``).join(',');
let type = change.type;
if (type === 'PRIMARY')
alterColumns.push(`ADD PRIMARY KEY (${fields})`);
alterColumnsChange.push(`ADD PRIMARY KEY (${fields})`);
else {
if (type === 'UNIQUE')
type = 'UNIQUE INDEX';
alterColumns.push(`ADD ${type} \`${change.name}\` (${fields})`);
alterColumnsChange.push(`ADD ${type} \`${change.name}\` (${fields})`);
}
});
// CHANGE FOREIGN KEYS
foreignChanges.changes.forEach(change => {
alterColumns.push(`DROP FOREIGN KEY \`${change.oldName}\``);
alterColumns.push(`ADD CONSTRAINT \`${change.constraintName}\` FOREIGN KEY (\`${change.field}\`) REFERENCES \`${change.refTable}\` (\`${change.refField}\`) ON UPDATE ${change.onUpdate} ON DELETE ${change.onDelete}`);
alterColumnsChange.push(`DROP FOREIGN KEY \`${change.oldName}\``);
alterColumnsChange.push(`ADD CONSTRAINT \`${change.constraintName}\` FOREIGN KEY (\`${change.field}\`) REFERENCES \`${change.refTable}\` (\`${change.refField}\`) ON UPDATE ${change.onUpdate} ON DELETE ${change.onDelete}`);
});
// DROP FIELDS
deletions.forEach(deletion => {
alterColumns.push(`DROP COLUMN \`${deletion.name}\``);
alterColumnsDrop.push(`DROP COLUMN \`${deletion.name}\``);
});
// DROP INDEX
indexChanges.deletions.forEach(deletion => {
if (deletion.type === 'PRIMARY')
alterColumns.push('DROP PRIMARY KEY');
alterColumnsDrop.push('DROP PRIMARY KEY');
else
alterColumns.push(`DROP INDEX \`${deletion.name}\``);
alterColumnsDrop.push(`DROP INDEX \`${deletion.name}\``);
});
// DROP FOREIGN KEYS
foreignChanges.deletions.forEach(deletion => {
alterColumns.push(`DROP FOREIGN KEY \`${deletion.constraintName}\``);
alterColumnsDrop.push(`DROP FOREIGN KEY \`${deletion.constraintName}\``);
});
sql += alterColumns.join(', ');
const alterQueryes = [];
if (alterColumnsAdd.length) alterQueryes.push(sql+alterColumnsAdd.join(', '));
if (alterColumnsChange.length) alterQueryes.push(sql+alterColumnsChange.join(', '));
if (alterColumnsDrop.length) alterQueryes.push(sql+alterColumnsDrop.join(', '));
// RENAME
if (options.name) sql += `; RENAME TABLE \`${schema}\`.\`${table}\` TO \`${schema}\`.\`${options.name}\``;
if (options.name) alterQueryes.push(`RENAME TABLE \`${schema}\`.\`${table}\` TO \`${schema}\`.\`${options.name}\``);
return await this.raw(sql);
return await this.raw(alterQueryes.join(';'));
}
async duplicateTable (params: { schema: string; table: string }) {
@@ -1604,7 +1679,7 @@ export class MySQLClient extends AntaresCore {
if (!field || Array.isArray(field))
return undefined;
const type = this._getType(field);
const type = this._getType(field as undefined);
return {
name: field.orgName,

View File

@@ -1,11 +1,11 @@
import * as antares from 'common/interfaces/antares';
import * as mysql from 'mysql2';
import * as pg from 'pg';
import * as pgAst from 'pgsql-ast-parser';
import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/postgresql';
import SSH2Promise = require('ssh2-promise');
import SSHConfig from 'ssh2-promise/lib/sshConfig';
import { ConnectionOptions } from 'tls';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function pgToString (value: any) {
@@ -84,6 +84,8 @@ export class PostgreSQLClient extends AntaresCore {
private _schema?: string;
private _runningConnections: Map<string, number>;
private _connectionsToCommit: Map<string, pg.Client | pg.PoolClient>;
private _keepaliveTimer: NodeJS.Timer;
private _keepaliveMs: number;
protected _connection?: pg.Client | pg.Pool;
private types: {[key: string]: string} = {};
private _arrayTypes: {[key: string]: string} = {
@@ -96,7 +98,7 @@ export class PostgreSQLClient extends AntaresCore {
_varchar: 'CHARACTER VARYING'
}
_params: pg.ClientConfig & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean};
_params: pg.ClientConfig & {schema: string; ssl?: ConnectionOptions; ssh?: SSHConfig; readonly: boolean};
constructor (args: antares.ClientParams) {
super(args);
@@ -104,6 +106,7 @@ export class PostgreSQLClient extends AntaresCore {
this._schema = null;
this._runningConnections = new Map();
this._connectionsToCommit = new Map();
this._keepaliveMs = 10*60*1000;
for (const key in pg.types.builtins) {
const builtinKey = key as builtinsTypes;
@@ -151,9 +154,9 @@ export class PostgreSQLClient extends AntaresCore {
host: this._params.host,
port: this._params.port,
user: this._params.user,
database: undefined as string | undefined,
database: 'postgres' as string,
password: this._params.password,
ssl: null as mysql.SslOptions
ssl: null as ConnectionOptions
};
if (this._params.database?.length) dbConfig.database = this._params.database;
@@ -162,7 +165,11 @@ export class PostgreSQLClient extends AntaresCore {
if (this._params.ssh) {
try {
this._ssh = new SSH2Promise({ ...this._params.ssh });
this._ssh = new SSH2Promise({
...this._params.ssh,
keepaliveInterval: 30*60*1000,
debug: process.env.NODE_ENV !== 'production' ? (s) => console.log(s) : null
});
const tunnel = await this._ssh.addTunnel({
remoteAddr: this._params.host,
@@ -218,14 +225,26 @@ export class PostgreSQLClient extends AntaresCore {
});
}
this._keepaliveTimer = setInterval(async () => {
await this.keepAlive();
}, this._keepaliveMs);
return connection;
}
destroy () {
this._connection.end();
clearInterval(this._keepaliveTimer);
this._keepaliveTimer = undefined;
if (this._ssh) this._ssh.close();
}
private async keepAlive () {
const connection = await this._connection.connect() as pg.PoolClient;
await connection.query('SELECT 1+1');
connection.release();
}
use (schema: string, connection?: pg.Client | pg.PoolClient) {
this._schema = schema;
@@ -243,6 +262,18 @@ export class PostgreSQLClient extends AntaresCore {
return [];
}
async getDatabases () {
const { rows } = await this.raw('SELECT datname FROM pg_database WHERE datistemplate = false');
if (rows) {
return rows.reduce((acc, cur) => {
acc.push(cur.datname);
return acc;
}, [] as string[]);
}
else
return [];
}
async getStructure (schemas: Set<string>) {
/* eslint-disable camelcase */
interface ShowTableResult {
@@ -563,6 +594,142 @@ export class PostgreSQLClient extends AntaresCore {
}, {} as {table: string; schema: string}[]);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async getTableDll ({ schema, table }: { schema: string; table: string }) {
// const { rows } = await this.raw<antares.QueryResult<{'ddl'?: string}>>(`
// SELECT
// 'CREATE TABLE ' || relname || E'\n(\n' ||
// array_to_string(
// array_agg(' ' || column_name || ' ' || type || ' '|| not_null)
// , E',\n'
// ) || E'\n);\n' AS ddl
// FROM (
// SELECT
// a.attname AS column_name
// , pg_catalog.format_type(a.atttypid, a.atttypmod) AS type
// , CASE WHEN a.attnotnull THEN 'NOT NULL' ELSE 'NULL' END AS not_null
// , c.relname
// FROM pg_attribute a, pg_class c, pg_type t
// WHERE a.attnum > 0
// AND a.attrelid = c.oid
// AND a.atttypid = t.oid
// AND c.relname = '${table}'
// ORDER BY a.attnum
// ) AS tabledefinition
// GROUP BY relname
// `);
// if (rows.length)
// return rows[0].ddl;
// else return '';
/* eslint-disable camelcase */
interface SequenceRecord {
sequence_catalog: string;
sequence_schema: string;
sequence_name: string;
data_type: string;
numeric_precision: number;
numeric_precision_radix: number;
numeric_scale: number;
start_value: string;
minimum_value: string;
maximum_value: string;
increment: string;
cycle_option: string;
}
/* eslint-enable camelcase */
let createSql = '';
const sequences = [];
const columnsSql = [];
const arrayTypes: {[key: string]: string} = {
_int2: 'smallint',
_int4: 'integer',
_int8: 'bigint',
_float4: 'real',
_float8: 'double precision',
_char: '"char"',
_varchar: 'character varying'
};
// Table columns
const { rows } = await this.raw(`
SELECT *
FROM "information_schema"."columns"
WHERE "table_schema" = '${schema}'
AND "table_name" = '${table}'
ORDER BY "ordinal_position" ASC
`, { schema: 'information_schema' });
if (!rows.length) return '';
for (const column of rows) {
let fieldType = column.data_type;
if (fieldType === 'USER-DEFINED') fieldType = `"${schema}".${column.udt_name}`;
else if (fieldType === 'ARRAY') {
if (Object.keys(arrayTypes).includes(fieldType))
fieldType = arrayTypes[column.udt_name] + '[]';
else
fieldType = column.udt_name.replaceAll('_', '') + '[]';
}
const columnArr = [
`"${column.column_name}"`,
`${fieldType}${column.character_maximum_length ? `(${column.character_maximum_length})` : ''}`
];
if (column.column_default) {
columnArr.push(`DEFAULT ${column.column_default}`);
if (column.column_default.includes('nextval')) {
const sequenceName = column.column_default.split('\'')[1];
sequences.push(sequenceName);
}
}
if (column.is_nullable === 'NO') columnArr.push('NOT NULL');
columnsSql.push(columnArr.join(' '));
}
// Table sequences
for (let sequence of sequences) {
if (sequence.includes('.')) sequence = sequence.split('.')[1];
const { rows } = await this.select('*')
.schema('information_schema')
.from('sequences')
.where({ sequence_schema: `= '${schema}'`, sequence_name: `= '${sequence}'` })
.run<SequenceRecord>();
if (rows.length) {
createSql += `CREATE SEQUENCE "${schema}"."${sequence}"
START WITH ${rows[0].start_value}
INCREMENT BY ${rows[0].increment}
MINVALUE ${rows[0].minimum_value}
MAXVALUE ${rows[0].maximum_value}
CACHE 1;\n`;
}
}
// Table create
createSql += `\nCREATE TABLE "${schema}"."${table}"(
${columnsSql.join(',\n ')}
);\n`;
// Table indexes
createSql += '\n';
const { rows: indexes } = await this.select('*')
.schema('pg_catalog')
.from('pg_indexes')
.where({ schemaname: `= '${schema}'`, tablename: `= '${table}'` })
.run<{indexdef: string}>();
for (const index of indexes)
createSql += `${index.indexdef};\n`;
return createSql;
}
async getKeyUsage ({ schema, table }: { schema: string; table: string }) {
/* eslint-disable camelcase */
interface KeyResult {
@@ -1412,7 +1579,7 @@ export class PostgreSQLClient extends AntaresCore {
if (args.tabUid && isPool)
this._runningConnections.set(args.tabUid, (connection as pg.PoolClient & { processID: number }).processID);
if (args.schema && args.schema !== 'public')
if (args.schema)
await this.use(args.schema, connection);
for (const query of queries) {

View File

@@ -344,6 +344,20 @@ export class SQLiteClient extends AntaresCore {
const tmpName = `Antares_${table}_tmp`;
await this.raw(`CREATE TABLE "${tmpName}" AS SELECT * FROM "${table}"`);
// Get table triggers before drop
const { rows: triggers } = await this.raw(`SELECT * FROM "${schema}".sqlite_master WHERE type='trigger' AND tbl_name = '${table}'`);
const remappedTriggers = triggers.map((row) => {
return {
schema,
sql: row.sql.match(/(BEGIN|begin)(.*)(END|end)/gs)[0],
name: row.name,
table: row.sql.match(/(?<=ON `).*?(?=`)/gs)[0],
activation: row.sql.match(/(BEFORE|AFTER)/gs)[0],
event: row.sql.match(/(INSERT|UPDATE|DELETE)/gs)[0]
};
});
await this.dropTable(params);
const createTableParams = {
@@ -376,6 +390,11 @@ export class SQLiteClient extends AntaresCore {
await this.raw(`INSERT INTO "${createTableParams.options.name}" (${insertFields.join(',')}) SELECT ${selectFields.join(',')} FROM "${tmpName}"`);
await this.dropTable({ schema: schema, table: tmpName });
// Recreates triggers
for (const trigger of remappedTriggers)
await this.createTrigger(trigger);
await this.raw('PRAGMA foreign_keys = 1');
await this.raw('COMMIT');
}
@@ -406,7 +425,7 @@ export class SQLiteClient extends AntaresCore {
return results.rows.map(row => {
return {
sql: row.sql.match(/(?<=AS ).*?$/gs)[0],
sql: row.sql.match(/(?<=(AS|as)( |\n)).*?$/gs)[0],
name: view
};
})[0];
@@ -672,8 +691,8 @@ export class SQLiteClient extends AntaresCore {
if ([...TIME, ...DATETIME].includes(parsedType)) {
const firstNotNull = queryAllResult.find(res => res[field.name] !== null);
if (firstNotNull && firstNotNull[field.name].includes('.'))
length = firstNotNull[field.name].split('.').pop().length;
if (firstNotNull && String(firstNotNull[field.name]).includes('.'))
length = String(firstNotNull[field.name]).split('.').pop().length;
}
return {

View File

@@ -184,9 +184,9 @@ CREATE TABLE \`${view.Name}\`(
const { rows: triggers } = await this._client.raw(
`SHOW TRIGGERS FROM \`${this.schemaName}\``
);
const generatedTables = this._tables
.filter(t => t.includeStructure)
.map(t => t.table);
// const generatedTables = this._tables
// .filter(t => t.includeStructure)
// .map(t => t.table);
let sqlString = '';
@@ -200,7 +200,7 @@ CREATE TABLE \`${view.Name}\`(
sql_mode: sqlMode
} = trigger;
if (!generatedTables.includes(table)) continue;
// if (!generatedTables.includes(table)) continue;// Commented to avoid issues if export contains triggers without tables
const definer = this.getEscapedDefiner(trigger.Definer);
sqlString += '/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;;\n';

View File

@@ -47,7 +47,7 @@ export default class MySQLImporter extends BaseImporter {
catch (error) {
this.emit('query-error', {
sql: query,
message: error.sqlMessage,
message: error.sqlMessage || error.message,
sqlSnippet: error.sql,
time: new Date().getTime()
});

View File

@@ -37,6 +37,7 @@ async function createMainWindow () {
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
devTools: isDevelopment,
spellcheck: false
},
autoHideMenuBar: true,
@@ -171,9 +172,6 @@ function createAppMenu () {
{
role: 'editMenu'
},
{
role: 'viewMenu'
},
{
role: 'windowMenu'
}

View File

@@ -7,7 +7,7 @@ import MysqlExporter from '../libs/exporters/sql/MysqlExporter';
import PostgreSQLExporter from '../libs/exporters/sql/PostgreSQLExporter';
let exporter: antares.Exporter;
process.on('message', async ({ type, client, tables, options }) => {
process.on('message', async ({ type, client, tables, options }: any) => {
if (type === 'init') {
const connection = await ClientsFactory.getClient({
client: client.name,

View File

@@ -6,10 +6,20 @@ import { PostgreSQLClient } from '../libs/clients/PostgreSQLClient';
import { ClientsFactory } from '../libs/ClientsFactory';
import MySQLImporter from '../libs/importers/sql/MySQLlImporter';
import PostgreSQLImporter from '../libs/importers/sql/PostgreSQLImporter';
import SSHConfig from 'ssh2-promise/lib/sshConfig';
import { ImportOptions } from 'common/interfaces/importer';
let importer: antares.Importer;
process.on('message', async ({ type, dbConfig, options }) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
process.on('message', async ({ type, dbConfig, options }: {
type: string;
dbConfig: mysql.ConnectionOptions & { schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean }
| pg.ClientConfig & { schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean }
| { databasePath: string; readonly: boolean };
options: ImportOptions;
}) => {
if (type === 'init') {
try {
const connection = await ClientsFactory.getClient({
client: options.type,
params: {
@@ -72,6 +82,30 @@ process.on('message', async ({ type, dbConfig, options }) => {
importer.run();
}
catch (err) {
console.error(err);
process.send({
type: 'error',
payload: err.toString()
});
}
}
else if (type === 'cancel')
importer.cancel();
});
process.on('uncaughtException', (err) => {
console.error(err);
process.send({
type: 'error',
payload: err.toString()
});
});
process.on('unhandledRejection', (err) => {
console.error(err);
process.send({
type: 'error',
payload: err.toString()
});
});

View File

@@ -21,7 +21,15 @@
<BaseTextEditor class="d-none" value="" />
</div>
</div>
<ModalAllConnections v-if="isAllConnectionsModal" @close="isAllConnectionsModal = false" />
<ModalAllConnections
v-if="isAllConnectionsModal"
@close="isAllConnectionsModal = false"
/>
<ModalExportSchema
v-if="isExportSchemaModal"
@close="hideExportModal"
/>
</div>
</template>
@@ -33,9 +41,11 @@ import { useI18n } from 'vue-i18n';
import { Menu, getCurrentWindow } from '@electron/remote';
import { useApplicationStore } from '@/stores/application';
import { useConnectionsStore } from '@/stores/connections';
import { useSchemaExportStore } from '@/stores/schemaExport';
import { useSettingsStore } from '@/stores/settings';
import { useWorkspacesStore } from '@/stores/workspaces';
import TheSettingBar from '@/components/TheSettingBar.vue';
import ModalExportSchema from '@/components/ModalExportSchema.vue';
const { t } = useI18n();
@@ -65,6 +75,10 @@ const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { checkVersionUpdate } = applicationStore;
const { changeApplicationTheme } = settingsStore;
const schemaExportStore = useSchemaExportStore();
const { hideExportModal } = schemaExportStore;
const { isExportModal: isExportSchemaModal } = storeToRefs(schemaExportStore);
const isAllConnectionsModal: Ref<boolean> = ref(false);
document.addEventListener('DOMContentLoaded', () => {
@@ -95,22 +109,22 @@ onMounted(() => {
const InputMenu = Menu.buildFromTemplate([
{
label: t('word.cut'),
label: t('general.cut'),
role: 'cut'
},
{
label: t('word.copy'),
label: t('general.copy'),
role: 'copy'
},
{
label: t('word.paste'),
label: t('general.paste'),
role: 'paste'
},
{
type: 'separator'
},
{
label: t('message.selectAll'),
label: t('general.selectAll'),
role: 'selectAll'
}
]);
@@ -130,6 +144,12 @@ onMounted(() => {
node = node.parentNode;
}
});
document.addEventListener('keydown', e => {
if (e.altKey && e.key === 'Alt') { // Prevent Alt key to trigger hidden shortcut menu
e.preventDefault();
}
});
});
</script>

View File

@@ -31,13 +31,13 @@
class="btn btn-primary mr-2"
@click.stop="confirmModal"
>
{{ confirmText || t('word.confirm') }}
{{ confirmText || t('general.confirm') }}
</button>
<button
class="btn btn-link"
@click="hideModal"
>
{{ cancelText || t('word.cancel') }}
{{ cancelText || t('general.cancel') }}
</button>
</div>
</div>

View File

@@ -1,5 +1,5 @@
<template>
<div class="context" :class="{'bottom': isBottom}">
<div class="context" :class="[{ 'bottom': isBottom }, { 'right': isRight }]">
<a
class="context-overlay"
@click="close"
@@ -19,8 +19,9 @@
import { computed, onBeforeUnmount, onMounted, Ref, ref } from 'vue';
const contextContent: Ref<HTMLDivElement> = ref(null);
const contextSize: Ref<DOMRect> = ref(null);
const contextSize: Ref<{height: number; width: number; subHeight?: number; subWidth?: number}> = ref(null);
const isBottom: Ref<boolean> = ref(false);
const isRight: Ref<boolean> = ref(false);
const props = defineProps<{ contextEvent: MouseEvent }>();
const emit = defineEmits(['close-context']);
@@ -39,8 +40,16 @@ const position = computed(() => {
isBottom.value = true;
}
if (clientX + contextSize.value.width + 5 >= window.innerWidth)
if (clientY + contextSize.value.subHeight + contextSize.value.height >= window.innerHeight)
isBottom.value = true;
if (clientX + contextSize.value.width + 5 >= window.innerWidth) {
leftCord = `${clientX - contextSize.value.width}px`;
isRight.value = true;
}
if (clientX + contextSize.value.subWidth + contextSize.value.width >= window.innerWidth)
isRight.value = true;
}
}
@@ -62,8 +71,20 @@ const onKey = (e: KeyboardEvent) => {
window.addEventListener('keydown', onKey);
onMounted(() => {
if (contextContent.value)
if (contextContent.value) {
contextSize.value = contextContent.value.getBoundingClientRect();
const submenus = contextContent.value.querySelectorAll<HTMLDivElement>('.context-submenu');
for (const submenu of submenus) {
const submenuSize = submenu.getBoundingClientRect();
if (!contextSize.value.subHeight || submenuSize.height > contextSize.value.subHeight)
contextSize.value.subHeight = submenuSize.height;
if (!contextSize.value.subWidth || submenuSize.width > contextSize.value.subWidth)
contextSize.value.subWidth = submenuSize.width;
}
}
});
onBeforeUnmount(() => {
@@ -94,6 +115,14 @@ onBeforeUnmount(() => {
bottom: -0.2rem;
}
&:not(.right) .context-submenu {
left: 100%;
}
&.right .context-submenu {
right: 100%;
}
.context-container {
min-width: 100px;
z-index: 10;
@@ -124,7 +153,6 @@ onBeforeUnmount(() => {
visibility: hidden;
transition: opacity 0.2s;
position: absolute;
left: 100%;
min-width: 100px;
background: #1d1d1d;
}
@@ -156,5 +184,4 @@ onBeforeUnmount(() => {
filter: grayscale(100%);
opacity: 0.5;
}
</style>

View File

@@ -44,6 +44,11 @@ watch(() => props.mode, () => {
editor.session.setMode(`ace/mode/${props.mode}`);
});
watch(() => props.modelValue, () => {
if (editor && props.readOnly)
editor.session.setValue(props.modelValue);
});
watch(editorTheme, () => {
if (editor)
editor.setTheme(`ace/theme/${editorTheme.value}`);
@@ -113,6 +118,8 @@ onMounted(() => {
}, 20);
}
editor.commands.removeCommand('showSettingsMenu');
setTimeout(() => {
editor.resize();
}, 20);

View File

@@ -16,6 +16,7 @@
:id="`id_${id}`"
class="file-uploader-input"
type="file"
:accept="accept"
@change="$emit('change', $event)"
>
</form>
@@ -33,6 +34,10 @@ defineProps({
default: 'Browse',
type: String
},
accept: {
default: '',
type: String
},
modelValue: {
default: '',
type: String

View File

@@ -4,7 +4,7 @@
v-model="selectedGroup"
class="form-select"
:options="[{name: 'manual'}, ...fakerGroups]"
:option-label="(opt: any) => opt.name === 'manual' ? t('message.manualValue') : t(`faker.${opt.name}`)"
:option-label="(opt: any) => opt.name === 'manual' ? t('general.manualValue') : t(`faker.${opt.name}`)"
option-track-by="name"
:disabled="!isChecked"
:style="'flex-grow: 0;'"
@@ -41,7 +41,7 @@
<BaseUploadInput
v-else-if="inputProps().type === 'file'"
:model-value="selectedValue"
:message="t('word.browse')"
:message="t('general.browse')"
@clear="clearValue"
@change="filesChange($event)"
/>

View File

@@ -4,7 +4,7 @@
class="form-input"
type="text"
:value="pressedKeys"
:placeholder="t('message.registerAShortcut')"
:placeholder="t('application.registerAShortcut')"
@focus="isFocus = true"
@blur="isFocus = false"
@keydown.prevent.stop="onKey"
@@ -49,7 +49,7 @@ const pressedKeys = computed(() => {
keys.push('Shift');
if (keyboardEvent.value.code) {
if (keys.length === 0 && (keyboardEvent.value.key.length === 1 || singleKeysToIgnore.includes(keyboardEvent.value.key)))
return t('message.invalidShortcutMessage');
return t('application.invalidShortcutMessage');
else if (!specialKeys.includes(keyboardEvent.value.key)) {
if (keyboardEvent.value.key === 'Dead') {
keys.push(keyboardEvent.value.code
@@ -82,7 +82,7 @@ const pressedKeys = computed(() => {
}
}
else
return t('message.invalidShortcutMessage');
return t('application.invalidShortcutMessage');
}
}
@@ -96,7 +96,7 @@ const onKey = (e: KeyboardEvent) => {
};
watch(pressedKeys, (value) => {
if (value !== t('message.invalidShortcutMessage'))
if (value !== t('application.invalidShortcutMessage'))
emit('update:modelValue', pressedKeys.value);
});

View File

@@ -7,7 +7,7 @@
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-apps mr-1" />
<span class="cut-text">{{ t('message.allConnections') }}</span>
<span class="cut-text">{{ t('connection.allConnections') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -21,7 +21,7 @@
v-model="searchTerm"
class="form-input"
type="text"
:placeholder="t('message.searchForConnections')"
:placeholder="t('connection.searchForConnections')"
@keypress.esc="searchTerm = ''"
>
<i v-if="!searchTerm" class="form-icon mdi mdi-magnify mdi-18px pr-4" />
@@ -58,7 +58,7 @@
<div class="all-connections-buttons p-absolute d-flex" :style="'top: 0; right: 0;'">
<i
class="all-connections-delete mdi mdi-delete mdi-18px ml-2"
:title="t('word.delete')"
:title="t('general.delete')"
@click.stop="askToDelete(connection)"
/>
</div>
@@ -99,7 +99,7 @@
</div>
<div class="panel-footer text-center py-0">
<div v-if="connection.ssl" class="chip bg-success mt-2">
<i class="mdi mdi-lock mdi-18px mr-1" />
<i class="mdi mdi-shield-key mdi-18px mr-1" />
SSL
</div>
<div v-if="connection.ssh" class="chip bg-success mt-2">
@@ -130,12 +130,12 @@
>
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-server-remove mr-1" /> {{ t('message.deleteConnection') }}
<i class="mdi mdi-24px mdi-server-remove mr-1" /> {{ t('connection.deleteConnection') }}
</div>
</template>
<template #body>
<div class="mb-2">
{{ t('message.deleteCorfirm') }} <b>{{ selectedConnectionName }}</b>?
{{ t('general.deleteConfirm') }} <b>{{ selectedConnectionName }}</b>?
</div>
</template>
</ConfirmModal>

View File

@@ -6,7 +6,7 @@
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-key-variant mr-1" /> {{ t('word.credentials') }}
<i class="mdi mdi-24px mdi-key-variant mr-1" /> {{ t('connection.credentials') }}
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -16,7 +16,7 @@
<form class="form-horizontal">
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ t('word.user') }}</label>
<label class="form-label">{{ t('connection.user') }}</label>
</div>
<div class="col-9">
<input
@@ -29,7 +29,7 @@
</div>
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ t('word.password') }}</label>
<label class="form-label">{{ t('connection.password') }}</label>
</div>
<div class="col-9">
<input
@@ -44,10 +44,10 @@
</div>
<div class="modal-footer">
<button class="btn btn-primary mr-2" @click.stop="sendCredentials">
{{ t('word.send') }}
{{ t('general.send') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ t('word.close') }}
{{ t('general.close') }}
</button>
</div>
</div>

View File

@@ -1,7 +1,7 @@
<template>
<ConfirmModal
:confirm-text="t('word.run')"
:cancel-text="t('word.cancel')"
:confirm-text="t('general.run')"
:cancel-text="t('general.cancel')"
size="400"
@confirm="runRoutine"
@hide="closeModal"
@@ -9,7 +9,7 @@
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-play mr-1" />
<span class="cut-text">{{ t('word.parameters') }}: {{ localRoutine.name }}</span>
<span class="cut-text">{{ t('database.parameters') }}: {{ localRoutine.name }}</span>
</div>
</template>
<template #body>

View File

@@ -7,7 +7,7 @@
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-brush-variant mr-1" />
<span class="cut-text">{{ t('message.editConnectionAppearence') }}</span>
<span class="cut-text">{{ t('application.editConnectionAppearance') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -17,7 +17,7 @@
<form class="form-horizontal">
<div class="form-group mb-4">
<div class="col-3">
<label class="form-label">{{ t('word.label') }}</label>
<label class="form-label">{{ t('application.label') }}</label>
</div>
<div class="col-9">
<input
@@ -31,7 +31,7 @@
</div>
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ t('word.icon') }}</label>
<label class="form-label">{{ t('application.icon') }}</label>
</div>
<div class="col-9 icons-wrapper">
<div
@@ -48,11 +48,11 @@
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary mr-2" @click.stop="editFolderAppearence">
{{ t('word.update') }}
<button class="btn btn-primary mr-2" @click.stop="editFolderAppearance">
{{ t('application.update') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ t('word.close') }}
{{ t('general.close') }}
</button>
</div>
</div>
@@ -136,7 +136,7 @@ const { trapRef } = useFocusTrap();
const firstInput: Ref<HTMLInputElement> = ref(null);
const localConnection: Ref<SidebarElement> = ref(unproxify(props.connection));
const editFolderAppearence = () => {
const editFolderAppearance = () => {
updateConnectionOrder(localConnection.value);
closeModal();
};

View File

@@ -1,18 +1,18 @@
<template>
<ConfirmModal
:confirm-text="t('word.discard')"
:cancel-text="t('word.stay')"
:confirm-text="t('general.discard')"
:cancel-text="t('general.stay')"
@confirm="emit('confirm')"
@hide="emit('close')"
>
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-content-save-alert mr-1" /> {{ t('message.unsavedChanges') }}
<i class="mdi mdi-24px mdi-content-save-alert mr-1" /> {{ t('application.unsavedChanges') }}
</div>
</template>
<template #body>
<div>
{{ t('message.discardUnsavedChanges') }}
{{ t('application.discardUnsavedChanges') }}
</div>
</template>
</ConfirmModal>

View File

@@ -7,7 +7,7 @@
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-database-edit mr-1" />
<span class="cut-text">{{ t('message.editSchema') }}</span>
<span class="cut-text">{{ t('database.editSchema') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -17,7 +17,7 @@
<form class="form-horizontal">
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ t('word.name') }}</label>
<label class="form-label">{{ t('general.name') }}</label>
</div>
<div class="col-9">
<input
@@ -26,14 +26,14 @@
class="form-input"
type="text"
required
:placeholder="t('message.schemaName')"
:placeholder="t('database.schemaName')"
readonly
>
</div>
</div>
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ t('word.collation') }}</label>
<label class="form-label">{{ t('database.collation') }}</label>
</div>
<div class="col-9">
<BaseSelect
@@ -44,7 +44,7 @@
option-label="collation"
option-track-by="collation"
/>
<small>{{ t('message.serverDefault') }}: {{ defaultCollation }}</small>
<small>{{ t('database.serverDefault') }}: {{ defaultCollation }}</small>
</div>
</div>
</form>
@@ -52,10 +52,10 @@
</div>
<div class="modal-footer">
<button class="btn btn-primary mr-2" @click.stop="updateSchema">
{{ t('word.update') }}
{{ t('application.update') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ t('word.close') }}
{{ t('general.close') }}
</button>
</div>
</div>

View File

@@ -6,8 +6,8 @@
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-database-arrow-down mr-1" />
<span class="cut-text">{{ t('message.exportSchema') }}</span>
<i class="mdi mdi-24px mdi-database-export mr-1" />
<span class="cut-text">{{ t('database.exportSchema') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -16,7 +16,7 @@
<div class="container">
<div class="columns">
<div class="col-3">
<label class="form-label">{{ t('message.directoryPath') }}</label>
<label class="form-label">{{ t('general.directoryPath') }}</label>
</div>
<div class="col-9">
<fieldset class="input-group">
@@ -26,14 +26,14 @@
type="text"
required
readonly
:placeholder="t('message.schemaName')"
@click.prevent="openPathDialog"
>
<button
type="button"
class="btn btn-primary input-group-btn"
@click.prevent="openPathDialog"
>
{{ t('word.change') }}
{{ t('general.change') }}
</button>
</fieldset>
</div>
@@ -42,35 +42,41 @@
<div class="columns export-options">
<div class="column col-8 left">
<div class="columns mb-2">
<div class="column col-auto d-flex text-italic ">
<i class="mdi mdi-file-document-outline mr-2" />
{{ filename }}
<div class="columns mb-2 mt-1 p-vcentered">
<div class="column col-auto input-group d-flex text-italic" :style="'flex-grow: 1'">
<i class="input-group-addon mdi mdi-file-document-outline" />
<input
v-model="chosenFilename"
class="form-input"
type="text"
:placeholder="filename"
:title="t('application.fileName')"
>
</div>
<div class="column col-auto col-ml-auto ">
<button
class="btn btn-dark btn-sm"
:title="t('word.refresh')"
:title="t('general.refresh')"
@click="refresh"
>
<i class="mdi mdi-database-refresh" />
<i class="mdi mdi-refresh" />
</button>
<button
class="btn btn-dark btn-sm mx-1"
:title="t('message.uncheckAllTables')"
:title="t('database.uncheckAllTables')"
:disabled="isRefreshing"
@click="uncheckAllTables"
>
<i class="mdi mdi-file-tree-outline" />
<i class="mdi mdi-checkbox-blank-outline" />
</button>
<button
class="btn btn-dark btn-sm"
:title="t('message.checkAllTables')"
:title="t('database.checkAllTables')"
:disabled="isRefreshing"
@click="checkAllTables"
>
<i class="mdi mdi-file-tree" />
<i class="mdi mdi-checkbox-marked-outline" />
</button>
</div>
</div>
@@ -78,7 +84,7 @@
<div ref="table" class="table table-hover">
<div class="thead">
<div class="tr text-center">
<div class="th no-border" style="width: 50%;" />
<div class="th no-border" :style="'width: 50%;'" />
<div class="th no-border">
<label
class="form-checkbox m-0 px-2 form-inline"
@@ -120,24 +126,24 @@
</div>
</div>
<div class="tr">
<div class="th" style="width: 50%;">
<div class="th" :style="'width: 50%;'">
<div class="table-column-title">
<span>{{ t('word.table') }}</span>
<span>{{ t('database.table') }}</span>
</div>
</div>
<div class="th text-center">
<div class="table-column-title">
<span>{{ t('word.structure') }}</span>
<span>{{ t('database.structure') }}</span>
</div>
</div>
<div class="th text-center">
<div class="table-column-title">
<span>{{ t('word.content') }}</span>
<span>{{ t('general.content') }}</span>
</div>
</div>
<div class="th text-center">
<div class="table-column-title">
<span>{{ t('word.drop') }}</span>
<span>{{ t('database.drop') }}</span>
</div>
</div>
</div>
@@ -148,6 +154,7 @@
v-for="item in tables"
:key="item.table"
class="tr"
:class="{'selected': item.table === selectedTable}"
>
<div class="td">
{{ item.table }}
@@ -183,19 +190,19 @@
</div>
<div class="column col-4">
<h5 class="h5">
{{ t('word.options') }}
{{ t('general.options') }}
</h5>
<span class="h6">{{ t('word.includes') }}:</span>
<span class="h6">{{ t('general.includes') }}:</span>
<label
v-for="(_, key) in options.includes"
:key="key"
class="form-checkbox"
>
<input v-model="options.includes[key]" type="checkbox"><i class="form-icon" /> {{ t(`word.${key}`, 2) }}
<input v-model="options.includes[key]" type="checkbox"><i class="form-icon" /> {{ t(`database.${String(key).slice(0, -1)}`, 2) }}
</label>
<div v-if="clientCustoms.exportByChunks">
<div class="h6 mt-4 mb-2">
{{ t('message.newInserStmtEvery') }}:
{{ t('database.newInsertStmtEvery') }}:
</div>
<div class="columns">
<div class="column col-6">
@@ -209,21 +216,21 @@
<BaseSelect
v-model="options.sqlInsertDivider"
class="form-select"
:options="[{value: 'bytes', label: 'KiB'}, {value: 'rows', label: t('word.row', 2)}]"
:options="[{value: 'bytes', label: 'KiB'}, {value: 'rows', label: t('database.row', 2)}]"
/>
</div>
</div>
</div>
<div class="h6 mb-2 mt-4">
{{ t('message.ourputFormat') }}:
{{ t('general.outputFormat') }}:
</div>
<div class="columns">
<div class="column h5 mb-4">
<BaseSelect
v-model="options.outputFormat"
class="form-select"
:options="[{value: 'sql', label: t('message.singleFile', {ext: '.sql'})}, {value: 'sql.zip', label: t('message.zipCompressedFile', {ext: '.sql'})}]"
:options="[{value: 'sql', label: t('general.singleFile', {ext: '.sql'})}, {value: 'sql.zip', label: t('general.zipCompressedFile', {ext: '.sql'})}]"
/>
</div>
</div>
@@ -244,8 +251,8 @@
</div>
</div>
<div class="column col-auto px-0">
<button class="btn btn-link" @click.stop="closeModal">
{{ t('word.close') }}
<button class="btn btn-link mr-2" @click.stop="closeModal">
{{ t('general.close') }}
</button>
<button
class="btn btn-primary mr-2"
@@ -254,7 +261,7 @@
autofocus
@click.prevent="startExport"
>
{{ t('word.export') }}
{{ t('database.export') }}
</button>
</div>
</div>
@@ -272,6 +279,7 @@ import { useI18n } from 'vue-i18n';
import { ClientCode, SchemaInfos } from 'common/interfaces/antares';
import { ExportOptions, ExportState } from 'common/interfaces/exporter';
import { useNotificationsStore } from '@/stores/notifications';
import { useSchemaExportStore } from '@/stores/schemaExport';
import { useWorkspacesStore } from '@/stores/workspaces';
import { useFocusTrap } from '@/composables/useFocusTrap';
import Application from '@/ipc-api/Application';
@@ -279,15 +287,12 @@ import Schema from '@/ipc-api/Schema';
import { Customizations } from 'common/interfaces/customizations';
import BaseSelect from '@/components/BaseSelect.vue';
const props = defineProps({
selectedSchema: String
});
const emit = defineEmits(['close']);
const { t } = useI18n();
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const schemaExportStore = useSchemaExportStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
@@ -298,6 +303,8 @@ const {
refreshSchema
} = workspacesStore;
const { selectedTable, selectedSchema } = storeToRefs(schemaExportStore);
const isExporting = ref(false);
const isRefreshing = ref(false);
const progressPercentage = ref(0);
@@ -309,28 +316,29 @@ const tables: Ref<{
includeDropStatement: boolean;
}[]> = ref([]);
const options: Ref<Partial<ExportOptions>> = ref({
schema: props.selectedSchema,
schema: selectedSchema.value,
includes: {} as {[key: string]: boolean},
outputFormat: 'sql' as 'sql' | 'sql.zip',
sqlInsertAfter: 250,
sqlInsertDivider: 'bytes' as 'bytes' | 'rows'
});
const basePath = ref('');
const chosenFilename = ref('');
const currentWorkspace = computed(() => getWorkspace(selectedWorkspace.value));
const clientCustoms: Ref<Customizations> = computed(() => currentWorkspace.value.customizations);
const schemaItems = computed(() => {
const db: SchemaInfos = currentWorkspace.value.structure.find((db: SchemaInfos) => db.name === props.selectedSchema);
const db: SchemaInfos = currentWorkspace.value.structure.find((db: SchemaInfos) => db.name === selectedSchema.value);
if (db)
return db.tables.filter(table => table.type === 'table');
return [];
});
const filename = computed(() => {
const date = moment().format('YYYY-MM-DD');
return `${props.selectedSchema}_${date}.${options.value.outputFormat}`;
const date = moment().format('YYYY-MM-DD_HH-mm-ss');
return `${selectedTable.value || selectedSchema.value}_${date}`;
});
const dumpFilePath = computed(() => `${basePath.value}/${filename.value}`);
const dumpFilePath = computed(() => `${basePath.value}/${chosenFilename.value || filename.value}.${options.value.outputFormat}`);
const includeStructureStatus = computed(() => {
if (tables.value.every(item => item.includeStructure)) return 1;
else if (tables.value.some(item => item.includeStructure)) return 2;
@@ -353,7 +361,7 @@ const startExport = async () => {
const params = {
uid,
type: client,
schema: props.selectedSchema,
schema: selectedSchema.value,
outputFile: dumpFilePath.value,
tables: [...tables.value],
...options.value
@@ -362,7 +370,7 @@ const startExport = async () => {
try {
const { status, response } = await Schema.export(params);
if (status === 'success')
progressStatus.value = response.cancelled ? t('word.aborted') : t('word.completed');
progressStatus.value = response.cancelled ? t('general.aborted') : t('general.completed');
else {
progressStatus.value = response;
addNotification({ status: 'error', message: response });
@@ -379,13 +387,13 @@ const updateProgress = (event: Event, state: ExportState) => {
progressPercentage.value = Number((state.currentItemIndex / state.totalItems * 100).toFixed(1));
switch (state.op) {
case 'PROCESSING':
progressStatus.value = t('message.processingTableExport', { table: state.currentItem });
progressStatus.value = t('database.processingTableExport', { table: state.currentItem });
break;
case 'FETCH':
progressStatus.value = t('message.fechingTableExport', { table: state.currentItem });
progressStatus.value = t('database.fetchingTableExport', { table: state.currentItem });
break;
case 'WRITE':
progressStatus.value = t('message.writingTableExport', { table: state.currentItem });
progressStatus.value = t('database.writingTableExport', { table: state.currentItem });
break;
}
};
@@ -431,7 +439,7 @@ const toggleAllTablesOption = (option: 'includeStructure' | 'includeContent' |'i
const refresh = async () => {
isRefreshing.value = true;
await refreshSchema({ uid: currentWorkspace.value.uid, schema: props.selectedSchema });
await refreshSchema({ uid: currentWorkspace.value.uid, schema: selectedSchema.value });
isRefreshing.value = false;
};
@@ -446,12 +454,31 @@ const openPathDialog = async () => {
window.addEventListener('keydown', onKey);
if (selectedTable.value) {
setTimeout(() => {
const element = document.querySelector<HTMLElement>('.modal.active .selected');
if (element) {
const rect = element.getBoundingClientRect();
const elemTop = rect.top;
const elemBottom = rect.bottom;
const isVisible = (elemTop >= 0) && (elemBottom <= window.innerHeight);
if (!isVisible) {
element.setAttribute('tabindex', '-1');
element.focus();
element.removeAttribute('tabindex');
}
}
}, 100);
}
basePath.value = await Application.getDownloadPathDirectory();
tables.value = schemaItems.value.map(item => ({
table: item.name,
includeStructure: true,
includeContent: true,
includeDropStatement: true
includeStructure: !selectedTable.value ? true : selectedTable.value === item.name,
includeContent: !selectedTable.value ? true : selectedTable.value === item.name,
includeDropStatement: !selectedTable.value ? true : selectedTable.value === item.name
}));
const structure = ['functions', 'views', 'triggers', 'routines', 'schedulers'];
@@ -459,7 +486,7 @@ const openPathDialog = async () => {
structure.forEach((feat: keyof Customizations) => {
const val = clientCustoms.value[feat];
if (val)
options.value.includes[feat] = true;
options.value.includes[feat] = !selectedTable.value;
});
ipcRenderer.on('export-progress', updateProgress);

View File

@@ -7,7 +7,7 @@
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span class="cut-text">{{ t('message.insertRow', 2) }}</span>
<span class="cut-text">{{ t('database.insertRow', 2) }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -39,7 +39,7 @@
<span class="input-group-addon field-type" :class="typeClass(field.type)">
{{ field.type }} {{ wrapNumber(fieldLength(field)) }}
</span>
<label class="form-checkbox ml-3" :title="t('word.insert')">
<label class="form-checkbox ml-3" :title="t('general.insert')">
<input
type="checkbox"
:checked="!fieldsToExclude.includes(field.name)"
@@ -55,7 +55,7 @@
</div>
<div class="modal-footer columns">
<div class="column d-flex" :class="hasFakes ? 'col-4' : 'col-2'">
<div class="input-group tooltip tooltip-right" :data-tooltip="t('message.numberOfInserts')">
<div class="input-group tooltip tooltip-right" :data-tooltip="t('database.numberOfInserts')">
<input
v-model="nInserts"
type="number"
@@ -70,7 +70,7 @@
<div
v-if="hasFakes"
class="tooltip tooltip-right ml-2"
:data-tooltip="t('message.fakeDataLanguage')"
:data-tooltip="t('database.fakeDataLanguage')"
>
<BaseSelect
v-model="fakerLocale"
@@ -85,10 +85,10 @@
:class="{'loading': isInserting}"
@click.stop="insertRows"
>
{{ t('word.insert') }}
{{ t('general.insert') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ t('word.close') }}
{{ t('general.close') }}
</button>
</div>
</div>

View File

@@ -7,7 +7,7 @@
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-folder-edit mr-1" />
<span class="cut-text">{{ t('message.editFolder') }}</span>
<span class="cut-text">{{ t('application.editFolder') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -17,7 +17,7 @@
<form class="form-horizontal">
<div class="form-group mb-4">
<div class="col-3">
<label class="form-label">{{ t('word.name') }}</label>
<label class="form-label">{{ t('general.name') }}</label>
</div>
<div class="col-9">
<input
@@ -26,13 +26,13 @@
class="form-input"
type="text"
required
:placeholder="t('message.folderName')"
:placeholder="t('application.folderName')"
>
</div>
</div>
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ t('word.color') }}</label>
<label class="form-label">{{ t('application.color') }}</label>
</div>
<div class="col-9 color-wrapper">
<div
@@ -51,11 +51,11 @@
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary mr-2" @click.stop="editFolderAppearence">
{{ t('word.update') }}
<button class="btn btn-primary mr-2" @click.stop="editFolderAppearance">
{{ t('application.update') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ t('word.close') }}
{{ t('general.close') }}
</button>
</div>
</div>
@@ -110,7 +110,7 @@ const { trapRef } = useFocusTrap();
const firstInput: Ref<HTMLInputElement> = ref(null);
const localFolder: Ref<SidebarElement> = ref(unproxify(props.folder));
const editFolderAppearence = () => {
const editFolderAppearance = () => {
updateConnectionOrder(localFolder.value);
closeModal();
};

View File

@@ -7,7 +7,7 @@
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-history mr-1" />
<span class="cut-text">{{ t('word.history') }}: {{ connectionName }}</span>
<span class="cut-text">{{ t('general.history') }}: {{ connectionName }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -22,7 +22,7 @@
v-model="searchTerm"
class="form-input"
type="text"
:placeholder="t('message.searchForQueries')"
:placeholder="t('database.searchForQueries')"
>
<i v-if="!searchTerm" class="form-icon mdi mdi-magnify mdi-18px pr-4" />
<i
@@ -67,13 +67,13 @@
<small class="tile-subtitle">{{ query.schema }} · {{ formatDate(query.date) }}</small>
<div class="tile-history-buttons">
<button class="btn btn-link pl-1" @click.stop="$emit('select-query', query.sql)">
<i class="mdi mdi-open-in-app pr-1" /> {{ t('word.select') }}
<i class="mdi mdi-open-in-app pr-1" /> {{ t('general.select') }}
</button>
<button class="btn btn-link pl-1" @click="copyQuery(query.sql)">
<i class="mdi mdi-content-copy pr-1" /> {{ t('word.copy') }}
<i class="mdi mdi-content-copy pr-1" /> {{ t('general.copy') }}
</button>
<button class="btn btn-link pl-1" @click="deleteQuery(query)">
<i class="mdi mdi-delete-forever pr-1" /> {{ t('word.delete') }}
<i class="mdi mdi-delete-forever pr-1" /> {{ t('general.delete') }}
</button>
</div>
</div>
@@ -88,7 +88,7 @@
<i class="mdi mdi-history mdi-48px" />
</div>
<p class="empty-title h5">
{{ t('message.thereIsNoQueriesYet') }}
{{ t('database.thereIsNoQueriesYet') }}
</p>
</div>
</div>

View File

@@ -6,8 +6,8 @@
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-database-arrow-up mr-1" />
<span class="cut-text">{{ t('message.importSchema') }}</span>
<i class="mdi mdi-24px mdi-database-import mr-1" />
<span class="cut-text">{{ t('database.importSchema') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -15,7 +15,7 @@
<div class="modal-body pb-0">
{{ sqlFile }}
<div v-if="queryErrors.length > 0" class="mt-2">
<label>{{ t('message.importQueryErrors', queryErrors.length) }}</label>
<label>{{ t('database.importQueryErrors', queryErrors.length) }}</label>
<textarea
v-model="formattedQueryErrors"
class="form-input"
@@ -28,7 +28,7 @@
<div class="column col modal-progress-wrapper text-left">
<div class="import-progress">
<span class="progress-status">
{{ progressPercentage }}% - {{ progressStatus }} - {{ t('message.executedQueries', queryCount) }}
{{ progressPercentage }}% - {{ progressStatus }} - {{ t('database.executedQueries', queryCount) }}
</span>
<progress
class="progress d-block"
@@ -39,7 +39,7 @@
</div>
<div class="column col-auto px-0">
<button class="btn btn-link" @click.stop="closeModal">
{{ completed ? t('word.close') : t('word.cancel') }}
{{ completed ? t('general.close') : t('general.cancel') }}
</button>
</div>
</div>
@@ -108,7 +108,7 @@ const startImport = async (file: string) => {
const { status, response } = await Schema.import(params);
if (status === 'success')
progressStatus.value = response.cancelled ? t('word.aborted') : t('word.completed');
progressStatus.value = response.cancelled ? t('general.aborted') : t('general.completed');
else {
progressStatus.value = response;
addNotification({ status: 'error', message: response });

View File

@@ -7,7 +7,7 @@
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-database-plus mr-1" />
<span class="cut-text">{{ t('message.createNewSchema') }}</span>
<span class="cut-text">{{ t('database.createNewSchema') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -17,7 +17,7 @@
<form class="form-horizontal" @submit.prevent="createSchema">
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ t('word.name') }}</label>
<label class="form-label">{{ t('general.name') }}</label>
</div>
<div class="col-9">
<input
@@ -26,13 +26,13 @@
class="form-input"
type="text"
required
:placeholder="t('message.schemaName')"
:placeholder="t('database.schemaName')"
>
</div>
</div>
<div v-if="customizations.collations" class="form-group">
<div class="col-3">
<label class="form-label">{{ t('word.collation') }}</label>
<label class="form-label">{{ t('database.collation') }}</label>
</div>
<div class="col-9">
<BaseSelect
@@ -43,7 +43,7 @@
option-label="collation"
option-track-by="collation"
/>
<small>{{ t('message.serverDefault') }}: {{ defaultCollation }}</small>
<small>{{ t('database.serverDefault') }}: {{ defaultCollation }}</small>
</div>
</div>
</form>
@@ -55,10 +55,10 @@
:class="{'loading': isLoading}"
@click.stop="createSchema"
>
{{ t('word.add') }}
{{ t('general.add') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ t('word.close') }}
{{ t('general.close') }}
</button>
</div>
</div>

View File

@@ -17,7 +17,7 @@
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-memory mr-1" />
<span class="cut-text">{{ t('message.processesList') }}: {{ connectionName }}</span>
<span class="cut-text">{{ t('database.processesList') }}: {{ connectionName }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -29,7 +29,7 @@
<button
class="btn btn-dark btn-sm mr-0 pr-1 d-flex"
:class="{'loading':isQuering}"
:title="`${t('word.refresh')}`"
:title="`${t('general.refresh')}`"
@click="getProcessesList"
>
<i v-if="!+autorefreshTimer" class="mdi mdi-24px mdi-refresh mr-1" />
@@ -39,7 +39,7 @@
<i class="mdi mdi-24px mdi-menu-down" />
</div>
<div class="menu px-3">
<span>{{ t('word.autoRefresh') }}: <b>{{ +autorefreshTimer ? `${autorefreshTimer}s` : 'OFF' }}</b></span>
<span>{{ t('general.autoRefresh') }}: <b>{{ +autorefreshTimer ? `${autorefreshTimer}s` : 'OFF' }}</b></span>
<input
v-model="autorefreshTimer"
class="slider no-border"
@@ -59,7 +59,7 @@
tabindex="0"
>
<i class="mdi mdi-24px mdi-file-export mr-1" />
<span>{{ t('word.export') }}</span>
<span>{{ t('database.export') }}</span>
<i class="mdi mdi-24px mdi-menu-down" />
</button>
<ul class="menu text-left">
@@ -74,7 +74,7 @@
</div>
<div class="workspace-query-info">
<div v-if="sortedResults.length">
{{ t('word.processes') }}: <b>{{ sortedResults.length.toLocaleString() }}</b>
{{ t('database.processes') }}: <b>{{ sortedResults.length.toLocaleString() }}</b>
</div>
</div>
</div>

View File

@@ -4,7 +4,7 @@
@close-context="closeContext"
>
<div v-if="props.selectedRow" class="context-element">
<span class="d-flex"><i class="mdi mdi-18px mdi-content-copy text-light pr-1" /> {{ t('word.copy') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-content-copy text-light pr-1" /> {{ t('general.copy') }}</span>
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
<div class="context-submenu">
<div
@@ -13,7 +13,7 @@
@click="copyCell"
>
<span class="d-flex">
<i class="mdi mdi-18px mdi-numeric-0 mdi-rotate-90 text-light pr-1" /> {{ t('word.cell', 1) }}
<i class="mdi mdi-18px mdi-numeric-0 mdi-rotate-90 text-light pr-1" /> {{ t('database.cell', 1) }}
</span>
</div>
<div
@@ -22,7 +22,7 @@
@click="copyRow"
>
<span class="d-flex">
<i class="mdi mdi-18px mdi-table-row text-light pr-1" /> {{ t('word.row', 1) }}
<i class="mdi mdi-18px mdi-table-row text-light pr-1" /> {{ t('database.row', 1) }}
</span>
</div>
</div>
@@ -33,7 +33,7 @@
@click="killProcess"
>
<span class="d-flex">
<i class="mdi mdi-18px mdi-close-circle-outline text-light pr-1" /> {{ t('message.killProcess') }}
<i class="mdi mdi-18px mdi-close-circle-outline text-light pr-1" /> {{ t('database.killProcess') }}
</span>
</div>
</BaseContextMenu>

View File

@@ -16,15 +16,15 @@
</div>
<ConfirmModal
v-if="isInfoModal"
:confirm-text="t('word.update')"
:cancel-text="t('word.close')"
:confirm-text="t('application.update')"
:cancel-text="t('general.close')"
size="medium"
:hide-footer="true"
@hide="hideInfoModal"
>
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-information-outline mr-1" /> {{ t('message.processInfo') }}
<i class="mdi mdi-24px mdi-information-outline mr-1" /> {{ t('database.processInfo') }}
</div>
</template>
<template #body>

View File

@@ -7,7 +7,7 @@
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-cog mr-1" />
<span class="cut-text">{{ t('word.settings') }}</span>
<span class="cut-text">{{ t('application.settings') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click="closeModal" />
@@ -21,21 +21,28 @@
:class="{'active': selectedTab === 'general'}"
@click="selectTab('general')"
>
<a class="tab-link">{{ t('word.general') }}</a>
<a class="tab-link">{{ t('application.general') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'themes'}"
@click="selectTab('themes')"
>
<a class="tab-link">{{ t('word.themes') }}</a>
<a class="tab-link">{{ t('application.themes') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'shortcuts'}"
@click="selectTab('shortcuts')"
>
<a class="tab-link">{{ t('word.shortcuts') }}</a>
<a class="tab-link">{{ t('application.shortcuts') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'data'}"
@click="selectTab('data')"
>
<a class="tab-link">{{ t('application.data') }}</a>
</li>
<li
v-if="updateStatus !== 'disabled'"
@@ -43,21 +50,21 @@
:class="{'active': selectedTab === 'update'}"
@click="selectTab('update')"
>
<a class="tab-link" :class="{'badge badge-update': hasUpdates}">{{ t('word.update') }}</a>
<a class="tab-link" :class="{'badge badge-update': hasUpdates}">{{ t('application.update') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'changelog'}"
@click="selectTab('changelog')"
>
<a class="tab-link">{{ t('word.changelog') }}</a>
<a class="tab-link">{{ t('application.changelog') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'about'}"
@click="selectTab('about')"
>
<a class="tab-link">{{ t('word.about') }}</a>
<a class="tab-link">{{ t('application.about') }}</a>
</li>
</ul>
</div>
@@ -65,14 +72,14 @@
<div class="container">
<form class="form-horizontal columns">
<div class="column col-12 h6 text-uppercase mb-1">
{{ t('word.application') }}
{{ t('application.application') }}
</div>
<div class="column col-12 col-sm-12 mb-2 columns">
<div class="form-group column col-12">
<div class="col-5 col-sm-12">
<label class="form-label">
<i class="mdi mdi-18px mdi-translate mr-1" />
{{ t('word.language') }}
{{ t('application.language') }}
</label>
</div>
<div class="col-3 col-sm-12">
@@ -86,16 +93,16 @@
/>
</div>
<div class="col-4 col-sm-12 px-2 p-vcentered">
<small class="d-block" style="line-height: 1.1; font-size: 70%;">
{{ t('message.missingOrIncompleteTranslation') }}<br>
<a class="text-bold c-hand" @click="openOutside('https://github.com/antares-sql/antares/wiki/Translate-Antares')">{{ t('message.findOutHowToContribute') }}</a>
<small class="d-block" :style="'line-height: 1.1; font-size: 70%;'">
{{ t('application.missingOrIncompleteTranslation') }}<br>
<a class="text-bold c-hand" @click="openOutside('https://github.com/antares-sql/antares/wiki/Translate-Antares')">{{ t('application.findOutHowToContribute') }}</a>
</small>
</div>
</div>
<div class="form-group column col-12">
<div class="col-5 col-sm-12">
<label class="form-label">
{{ t('message.dataTabPageSize') }}
{{ t('application.dataTabPageSize') }}
</label>
</div>
<div class="col-3 col-sm-12">
@@ -110,7 +117,7 @@
<div class="form-group column col-12 mb-0">
<div class="col-5 col-sm-12">
<label class="form-label">
{{ t('message.restorePreviourSession') }}
{{ t('application.restorePreviousSession') }}
</label>
</div>
<div class="col-3 col-sm-12">
@@ -123,7 +130,25 @@
<div class="form-group column col-12 mb-0">
<div class="col-5 col-sm-12">
<label class="form-label">
{{ t('message.disableBlur') }}
{{ t('application.showTableSize') }}
</label>
</div>
<div class="col-1 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleShowTableSize">
<input type="checkbox" :checked="showTableSize">
<i class="form-icon" />
</label>
</div>
<div class="col-6 col-sm-12 px-2 p-vcentered">
<small class="d-block" :style="'line-height: 1.1; font-size: 70%;'">
{{ t('application.showTableSizeDescription') }}
</small>
</div>
</div>
<div class="form-group column col-12 mb-0">
<div class="col-5 col-sm-12">
<label class="form-label">
{{ t('application.disableBlur') }}
</label>
</div>
<div class="col-3 col-sm-12">
@@ -136,7 +161,7 @@
<div class="form-group column col-12 mb-0">
<div class="col-5 col-sm-12">
<label class="form-label">
{{ t('message.disableScratchpad') }}
{{ t('application.disableScratchpad') }}
</label>
</div>
<div class="col-3 col-sm-12">
@@ -149,7 +174,7 @@
<div class="form-group column col-12">
<div class="col-5 col-sm-12">
<label class="form-label">
{{ t('message.notificationsTimeout') }}
{{ t('application.notificationsTimeout') }}
</label>
</div>
<div class="col-3 col-sm-12">
@@ -161,19 +186,19 @@
min="1"
@focusout="checkNotificationsTimeout"
>
<span class="input-group-addon">{{ t('word.seconds') }}</span>
<span class="input-group-addon">{{ t('general.seconds') }}</span>
</div>
</div>
</div>
</div>
<div class="column col-12 h6 mt-4 text-uppercase mb-1">
{{ t('word.editor') }}
{{ t('application.editor') }}
</div>
<div class="column col-12 col-sm-12 columns">
<div class="form-group column col-12 mb-0">
<div class="col-5 col-sm-12">
<label class="form-label">
{{ t('word.autoCompletion') }}
{{ t('application.autoCompletion') }}
</label>
</div>
<div class="col-3 col-sm-12">
@@ -186,7 +211,7 @@
<div class="form-group column col-12 mb-0">
<div class="col-5 col-sm-12">
<label class="form-label">
{{ t('message.wrapLongLines') }}
{{ t('application.wrapLongLines') }}
</label>
</div>
<div class="col-3 col-sm-12">
@@ -196,6 +221,41 @@
</label>
</div>
</div>
<div class="form-group column col-12 mb-0">
<div class="col-5 col-sm-12">
<label class="form-label">
{{ t('database.executeSelectedQuery') }}
</label>
</div>
<div class="col-3 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleExecuteSelected">
<input type="checkbox" :checked="selectedExecuteSelected">
<i class="form-icon" />
</label>
</div>
</div>
</div>
<div class="column col-12 h6 mt-4 text-uppercase mb-1">
{{ t('database.resultsTable') }}
</div>
<div class="column col-12 col-sm-12 columns">
<div class="form-group column col-12">
<div class="col-5 col-sm-12">
<label class="form-label">
{{ t('application.defaultCopyType') }}
</label>
</div>
<div class="col-3 col-sm-12">
<BaseSelect
v-model="defaultCopyType"
class="form-select"
:options="copyTypes"
option-track-by="code"
option-label="name"
@change="changeDefaultCopyType(defaultCopyType)"
/>
</div>
</div>
</div>
</form>
</div>
@@ -205,7 +265,7 @@
<div class="container">
<div class="columns">
<div class="column col-12 h6 text-uppercase mb-2">
{{ t('message.applicationTheme') }}
{{ t('application.applicationTheme') }}
</div>
<div
class="column col-6 c-hand theme-block"
@@ -216,7 +276,7 @@
<div class="theme-name text-light">
<i class="mdi mdi-moon-waning-crescent mdi-48px" />
<div class="h6 mt-4">
{{ t('word.dark') }}
{{ t('application.dark') }}
</div>
</div>
</div>
@@ -229,7 +289,7 @@
<div class="theme-name text-dark">
<i class="mdi mdi-white-balance-sunny mdi-48px" />
<div class="h6 mt-4">
{{ t('word.light') }}
{{ t('application.light') }}
</div>
</div>
</div>
@@ -237,7 +297,7 @@
<div class="columns mt-4">
<div class="column col-12 h6 text-uppercase mb-2 mt-4">
{{ t('message.editorTheme') }}
{{ t('application.editorTheme') }}
</div>
<div class="column col-5 h5 mb-4">
<BaseSelect
@@ -313,6 +373,9 @@
<div v-show="selectedTab === 'shortcuts'" class="panel-body py-4">
<ModalSettingsShortcuts />
</div>
<div v-show="selectedTab === 'data'" class="panel-body py-4">
<ModalSettingsData />
</div>
<div v-show="selectedTab === 'update'" class="panel-body py-4">
<ModalSettingsUpdate />
</div>
@@ -325,16 +388,16 @@
<img :src="appLogo" width="128">
<h4>{{ appName }}</h4>
<p class="mb-2">
{{ t('word.version') }} {{ appVersion }}<br>
{{ t('general.version') }} {{ appVersion }}<br>
<a class="c-hand" @click="openOutside('https://github.com/antares-sql/antares')"><i class="mdi mdi-github d-inline" /> GitHub</a> <a class="c-hand" @click="openOutside('https://twitter.com/AntaresSQL')"><i class="mdi mdi-twitter d-inline" /> Twitter</a> <a class="c-hand" @click="openOutside('https://antares-sql.app/')"><i class="mdi mdi-web d-inline" /> Website</a><br>
<small>{{ t('word.author') }} <a class="c-hand" @click="openOutside('https://github.com/Fabio286')">{{ appAuthor }}</a></small><br>
<small>{{ t('general.author') }} <a class="c-hand" @click="openOutside('https://github.com/Fabio286')">{{ appAuthor }}</a></small><br>
</p>
<div class="mb-2">
<small class="d-block text-uppercase">{{ t('word.contributors') }}:</small>
<small class="d-block text-uppercase">{{ t('general.contributors') }}:</small>
<div class="d-block py-1">
<small v-for="(contributor, i) in otherContributors" :key="i">{{ i !== 0 ? ', ' : '' }}{{ contributor }}</small>
</div>
<small>{{ t('message.madeWithJS') }}</small>
<small>{{ t('application.madeWithJS') }}</small>
</div>
</div>
</div>
@@ -358,6 +421,7 @@ import { localesNames } from '@/i18n/supported-locales';
import ModalSettingsUpdate from '@/components/ModalSettingsUpdate.vue';
import ModalSettingsChangelog from '@/components/ModalSettingsChangelog.vue';
import ModalSettingsShortcuts from '@/components/ModalSettingsShortcuts.vue';
import ModalSettingsData from '@/components/ModalSettingsData.vue';
import BaseTextEditor from '@/components/BaseTextEditor.vue';
import BaseSelect from '@/components/BaseSelect.vue';
import { AvailableLocale } from '@/i18n';
@@ -379,8 +443,11 @@ const {
dataTabLimit: pageSize,
autoComplete: selectedAutoComplete,
lineWrap: selectedLineWrap,
executeSelected: selectedExecuteSelected,
defaultCopyType: selectedCopyType,
notificationsTimeout,
restoreTabs,
showTableSize,
disableBlur,
disableScratchpad,
applicationTheme,
@@ -398,10 +465,13 @@ const {
changeDisableScratchpad,
changeAutoComplete,
changeLineWrap,
changeExecuteSelected,
changeApplicationTheme,
changeEditorTheme,
changeEditorFontSize,
updateNotificationsTimeout
updateNotificationsTimeout,
changeDefaultCopyType,
changeShowTableSize
} = settingsStore;
const {
hideSettingModal: closeModal,
@@ -433,6 +503,7 @@ ORDER BY
`;
const localLocale: Ref<AvailableLocale> = ref(null);
const defaultCopyType: Ref<string> = ref(null);
const localPageSize: Ref<number> = ref(null);
const localTimeout: Ref<number> = ref(null);
const localEditorTheme: Ref<string> = ref(null);
@@ -440,7 +511,7 @@ const selectedTab: Ref<string> = ref('general');
const editorThemes = computed(() => [
{
group: t('word.light'),
group: t('application.light'),
themes: [
{ code: 'chrome', name: 'Chrome' },
{ code: 'clouds', name: 'Clouds' },
@@ -460,7 +531,7 @@ const editorThemes = computed(() => [
]
},
{
group: t('word.dark'),
group: t('application.dark'),
themes: [
{ code: 'ambiance', name: 'Ambiance' },
{ code: 'chaos', name: 'Chaos' },
@@ -493,9 +564,17 @@ const locales = computed(() => {
for (const locale of Object.keys(localesNames))
locales.push({ code: locale, name: localesNames[locale] });
return locales;
return locales.sort((a, b) => (a.name.localeCompare(b.name)));
});
const copyTypes = computed(() => [
{ code: 'cell', name: t('database.cell') },
{ code: 'html', name: t('database.table') },
{ code: 'json', name: 'JSON' },
{ code: 'csv', name: 'CSV' },
{ code: 'sql', name: 'SQL insert' }
]);
const hasUpdates = computed(() => ['available', 'downloading', 'downloaded', 'link'].includes(updateStatus.value));
const workspace = computed(() => {
@@ -534,6 +613,10 @@ const toggleRestoreSession = () => {
changeRestoreTabs(!restoreTabs.value);
};
const toggleShowTableSize = () => {
changeShowTableSize(!showTableSize.value);
};
const toggleDisableBlur = () => {
changeDisableBlur(!disableBlur.value);
};
@@ -550,7 +633,12 @@ const toggleLineWrap = () => {
changeLineWrap(!selectedLineWrap.value);
};
const toggleExecuteSelected = () => {
changeExecuteSelected(!selectedExecuteSelected.value);
};
localLocale.value = selectedLocale.value;
defaultCopyType.value = selectedCopyType.value;
localPageSize.value = pageSize.value as number;
localTimeout.value = notificationsTimeout.value as number;
localEditorTheme.value = editorTheme.value as string;
@@ -571,11 +659,15 @@ onBeforeUnmount(() => {
.modal-body {
overflow: hidden;
.tab-item {
max-width: 20%;
.tab-link {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.panel-body {
min-height: calc(25vh - 70px);

View File

@@ -18,6 +18,7 @@ import { marked } from 'marked';
import BaseLoader from '@/components/BaseLoader.vue';
import { useApplicationStore } from '@/stores/application';
import { ref } from 'vue';
import { shell } from 'electron';
const { appVersion } = useApplicationStore();
@@ -26,6 +27,10 @@ const isLoading = ref(true);
const error = ref('');
const isError = ref(false);
const openOutside = (link: string) => {
shell.openExternal(link);
};
const getChangelog = async () => {
try {
const apiRes = await fetch(`https://api.github.com/repos/antares-sql/antares/releases/tags/v${appVersion}`, {
@@ -40,7 +45,7 @@ const getChangelog = async () => {
const renderer = {
link (href: string, title: string, text: string) {
return text;
return `<a class="changelog-link" href="${href}" title="${title || ''}" target="_blank">${text}</a>`;
},
listitem (text: string) {
return `<li>${text.replace(/ *\([^)]*\) */g, '')}</li>`;
@@ -57,6 +62,17 @@ const getChangelog = async () => {
}
isLoading.value = false;
setTimeout(() => {
const links = document.querySelectorAll<HTMLAnchorElement>('.changelog-link');
for (const link of links) {
link.addEventListener('click', e => {
e.preventDefault();
openOutside(link.href);
});
}
}, 0);
};
getChangelog();

View File

@@ -0,0 +1,60 @@
<template>
<div class="container">
<div class="columns">
<div class="column col-12 h6 text-uppercase mb-2">
{{ t('application.exportData') }}
</div>
<div class="column col-12">
{{ t('application.exportDataExplanation') }}
</div>
<div class="column col-12 text-right">
<button
class="btn btn-primary d-inline-flex"
@click="isExportModal=true"
>
<i class="mdi mdi-tray-arrow-up mdi-24px pr-2" />
<span>{{ t('application.exportData') }}</span>
</button>
</div>
</div>
<div class="columns mt-4 mb-2">
<div class="column col-12 h6 text-uppercase mb-2 mt-4">
{{ t('application.importData') }}
</div>
<div class="column col-12">
{{ t('application.importDataExplanation') }}
</div>
<div class="column col-12 text-right">
<button
class="btn btn-dark d-inline-flex ml-auto"
@click="isImportModal=true"
>
<i class="mdi mdi-tray-arrow-down mdi-24px pr-2" />
<span>{{ t('application.importData') }}</span>
</button>
</div>
</div>
<ModalSettingsDataExport
v-if="isExportModal"
@close="isExportModal = false"
/>
<ModalSettingsDataImport
v-if="isImportModal"
@close="isImportModal = false"
/>
</div>
</template>
<script setup lang="ts">
// import { useApplicationStore } from '@/stores/application';
import ModalSettingsDataExport from '@/components/ModalSettingsDataExport.vue';
import ModalSettingsDataImport from '@/components/ModalSettingsDataImport.vue';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const isExportModal = ref(false);
const isImportModal = ref(false);
</script>

View File

@@ -0,0 +1,331 @@
<template>
<Teleport to="#window-content">
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div ref="trapRef" class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-tray-arrow-up mr-1" /> {{ t('application.exportData') }}
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body pb-0">
<div class="columns export-options">
<div class="column col-8 left">
<div class="workspace-query-results" :style="'min-height: 300px;'">
<div ref="table" class="table table-hover">
<div class="thead">
<div class="tr text-center">
<div class="th no-border" :style="'width:50%'" />
<div class="th no-border" />
<div class="th no-border">
<label
class="form-checkbox m-0 px-2 form-inline"
@click.prevent="toggleAllConnections()"
>
<input
type="checkbox"
:indeterminate="includeConnectionStatus === 2"
:checked="!!includeConnectionStatus"
>
<i class="form-icon" />
</label>
</div>
</div>
<div class="tr">
<div class="th">
<div class="table-column-title">
<span>{{ t('connection.connectionName') }}</span>
</div>
</div>
<div class="th">
<div class="table-column-title">
<span>{{ t('connection.client') }}</span>
</div>
</div>
<div class="th text-center">
<div class="table-column-title">
<span>{{ t('general.include') }}</span>
</div>
</div>
</div>
</div>
<div class="tbody">
<div
v-for="(item, i) in connections"
:key="i"
class="tr"
>
<div class="td">
{{ getConnectionName(item.uid) }}
</div>
<div class="td">
{{ item.client }}
</div>
<div class="td text-center">
<label class="form-checkbox m-0 px-2 form-inline">
<input v-model="connectionToggles[item.uid]" type="checkbox">
<i class="form-icon" />
</label>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="column col-4">
<h5 class="h5">
{{ t('general.options') }}
</h5>
<label class="form-checkbox">
<input v-model="options.includes.passwords" type="checkbox">
<i class="form-icon" />
{{ t(`application.includeConnectionPasswords`) }}
</label>
<label class="form-checkbox">
<input v-model="options.includes.folders" type="checkbox">
<i class="form-icon" />
{{ t(`application.includeFolders`) }}
</label>
<div class="h6 mt-4 mb-2">
{{ t('application.encryptionPassword') }}
</div>
<fieldset class="form-group" :class="{'has-error': isPasswordError}">
<div class="input-group">
<input
ref="passkey"
v-model="options.passkey"
:type="isPasswordVisible ? 'text' : 'password'"
class="form-input"
:placeholder="t('application.required')"
>
<button
type="button"
class="btn btn-link input-group-addon"
@click="isPasswordVisible = !isPasswordVisible"
>
<i v-if="isPasswordVisible" class="mdi mdi-eye px-1" />
<i v-else class="mdi mdi-eye-off px-1" />
</button>
</div>
<span v-if="isPasswordError" class="form-input-hint">
{{ t('application.encryptionPasswordError') }}
</span>
</fieldset>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-link mr-2" @click.stop="closeModal">
{{ t('general.close') }}
</button>
<button
class="btn btn-primary mr-2"
autofocus
@click.prevent="exportData()"
>
{{ t('database.export') }}
</button>
</div>
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, Ref, ref } from 'vue';
import * as moment from 'moment';
import { useI18n } from 'vue-i18n';
import { useFocusTrap } from '@/composables/useFocusTrap';
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
import { unproxify } from '@/libs/unproxify';
import { uidGen } from 'common/libs/uidGen';
import { storeToRefs } from 'pinia';
import { encrypt } from 'common/libs/encrypter';
import { ConnectionParams } from 'common/interfaces/antares';
const { t } = useI18n();
const emit = defineEmits(['close']);
const { trapRef } = useFocusTrap();
const { getConnectionName } = useConnectionsStore();
const { connectionsOrder, connections } = storeToRefs(useConnectionsStore());
const localConnections = unproxify<ConnectionParams[]>(connections.value);
const localConnectionsOrder = unproxify<SidebarElement[]>(connectionsOrder.value);
const isPasswordVisible = ref(false);
const isPasswordError = ref(false);
const connectionToggles: Ref<{[k:string]: boolean}> = ref({});
const options = ref({
passkey: '',
includes: {
passwords: true,
folders: true
}
});
const filename = computed(() => {
const date = moment().format('YYYY-MM-DD');
return `backup_${date}`;
});
const includeConnectionStatus = computed(() => {
if (Object.values(connectionToggles.value).every(item => item)) return 1;
else if (Object.values(connectionToggles.value).some(item => item)) return 2;
else return 0;
});
const exportData = () => {
if (options.value.passkey.length < 8)
isPasswordError.value = true;
else {
isPasswordError.value = false;
const connectionsToInclude: string[] = [];
const connectionsUidMap = new Map<string, string>();
for (const cUid in connectionToggles.value)
if (connectionToggles.value[cUid]) connectionsToInclude.push(cUid);
let filteredConnections = unproxify<typeof localConnections>(localConnections.filter(conn => connectionsToInclude.includes(conn.uid)));
filteredConnections = filteredConnections.map(c => {
const newUid = uidGen('C');
connectionsUidMap.set(c.uid, newUid);
c.uid = newUid;
return c;
});
if (!options.value.includes.passwords) { // Remove passwords and set ask:true
filteredConnections.map(c => {
if (c.password) {
c.password = '';
c.ask = true;
}
return c;
});
}
let filteredOrders = [];
for (const [oldVal, newVal] of connectionsUidMap) {
const connOrder = unproxify(localConnectionsOrder.find(c => c.uid === oldVal));
connOrder.uid = newVal;
filteredOrders.push(connOrder);
}
if (options.value.includes.folders) { // Includes folders
const oldConnUids = Array.from(connectionsUidMap.keys());
const newConnUids = Array.from(connectionsUidMap.values());
const foldersToInclude = unproxify(localConnectionsOrder).filter(f => (
f.isFolder && oldConnUids.some(uid => f.connections.includes(uid))
)).map(f => {
f.uid = uidGen('F');
f.connections = f.connections
.map(fc => connectionsUidMap.get(fc))
.filter(fc => newConnUids.includes(fc));
return f;
});
filteredOrders = [...filteredOrders, ...foldersToInclude];
}
const exportObj = encrypt(JSON.stringify({
connections: filteredConnections,
connectionsOrder: filteredOrders
}), options.value.passkey);
// console.log(exportObj, JSON.parse(decrypt(exportObj, options.value.passkey)));
const blobContent = Buffer.from(JSON.stringify(exportObj), 'utf-8').toString('hex');
const file = new Blob([blobContent], { type: 'application/octet-stream' });
const downloadLink = document.createElement('a');
downloadLink.download = `${filename.value}.antares`;
downloadLink.href = window.URL.createObjectURL(file);
downloadLink.style.display = 'none';
document.body.appendChild(downloadLink);
downloadLink.click();
downloadLink.remove();
closeModal();
}
};
const closeModal = () => {
emit('close');
};
const onKey = (e: KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape')
closeModal();
};
const toggleAllConnections = () => {
if (includeConnectionStatus.value !== 1) {
connectionToggles.value = localConnections.reduce((acc, curr) => {
acc[curr.uid] = true;
return acc;
}, {} as {[k:string]: boolean});
}
else {
connectionToggles.value = localConnections.reduce((acc, curr) => {
acc[curr.uid] = false;
return acc;
}, {} as {[k:string]: boolean});
}
};
connectionToggles.value = localConnections.reduce((acc, curr) => {
acc[curr.uid] = true;
return acc;
}, {} as {[k:string]: boolean});
window.addEventListener('keydown', onKey);
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script>
<style lang="scss" scoped>
.export-options {
flex: 1;
overflow: hidden;
.left {
display: flex;
flex-direction: column;
flex: 1;
}
}
.workspace-query-results {
flex: 1 0 1px;
.table {
width: 100% !important;
}
.form-checkbox {
min-height: 0.8rem;
padding: 0;
.form-icon {
top: 0.1rem;
}
}
}
.modal {
.modal-container {
max-width: 800px;
}
.modal-body {
max-height: 60vh;
display: flex;
flex-direction: column;
}
}
</style>

View File

@@ -0,0 +1,263 @@
<template>
<Teleport to="#window-content">
<div class="modal modal-sm active">
<a class="modal-overlay" @click.stop="closeModal" />
<div ref="trapRef" class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-tray-arrow-down mr-1" /> {{ t('application.importData') }}
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body pb-0">
<div class="mb-2">
<div class="h6 mb-2">
{{ t('application.choseFile') }}
</div>
<BaseUploadInput
:model-value="filePath"
:message="t('general.browse')"
accept=".antares"
@clear="filePath = ''"
@change="filesChange($event)"
/>
</div>
<div class="mb-2">
<div class="h6 mb-2">
{{ t('application.password') }}
</div>
<fieldset class="form-group" :class="{'has-error': isPasswordError}">
<div class="input-group">
<input
ref="passkey"
v-model="options.passkey"
:type="isPasswordVisible ? 'text' : 'password'"
class="form-input"
:placeholder="t('application.required')"
>
<button
type="button"
class="btn btn-link input-group-addon"
@click="isPasswordVisible = !isPasswordVisible"
>
<i v-if="isPasswordVisible" class="mdi mdi-eye px-1" />
<i v-else class="mdi mdi-eye-off px-1" />
</button>
</div>
<span v-if="isPasswordError" class="form-input-hint">
{{ t('application.encryptionPasswordError') }}
</span>
</fieldset>
</div>
<div class="mb-2">
<label class="form-checkbox">
<input v-model="options.ignoreDuplicates" type="checkbox">
<i class="form-icon" />
{{ t(`application.ignoreDuplicates`) }}
</label>
</div>
</div>
<div class="modal-footer">
<button
class="btn btn-link mr-2"
@click.stop="closeModal"
>
{{ t('general.close') }}
</button>
<button
class="btn btn-primary mr-2"
:disabled="!filePath"
@click.prevent="importData()"
>
{{ t('database.import') }}
</button>
</div>
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { onBeforeUnmount, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import BaseUploadInput from '@/components/BaseUploadInput.vue';
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
import { unproxify } from '@/libs/unproxify';
import { storeToRefs } from 'pinia';
import { decrypt } from 'common/libs/encrypter';
import { useNotificationsStore } from '@/stores/notifications';
import { ConnectionParams } from 'common/interfaces/antares';
const { t } = useI18n();
const emit = defineEmits(['close']);
const { addNotification } = useNotificationsStore();
const connectionsStore = useConnectionsStore();
const { importConnections } = connectionsStore;
const { connections } = storeToRefs(connectionsStore);
const filePath = ref('');
const fileContent = ref(null);
const isPasswordVisible = ref(false);
const isPasswordError = ref(false);
const options = ref({
passkey: '',
ignoreDuplicates: true
});
const closeModal = () => {
emit('close');
};
const filesChange = ({ target } : {target: HTMLInputElement }) => {
const { files } = target;
if (!files.length) return;
const reader = new FileReader();
reader.readAsText(files[0]);
reader.onload = () => {
fileContent.value = reader.result;
filePath.value = files[0].path;
};
};
const importData = () => {
if (options.value.passkey.length < 8)
isPasswordError.value = true;
else {
try {
const hash = JSON.parse(Buffer.from(fileContent.value, 'hex').toString('utf-8'));
try {
const importObj: {
connections: ConnectionParams[];
connectionsOrder: SidebarElement[];
} = JSON.parse(decrypt(hash, options.value.passkey));
if (options.value.ignoreDuplicates) {
const actualConnections = unproxify(connections.value).map(c => {
delete c.uid;
delete c.name;
delete c.password;
delete c.ask;
delete c.key;
delete c.cert;
delete c.ca;
delete c.sshKey;
return JSON.stringify(c);
});
const incomingConnections = unproxify<ConnectionParams[]>(importObj.connections).map(c => {
const uid = c.uid;
delete c.uid;
delete c.name;
delete c.password;
delete c.ask;
delete c.key;
delete c.cert;
delete c.ca;
delete c.sshKey;
return { uid, jsonString: JSON.stringify(c) };
});
const newConnectionsUid = incomingConnections
.filter(c => !actualConnections.includes(c.jsonString))
.reduce((acc, cur) => {
acc.push(cur.uid);
return acc;
}, [] as string[]);
importObj.connections = importObj.connections.filter(c => newConnectionsUid.includes(c.uid));
importObj.connectionsOrder = importObj.connectionsOrder
.filter(c => newConnectionsUid
.includes(c.uid) ||
(c.isFolder && c.connections.every(c => newConnectionsUid.includes(c))));
}
importConnections(importObj);
addNotification({
status: 'success',
message: t('application.dataImportSuccess')
});
closeModal();
}
catch (error) {
addNotification({
status: 'error',
message: t('application.wrongImportPassword')
});
}
}
catch (error) {
addNotification({
status: 'error',
message: t('application.wrongFileFormat')
});
}
}
};
const onKey = (e: KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape')
closeModal();
};
window.addEventListener('keydown', onKey);
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script>
<style lang="scss" scoped>
.export-options {
flex: 1;
overflow: hidden;
.left {
display: flex;
flex-direction: column;
flex: 1;
}
}
.workspace-query-results {
flex: 1 0 1px;
.table {
width: 100% !important;
}
.form-checkbox {
min-height: 0.8rem;
padding: 0;
.form-icon {
top: 0.1rem;
}
}
}
.modal {
.modal-body {
max-height: 60vh;
display: flex;
flex-direction: column;
}
}
</style>

View File

@@ -2,10 +2,10 @@
<div class="p-relative">
<div class="shortcuts-tools pb-2 px-2">
<button class="btn btn-dark btn-sm d-flex ml-2" @click="showAddModal">
<i class="mdi mdi-24px mdi-plus mr-1" /><span>{{ t('message.addShortcut') }}</span>
<i class="mdi mdi-24px mdi-plus mr-1" /><span>{{ t('application.addShortcut') }}</span>
</button>
<button class="btn btn-dark btn-sm d-flex ml-2" @click="isConfirmRestoreModal = true">
<i class="mdi mdi-24px mdi-undo mr-1" /><span>{{ t('message.restoreDefaults') }}</span>
<i class="mdi mdi-24px mdi-undo mr-1" /><span>{{ t('application.restoreDefaults') }}</span>
</button>
</div>
<div class="container workspace-query-results">
@@ -14,12 +14,12 @@
<div class="tr text-uppercase">
<div class="th no-border">
<div>
{{ t('word.event') }}
{{ t('application.event') }}
</div>
</div>
<div class="th no-border" style="width: 100%;">
<div>
{{ t('word.key', 2) }}
{{ t('application.key', 2) }}
</div>
</div>
<div class="th no-border" />
@@ -43,10 +43,10 @@
/>
<div class="td py-1 pr-2">
<button class="shortcut-button btn btn-link btn-sm d-flex p-0 px-1 mr-2" @click="showEditModal({...shortcut, index: i})">
<span>{{ t('word.edit') }}</span><i class="mdi mdi-pencil ml-1" />
<span>{{ t('general.edit') }}</span><i class="mdi mdi-pencil ml-1" />
</button>
<button class="shortcut-button btn btn-link btn-sm d-flex p-0 px-1" @click="showDeleteModal(shortcut)">
<span>{{ t('word.delete') }}</span><i class="mdi mdi-delete-outline ml-1" />
<span>{{ t('general.delete') }}</span><i class="mdi mdi-delete-outline ml-1" />
</button>
</div>
</div>
@@ -58,20 +58,20 @@
<ConfirmModal
v-if="isConfirmAddModal"
:disable-autofocus="true"
:confirm-text="t('word.save')"
:confirm-text="t('general.save')"
:close-on-confirm="false"
@confirm="addShortcut"
@hide="closeAddModal"
>
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-plus mr-1" /> {{ t('message.addShortcut') }}
<i class="mdi mdi-24px mdi-plus mr-1" /> {{ t('application.addShortcut') }}
</div>
</template>
<template #body>
<div class="mb-2">
<div class="form-group">
<label class="form-label">{{ t('word.event') }}</label>
<label class="form-label">{{ t('application.event') }}</label>
<BaseSelect
v-model="shortcutToAdd.event"
class="form-select"
@@ -81,31 +81,31 @@
</div>
<div class="mb-2">
<div class="form-group">
<label class="form-label">{{ t('word.key', 2) }}</label>
<label class="form-label">{{ t('application.key', 2) }}</label>
<KeyPressDetector v-model="typedShortcut" />
</div>
</div>
<small v-if="doesShortcutExists" class="text-warning">{{ t('message.shortcutAlreadyExists') }}</small>
<small v-if="doesShortcutExists" class="text-warning">{{ t('application.shortcutAlreadyExists') }}</small>
</template>
</ConfirmModal>
<ConfirmModal
v-if="isConfirmEditModal"
:disable-autofocus="true"
:confirm-text="t('word.save')"
:confirm-text="t('general.save')"
:close-on-confirm="false"
@confirm="editShortcut"
@hide="closeEditModal"
>
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-plus mr-1" /> {{ t('message.editShortcut') }}
<i class="mdi mdi-24px mdi-plus mr-1" /> {{ t('application.editShortcut') }}
</div>
</template>
<template #body>
<div class="mb-2">
<div class="form-group">
<label class="form-label">{{ t('word.event') }}</label>
<label class="form-label">{{ t('application.event') }}</label>
<BaseSelect
v-model="shortcutToEdit.event"
class="form-select"
@@ -116,11 +116,11 @@
</div>
<div class="mb-2">
<div class="form-group">
<label class="form-label">{{ t('word.key', 2) }}</label>
<label class="form-label">{{ t('application.key', 2) }}</label>
<KeyPressDetector v-model="shortcutToEdit.keys[0]" />
</div>
</div>
<small v-if="doesShortcutExists" class="text-warning">{{ t('message.shortcutAlreadyExists') }}</small>
<small v-if="doesShortcutExists" class="text-warning">{{ t('application.shortcutAlreadyExists') }}</small>
</template>
</ConfirmModal>
@@ -132,12 +132,12 @@
>
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-delete mr-1" /> {{ t('message.deleteShortcut') }}
<i class="mdi mdi-24px mdi-delete mr-1" /> {{ t('application.deleteShortcut') }}
</div>
</template>
<template #body>
<div class="mb-2">
{{ t('message.deleteCorfirm') }} <b>{{ t(shortcutEvents[shortcutToDelete.event].l18n, {param: shortcutEvents[shortcutToDelete.event].l18nParam}) }} (<span v-html="parseKeys(shortcutToDelete.keys)" />)</b>?
{{ t('general.deleteConfirm') }} <b>{{ t(shortcutEvents[shortcutToDelete.event].l18n, {param: shortcutEvents[shortcutToDelete.event].l18nParam}) }} (<span v-html="parseKeys(shortcutToDelete.keys)" />)</b>?
</div>
</template>
</ConfirmModal>
@@ -150,12 +150,12 @@
>
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-undo mr-1" /> {{ t('message.restoreDefaults') }}
<i class="mdi mdi-24px mdi-undo mr-1" /> {{ t('application.restoreDefaults') }}
</div>
</template>
<template #body>
<div class="mb-2">
{{ t('message.restoreDefaultsQuestion') }}
{{ t('application.restoreDefaultsQuestion') }}
</div>
</template>
</ConfirmModal>

View File

@@ -26,27 +26,27 @@
:class="{'loading': updateStatus === 'checking'}"
@click="checkForUpdates"
>
{{ t('message.checkForUpdates') }}
{{ t('application.checkForUpdates') }}
</button>
<button
v-else-if="updateStatus === 'downloaded'"
class="btn btn-primary"
@click="restartToUpdate"
>
{{ t('message.restartToInstall') }}
{{ t('application.restartToInstall') }}
</button>
<button
v-else-if="updateStatus === 'link'"
class="btn btn-primary"
@click="openOutside('https://antares-sql.app/download.html')"
>
{{ t('message.goToDownloadPage') }}
{{ t('application.goToDownloadPage') }}
</button>
</div>
<div class="form-group mt-4">
<label class="form-switch d-inline-block disabled" @click.prevent="toggleAllowPrerelease">
<label class="form-switch d-inline-block" @click.prevent="toggleAllowPrerelease">
<input type="checkbox" :checked="allowPrerelease">
<i class="form-icon" /> {{ t('message.includeBetaUpdates') }}
<i class="form-icon" /> {{ t('application.includeBetaUpdates') }}
</label>
</div>
</div>
@@ -76,19 +76,19 @@ const { changeAllowPrerelease } = settingsStore;
const updateMessage = computed(() => {
switch (updateStatus.value) {
case 'noupdate':
return t('message.noUpdatesAvailable');
return t('application.noUpdatesAvailable');
case 'checking':
return t('message.checkingForUpdate');
return t('application.checkingForUpdate');
case 'nocheck':
return t('message.checkFailure');
return t('application.checkFailure');
case 'available':
return t('message.updateAvailable');
return t('application.updateAvailable');
case 'downloading':
return t('message.downloadingUpdate');
return t('application.downloadingUpdate');
case 'downloaded':
return t('message.updateDownloaded');
return t('application.updateDownloaded');
case 'link':
return t('message.updateAvailable');
return t('application.updateAvailable');
default:
return updateStatus.value;
}

View File

@@ -373,6 +373,8 @@ onMounted(() => {
e.stop();
});
editor.value.commands.removeCommand('showSettingsMenu');
if (props.autoFocus) {
setTimeout(() => {
editor.value.focus();

View File

@@ -26,7 +26,7 @@
content: getConnectionName(element.uid)
}"
class="settingbar-element btn btn-link"
:class="{ 'selected': element.uid === selectedWorkspace }"
:class="{ 'selected': element.uid === selectedWorkspace && coveredElement !== element.uid }"
placement="right"
strategy="fixed"
@click.stop="selectWorkspace(element.uid)"

View File

@@ -258,6 +258,14 @@ emit('folder-sort');// To apply changes on component key change
max-height: initial;
background: transparent;
&.ghost {
background: $bg-color-light-dark;
&.selected::before {
height: 0;
position: absolute;
}
}
&.selected {
opacity: 1;
@@ -372,7 +380,7 @@ emit('folder-sort');// To apply changes on component key change
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 1;
line-height: 1.02;
transition: bottom .2s;
}
}
@@ -436,7 +444,7 @@ emit('folder-sort');// To apply changes on component key change
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 1;
line-height: 1.02;
}
}
}

View File

@@ -8,20 +8,20 @@
class="context-element"
@click="disconnect"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-power text-light pr-1" /> {{ t('word.disconnect') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-power text-light pr-1" /> {{ t('connection.disconnect') }}</span>
</div>
<div
v-if="!contextConnection.isFolder"
class="context-element"
@click="duplicateConnection"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-content-duplicate text-light pr-1" /> {{ t('word.duplicate') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-content-duplicate text-light pr-1" /> {{ t('general.duplicate') }}</span>
</div>
<div class="context-element" @click.stop="showAppearenceModal">
<span class="d-flex"><i class="mdi mdi-18px mdi-brush-variant text-light pr-1" /> {{ t('word.appearence') }}</span>
<div class="context-element" @click.stop="showAppearanceModal">
<span class="d-flex"><i class="mdi mdi-18px mdi-brush-variant text-light pr-1" /> {{ t('application.appearance') }}</span>
</div>
<div class="context-element" @click="showConfirmModal">
<span class="d-flex"><i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ t('word.delete') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ t('general.delete') }}</span>
</div>
<ConfirmModal
@@ -31,24 +31,24 @@
>
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mr-1" :class="[contextConnection.isFolder ? 'mdi-folder-remove' : 'mdi-server-remove']" /> {{ t(contextConnection.isFolder ? 'message.deleteFolder' : 'message.deleteConnection') }}
<i class="mdi mdi-24px mr-1" :class="[contextConnection.isFolder ? 'mdi-folder-remove' : 'mdi-server-remove']" /> {{ t(contextConnection.isFolder ? 'application.deleteFolder' : 'connection.deleteConnection') }}
</div>
</template>
<template #body>
<div class="mb-2">
{{ t('message.deleteCorfirm') }} <b>{{ connectionName }}</b>?
{{ t('general.deleteConfirm') }} <b>{{ connectionName }}</b>?
</div>
</template>
</ConfirmModal>
<ModalFolderAppearence
<ModalFolderAppearance
v-if="isFolderEdit"
:folder="contextConnection"
@close="hideAppearenceModal"
@close="hideAppearanceModal"
/>
<ModalConnectionAppearence
<ModalConnectionAppearance
v-if="isConnectionEdit"
:connection="contextConnection"
@close="hideAppearenceModal"
@close="hideAppearanceModal"
/>
</BaseContextMenu>
</template>
@@ -62,8 +62,8 @@ import { SidebarElement, useConnectionsStore } from '@/stores/connections';
import { useWorkspacesStore } from '@/stores/workspaces';
import BaseContextMenu from '@/components/BaseContextMenu.vue';
import ConfirmModal from '@/components/BaseConfirmModal.vue';
import ModalFolderAppearence from '@/components/ModalFolderAppearence.vue';
import ModalConnectionAppearence from '@/components/ModalConnectionAppearence.vue';
import ModalFolderAppearance from '@/components/ModalFolderAppearance.vue';
import ModalConnectionAppearance from '@/components/ModalConnectionAppearance.vue';
const { t } = useI18n();
@@ -96,7 +96,7 @@ const isConfirmModal = ref(false);
const isFolderEdit = ref(false);
const isConnectionEdit = ref(false);
const connectionName = computed(() => props.contextConnection.name || getConnectionName(props.contextConnection.uid) || t('word.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 confirmDeleteConnection = () => {
@@ -118,14 +118,14 @@ const duplicateConnection = () => {
closeContext();
};
const showAppearenceModal = () => {
const showAppearanceModal = () => {
if (props.contextConnection.isFolder)
isFolderEdit.value = true;
else
isConnectionEdit.value = true;
};
const hideAppearenceModal = () => {
const hideAppearanceModal = () => {
isConnectionEdit.value = false;
isFolderEdit.value = false;
closeContext();

View File

@@ -10,6 +10,18 @@
<i class="mdi mdi-18px mdi-database mr-1" />
<small>{{ versionString }}</small>
</li>
<li v-if="connectionInfos && connectionInfos.readonly" class="footer-element">
<i class="mdi mdi-18px mdi-lock mr-1" />
<small>{{ t('connection.readOnlyMode') }}</small>
</li>
<li v-if="connectionInfos && connectionInfos.ssl" class="footer-element">
<i class="mdi mdi-18px mdi-shield-key mr-1" />
<small>SSL</small>
</li>
<li v-if="connectionInfos && connectionInfos.ssh" class="footer-element">
<i class="mdi mdi-18px mdi-console-network mr-1" />
<small>SSH</small>
</li>
</ul>
</div>
@@ -21,22 +33,22 @@
@click="toggleConsole()"
>
<i class="mdi mdi-18px mdi-console-line mr-1" />
<small>{{ t('word.console') }}</small>
<small>{{ t('application.console') }}</small>
</li>
<li class="footer-element footer-link" @click="openOutside('https://www.paypal.com/paypalme/fabiodistasio')">
<i class="mdi mdi-18px mdi-coffee mr-1" />
<small>{{ t('word.donate') }}</small>
<small>{{ t('general.donate') }}</small>
</li>
<li
class="footer-element footer-link"
:title="t('message.reportABug')"
:title="t('application.reportABug')"
@click="openOutside('https://github.com/antares-sql/antares/issues')"
>
<i class="mdi mdi-18px mdi-bug" />
</li>
<li
class="footer-element footer-link"
:title="t('word.about')"
:title="t('application.about')"
@click="showSettingModal('about')"
>
<i class="mdi mdi-18px mdi-information-outline" />
@@ -76,10 +88,11 @@ const { toggleConsole } = useConsoleStore();
const { showSettingModal } = applicationStore;
const { getWorkspace } = workspacesStore;
const { getConnectionFolder } = connectionsStore;
const { getConnectionFolder, getConnectionByUid } = connectionsStore;
const workspace = computed(() => getWorkspace(workspaceUid.value));
const footerColor = computed(() => getConnectionFolder(workspaceUid.value)?.color || '#E36929');
const connectionInfos = computed(() => getConnectionByUid(workspaceUid.value));
const version: ComputedRef<DatabaseInfos> = computed(() => {
return getWorkspace(workspaceUid.value) ? workspace.value.version : null;
});

View File

@@ -1,14 +1,14 @@
<template>
<ConfirmModal
:confirm-text="t('word.update')"
:cancel-text="t('word.close')"
:confirm-text="t('application.update')"
:cancel-text="t('general.close')"
size="large"
:hide-footer="true"
@hide="hideScratchpad"
>
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-notebook-edit-outline mr-1" /> {{ t('word.scratchpad') }}
<i class="mdi mdi-24px mdi-notebook-edit-outline mr-1" /> {{ t('application.scratchpad') }}
</div>
</template>
<template #body>
@@ -22,7 +22,7 @@
:show-line-numbers="false"
/>
</div>
<small class="text-gray">{{ t('message.markdownSupported') }}</small>
<small class="text-gray">{{ t('application.markdownSupported') }}</small>
</div>
</template>
</ConfirmModal>

View File

@@ -22,7 +22,7 @@
v-tooltip="{
strategy: 'fixed',
placement: 'right',
content: t('message.allConnections')
content: t('connection.allConnections')
}"
class="settingbar-element btn btn-link"
@click="emit('show-connections-modal')"
@@ -35,7 +35,7 @@
v-tooltip="{
strategy: 'fixed',
placement: 'right',
content: t('message.addConnection')
content: t('connection.addConnection')
}"
class="settingbar-element btn btn-link"
:class="{ 'selected': 'NEW' === selectedWorkspace }"
@@ -55,7 +55,7 @@
v-tooltip="{
strategy: 'fixed',
placement: 'right',
content: t('word.scratchpad')
content: t('application.scratchpad')
}"
class="settingbar-element btn btn-link"
@click="showScratchpad"
@@ -66,7 +66,7 @@
v-tooltip="{
strategy: 'fixed',
placement: 'right',
content: t('word.settings')
content: t('application.settings')
}"
class="settingbar-element btn btn-link"
@click="showSettingModal('general')"
@@ -190,6 +190,18 @@ if (!connectionsArr.value.length)
li {
margin: 0;
background: $bg-color-light-dark;
&.ghost {
border-radius: $border-radius;
.settingbar-element {
&.selected::before {
height: 0;
width: 0;
}
}
}
}
.settingbar-element {

View File

@@ -63,7 +63,7 @@ const isLinux = process.platform === 'linux';
const windowTitle = computed(() => {
if (!selectedWorkspace.value) return '';
if (selectedWorkspace.value === 'NEW') return t('message.createNewConnection');
if (selectedWorkspace.value === 'NEW') return t('connection.createNewConnection');
const connectionName = getConnectionName(selectedWorkspace.value);
const workspace = getWorkspace(selectedWorkspace.value);

View File

@@ -1,5 +1,15 @@
<template>
<div v-show="isSelected" class="workspace column columns col-gapless">
<WorkspaceTabsContext
v-if="isTabContext"
:context-event="contextEvent"
:selected-tab="selectedContextTab"
@close-all-tabs="closeAllTabs"
@close-other-tabs="closeOtherTabs"
@close-to-left="closeTabsToLeft"
@close-to-right="closeTabsToRight"
@close-context="closeContext"
/>
<WorkspaceExploreBar
v-if="workspace?.connectionStatus === 'connected'"
:connection="connection"
@@ -23,6 +33,7 @@
:class="{'active': selectedTab === element.uid}"
@mousedown.left="selectTab({uid: workspace.uid, tab: element.uid})"
@mouseup.middle="closeTab(element)"
@contextmenu.prevent="contextMenu($event, element)"
>
<a
v-if="element.type === 'query'"
@@ -34,7 +45,7 @@
<span>{{ cutText(element.content || 'Query', 20, true) }} #{{ element.index }}</span>
<span
class="btn btn-clear"
:title="t('word.close')"
:title="t('general.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
@@ -47,11 +58,11 @@
@dblclick="openAsPermanentTab(element)"
>
<i class="mdi mdi-18px mr-1" :class="element.elementType === 'view' ? 'mdi-table-eye' : 'mdi-table'" />
<span :title="`${t('word.data').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
<span :title="`${t('general.data').toUpperCase()}: ${t(`database.${element.elementType}`)}`">
<span class=" text-italic">{{ cutText(element.elementName, 20, true) }}</span>
<span
class="btn btn-clear"
:title="t('word.close')"
:title="t('general.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
@@ -60,11 +71,11 @@
<a v-else-if="element.type === 'data'" class="tab-link">
<i class="mdi mdi-18px mr-1" :class="element.elementType === 'view' ? 'mdi-table-eye' : 'mdi-table'" />
<span :title="`${t('word.data').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
<span :title="`${t('general.data').toUpperCase()}: ${t(`database.${element.elementType}`)}`">
{{ cutText(element.elementName, 20, true) }}
<span
class="btn btn-clear"
:title="t('word.close')"
:title="t('general.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
@@ -77,11 +88,11 @@
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${t('word.new').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
{{ t('message.newTable') }}
<span :title="`${t('general.new').toUpperCase()}: ${t(`database.${element.elementType}`)}`">
{{ t('database.newTable') }}
<span
class="btn btn-clear"
:title="t('word.close')"
:title="t('general.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
@@ -93,12 +104,12 @@
class="tab-link"
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-tune-vertical-variant mdi-18px mr-1" />
<span :title="`${t('word.settings').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
<i class="mdi mdi-wrench-cog mdi-18px mr-1" />
<span :title="`${t('application.settings').toUpperCase()}: ${t(`database.${element.elementType}`)}`">
{{ cutText(element.elementName, 20, true) }}
<span
class="btn btn-clear"
:title="t('word.close')"
:title="t('general.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
@@ -110,12 +121,12 @@
class="tab-link"
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-tune-vertical-variant mdi-18px mr-1" />
<span :title="`${t('word.settings').toUpperCase()}: ${t(`word.view`)}`">
<i class="mdi mdi-wrench-cog mdi-18px mr-1" />
<span :title="`${t('application.settings').toUpperCase()}: ${t(`database.view`)}`">
{{ cutText(element.elementName, 20, true) }}
<span
class="btn btn-clear"
:title="t('word.close')"
:title="t('general.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
@@ -128,11 +139,11 @@
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${t('word.new').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
{{ t('message.newView') }}
<span :title="`${t('general.new').toUpperCase()}: ${t(`database.${element.elementType}`)}`">
{{ t('database.newView') }}
<span
class="btn btn-clear"
:title="t('word.close')"
:title="t('general.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
@@ -145,11 +156,11 @@
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${t('word.new').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
{{ t('message.newTrigger') }}
<span :title="`${t('general.new').toUpperCase()}: ${t(`database.${element.elementType}`)}`">
{{ t('database.newTrigger') }}
<span
class="btn btn-clear"
:title="t('word.close')"
:title="t('general.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
@@ -162,11 +173,11 @@
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${t('word.new').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
{{ t('message.newRoutine') }}
<span :title="`${t('general.new').toUpperCase()}: ${t(`database.${element.elementType}`)}`">
{{ t('database.newRoutine') }}
<span
class="btn btn-clear"
:title="t('word.close')"
:title="t('general.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
@@ -179,11 +190,11 @@
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${t('word.new').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
{{ t('message.newFunction') }}
<span :title="`${t('general.new').toUpperCase()}: ${t(`database.${element.elementType}`)}`">
{{ t('database.newFunction') }}
<span
class="btn btn-clear"
:title="t('word.close')"
:title="t('general.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
@@ -196,11 +207,11 @@
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${t('word.new').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
{{ t('message.newTriggerFunction') }}
<span :title="`${t('general.new').toUpperCase()}: ${t(`database.${element.elementType}`)}`">
{{ t('database.newTriggerFunction') }}
<span
class="btn btn-clear"
:title="t('word.close')"
:title="t('general.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
@@ -213,11 +224,11 @@
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${t('word.new').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
{{ t('message.newScheduler') }}
<span :title="`${t('general.new').toUpperCase()}: ${t(`database.${element.elementType}`)}`">
{{ t('database.newScheduler') }}
<span
class="btn btn-clear"
:title="t('word.close')"
:title="t('general.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
@@ -230,12 +241,12 @@
:class="{'badge': element.isChanged}"
@dblclick="openAsPermanentTab(element)"
>
<i class="mdi mdi-18px mdi-tune-vertical-variant mr-1" />
<span :title="`${t('word.settings').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
<i class="mdi mdi-18px mdi-wrench-cog mr-1" />
<span :title="`${t('application.settings').toUpperCase()}: ${t(`database.${element.elementType}`)}`">
<span class=" text-italic">{{ cutText(element.elementName, 20, true) }}</span>
<span
class="btn btn-clear"
:title="t('word.close')"
:title="t('general.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
@@ -247,12 +258,12 @@
class="tab-link"
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-18px mdi-tune-vertical-variant mr-1" />
<span :title="`${t('word.settings').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
<i class="mdi mdi-18px mdi-wrench-cog mr-1" />
<span :title="`${t('application.settings').toUpperCase()}: ${t(`database.${element.elementType}`)}`">
{{ cutText(element.elementName, 20, true) }}
<span
class="btn btn-clear"
:title="t('word.close')"
:title="t('general.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
@@ -268,7 +279,7 @@
<a
class="tab-link workspace-tools-link dropdown-toggle"
tabindex="0"
:title="t('word.tools')"
:title="t('general.tools')"
>
<i class="mdi mdi-24px mdi-tools" />
</a>
@@ -276,13 +287,13 @@
<li class="menu-item">
<a class="c-hand p-vcentered" @click="showProcessesModal">
<i class="mdi mdi-memory mr-1 tool-icon" />
<span>{{ t('message.processesList') }}</span>
<span>{{ t('database.processesList') }}</span>
</a>
</li>
<li class="menu-item">
<a class="c-hand p-vcentered" @click="toggleConsole">
<i class="mdi mdi-console-line mr-1 tool-icon" />
<span>{{ t('word.console') }}</span>
<span>{{ t('application.console') }}</span>
</a>
</li>
<li
@@ -292,7 +303,7 @@
>
<a class="c-hand p-vcentered disabled">
<i class="mdi mdi-shape mr-1 tool-icon" />
<span>{{ t('word.variables') }}</span>
<span>{{ t('database.variables') }}</span>
</a>
</li>
<li
@@ -302,7 +313,7 @@
>
<a class="c-hand p-vcentered disabled">
<i class="mdi mdi-account-group mr-1 tool-icon" />
<span>{{ t('message.manageUsers') }}</span>
<span>{{ t('database.manageUsers') }}</span>
</a>
</li>
</ul>
@@ -312,7 +323,7 @@
<li class="tab-item">
<a
class="tab-add"
:title="t('message.openNewTab')"
:title="t('application.openNewTab')"
@click="addQueryTab"
>
<i class="mdi mdi-24px mdi-plus" />
@@ -320,8 +331,8 @@
</li>
</template>
</Draggable>
<WorkspaceEmptyState v-if="!workspace.tabs.length" @new-tab="addQueryTab" />
<template v-for="tab of workspace.tabs" :key="tab.uid">
<WorkspaceEmptyState v-if="!draggableTabs.length" @new-tab="addQueryTab" />
<template v-for="tab of draggableTabs" :key="tab.uid">
<WorkspaceTabQuery
v-if="tab.type ==='query'"
:tab-uid="tab.uid"
@@ -501,6 +512,7 @@ import WorkspaceTabNewRoutine from '@/components/WorkspaceTabNewRoutine.vue';
import WorkspaceTabNewFunction from '@/components/WorkspaceTabNewFunction.vue';
import WorkspaceTabNewScheduler from '@/components/WorkspaceTabNewScheduler.vue';
import WorkspaceTabNewTriggerFunction from '@/components/WorkspaceTabNewTriggerFunction.vue';
import WorkspaceTabsContext from '@/components/WorkspaceTabsContext.vue';
import WorkspaceTabPropsTable from '@/components/WorkspaceTabPropsTable.vue';
import WorkspaceTabPropsView from '@/components/WorkspaceTabPropsView.vue';
@@ -545,11 +557,18 @@ const hasWheelEvent = ref(false);
const isProcessesModal = ref(false);
const unsavedTab = ref(null);
const tabWrap = ref(null);
const contextEvent = ref(null);
const isTabContext = ref(false);
const selectedContextTab = ref(null);
const workspace = computed(() => getWorkspace(props.connection.uid));
const draggableTabs = computed<WorkspaceTab[]>({
get () {
if (workspace.value.customizations.database)
return workspace.value.tabs.filter(tab => tab.type === 'query' || tab.database === workspace.value.database);
else
return workspace.value.tabs;
},
set (val) {
@@ -627,6 +646,34 @@ const closeTab = (tab: WorkspaceTab, force = false) => {
removeTab({ uid: props.connection.uid, tab: tab.uid });
};
const closeAllTabs = () => {
for (const tab of draggableTabs.value)
removeTab({ uid: props.connection.uid, tab: tab.uid });
};
const closeOtherTabs = () => {
const otherTabs = draggableTabs.value.filter(t => t.uid !== selectedContextTab.value.uid);
for (const tab of otherTabs)
removeTab({ uid: props.connection.uid, tab: tab.uid });
};
const closeTabsToLeft = () => {
const tabIndex = draggableTabs.value.findIndex(t => t.uid === selectedContextTab.value.uid);
const leftTabs = draggableTabs.value.filter((t, i) => i < tabIndex);
for (const tab of leftTabs)
removeTab({ uid: props.connection.uid, tab: tab.uid });
};
const closeTabsToRight = () => {
const tabIndex = draggableTabs.value.findIndex(t => t.uid === selectedContextTab.value.uid);
const leftTabs = draggableTabs.value.filter((t, i) => i > tabIndex);
for (const tab of leftTabs)
removeTab({ uid: props.connection.uid, tab: tab.uid });
};
const showProcessesModal = () => {
isProcessesModal.value = true;
};
@@ -647,6 +694,16 @@ const addWheelEvent = () => {
}
};
const contextMenu = (event: MouseEvent, tab: WorkspaceTab) => {
selectedContextTab.value = tab;
contextEvent.value = event;
isTabContext.value = true;
};
const closeContext = () => {
isTabContext.value = false;
};
(async () => {
await addWorkspace(props.connection.uid);
const isInitiated = await Connection.checkConnection(props.connection.uid);

View File

@@ -8,7 +8,7 @@
:class="{'active': selectedTab === 'general'}"
@click="selectTab('general')"
>
<a class="tab-link">{{ t('word.general') }}</a>
<a class="tab-link">{{ t('application.general') }}</a>
</li>
<li
v-if="clientCustomizations.sslConnection"
@@ -16,7 +16,7 @@
:class="{'active': selectedTab === 'ssl'}"
@click="selectTab('ssl')"
>
<a class="tab-link">{{ t('word.ssl') }}</a>
<a class="tab-link">{{ t('connection.ssl') }}</a>
</li>
<li
v-if="clientCustomizations.sshConnection"
@@ -24,7 +24,7 @@
:class="{'active': selectedTab === 'ssh'}"
@click="selectTab('ssh')"
>
<a class="tab-link">{{ t('word.sshTunnel') }}</a>
<a class="tab-link">{{ t('connection.sshTunnel') }}</a>
</li>
</ul>
</div>
@@ -34,7 +34,7 @@
<fieldset class="m-0" :disabled="isBusy">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.connectionName') }}</label>
<label class="form-label cut-text">{{ t('connection.connectionName') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -47,7 +47,7 @@
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.client') }}</label>
<label class="form-label cut-text">{{ t('connection.client') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseSelect
@@ -61,7 +61,7 @@
</div>
<div v-if="connection.client === 'pg'" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.connectionString') }}</label>
<label class="form-label cut-text">{{ t('connection.connectionString') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -74,7 +74,7 @@
</div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.hostName') }}/IP</label>
<label class="form-label cut-text">{{ t('connection.hostName') }}/IP</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -86,12 +86,12 @@
</div>
<div v-if="clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.database') }}</label>
<label class="form-label cut-text">{{ t('database.database') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:model-value="connection.databasePath"
:message="t('word.browse')"
:message="t('general.browse')"
@clear="pathClear('databasePath')"
@change="pathSelection($event, 'databasePath')"
/>
@@ -99,7 +99,7 @@
</div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.port') }}</label>
<label class="form-label cut-text">{{ t('connection.port') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -113,19 +113,20 @@
</div>
<div v-if="clientCustomizations.database" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.database') }}</label>
<label class="form-label cut-text">{{ t('database.database') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.database"
class="form-input"
type="text"
:placeholder="clientCustomizations.defaultDatabase"
>
</div>
</div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.user') }}</label>
<label class="form-label cut-text">{{ t('connection.user') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -138,7 +139,7 @@
</div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.password') }}</label>
<label class="form-label cut-text">{{ t('connection.password') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -151,14 +152,14 @@
</div>
<div v-if="clientCustomizations.connectionSchema" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.schema') }}</label>
<label class="form-label cut-text">{{ t('database.schema') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.schema"
class="form-input"
type="text"
:placeholder="t('word.all')"
:placeholder="t('general.all')"
>
</div>
</div>
@@ -166,7 +167,7 @@
<div class="column col-4 col-sm-12" />
<div class="column col-8 col-sm-12">
<label class="form-checkbox form-inline">
<input v-model="connection.readonly" type="checkbox"><i class="form-icon" /> {{ t('message.readOnlyMode') }}
<input v-model="connection.readonly" type="checkbox"><i class="form-icon" /> {{ t('connection.readOnlyMode') }}
</label>
</div>
</div>
@@ -174,7 +175,7 @@
<div class="column col-4 col-sm-12" />
<div class="column col-8 col-sm-12">
<label class="form-checkbox form-inline">
<input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ t('message.askCredentials') }}
<input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ t('connection.askCredentials') }}
</label>
</div>
</div>
@@ -188,7 +189,7 @@
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">
{{ t('message.enableSsl') }}
{{ t('connection.enableSsl') }}
</label>
</div>
<div class="column col-8 col-sm-12">
@@ -201,12 +202,12 @@
<fieldset class="m-0" :disabled="isBusy || !connection.ssl">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.privateKey') }}</label>
<label class="form-label cut-text">{{ t('connection.privateKey') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:model-value="connection.key"
:message="t('word.browse')"
:message="t('general.browse')"
@clear="pathClear('key')"
@change="pathSelection($event, 'key')"
/>
@@ -214,12 +215,12 @@
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.certificate') }}</label>
<label class="form-label cut-text">{{ t('connection.certificate') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:model-value="connection.cert"
:message="t('word.browse')"
:message="t('general.browse')"
@clear="pathClear('cert')"
@change="pathSelection($event, 'cert')"
/>
@@ -227,12 +228,12 @@
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.caCertificate') }}</label>
<label class="form-label cut-text">{{ t('connection.caCertificate') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:model-value="connection.ca"
:message="t('word.browse')"
:message="t('general.browse')"
@clear="pathClear('ca')"
@change="pathSelection($event, 'ca')"
/>
@@ -240,7 +241,7 @@
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.ciphers') }}</label>
<label class="form-label cut-text">{{ t('connection.ciphers') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -255,7 +256,7 @@
<div class="column col-4 col-sm-12" />
<div class="column col-8 col-sm-12">
<label class="form-checkbox form-inline">
<input v-model="connection.untrustedConnection" type="checkbox"><i class="form-icon" /> {{ t('message.untrustedConnection') }}
<input v-model="connection.untrustedConnection" type="checkbox"><i class="form-icon" /> {{ t('connection.untrustedConnection') }}
</label>
</div>
</div>
@@ -269,7 +270,7 @@
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">
{{ t('message.enableSsh') }}
{{ t('connection.enableSsh') }}
</label>
</div>
<div class="column col-8 col-sm-12">
@@ -282,7 +283,7 @@
<fieldset class="m-0" :disabled="isBusy || !connection.ssh">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.hostName') }}/IP</label>
<label class="form-label cut-text">{{ t('connection.hostName') }}/IP</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -294,7 +295,7 @@
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.user') }}</label>
<label class="form-label cut-text">{{ t('connection.user') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -306,7 +307,7 @@
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.password') }}</label>
<label class="form-label cut-text">{{ t('connection.password') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -318,7 +319,7 @@
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.port') }}</label>
<label class="form-label cut-text">{{ t('connection.port') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -332,12 +333,12 @@
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.privateKey') }}</label>
<label class="form-label cut-text">{{ t('connection.privateKey') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:model-value="connection.sshKey"
:message="t('word.browse')"
:message="t('general.browse')"
@clear="pathClear('sshKey')"
@change="pathSelection($event, 'sshKey')"
/>
@@ -345,7 +346,7 @@
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.passphrase') }}</label>
<label class="form-label cut-text">{{ t('connection.passphrase') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -368,7 +369,7 @@
@click="startTest"
>
<i class="mdi mdi-24px mdi-lightning-bolt mr-1" />
{{ t('message.testConnection') }}
{{ t('connection.testConnection') }}
</button>
<button
id="connection-save"
@@ -377,7 +378,7 @@
@click="saveConnection"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
{{ t('word.save') }}
{{ t('general.save') }}
</button>
</div>
</div>
@@ -416,7 +417,7 @@ const clients = [
{ name: 'MariaDB', slug: 'maria' },
{ name: 'PostgreSQL', slug: 'pg' },
{ name: 'SQLite', slug: 'sqlite' },
{ name: 'Firebird SQL (experimental)', slug: 'firebird' }
{ name: 'Firebird SQL', slug: 'firebird' }
];
const connection = ref({
@@ -483,7 +484,7 @@ const startTest = async () => {
if (res.status === 'error')
addNotification({ status: 'error', message: res.response.message || res.response.toString() });
else
addNotification({ status: 'success', message: t('message.connectionSuccessfullyMade') });
addNotification({ status: 'success', message: t('connection.connectionSuccessfullyMade') });
}
catch (err) {
addNotification({ status: 'error', message: err.stack });
@@ -507,7 +508,7 @@ const continueTest = async (credentials: { user: string; password: string }) =>
if (res.status === 'error')
addNotification({ status: 'error', message: res.response.message || res.response.toString() });
else
addNotification({ status: 'success', message: t('message.connectionSuccessfullyMade') });
addNotification({ status: 'success', message: t('connection.connectionSuccessfullyMade') });
}
}
catch (err) {

View File

@@ -8,7 +8,7 @@
:class="{'active': selectedTab === 'general'}"
@click="selectTab('general')"
>
<a class="tab-link">{{ t('word.general') }}</a>
<a class="tab-link">{{ t('application.general') }}</a>
</li>
<li
v-if="clientCustomizations.sslConnection"
@@ -16,7 +16,7 @@
:class="{'active': selectedTab === 'ssl'}"
@click="selectTab('ssl')"
>
<a class="tab-link">{{ t('word.ssl') }}</a>
<a class="tab-link">{{ t('connection.ssl') }}</a>
</li>
<li
v-if="clientCustomizations.sshConnection"
@@ -24,7 +24,7 @@
:class="{'active': selectedTab === 'ssh'}"
@click="selectTab('ssh')"
>
<a class="tab-link">{{ t('word.sshTunnel') }}</a>
<a class="tab-link">{{ t('connection.sshTunnel') }}</a>
</li>
</ul>
</div>
@@ -34,7 +34,7 @@
<fieldset class="m-0" :disabled="isBusy">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.connectionName') }}</label>
<label class="form-label cut-text">{{ t('connection.connectionName') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -47,7 +47,7 @@
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.client') }}</label>
<label class="form-label cut-text">{{ t('connection.client') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseSelect
@@ -63,7 +63,7 @@
</div>
<div v-if="localConnection.client === 'pg'" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.connectionString') }}</label>
<label class="form-label cut-text">{{ t('connection.connectionString') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -76,7 +76,7 @@
</div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.hostName') }}/IP</label>
<label class="form-label cut-text">{{ t('connection.hostName') }}/IP</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -88,12 +88,12 @@
</div>
<div v-if="clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.database') }}</label>
<label class="form-label cut-text">{{ t('database.database') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:model-value="localConnection.databasePath"
:message="t('word.browse')"
:message="t('general.browse')"
@clear="pathClear('databasePath')"
@change="pathSelection($event, 'databasePath')"
/>
@@ -101,7 +101,7 @@
</div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.port') }}</label>
<label class="form-label cut-text">{{ t('connection.port') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -115,19 +115,20 @@
</div>
<div v-if="clientCustomizations.database" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.database') }}</label>
<label class="form-label cut-text">{{ t('database.database') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.database"
class="form-input"
type="text"
:placeholder="clientCustomizations.defaultDatabase"
>
</div>
</div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.user') }}</label>
<label class="form-label cut-text">{{ t('connection.user') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -140,7 +141,7 @@
</div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.password') }}</label>
<label class="form-label cut-text">{{ t('connection.password') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -153,14 +154,14 @@
</div>
<div v-if="clientCustomizations.connectionSchema" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.schema') }}</label>
<label class="form-label cut-text">{{ t('database.schema') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.schema"
class="form-input"
type="text"
:placeholder="t('word.all')"
:placeholder="t('general.all')"
>
</div>
</div>
@@ -168,7 +169,7 @@
<div class="column col-4 col-sm-12" />
<div class="column col-8 col-sm-12">
<label class="form-checkbox form-inline">
<input v-model="localConnection.readonly" type="checkbox"><i class="form-icon" /> {{ t('message.readOnlyMode') }}
<input v-model="localConnection.readonly" type="checkbox"><i class="form-icon" /> {{ t('connection.readOnlyMode') }}
</label>
</div>
</div>
@@ -176,7 +177,7 @@
<div class="column col-4 col-sm-12" />
<div class="column col-8 col-sm-12">
<label class="form-checkbox form-inline">
<input v-model="localConnection.ask" type="checkbox"><i class="form-icon" /> {{ t('message.askCredentials') }}
<input v-model="localConnection.ask" type="checkbox"><i class="form-icon" /> {{ t('connection.askCredentials') }}
</label>
</div>
</div>
@@ -190,7 +191,7 @@
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">
{{ t('message.enableSsl') }}
{{ t('connection.enableSsl') }}
</label>
</div>
<div class="column col-8 col-sm-12">
@@ -203,12 +204,12 @@
<fieldset class="m-0" :disabled="isBusy || !localConnection.ssl">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.privateKey') }}</label>
<label class="form-label cut-text">{{ t('connection.privateKey') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:model-value="localConnection.key"
:message="t('word.browse')"
:message="t('general.browse')"
@clear="pathClear('key')"
@change="pathSelection($event, 'key')"
/>
@@ -216,12 +217,12 @@
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.certificate') }}</label>
<label class="form-label cut-text">{{ t('connection.certificate') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:model-value="localConnection.cert"
:message="t('word.browse')"
:message="t('general.browse')"
@clear="pathClear('cert')"
@change="pathSelection($event, 'cert')"
/>
@@ -229,12 +230,12 @@
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.caCertificate') }}</label>
<label class="form-label cut-text">{{ t('connection.caCertificate') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:model-value="localConnection.ca"
:message="t('word.browse')"
:message="t('general.browse')"
@clear="pathClear('ca')"
@change="pathSelection($event, 'ca')"
/>
@@ -242,7 +243,7 @@
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.ciphers') }}</label>
<label class="form-label cut-text">{{ t('connection.ciphers') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -257,7 +258,7 @@
<div class="column col-4 col-sm-12" />
<div class="column col-8 col-sm-12">
<label class="form-checkbox form-inline">
<input v-model="localConnection.untrustedConnection" type="checkbox"><i class="form-icon" /> {{ t('message.untrustedConnection') }}
<input v-model="localConnection.untrustedConnection" type="checkbox"><i class="form-icon" /> {{ t('connection.untrustedConnection') }}
</label>
</div>
</div>
@@ -271,7 +272,7 @@
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">
{{ t('message.enableSsh') }}
{{ t('connection.enableSsh') }}
</label>
</div>
<div class="column col-8 col-sm-12">
@@ -284,7 +285,7 @@
<fieldset class="m-0" :disabled="isBusy || !localConnection.ssh">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.hostName') }}/IP</label>
<label class="form-label cut-text">{{ t('connection.hostName') }}/IP</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -296,7 +297,7 @@
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.user') }}</label>
<label class="form-label cut-text">{{ t('connection.user') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -308,7 +309,7 @@
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.password') }}</label>
<label class="form-label cut-text">{{ t('connection.password') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -320,7 +321,7 @@
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.port') }}</label>
<label class="form-label cut-text">{{ t('connection.port') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -334,12 +335,12 @@
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.privateKey') }}</label>
<label class="form-label cut-text">{{ t('connection.privateKey') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:model-value="localConnection.sshKey"
:message="t('word.browse')"
:message="t('general.browse')"
@clear="pathClear('sshKey')"
@change="pathSelection($event, 'sshKey')"
/>
@@ -347,7 +348,7 @@
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label cut-text">{{ t('word.passphrase') }}</label>
<label class="form-label cut-text">{{ t('connection.passphrase') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
@@ -370,7 +371,7 @@
@click="startTest"
>
<i class="mdi mdi-24px mdi-lightning-bolt mr-1" />
{{ t('message.testConnection') }}
{{ t('connection.testConnection') }}
</button>
<button
id="connection-save"
@@ -379,7 +380,7 @@
@click="saveConnection"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
{{ t('word.save') }}
{{ t('general.save') }}
</button>
<button
id="connection-connect"
@@ -389,7 +390,7 @@
@click="startConnection"
>
<i class="mdi mdi-24px mdi-connection mr-1" />
{{ t('word.connect') }}
{{ t('connection.connect') }}
</button>
</div>
</div>
@@ -429,7 +430,7 @@ const clients = [
{ name: 'MariaDB', slug: 'maria' },
{ name: 'PostgreSQL', slug: 'pg' },
{ name: 'SQLite', slug: 'sqlite' },
{ name: 'Firebird SQL (experimental)', slug: 'firebird' }
{ name: 'Firebird SQL', slug: 'firebird' }
];
const firstInput: Ref<HTMLInputElement> = ref(null);
@@ -478,7 +479,7 @@ const startTest = async () => {
if (res.status === 'error')
addNotification({ status: 'error', message: res.response.message || res.response.toString() });
else
addNotification({ status: 'success', message: t('message.connectionSuccessfullyMade') });
addNotification({ status: 'success', message: t('connection.connectionSuccessfullyMade') });
}
catch (err) {
addNotification({ status: 'error', message: err.stack });
@@ -502,7 +503,7 @@ const continueTest = async (credentials: {user: string; password: string }) => {
if (res.status === 'error')
addNotification({ status: 'error', message: res.response.message || res.response.toString() });
else
addNotification({ status: 'success', message: t('message.connectionSuccessfullyMade') });
addNotification({ status: 'success', message: t('connection.connectionSuccessfullyMade') });
}
}
catch (err) {

View File

@@ -4,12 +4,12 @@
<img :src="logos[applicationTheme]" width="200">
</div>
<p class="h6 empty-subtitle">
{{ t('message.noOpenTabs') }}
{{ t('application.noOpenTabs') }}
</p>
<div class="empty-action">
<button class="btn btn-primary d-flex" @click="emit('new-tab')">
<i class="mdi mdi-24px mdi-tab-plus mr-2" />
{{ t('message.openNewTab') }}
{{ t('application.openNewTab') }}
</button>
</div>
</div>
@@ -45,7 +45,7 @@ const workspace = computed(() => {
changeBreadcrumbs({ schema: workspace.value.breadcrumbs.schema });
</script>
<style scoped>
<style lang="scss" scoped>
.empty {
height: 100%;
border-radius: 0;

View File

@@ -10,35 +10,53 @@
@keydown="explorebarSearch"
>
<div class="workspace-explorebar-header">
<span class="workspace-explorebar-title">{{ connectionName }}</span>
<div
v-if="customizations.database"
class="workspace-explorebar-database-switch"
:title="t('database.switchDatabase')"
>
<BaseSelect
v-model="selectedDatabase"
:options="databases"
class="form-select select-sm text-bold my-0"
/>
</div>
<span v-else class="workspace-explorebar-title">{{ connectionName }}</span>
<span v-if="workspace.connectionStatus === 'connected'" class="workspace-explorebar-tools">
<i
v-if="customizations.schemas"
class="mdi mdi-18px mdi-database-plus c-hand mr-2"
:title="t('message.createNewSchema')"
:title="t('database.createNewSchema')"
@click="showNewDBModal"
/>
<i
class="mdi mdi-18px mdi-refresh c-hand mr-2"
:class="{'rotate':isRefreshing}"
:title="t('word.refresh')"
:title="t('general.refresh')"
@click="refresh"
/>
<i
class="mdi mdi-18px mdi-power c-hand"
:title="t('word.disconnect')"
:title="t('connection.disconnect')"
@click="disconnectWorkspace(connection.uid)"
/>
</span>
</div>
<div class="workspace-explorebar-search">
<div v-if="workspace.connectionStatus === 'connected'" class="has-icon-right">
<div v-if="workspace.connectionStatus === 'connected'" class="input-group has-icon-right">
<div
class="input-group-addon px-1 py-0 p-vcentered c-hand"
:title="t('application.switchSearchMethod')"
@click="toggleSearchMethod"
>
<i class="mdi mdi-18px" :class="[searchMethod === 'elements' ? 'mdi-shape' : 'mdi-database']" />
</div>
<input
ref="searchInput"
v-model="searchTerm"
class="form-input input-sm"
type="text"
:placeholder="t('message.searchForElements')"
:placeholder="searchMethod === 'elements' ? t('database.searchForElements') : t('database.searchForSchemas')"
>
<i v-if="!searchTerm" class="form-icon mdi mdi-magnify mdi-18px" />
<i
@@ -50,11 +68,12 @@
</div>
<div class="workspace-explorebar-body" @click="explorebar.focus()">
<WorkspaceExploreBarSchema
v-for="db of workspace.structure"
v-for="db of filteredSchemas"
:key="db.name"
ref="schema"
:database="db"
:connection="connection"
:search-method="searchMethod"
@show-schema-context="openSchemaContext"
@show-table-context="openTableContext"
@show-misc-context="openMiscContext"
@@ -116,10 +135,11 @@
</template>
<script setup lang="ts">
import { Component, computed, onMounted, Ref, ref, watch } from 'vue';
import { Component, computed, onMounted, Prop, Ref, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { useConnectionsStore } from '@/stores/connections';
import { ConnectionParams } from 'common/interfaces/antares';
import { useNotificationsStore } from '@/stores/notifications';
import { useSettingsStore } from '@/stores/settings';
import { useWorkspacesStore } from '@/stores/workspaces';
@@ -133,12 +153,14 @@ import TableContext from '@/components/WorkspaceExploreBarTableContext.vue';
import MiscContext from '@/components/WorkspaceExploreBarMiscContext.vue';
import MiscFolderContext from '@/components/WorkspaceExploreBarMiscFolderContext.vue';
import ModalNewSchema from '@/components/ModalNewSchema.vue';
import BaseSelect from '@/components/BaseSelect.vue';
import { useI18n } from 'vue-i18n';
import Databases from '@/ipc-api/Databases';
const { t } = useI18n();
const props = defineProps({
connection: Object,
connection: Object as Prop<ConnectionParams>,
isSelected: Boolean
});
@@ -152,11 +174,13 @@ const { explorebarSize } = storeToRefs(settingsStore);
const { changeExplorebarSize } = settingsStore;
const {
getWorkspace,
switchConnection,
removeConnected: disconnectWorkspace,
refreshStructure,
newTab,
removeTabs,
setSearchTerm,
setDatabase,
addLoadingElement,
removeLoadingElement
} = workspacesStore;
@@ -164,6 +188,7 @@ const {
const searchInput: Ref<HTMLInputElement> = ref(null);
const explorebar: Ref<HTMLInputElement> = ref(null);
const resizer: Ref<HTMLInputElement> = ref(null);
const databases: Ref<string[]> = ref([]);
const schema: Ref<Component & { selectSchema: (name: string) => void; $refs: {schemaAccordion: HTMLDetailsElement} }[]> = ref(null);
const isRefreshing = ref(false);
const isNewDBModal = ref(false);
@@ -177,10 +202,12 @@ const isMiscFolderContext = ref(false);
const databaseContextEvent = ref(null);
const tableContextEvent = ref(null);
const miscContextEvent = ref(null);
const selectedDatabase = ref(props.connection.database);
const selectedSchema = ref('');
const selectedTable = ref(null);
const selectedMisc = ref(null);
const searchTerm = ref('');
const searchMethod: Ref<'elements' | 'schemas'> = ref('elements');
const workspace = computed(() => {
return getWorkspace(props.connection.uid);
@@ -194,6 +221,13 @@ const customizations = computed(() => {
return workspace.value.customizations;
});
const filteredSchemas = computed(() => {
if (searchMethod.value === 'schemas')
return workspace.value.structure.filter(schema => schema.name.search(searchTerm.value) >= 0);
else
return workspace.value.structure;
});
watch(localWidth, (val: number) => {
clearTimeout(explorebarWidthInterval.value);
@@ -214,9 +248,14 @@ watch(searchTerm, () => {
}, 200);
});
watch(selectedDatabase, (val, oldVal) => {
if (oldVal)
switchConnection({ ...props.connection, database: selectedDatabase.value });
});
localWidth.value = explorebarSize.value;
onMounted(() => {
onMounted(async () => {
resizer.value.addEventListener('mousedown', (e: MouseEvent) => {
e.preventDefault();
@@ -224,10 +263,28 @@ onMounted(() => {
window.addEventListener('mouseup', stopResize);
});
if (workspace.value.structure.length === 1) { // Auto-open if juust one schema
if (workspace.value.structure.length === 1) { // Auto-open if just one schema
schema.value[0].selectSchema(workspace.value.structure[0].name);
schema.value[0].$refs.schemaAccordion.open = true;
}
if (customizations.value.database) {
try {
const { status, response } = await Databases.getDatabases(props.connection.uid);
if (status === 'success') {
databases.value = response;
if (selectedDatabase.value === '') {
selectedDatabase.value = response[0];
setDatabase(selectedDatabase.value);
}
}
else
addNotification({ status: 'error', message: response });
}
catch (err) {
addNotification({ status: 'error', message: err.stack });
}
}
});
const refresh = async () => {
@@ -238,7 +295,10 @@ const refresh = async () => {
}
};
const explorebarSearch = () => {
const explorebarSearch = (event: KeyboardEvent) => {
const isLetter = (event.key >= 'a' && event.key <= 'z');
const isNumber = (event.key >= '0' && event.key <= '9');
if (isLetter || isNumber)
searchInput.value.focus();
};
@@ -403,6 +463,15 @@ const duplicateTable = async (payload: { schema: string; table: { name: string }
});
};
const toggleSearchMethod = () => {
searchTerm.value = '';
setSearchTerm(searchTerm.value);
if (searchMethod.value === 'elements')
searchMethod.value = 'schemas';
else
searchMethod.value = 'elements';
};
</script>
<style lang="scss">
@@ -472,12 +541,32 @@ const duplicateTable = async (payload: { schema: string; table: { name: string }
}
}
.workspace-explorebar-database-switch {
width: 100%;
display: flex;
justify-content: space-between;
z-index: 20;
margin-right: 5px;
margin-left: -4px;
margin-top: -3px;
margin-bottom: -0.5rem;
height: 24px;
.form-select.select-sm {
font-size: 0.6rem;
height: 1.2rem;
line-height: 1rem;
}
}
.workspace-explorebar-search {
width: 100%;
display: flex;
justify-content: space-between;
font-size: 0.6rem;
height: 28px;
margin: 0 0 5px 0;
z-index: 10;
.has-icon-right {
width: 100%;
@@ -491,6 +580,7 @@ const duplicateTable = async (payload: { schema: string; table: { name: string }
.form-input {
height: 1.2rem;
padding-left: 0.2rem;
border-radius:0 $border-radius $border-radius 0;
&:focus + .form-icon {
opacity: 0.9;
@@ -505,7 +595,7 @@ const duplicateTable = async (payload: { schema: string; table: { name: string }
.workspace-explorebar-body {
width: 100%;
height: calc((100vh - 58px) - #{$excluding-size});
height: calc((100vh - 63px) - #{$excluding-size});
overflow: overlay;
padding: 0 0.1rem;
}

View File

@@ -8,7 +8,7 @@
class="context-element"
@click="runElementCheck"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-play text-light pr-1" /> {{ t('word.run') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-play text-light pr-1" /> {{ t('general.run') }}</span>
</div>
<div
v-if="selectedMisc.type === 'trigger' && customizations.triggerEnableDisable"
@@ -16,10 +16,10 @@
@click="toggleTrigger"
>
<span v-if="!selectedMisc.enabled" class="d-flex">
<i class="mdi mdi-18px mdi-play text-light pr-1" /> {{ t('word.enable') }}
<i class="mdi mdi-18px mdi-play text-light pr-1" /> {{ t('general.enable') }}
</span>
<span v-else class="d-flex">
<i class="mdi mdi-18px mdi-pause text-light pr-1" /> {{ t('word.disable') }}
<i class="mdi mdi-18px mdi-pause text-light pr-1" /> {{ t('general.disable') }}
</span>
</div>
<div
@@ -28,14 +28,14 @@
@click="toggleScheduler"
>
<span v-if="!selectedMisc.enabled" class="d-flex">
<i class="mdi mdi-18px mdi-play text-light pr-1" /> {{ t('word.enable') }}
<i class="mdi mdi-18px mdi-play text-light pr-1" /> {{ t('general.enable') }}
</span>
<span v-else class="d-flex">
<i class="mdi mdi-18px mdi-pause text-light pr-1" /> {{ t('word.disable') }}
<i class="mdi mdi-18px mdi-pause text-light pr-1" /> {{ t('general.disable') }}
</span>
</div>
<div class="context-element" @click="showDeleteModal">
<span class="d-flex"><i class="mdi mdi-18px mdi-table-remove text-light pr-1" /> {{ t('word.delete') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-remove text-light pr-1" /> {{ t('general.delete') }}</span>
</div>
<ConfirmModal
v-if="isDeleteModal"
@@ -50,7 +50,7 @@
</template>
<template #body>
<div class="mb-2">
{{ t('message.deleteCorfirm') }} "<b>{{ selectedMisc.name }}</b>"?
{{ t('general.deleteConfirm') }} "<b>{{ selectedMisc.name }}</b>"?
</div>
</template>
</ConfirmModal>
@@ -117,14 +117,14 @@ const customizations = computed(() => {
const deleteMessage = computed(() => {
switch (props.selectedMisc.type) {
case 'trigger':
return t('message.deleteTrigger');
return t('database.deleteTrigger');
case 'procedure':
return t('message.deleteRoutine');
return t('database.deleteRoutine');
case 'function':
case 'triggerFunction':
return t('message.deleteFunction');
return t('database.deleteFunction');
case 'scheduler':
return t('message.deleteScheduler');
return t('database.deleteScheduler');
default:
return '';
}

View File

@@ -8,35 +8,35 @@
class="context-element"
@click="emit('open-create-trigger-tab')"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-cog text-light pr-1" /> {{ t('message.createNewTrigger') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-cog text-light pr-1" /> {{ t('database.createNewTrigger') }}</span>
</div>
<div
v-if="['procedure', 'routine'].includes(props.selectedMisc)"
class="context-element"
@click="emit('open-create-routine-tab')"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-sync-circle text-light pr-1" /> {{ t('message.createNewRoutine') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-sync-circle text-light pr-1" /> {{ t('database.createNewRoutine') }}</span>
</div>
<div
v-if="props.selectedMisc === 'function'"
class="context-element"
@click="emit('open-create-function-tab')"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-arrow-right-bold-box text-light pr-1" /> {{ t('message.createNewFunction') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-arrow-right-bold-box text-light pr-1" /> {{ t('database.createNewFunction') }}</span>
</div>
<div
v-if="props.selectedMisc === 'triggerFunction'"
class="context-element"
@click="emit('open-create-trigger-function-tab')"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-cog-clockwise text-light pr-1" /> {{ t('message.createNewFunction') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-cog-clockwise text-light pr-1" /> {{ t('database.createNewFunction') }}</span>
</div>
<div
v-if="props.selectedMisc === 'scheduler'"
class="context-element"
@click="emit('open-create-scheduler-tab')"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-calendar-clock text-light pr-1" /> {{ t('message.createNewScheduler') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-calendar-clock text-light pr-1" /> {{ t('database.createNewScheduler') }}</span>
</div>
</BaseContextMenu>
</template>

View File

@@ -10,7 +10,7 @@
<i v-else class="icon mdi mdi-18px mdi-chevron-right" />
<i class="database-icon mdi mdi-18px mdi-database mr-1" />
<div class="">
<span>{{ database.name }}</span>
<span v-html="highlightWord(database.name, 'schemas')" />
<div
v-if="database.size"
class="schema-size tooltip tooltip-left mr-1"
@@ -42,7 +42,7 @@
<span v-html="highlightWord(table.name)" />
</a>
<div
v-if="table.type === 'table' && table.size !== false"
v-if="table.type === 'table' && table.size !== false && !isNaN(table.size)"
class="table-size tooltip tooltip-left mr-1"
:data-tooltip="formatBytes(table.size)"
>
@@ -60,7 +60,7 @@
@contextmenu.prevent="showMiscFolderContext($event, 'trigger')"
>
<i class="misc-icon mdi mdi-18px mdi-folder-cog mr-1" />
{{ t('word.trigger', 2) }}
{{ t('database.trigger', 2) }}
</summary>
<div class="accordion-body">
<div>
@@ -82,7 +82,7 @@
<div
v-if="trigger.enabled === false"
class="tooltip tooltip-left disabled-indicator"
:data-tooltip="t('word.disabled')"
:data-tooltip="t('general.disabled')"
>
<i class="table-icon mdi mdi-pause mdi-18px mr-1" />
</div>
@@ -101,7 +101,7 @@
@contextmenu.prevent="showMiscFolderContext($event, 'routine')"
>
<i class="misc-icon mdi mdi-18px mdi-folder-sync mr-1" />
{{ t('word.storedRoutine', 2) }}
{{ t('database.storedRoutine', 2) }}
</summary>
<div class="accordion-body">
<div>
@@ -134,7 +134,7 @@
@contextmenu.prevent="showMiscFolderContext($event, 'triggerFunction')"
>
<i class="misc-icon mdi mdi-18px mdi-folder-refresh mr-1" />
{{ t('word.triggerFunction', 2) }}
{{ t('database.triggerFunction', 2) }}
</summary>
<div class="accordion-body">
<div>
@@ -167,7 +167,7 @@
@contextmenu.prevent="showMiscFolderContext($event, 'function')"
>
<i class="misc-icon mdi mdi-18px mdi-folder-move mr-1" />
{{ t('word.function', 2) }}
{{ t('database.function', 2) }}
</summary>
<div class="accordion-body">
<div>
@@ -200,7 +200,7 @@
@contextmenu.prevent="showMiscFolderContext($event, 'scheduler')"
>
<i class="misc-icon mdi mdi-18px mdi-folder-clock mr-1" />
{{ t('word.scheduler', 2) }}
{{ t('database.scheduler', 2) }}
</summary>
<div class="accordion-body">
<div>
@@ -222,7 +222,7 @@
<div
v-if="scheduler.enabled === false"
class="tooltip tooltip-left disabled-indicator"
:data-tooltip="t('word.disabled')"
:data-tooltip="t('general.disabled')"
>
<i class="table-icon mdi mdi-pause mdi-18px mr-1" />
</div>
@@ -249,7 +249,8 @@ const { t } = useI18n();
const props = defineProps({
database: Object as Prop<WorkspaceStructure>,
connection: Object
connection: Object,
searchMethod: String as Prop<'elements' | 'schemas'>
});
const emit = defineEmits([
@@ -282,29 +283,51 @@ const searchTerm = computed(() => {
});
const filteredTables = computed(() => {
if (props.searchMethod === 'elements')
return props.database.tables.filter(table => table.name.search(searchTerm.value) >= 0);
else
return props.database.tables;
});
const filteredTriggers = computed(() => {
if (props.searchMethod === 'elements')
return props.database.triggers.filter(trigger => trigger.name.search(searchTerm.value) >= 0);
else
return props.database.triggers;
});
const filteredProcedures = computed(() => {
if (props.searchMethod === 'elements')
return props.database.procedures.filter(procedure => procedure.name.search(searchTerm.value) >= 0);
else
return props.database.procedures;
});
const filteredFunctions = computed(() => {
if (props.searchMethod === 'elements')
return props.database.functions.filter(func => func.name.search(searchTerm.value) >= 0);
else
return props.database.functions;
});
const filteredTriggerFunctions = computed(() => {
if (props.searchMethod === 'elements') {
return props.database.triggerFunctions
? props.database.triggerFunctions.filter(func => func.name.search(searchTerm.value) >= 0)
: [];
}
else {
return props.database.triggerFunctions
? props.database.triggerFunctions
: [];
}
});
const filteredSchedulers = computed(() => {
if (props.searchMethod === 'elements')
return props.database.schedulers.filter(scheduler => scheduler.name.search(searchTerm.value) >= 0);
else
return props.database.schedulers;
});
const workspace = computed(() => {
@@ -446,10 +469,10 @@ const setBreadcrumbs = (payload: Breadcrumb) => {
changeBreadcrumbs(payload);
};
const highlightWord = (string: string) => {
const highlightWord = (string: string, type = 'elements') => {
string = string.replaceAll('<', '&lt;').replaceAll('>', '&gt;');
if (searchTerm.value) {
if (searchTerm.value && props.searchMethod === type) {
const regexp = new RegExp(`(${searchTerm.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
return string.replace(regexp, '<span class="text-primary">$1</span>');
}

View File

@@ -4,7 +4,7 @@
@close-context="closeContext"
>
<div class="context-element">
<span class="d-flex"><i class="mdi mdi-18px mdi-plus text-light pr-1" /> {{ t('word.add') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-plus text-light pr-1" /> {{ t('general.add') }}</span>
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
<div class="context-submenu">
<div
@@ -12,49 +12,49 @@
class="context-element"
@click="openCreateTableTab"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-table text-light pr-1" /> {{ t('word.table') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-table text-light pr-1" /> {{ t('database.table') }}</span>
</div>
<div
v-if="workspace.customizations.viewAdd"
class="context-element"
@click="openCreateViewTab"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-eye text-light pr-1" /> {{ t('word.view') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-eye text-light pr-1" /> {{ t('database.view') }}</span>
</div>
<div
v-if="workspace.customizations.triggerAdd"
class="context-element"
@click="openCreateTriggerTab"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-cog text-light pr-1" /> {{ t('word.trigger', 1) }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-cog text-light pr-1" /> {{ t('database.trigger', 1) }}</span>
</div>
<div
v-if="workspace.customizations.routineAdd"
class="context-element"
@click="openCreateRoutineTab"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-sync-circle pr-1" /> {{ t('word.storedRoutine', 1) }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-sync-circle pr-1" /> {{ t('database.storedRoutine', 1) }}</span>
</div>
<div
v-if="workspace.customizations.functionAdd"
class="context-element"
@click="openCreateFunctionTab"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-arrow-right-bold-box pr-1" /> {{ t('word.function', 1) }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-arrow-right-bold-box pr-1" /> {{ t('database.function', 1) }}</span>
</div>
<div
v-if="workspace.customizations.triggerFunctionAdd"
class="context-element"
@click="openCreateTriggerFunctionTab"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-cog-clockwise pr-1" /> {{ t('word.triggerFunction', 1) }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-cog-clockwise pr-1" /> {{ t('database.triggerFunction', 1) }}</span>
</div>
<div
v-if="workspace.customizations.schedulerAdd"
class="context-element"
@click="openCreateSchedulerTab"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-calendar-clock text-light pr-1" /> {{ t('word.scheduler', 1) }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-calendar-clock text-light pr-1" /> {{ t('database.scheduler', 1) }}</span>
</div>
</div>
</div>
@@ -63,28 +63,28 @@
class="context-element"
@click="showExportSchemaModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-database-arrow-down text-light pr-1" /> {{ t('word.export') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-database-export text-light pr-1" /> {{ t('database.export') }}</span>
</div>
<div
v-if="workspace.customizations.schemaImport"
class="context-element"
@click="initImport"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-database-arrow-up text-light pr-1" /> {{ t('word.import') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-database-import text-light pr-1" /> {{ t('database.import') }}</span>
</div>
<div
v-if="workspace.customizations.schemaEdit"
class="context-element"
@click="showEditModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-database-edit text-light pr-1" /> {{ t('word.edit') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-database-edit text-light pr-1" /> {{ t('database.editSchema') }}</span>
</div>
<div
v-if="workspace.customizations.schemaDrop"
class="context-element"
@click="showDeleteModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-database-remove text-light pr-1" /> {{ t('word.delete') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-database-remove text-light pr-1" /> {{ t('database.deleteSchema') }}</span>
</div>
<ConfirmModal
@@ -95,12 +95,12 @@
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-database-remove mr-1" />
<span class="cut-text">{{ t('message.deleteSchema') }}</span>
<span class="cut-text">{{ t('database.deleteSchema') }}</span>
</div>
</template>
<template #body>
<div class="mb-2">
{{ t('message.deleteCorfirm') }} "<b>{{ selectedSchema }}</b>"?
{{ t('general.deleteConfirm') }} "<b>{{ selectedSchema }}</b>"?
</div>
</template>
</ConfirmModal>
@@ -109,11 +109,6 @@
:selected-schema="selectedSchema"
@close="hideEditModal"
/>
<ModalExportSchema
v-if="isExportSchemaModal"
:selected-schema="selectedSchema"
@close="hideExportSchemaModal"
/>
<ModalImportSchema
v-if="isImportSchemaModal"
ref="importModalRef"
@@ -128,10 +123,10 @@ import { Component, computed, nextTick, Ref, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import { useSchemaExportStore } from '@/stores/schemaExport';
import BaseContextMenu from '@/components/BaseContextMenu.vue';
import ConfirmModal from '@/components/BaseConfirmModal.vue';
import ModalEditSchema from '@/components/ModalEditSchema.vue';
import ModalExportSchema from '@/components/ModalExportSchema.vue';
import ModalImportSchema from '@/components/ModalImportSchema.vue';
import Schema from '@/ipc-api/Schema';
import Application from '@/ipc-api/Application';
@@ -158,6 +153,8 @@ const emit = defineEmits([
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const schemaExportStore = useSchemaExportStore();
const { showExportModal } = schemaExportStore;
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
@@ -169,7 +166,6 @@ const {
const importModalRef: Ref<Component & {startImport: (file: string) => void}> = ref(null);
const isDeleteModal = ref(false);
const isEditModal = ref(false);
const isExportSchemaModal = ref(false);
const isImportSchemaModal = ref(false);
const workspace = computed(() => getWorkspace(selectedWorkspace.value));
@@ -220,11 +216,7 @@ const hideEditModal = () => {
};
const showExportSchemaModal = () => {
isExportSchemaModal.value = true;
};
const hideExportSchemaModal = () => {
isExportSchemaModal.value = false;
showExportModal(props.selectedSchema);
closeContext();
};

View File

@@ -8,31 +8,38 @@
class="context-element"
@click="openTableSettingTab"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-tune-vertical-variant text-light pr-1" /> {{ t('word.settings') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-wrench-cog text-light pr-1" /> {{ t('application.settings') }}</span>
</div>
<div
v-if="selectedTable && selectedTable.type === 'table' && customizations.schemaExport"
class="context-element"
@click="showTableExportModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-arrow-right text-light pr-1" /> {{ t('database.exportTable') }}</span>
</div>
<div
v-if="selectedTable && selectedTable.type === 'view' && customizations.viewSettings"
class="context-element"
@click="openViewSettingTab"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-tune-vertical-variant text-light pr-1" /> {{ t('word.settings') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-wrench-cog text-light pr-1" /> {{ t('application.settings') }}</span>
</div>
<div
v-if="selectedTable && selectedTable.type === 'table' && customizations.tableDuplicate"
class="context-element"
@click="duplicateTable"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-multiple text-light pr-1" /> {{ t('message.duplicateTable') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-multiple text-light pr-1" /> {{ t('database.duplicateTable') }}</span>
</div>
<div
v-if="selectedTable && selectedTable.type === 'table'"
class="context-element"
@click="showEmptyModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-off text-light pr-1" /> {{ t('message.emptyTable') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-off text-light pr-1" /> {{ t('database.emptyTable') }}</span>
</div>
<div class="context-element" @click="showDeleteModal">
<span class="d-flex"><i class="mdi mdi-18px mdi-table-remove text-light pr-1" /> {{ t('word.delete') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-remove text-light pr-1" /> {{ t('database.deleteTable') }}</span>
</div>
<ConfirmModal
@@ -42,16 +49,16 @@
>
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-table-off mr-1" /> <span class="cut-text">{{ t('message.emptyTable') }}</span>
<i class="mdi mdi-24px mdi-table-off mr-1" /> <span class="cut-text">{{ t('database.emptyTable') }}</span>
</div>
</template>
<template #body>
<div class="mb-2">
{{ t('message.emptyCorfirm') }} "<b>{{ selectedTable.name }}</b>"?
{{ t('database.emptyConfirm') }} "<b>{{ selectedTable.name }}</b>"?
</div>
<div v-if="customizations.tableTruncateDisableFKCheck">
<label class="form-checkbox form-inline">
<input v-model="forceTruncate" type="checkbox"><i class="form-icon" /> {{ t('message.disableFKChecks') }}
<input v-model="forceTruncate" type="checkbox"><i class="form-icon" /> {{ t('database.disableFKChecks') }}
</label>
</div>
</template>
@@ -64,12 +71,12 @@
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-table-remove mr-1" />
<span class="cut-text">{{ selectedTable.type === 'table' ? t('message.deleteTable') : t('message.deleteView') }}</span>
<span class="cut-text">{{ selectedTable.type === 'table' ? t('database.deleteTable') : t('database.deleteView') }}</span>
</div>
</template>
<template #body>
<div class="mb-2">
{{ t('message.deleteCorfirm') }} "<b>{{ selectedTable.name }}</b>"?
{{ t('general.deleteConfirm') }} "<b>{{ selectedTable.name }}</b>"?
</div>
</template>
</ConfirmModal>
@@ -81,6 +88,7 @@ import { computed, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import { useSchemaExportStore } from '@/stores/schemaExport';
import BaseContextMenu from '@/components/BaseContextMenu.vue';
import ConfirmModal from '@/components/BaseConfirmModal.vue';
import Tables from '@/ipc-api/Tables';
@@ -98,6 +106,7 @@ const emit = defineEmits(['close-context', 'duplicate-table', 'reload', 'delete-
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { showExportModal } = useSchemaExportStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
@@ -116,6 +125,11 @@ const forceTruncate = ref(false);
const workspace = computed(() => getWorkspace(selectedWorkspace.value));
const customizations = computed(() => workspace.value && workspace.value.customizations ? workspace.value.customizations : null);
const showTableExportModal = () => {
showExportModal(props.selectedSchema, props.selectedTable.name);
closeContext();
};
const showDeleteModal = () => {
isDeleteModal.value = true;
};

View File

@@ -13,7 +13,7 @@
:style="{height: localHeight ? localHeight+'px' : ''}"
>
<div class="query-console-header">
<div>{{ t('word.console') }}</div>
<div>{{ t('application.console') }}</div>
<button class="btn btn-clear mr-1" @click="resizeConsole(0)" />
</div>
<div ref="queryConsoleBody" class="query-console-body">
@@ -35,7 +35,7 @@
@close-context="isContext = false"
>
<div class="context-element" @click="copyQuery">
<span class="d-flex"><i class="mdi mdi-18px mdi-content-copy text-light pr-1" /> {{ t('word.copy') }}</span>
<span class="d-flex"><i class="mdi mdi-18px mdi-content-copy text-light pr-1" /> {{ t('general.copy') }}</span>
</div>
</BaseContextMenu>
</template>

View File

@@ -10,27 +10,27 @@
@click="saveChanges"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ t('word.save') }}</span>
<span>{{ t('general.save') }}</span>
</button>
<button
:disabled="!isChanged"
class="btn btn-link btn-sm mr-0"
:title="t('message.clearChanges')"
:title="t('database.clearChanges')"
@click="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ t('word.clear') }}</span>
<span>{{ t('general.clear') }}</span>
</button>
<div class="divider-vert py-3" />
<button class="btn btn-dark btn-sm" @click="showParamsModal">
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
<span>{{ t('word.parameters') }}</span>
<span>{{ t('database.parameters') }}</span>
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="t('word.schema')">
<div class="d-flex" :title="t('database.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div>
</div>
@@ -41,7 +41,7 @@
<div class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('word.name') }}
{{ t('general.name') }}
</label>
<input
ref="firstInput"
@@ -54,7 +54,7 @@
<div v-if="customizations.languages" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('word.language') }}
{{ t('application.language') }}
</label>
<BaseSelect
v-model="localFunction.language"
@@ -66,11 +66,11 @@
<div v-if="customizations.definer" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('word.definer') }}
{{ t('database.definer') }}
</label>
<BaseSelect
v-model="localFunction.definer"
:options="[{value: '', name:t('message.currentUser')}, ...workspace.users]"
:options="[{value: '', name:t('database.currentUser')}, ...workspace.users]"
:option-label="(user: any) => user.value === '' ? user.name : `${user.name}@${user.host}`"
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select"
@@ -80,7 +80,7 @@
<div class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('word.returns') }}
{{ t('database.returns') }}
</label>
<div class="input-group">
<BaseSelect
@@ -100,7 +100,7 @@
class="form-input"
type="number"
min="0"
:placeholder="t('word.length')"
:placeholder="t('database.length')"
>
</div>
</div>
@@ -108,7 +108,7 @@
<div v-if="customizations.comment" class="column">
<div class="form-group">
<label class="form-label">
{{ t('word.comment') }}
{{ t('database.comment') }}
</label>
<input
v-model="localFunction.comment"
@@ -120,7 +120,7 @@
<div class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('message.sqlSecurity') }}
{{ t('database.sqlSecurity') }}
</label>
<BaseSelect
v-model="localFunction.security"
@@ -132,7 +132,7 @@
<div v-if="customizations.functionDataAccess" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('message.dataAccess') }}
{{ t('database.dataAccess') }}
</label>
<BaseSelect
v-model="localFunction.dataAccess"
@@ -145,7 +145,7 @@
<div class="form-group">
<label class="form-label d-invisible">.</label>
<label class="form-checkbox form-inline">
<input v-model="localFunction.deterministic" type="checkbox"><i class="form-icon" /> {{ t('word.deterministic') }}
<input v-model="localFunction.deterministic" type="checkbox"><i class="form-icon" /> {{ t('database.deterministic') }}
</label>
</div>
</div>
@@ -153,7 +153,7 @@
</div>
<div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ t('message.functionBody') }}</label>
<label class="form-label ml-2">{{ t('database.functionBody') }}</label>
<QueryEditor
v-show="isSelected"
ref="queryEditor"

View File

@@ -10,27 +10,27 @@
@click="saveChanges"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ t('word.save') }}</span>
<span>{{ t('general.save') }}</span>
</button>
<button
:disabled="!isChanged"
class="btn btn-link btn-sm mr-0"
:title="t('message.clearChanges')"
:title="t('database.clearChanges')"
@click="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ t('word.clear') }}</span>
<span>{{ t('general.clear') }}</span>
</button>
<div class="divider-vert py-3" />
<button class="btn btn-dark btn-sm" @click="showParamsModal">
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
<span>{{ t('word.parameters') }}</span>
<span>{{ t('database.parameters') }}</span>
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="t('word.schema')">
<div class="d-flex" :title="t('database.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div>
</div>
@@ -41,7 +41,7 @@
<div class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('word.name') }}
{{ t('general.name') }}
</label>
<input
ref="firstInput"
@@ -54,7 +54,7 @@
<div v-if="customizations.languages" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('word.language') }}
{{ t('application.language') }}
</label>
<BaseSelect
v-model="localRoutine.language"
@@ -66,11 +66,11 @@
<div v-if="customizations.definer" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('word.definer') }}
{{ t('database.definer') }}
</label>
<BaseSelect
v-model="localRoutine.definer"
:options="[{value: '', name:t('message.currentUser')}, ...workspace.users]"
:options="[{value: '', name:t('database.currentUser')}, ...workspace.users]"
:option-label="(user: any) => user.value === '' ? user.name : `${user.name}@${user.host}`"
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select"
@@ -80,7 +80,7 @@
<div v-if="customizations.comment" class="column">
<div class="form-group">
<label class="form-label">
{{ t('word.comment') }}
{{ t('database.comment') }}
</label>
<input
v-model="localRoutine.comment"
@@ -92,7 +92,7 @@
<div class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('message.sqlSecurity') }}
{{ t('database.sqlSecurity') }}
</label>
<BaseSelect
v-model="localRoutine.security"
@@ -104,7 +104,7 @@
<div v-if="customizations.procedureDataAccess" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('message.dataAccess') }}
{{ t('database.dataAccess') }}
</label>
<BaseSelect
v-model="localRoutine.dataAccess"
@@ -117,7 +117,7 @@
<div class="form-group">
<label class="form-label d-invisible">.</label>
<label class="form-checkbox form-inline">
<input v-model="localRoutine.deterministic" type="checkbox"><i class="form-icon" /> {{ t('word.deterministic') }}
<input v-model="localRoutine.deterministic" type="checkbox"><i class="form-icon" /> {{ t('database.deterministic') }}
</label>
</div>
</div>
@@ -125,7 +125,7 @@
</div>
<div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ t('message.routineBody') }}</label>
<label class="form-label ml-2">{{ t('database.routineBody') }}</label>
<QueryEditor
v-show="isSelected"
ref="queryEditor"

View File

@@ -10,26 +10,26 @@
@click="saveChanges"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ t('word.save') }}</span>
<span>{{ t('general.save') }}</span>
</button>
<button
:disabled="!isChanged"
class="btn btn-link btn-sm mr-0"
:title="t('message.clearChanges')"
:title="t('database.clearChanges')"
@click="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ t('word.clear') }}</span>
<span>{{ t('general.clear') }}</span>
</button>
<div class="divider-vert py-3" />
<button class="btn btn-dark btn-sm" @click="showTimingModal">
<i class="mdi mdi-24px mdi-timer mr-1" />
<span>{{ t('word.timing') }}</span>
<span>{{ t('database.timing') }}</span>
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="t('word.schema')">
<div class="d-flex" :title="t('database.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div>
</div>
@@ -39,7 +39,7 @@
<div class="columns">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ t('word.name') }}</label>
<label class="form-label">{{ t('general.name') }}</label>
<input
ref="firstInput"
v-model="localScheduler.name"
@@ -50,11 +50,11 @@
</div>
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ t('word.definer') }}</label>
<label class="form-label">{{ t('database.definer') }}</label>
<BaseSelect
v-model="localScheduler.definer"
:options="users"
:option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
: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"
/>
@@ -62,7 +62,7 @@
</div>
<div class="column">
<div class="form-group">
<label class="form-label">{{ t('word.comment') }}</label>
<label class="form-label">{{ t('database.comment') }}</label>
<input
v-model="localScheduler.comment"
class="form-input"
@@ -72,7 +72,7 @@
</div>
<div class="column">
<div class="form-group">
<label class="form-label mr-2">{{ t('word.state') }}</label>
<label class="form-label mr-2">{{ t('database.state') }}</label>
<label class="form-radio form-inline">
<input
v-model="localScheduler.state"
@@ -103,7 +103,7 @@
</div>
<div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ t('message.schedulerBody') }}</label>
<label class="form-label ml-2">{{ t('database.schedulerBody') }}</label>
<QueryEditor
v-show="isSelected"
ref="queryEditor"

View File

@@ -10,16 +10,16 @@
@click="saveChanges"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ t('word.save') }}</span>
<span>{{ t('general.save') }}</span>
</button>
<button
:disabled="!isChanged || isSaving"
class="btn btn-link btn-sm mr-0"
:title="t('message.clearChanges')"
:title="t('database.clearChanges')"
@click="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ t('word.clear') }}</span>
<span>{{ t('general.clear') }}</span>
</button>
<div class="divider-vert py-3" />
@@ -27,32 +27,33 @@
<button
:disabled="isSaving"
class="btn btn-dark btn-sm"
:title="t('message.addNewField')"
:title="t('database.addNewField')"
@click="addField"
>
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span>{{ t('word.add') }}</span>
<span>{{ t('general.add') }}</span>
</button>
<button
:disabled="isSaving || !localFields.length"
class="btn btn-dark btn-sm"
:title="t('message.manageIndexes')"
:title="t('database.manageIndexes')"
@click="showIntdexesModal"
>
<i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" />
<span>{{ t('word.indexes') }}</span>
<span>{{ t('database.indexes') }}</span>
</button>
<button
class="btn btn-dark btn-sm"
:disabled="isSaving || !localFields.length"
:title="t('database.manageForeignKeys')"
@click="showForeignModal"
>
<i class="mdi mdi-24px mdi-key-link mr-1" />
<span>{{ t('word.foreignKeys') }}</span>
<span>{{ t('database.foreignKeys') }}</span>
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="t('word.schema')">
<div class="d-flex" :title="t('database.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div>
</div>
@@ -62,7 +63,7 @@
<div class="columns mb-4">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ t('word.name') }}</label>
<label class="form-label">{{ t('general.name') }}</label>
<input
ref="firstInput"
v-model="localOptions.name"
@@ -73,7 +74,7 @@
</div>
<div v-if="workspace.customizations.comment" class="column">
<div class="form-group">
<label class="form-label">{{ t('word.comment') }}</label>
<label class="form-label">{{ t('database.comment') }}</label>
<input
v-model="localOptions.comment"
class="form-input"
@@ -85,7 +86,7 @@
<div v-if="workspace.customizations.collations" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('word.collation') }}
{{ t('database.collation') }}
</label>
<BaseSelect
v-model="localOptions.collation"
@@ -100,7 +101,7 @@
<div v-if="workspace.customizations.engines" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('word.engine') }}
{{ t('database.engine') }}
</label>
<BaseSelect
v-model="localOptions.engine"
@@ -312,9 +313,10 @@ const clearChanges = () => {
};
const addField = () => {
const uid = uidGen();
localFields.value.push({
_antares_id: uidGen(),
name: `${t('word.field', 1)}_${++newFieldsCounter.value}`,
_antares_id: uid,
name: `${t('database.field', 1)}_${uid.substring(0, 4)}`,
key: '',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type: (workspace.value.dataTypes[0] as any).types[0].name,

View File

@@ -1,12 +1,12 @@
<template>
<div class="column col-12 empty">
<p class="h6 empty-subtitle">
{{ t('message.thereAreNoTableFields') }}
{{ t('database.thereAreNoTableFields') }}
</p>
<div class="empty-action">
<button class="btn btn-gray d-flex" @click="emit('new-field')">
<i class="mdi mdi-24px mdi-playlist-plus mr-2" />
{{ t('message.addNewField') }}
{{ t('database.addNewField') }}
</button>
</div>
</div>

View File

@@ -10,20 +10,20 @@
@click="saveChanges"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ t('word.save') }}</span>
<span>{{ t('general.save') }}</span>
</button>
<button
:disabled="!isChanged"
class="btn btn-link btn-sm mr-0"
:title="t('message.clearChanges')"
:title="t('database.clearChanges')"
@click="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ t('word.clear') }}</span>
<span>{{ t('general.clear') }}</span>
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="t('word.schema')">
<div class="d-flex" :title="t('database.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div>
</div>
@@ -33,7 +33,7 @@
<div class="columns">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ t('word.name') }}</label>
<label class="form-label">{{ t('general.name') }}</label>
<input
ref="firstInput"
v-model="localTrigger.name"
@@ -44,11 +44,11 @@
</div>
<div v-if="customizations.definer" class="column col-auto">
<div class="form-group">
<label class="form-label">{{ t('word.definer') }}</label>
<label class="form-label">{{ t('database.definer') }}</label>
<BaseSelect
v-model="localTrigger.definer"
:options="users"
:option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
: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"
/>
@@ -57,7 +57,7 @@
<fieldset class="column columns mb-0" :disabled="customizations.triggerOnlyRename">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ t('word.table') }}</label>
<label class="form-label">{{ t('database.table') }}</label>
<BaseSelect
v-model="localTrigger.table"
:options="schemaTables"
@@ -69,7 +69,7 @@
</div>
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ t('word.event') }}</label>
<label class="form-label">{{ t('database.event') }}</label>
<div class="input-group">
<BaseSelect
v-model="localTrigger.activation"
@@ -100,7 +100,7 @@
</div>
<div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ t('message.triggerStatement') }}</label>
<label class="form-label ml-2">{{ t('database.triggerStatement') }}</label>
<QueryEditor
v-show="isSelected"
ref="queryEditor"

View File

@@ -10,16 +10,16 @@
@click="saveChanges"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ t('word.save') }}</span>
<span>{{ t('general.save') }}</span>
</button>
<button
:disabled="!isChanged"
class="btn btn-link btn-sm mr-0"
:title="t('message.clearChanges')"
:title="t('database.clearChanges')"
@click="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ t('word.clear') }}</span>
<span>{{ t('general.clear') }}</span>
</button>
</div>
</div>
@@ -29,7 +29,7 @@
<div class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('word.name') }}
{{ t('general.name') }}
</label>
<input
ref="firstInput"
@@ -42,7 +42,7 @@
<div v-if="customizations.triggerFunctionlanguages" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('word.language') }}
{{ t('application.language') }}
</label>
<BaseSelect
v-model="localFunction.language"
@@ -54,12 +54,12 @@
<div v-if="customizations.definer" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('word.definer') }}
{{ t('database.definer') }}
</label>
<BaseSelect
v-model="localFunction.definer"
:options="workspace.users"
:option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
: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"
/>
@@ -67,7 +67,7 @@
</div>
<div v-if="customizations.comment" class="form-group">
<label class="form-label">
{{ t('word.comment') }}
{{ t('database.comment') }}
</label>
<input
v-model="localFunction.comment"
@@ -79,7 +79,7 @@
</div>
<div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ t('message.functionBody') }}</label>
<label class="form-label ml-2">{{ t('database.functionBody') }}</label>
<QueryEditor
v-show="isSelected"
ref="queryEditor"

View File

@@ -10,20 +10,20 @@
@click="saveChanges"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ t('word.save') }}</span>
<span>{{ t('general.save') }}</span>
</button>
<button
:disabled="!isChanged"
class="btn btn-link btn-sm mr-0"
:title="t('message.clearChanges')"
:title="t('database.clearChanges')"
@click="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ t('word.clear') }}</span>
<span>{{ t('general.clear') }}</span>
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="t('word.schema')">
<div class="d-flex" :title="t('database.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div>
</div>
@@ -33,7 +33,7 @@
<div class="columns">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ t('word.name') }}</label>
<label class="form-label">{{ t('general.name') }}</label>
<input
ref="firstInput"
v-model="localView.name"
@@ -44,11 +44,11 @@
</div>
<div class="column col-auto">
<div v-if="workspace.customizations.definer" class="form-group">
<label class="form-label">{{ t('word.definer') }}</label>
<label class="form-label">{{ t('database.definer') }}</label>
<BaseSelect
v-model="localView.definer"
:options="users"
:option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
: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"
/>
@@ -56,7 +56,7 @@
</div>
<div class="column col-auto mr-2">
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
<label class="form-label">{{ t('message.sqlSecurity') }}</label>
<label class="form-label">{{ t('database.sqlSecurity') }}</label>
<BaseSelect
v-model="localView.security"
:options="['DEFINER', 'INVOKER']"
@@ -66,7 +66,7 @@
</div>
<div class="column col-auto mr-2">
<div v-if="workspace.customizations.viewAlgorithm" class="form-group">
<label class="form-label">{{ t('word.algorithm') }}</label>
<label class="form-label">{{ t('database.algorithm') }}</label>
<BaseSelect
v-model="localView.algorithm"
:options="['UNDEFINED', 'MERGE', 'TEMPTABLE']"
@@ -76,7 +76,7 @@
</div>
<div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2">
<div class="form-group">
<label class="form-label">{{ t('message.updateOption') }}</label>
<label class="form-label">{{ t('database.updateOption') }}</label>
<BaseSelect
v-model="localView.updateOption"
:option-track-by="(user: any) => user.value"
@@ -89,7 +89,7 @@
</div>
<div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ t('message.selectStatement') }}</label>
<label class="form-label ml-2">{{ t('database.selectStatement') }}</label>
<QueryEditor
v-show="isSelected"
ref="queryEditor"

View File

@@ -10,16 +10,16 @@
@click="saveChanges"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ t('word.save') }}</span>
<span>{{ t('general.save') }}</span>
</button>
<button
:disabled="!isChanged"
class="btn btn-link btn-sm mr-0"
:title="t('message.clearChanges')"
:title="t('database.clearChanges')"
@click="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ t('word.clear') }}</span>
<span>{{ t('general.clear') }}</span>
</button>
<div class="divider-vert py-3" />
@@ -30,15 +30,15 @@
@click="runFunctionCheck"
>
<i class="mdi mdi-24px mdi-play mr-1" />
<span>{{ t('word.run') }}</span>
<span>{{ t('general.run') }}</span>
</button>
<button class="btn btn-dark btn-sm" @click="showParamsModal">
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
<span>{{ t('word.parameters') }}</span>
<span>{{ t('database.parameters') }}</span>
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="t('word.schema')">
<div class="d-flex" :title="t('database.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div>
</div>
@@ -49,7 +49,7 @@
<div class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('word.name') }}
{{ t('general.name') }}
</label>
<input
ref="firstInput"
@@ -63,7 +63,7 @@
<div v-if="customizations.languages" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('word.language') }}
{{ t('application.language') }}
</label>
<BaseSelect
v-model="localFunction.language"
@@ -75,11 +75,11 @@
<div v-if="customizations.definer" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('word.definer') }}
{{ t('database.definer') }}
</label>
<BaseSelect
v-model="localFunction.definer"
:options="[{value: '', name:t('message.currentUser')}, ...workspace.users]"
:options="[{value: '', name:t('database.currentUser')}, ...workspace.users]"
:option-label="(user: any) => user.value === '' ? user.name : `${user.name}@${user.host}`"
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select"
@@ -89,7 +89,7 @@
<div class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('word.returns') }}
{{ t('database.returns') }}
</label>
<div class="input-group">
<BaseSelect
@@ -109,7 +109,7 @@
class="form-input"
type="number"
min="0"
:placeholder="t('word.length')"
:placeholder="t('database.length')"
>
</div>
</div>
@@ -117,7 +117,7 @@
<div v-if="customizations.comment" class="column">
<div class="form-group">
<label class="form-label">
{{ t('word.comment') }}
{{ t('database.comment') }}
</label>
<input
v-model="localFunction.comment"
@@ -129,7 +129,7 @@
<div class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('message.sqlSecurity') }}
{{ t('database.sqlSecurity') }}
</label>
<BaseSelect
v-model="localFunction.security"
@@ -141,7 +141,7 @@
<div v-if="customizations.functionDataAccess" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('message.dataAccess') }}
{{ t('database.dataAccess') }}
</label>
<BaseSelect
v-model="localFunction.dataAccess"
@@ -154,7 +154,7 @@
<div class="form-group">
<label class="form-label d-invisible">.</label>
<label class="form-checkbox form-inline">
<input v-model="localFunction.deterministic" type="checkbox"><i class="form-icon" /> {{ t('word.deterministic') }}
<input v-model="localFunction.deterministic" type="checkbox"><i class="form-icon" /> {{ t('database.deterministic') }}
</label>
</div>
</div>
@@ -162,7 +162,7 @@
</div>
<div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ t('message.functionBody') }}</label>
<label class="form-label ml-2">{{ t('database.functionBody') }}</label>
<QueryEditor
v-show="isSelected"
ref="queryEditor"

View File

@@ -1,6 +1,6 @@
<template>
<ConfirmModal
:confirm-text="t('word.confirm')"
:confirm-text="t('general.confirm')"
size="medium"
class="options-modal"
@confirm="confirmParametersChange"
@@ -9,7 +9,7 @@
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
<span class="cut-text">{{ t('word.parameters') }} "{{ func }}"</span>
<span class="cut-text">{{ t('database.parameters') }} "{{ func }}"</span>
</div>
</template>
<template #body>
@@ -20,16 +20,16 @@
<div class="d-flex">
<button class="btn btn-dark btn-sm d-flex" @click="addParameter">
<i class="mdi mdi-24px mdi-plus mr-1" />
<span>{{ t('word.add') }}</span>
<span>{{ t('general.add') }}</span>
</button>
<button
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
:title="t('message.clearChanges')"
:title="t('database.clearChanges')"
:disabled="!isChanged"
@click.prevent="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ t('word.clear') }}</span>
<span>{{ t('general.clear') }}</span>
</button>
</div>
</div>
@@ -55,7 +55,7 @@
<div class="tile-action">
<button
class="btn btn-link remove-field p-0 mr-2"
:title="t('word.delete')"
:title="t('general.delete')"
@click.prevent="removeParameter(param._antares_id)"
>
<i class="mdi mdi-close" />
@@ -74,7 +74,7 @@
>
<div class="form-group">
<label class="form-label col-3">
{{ t('word.name') }}
{{ t('general.name') }}
</label>
<div class="column">
<input
@@ -86,7 +86,7 @@
</div>
<div class="form-group">
<label class="form-label col-3">
{{ t('word.type') }}
{{ t('database.type') }}
</label>
<div class="column">
<BaseSelect
@@ -102,7 +102,7 @@
</div>
<div v-if="customizations.parametersLength" class="form-group">
<label class="form-label col-3">
{{ t('word.length') }}
{{ t('database.length') }}
</label>
<div class="column">
<input
@@ -115,7 +115,7 @@
</div>
<div v-if="customizations.functionContext" class="form-group">
<label class="form-label col-3">
{{ t('word.context') }}
{{ t('database.context') }}
</label>
<div class="column">
<label class="form-radio">
@@ -150,11 +150,11 @@
<i class="mdi mdi-dots-horizontal mdi-48px" />
</div>
<p class="empty-title h5">
{{ t('message.thereAreNoParameters') }}
{{ t('database.thereAreNoParameters') }}
</p>
<div class="empty-action">
<button class="btn btn-primary" @click="addParameter">
{{ t('message.createNewParameter') }}
{{ t('database.createNewParameter') }}
</button>
</div>
</div>

View File

@@ -10,16 +10,16 @@
@click="saveChanges"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ t('word.save') }}</span>
<span>{{ t('general.save') }}</span>
</button>
<button
:disabled="!isChanged"
class="btn btn-link btn-sm mr-0"
:title="t('message.clearChanges')"
:title="t('database.clearChanges')"
@click="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ t('word.clear') }}</span>
<span>{{ t('general.clear') }}</span>
</button>
<div class="divider-vert py-3" />
@@ -30,15 +30,15 @@
@click="runRoutineCheck"
>
<i class="mdi mdi-24px mdi-play mr-1" />
<span>{{ t('word.run') }}</span>
<span>{{ t('general.run') }}</span>
</button>
<button class="btn btn-dark btn-sm" @click="showParamsModal">
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
<span>{{ t('word.parameters') }}</span>
<span>{{ t('database.parameters') }}</span>
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="t('word.schema')">
<div class="d-flex" :title="t('database.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div>
</div>
@@ -49,7 +49,7 @@
<div class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('word.name') }}
{{ t('general.name') }}
</label>
<input
ref="firstInput"
@@ -63,7 +63,7 @@
<div v-if="customizations.languages" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('word.language') }}
{{ t('application.language') }}
</label>
<BaseSelect
v-model="localRoutine.language"
@@ -75,11 +75,11 @@
<div v-if="customizations.definer" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('word.definer') }}
{{ t('database.definer') }}
</label>
<BaseSelect
v-model="localRoutine.definer"
:options="[{value: '', name: t('message.currentUser')}, ...workspace.users]"
:options="[{value: '', name: t('database.currentUser')}, ...workspace.users]"
:option-label="(user: any) => user.value === '' ? user.name : `${user.name}@${user.host}`"
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select"
@@ -89,7 +89,7 @@
<div v-if="customizations.comment" class="column">
<div class="form-group">
<label class="form-label">
{{ t('word.comment') }}
{{ t('database.comment') }}
</label>
<input
v-model="localRoutine.comment"
@@ -101,7 +101,7 @@
<div class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('message.sqlSecurity') }}
{{ t('database.sqlSecurity') }}
</label>
<BaseSelect
v-model="localRoutine.security"
@@ -113,7 +113,7 @@
<div v-if="customizations.procedureDataAccess" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ t('message.dataAccess') }}
{{ t('database.dataAccess') }}
</label>
<BaseSelect
v-model="localRoutine.dataAccess"
@@ -126,7 +126,7 @@
<div class="form-group">
<label class="form-label d-invisible">.</label>
<label class="form-checkbox form-inline">
<input v-model="localRoutine.deterministic" type="checkbox"><i class="form-icon" /> {{ t('word.deterministic') }}
<input v-model="localRoutine.deterministic" type="checkbox"><i class="form-icon" /> {{ t('database.deterministic') }}
</label>
</div>
</div>
@@ -134,7 +134,7 @@
</div>
<div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ t('message.routineBody') }}</label>
<label class="form-label ml-2">{{ t('database.routineBody') }}</label>
<QueryEditor
v-show="isSelected"
ref="queryEditor"

View File

@@ -1,6 +1,6 @@
<template>
<ConfirmModal
:confirm-text="t('word.confirm')"
:confirm-text="t('general.confirm')"
size="medium"
class="options-modal"
@confirm="confirmParametersChange"
@@ -9,7 +9,7 @@
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
<span class="cut-text">{{ t('word.parameters') }} "{{ routine }}"</span>
<span class="cut-text">{{ t('database.parameters') }} "{{ routine }}"</span>
</div>
</template>
<template #body>
@@ -20,16 +20,16 @@
<div class="d-flex">
<button class="btn btn-dark btn-sm d-flex" @click="addParameter">
<i class="mdi mdi-24px mdi-plus mr-1" />
<span>{{ t('word.add') }}</span>
<span>{{ t('general.add') }}</span>
</button>
<button
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
:title="t('message.clearChanges')"
:title="t('database.clearChanges')"
:disabled="!isChanged"
@click.prevent="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ t('word.clear') }}</span>
<span>{{ t('general.clear') }}</span>
</button>
</div>
</div>
@@ -55,7 +55,7 @@
<div class="tile-action">
<button
class="btn btn-link remove-field p-0 mr-2"
:title="t('word.delete')"
:title="t('general.delete')"
@click.prevent="removeParameter(param._antares_id)"
>
<i class="mdi mdi-close" />
@@ -74,7 +74,7 @@
>
<div class="form-group">
<label class="form-label col-3">
{{ t('word.name') }}
{{ t('general.name') }}
</label>
<div class="column">
<input
@@ -86,7 +86,7 @@
</div>
<div class="form-group">
<label class="form-label col-3">
{{ t('word.type') }}
{{ t('database.type') }}
</label>
<div class="column">
<BaseSelect
@@ -102,7 +102,7 @@
</div>
<div v-if="customizations.parametersLength" class="form-group">
<label class="form-label col-3">
{{ t('word.length') }}
{{ t('database.length') }}
</label>
<div class="column">
<input
@@ -115,7 +115,7 @@
</div>
<div v-if="customizations.procedureContext" class="form-group">
<label class="form-label col-3">
{{ t('word.context') }}
{{ t('database.context') }}
</label>
<div class="column">
<label
@@ -138,11 +138,11 @@
<i class="mdi mdi-dots-horizontal mdi-48px" />
</div>
<p class="empty-title h5">
{{ t('message.thereAreNoParameters') }}
{{ t('database.thereAreNoParameters') }}
</p>
<div class="empty-action">
<button class="btn btn-primary" @click="addParameter">
{{ t('message.createNewParameter') }}
{{ t('database.createNewParameter') }}
</button>
</div>
</div>

View File

@@ -10,26 +10,26 @@
@click="saveChanges"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ t('word.save') }}</span>
<span>{{ t('general.save') }}</span>
</button>
<button
:disabled="!isChanged"
class="btn btn-link btn-sm mr-0"
:title="t('message.clearChanges')"
:title="t('database.clearChanges')"
@click="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ t('word.clear') }}</span>
<span>{{ t('general.clear') }}</span>
</button>
<div class="divider-vert py-3" />
<button class="btn btn-dark btn-sm" @click="showTimingModal">
<i class="mdi mdi-24px mdi-timer mr-1" />
<span>{{ t('word.timing') }}</span>
<span>{{ t('database.timing') }}</span>
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="t('word.schema')">
<div class="d-flex" :title="t('database.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div>
</div>
@@ -39,7 +39,7 @@
<div class="columns">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ t('word.name') }}</label>
<label class="form-label">{{ t('general.name') }}</label>
<input
v-model="localScheduler.name"
class="form-input"
@@ -49,11 +49,11 @@
</div>
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ t('word.definer') }}</label>
<label class="form-label">{{ t('database.definer') }}</label>
<BaseSelect
v-model="localScheduler.definer"
:options="users"
:option-label="(user: any) => user.value === '' ? t('message.currentUser') : `${user.name}@${user.host}`"
: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"
/>
@@ -61,7 +61,7 @@
</div>
<div class="column">
<div class="form-group">
<label class="form-label">{{ t('word.comment') }}</label>
<label class="form-label">{{ t('database.comment') }}</label>
<input
v-model="localScheduler.comment"
class="form-input"
@@ -71,7 +71,7 @@
</div>
<div class="column">
<div class="form-group">
<label class="form-label mr-2">{{ t('word.state') }}</label>
<label class="form-label mr-2">{{ t('database.state') }}</label>
<label class="form-radio form-inline">
<input
v-model="localScheduler.state"
@@ -102,7 +102,7 @@
</div>
<div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ t('message.schedulerBody') }}</label>
<label class="form-label ml-2">{{ t('database.schedulerBody') }}</label>
<QueryEditor
v-show="isSelected"
ref="queryEditor"

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