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

Compare commits

..

350 Commits

Author SHA1 Message Date
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
cf5247bf35 chore(release): 0.7.0 2022-11-30 13:22:26 +01:00
19db29663b refactor(UI): minor change 2022-11-30 13:09:07 +01:00
d010d5aa8f fix(UI): wrong copnnection icons color with light theme 2022-11-30 09:15:15 +01:00
0a1f50a9b9 fix: missing sidebar data after update 2022-11-29 18:18:56 +01:00
e7da5a7040 build(deps): update better-sqlite3 to 8.0.0 2022-11-29 15:19:23 +01:00
2d126d521c refactor: minor improvements 2022-11-29 14:25:30 +01:00
0fca70ebec fix(UI): folder to folder drag glitches 2022-11-29 13:19:41 +01:00
36358584fd Merge pull request #481 from antares-sql/new-sidebar
New connections sidebar
2022-11-28 15:19:24 +01:00
672896414e feat(UI): new settimgbar tooltips 2022-11-28 15:11:29 +01:00
b06bafe06c fix: deletion of connections inside folder 2022-11-28 13:06:01 +01:00
4fe9dfc4d7 feat(UI): footer color based on folder color 2022-11-28 11:56:02 +01:00
7af178a1e4 fix: wrong position moving elements outside folder 2022-11-28 09:42:18 +01:00
212b2bdba9 feat(UI): connections customization 2022-11-27 17:52:32 +01:00
72accb7b0e feat(UI): folders customization 2022-11-26 11:21:47 +01:00
321b387083 refactor(UI): various improvements on sidebar 2022-11-24 16:40:04 +01:00
ece6c6401d feat(UI): folders implementation 2022-11-23 17:52:08 +01:00
5a028a4ea2 refactor: changes to implement folders 2022-11-21 20:19:02 +01:00
83f9b12be0 chore(release): 0.6.0 2022-11-18 14:32:43 +01:00
1c1403f586 fix: incomplete list of collations, fixes #478 2022-11-18 11:30:10 +01:00
038cf68253 Merge pull request #472 from antares-sql/firebirdsql-support
Firebird SQL support
2022-11-17 16:19:59 +01:00
3580faebba chore: update README.md 2022-11-17 16:17:36 +01:00
ae312efbbc feat(Firebird SQL): procedure add/edit/delete support 2022-11-17 15:27:39 +01:00
8e422e3f07 feat(Firebird SQL): trigger add/edit/delete support 2022-11-16 15:16:12 +01:00
7d1967a609 feat(Firebird SQL): view add/edit/delete support 2022-11-16 12:00:12 +01:00
7ff8e2149e fix(Firebird SQL): connection pool issue 2022-11-16 10:12:44 +01:00
1b5cc315dd feat(Firebird SQL): table add/edit/delete support 2022-11-15 16:46:12 +01:00
27566c1dfa feat(Firebird SQL): manual commit mode 2022-11-10 15:52:31 +01:00
03777a2ea3 refactor(Firebird SQL): improve fields metadata detection 2022-11-09 17:41:31 +01:00
d91251d7cb build(deps): update better-sqlite to 7.6.2 2022-11-09 10:48:01 +01:00
0827a04d61 feat(Firebird SQL): support to blob fields 2022-11-08 15:53:21 +01:00
2c8509ff41 feat(Firebird SQL): support to indexes and foreign keys 2022-11-08 14:05:54 +01:00
76df6319c2 feat(Firebird SQL): connections pool 2022-11-07 09:49:36 +01:00
e6f6a022d1 feat: support to text blob fields 2022-11-05 10:22:12 +01:00
95bb41e9db feat(Firebird SQL): display table content and query results 2022-11-04 16:31:10 +01:00
7ab84bde57 initial firebird commit 2022-11-02 14:18:50 +01:00
d190a2dd61 fix: loss of precision updating BIGINT values, fixes #467 2022-10-26 12:26:09 +02:00
d8a298fd20 chore(release): 0.5.19 2022-10-22 14:54:15 +02:00
369622d5af Merge pull request #464 from antares-sql/context-cell-filler
Context menu option to fill table cells
2022-10-20 11:49:43 +02:00
a40d722d7c refactor: remove unnecessary console.log 2022-10-20 11:37:23 +02:00
440f74dfc1 fix: app stuck inserting a random value if field length high 2022-10-20 11:25:23 +02:00
8621ca5333 fix: unable to edit text fields if value is NULL, fixes #466 2022-10-20 10:55:30 +02:00
24edc82b1b feat: uuid fill for string cells 2022-10-19 10:40:56 +02:00
0a2124f2c2 feat: context menu option to fill cell with random values 2022-10-19 01:12:07 +02:00
a8521317a5 Merge branch 'master' of https://github.com/antares-sql/antares into context-cell-filler 2022-10-18 08:50:39 +02:00
88408da745 fix: error joining tables with different schema 2022-10-17 13:55:48 +02:00
d52b7af297 fix(SQLite): save boolean as integer to improve compativility, fixes #463 2022-10-17 12:06:22 +02:00
e4a4696dd3 chore(release): 0.5.18 2022-10-14 15:03:49 +02:00
f0255c0065 Merge pull request #455 from antares-sql/dependabot/npm_and_yarn/sql-formatter-11.0.2
build(deps): bump sql-formatter from 8.2.0 to 11.0.2
2022-10-11 10:36:00 +02:00
9991173685 Merge branch 'master' of https://github.com/antares-sql/antares into dependabot/npm_and_yarn/sql-formatter-11.0.2 2022-10-11 10:29:55 +02:00
dependabot[bot]
874dc6298b build(deps): bump sql-formatter from 8.2.0 to 11.0.2
Bumps [sql-formatter](https://github.com/sql-formatter-org/sql-formatter) from 8.2.0 to 11.0.2.
- [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/v8.2.0...v11.0.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-11 08:12:16 +00:00
5b1b8cf4cc build: update dependencies 2022-10-11 10:11:11 +02:00
a521274d01 feat(PostgreSQL): UUID random generation option on UUID fields, closes #424 2022-10-10 18:08:28 +02:00
86e4c1d58f Merge branch 'master' of https://github.com/antares-sql/antares 2022-10-04 10:12:19 +02:00
ba5a1b68ab fix: trackpad horizontal scroll on tabs not working properly 2022-10-04 10:12:16 +02:00
2c45bce1ee Merge pull request #448 from SmileYzn/master
Update pt-BR.ts
2022-10-01 22:22:34 +02:00
Cleverson
2e05fa4bdc Update pt-BR.ts 2022-09-30 14:43:06 -03:00
dd9c089d27 fix: auto-scroll on sidebar not working, fixes #447 2022-09-29 09:27:29 +02:00
e6955550fa refactor: minor changes in table content context 2022-09-29 09:17:47 +02:00
fe39c1d388 chore(release): 0.5.17 2022-09-22 09:34:28 +02:00
d114f8a651 feat: added more editor font sizes, closes #440 2022-09-21 10:33:44 +02:00
84168d1d75 fix: editor font size doesn't change on new tabs, fixes #442 2022-09-15 19:03:18 +02:00
498a9b48e2 fix: empty definer when editing a view, fixes #437 2022-09-15 18:58:19 +02:00
01f607cd40 fix: "run or reload" shortcut triggers on all connections open 2022-09-08 12:01:57 +02:00
efe134a059 fix: cant run procedures with parameters from leftbar 2022-09-07 18:18:15 +02:00
40cb4dd98d chore: minor changes on macos artifacts action 2022-08-27 09:27:07 +02:00
a142d3c4d7 fix(MacOS): empty options on macos menubar 2022-08-27 09:04:11 +02:00
89da957a49 ci: github action for macos artifacts 2022-08-27 09:02:09 +02:00
05c2f9836c chore(release): 0.5.16 2022-08-26 18:49:51 +02:00
ebc325ae0c fix: issue updating datetime cells with null value, closes #423 2022-08-26 18:48:26 +02:00
39326eb52e fix: unable to set null or delete rows without primary key 2022-08-26 18:31:47 +02:00
df681147aa fix: ts exceptions 2022-08-24 10:23:03 +02:00
ffc645ba5e fix: CTRL+Right/Left not working on text editor, closes #427 2022-08-24 10:04:25 +02:00
1fb1205319 chore: update README.md 2022-08-20 09:18:46 +02:00
c90ab0e880 fix(UI): wrong position of fields resizable area 2022-08-19 16:54:56 +02:00
9dc700e13e fix(UI): editor themes group not visible in select element 2022-08-18 16:10:29 +02:00
6950d0ce5a chore(release): 0.5.15 2022-08-17 16:21:37 +02:00
4df14c3693 feat: dynamic shortcut suggestions on empty query tabs 2022-08-17 16:20:36 +02:00
c05be8304f perf(translation): updated italian translation 2022-08-17 15:29:12 +02:00
31c575dad9 Merge pull request #405 from antares-sql/custom-shortcuts
Shortcuts customization
2022-08-17 11:12:49 +02:00
040657d5ca Merge branch 'master' of https://github.com/antares-sql/antares into custom-shortcuts 2022-08-17 11:05:09 +02:00
5043fafa93 feat: added more events in shortcuts setting 2022-08-17 10:00:23 +02:00
8eb127e458 feat: ability to edit shortcuts 2022-08-16 18:03:38 +02:00
d044a02cb7 feat: ability to add new shortcuts 2022-08-16 13:10:20 +02:00
8cb2c197c8 chore: suppress some stylelint warns 2022-08-15 18:14:51 +02:00
c50d17e82b fix: startup exception 2022-08-15 18:13:53 +02:00
Cleverson
7c186d2dee Update pt-BR.ts 2022-08-12 14:59:22 +02:00
0f219cf9b7 perf: improved keypress detector 2022-08-12 12:40:35 +02:00
75c5a34095 Merge branch 'master' of https://github.com/antares-sql/antares into custom-shortcuts 2022-08-11 11:22:25 +02:00
48877534d1 feat(UI): connection name on left bar, closes #382 #414 2022-08-11 11:14:43 +02:00
c22413fde9 feat: delete shortcuts and restore defaults 2022-08-10 17:59:59 +02:00
77ab8d8a03 build: minor change in ts config 2022-08-09 17:28:33 +02:00
4386c6ab95 chore(release): 0.5.14 2022-08-09 16:21:55 +02:00
19205e0736 style: general lint fix 2022-08-09 16:18:21 +02:00
4fc4ddd1d6 Merge branch 'master' of https://github.com/antares-sql/antares into custom-shortcuts 2022-08-09 16:10:26 +02:00
49b63bc6f2 feat(UI): shortcuts setting UI improved 2022-08-09 16:10:08 +02:00
44eb507a12 fix: unable to open settingbar context menu 2022-08-09 16:04:27 +02:00
1590ffaff0 build: icons for linux builds 2022-08-09 10:48:53 +02:00
3c1bae540f chore(release): 0.5.13 2022-08-09 09:13:11 +02:00
44bb75bc60 feat: list of available shortcuts in settings window 2022-08-08 16:44:40 +02:00
8bb5bb93cf Merge branch 'master' of https://github.com/antares-sql/antares into custom-shortcuts 2022-08-08 11:46:08 +02:00
f64a12a8e9 fix(MySQL): error with ANSI sql_mode 2022-08-07 19:00:12 +02:00
da25823868 Merge branch 'master' of https://github.com/antares-sql/antares into custom-shortcuts 2022-08-05 17:08:48 +02:00
a9fcfd57ec refactor: improved vue-i18n implementation 2022-08-05 17:03:16 +02:00
e2307341f3 Merge pull request #397 from antares-sql/dependabot/npm_and_yarn/vue-i18n-9.2.0
build(deps): bump vue-i18n from 9.1.10 to 9.2.0
2022-08-05 13:17:06 +02:00
09a372e96d refactor: vue-i18n ts improvements 2022-08-05 13:06:08 +02:00
f4da28cca0 refactor: vue-i18n ts improvements 2022-08-05 12:57:56 +02:00
89745b7391 Merge pull request #398 from antares-sql/dependabot/npm_and_yarn/mdi/font-7.0.96
build(deps): bump @mdi/font from 6.9.96 to 7.0.96
2022-08-05 12:26:44 +02:00
104b7c928b fix: set legacy: false 2022-08-05 12:25:14 +02:00
dependabot[bot]
427360d826 build(deps): bump sql-formatter from 4.0.2 to 8.2.0
Bumps [sql-formatter](https://github.com/sql-formatter-org/sql-formatter) from 4.0.2 to 8.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/v4.0.2...v8.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-05 12:18:53 +02:00
e29d86b409 refactor: shortcuts registration via ShortcutRegister class 2022-08-05 12:07:19 +02:00
Giulio Ganci
0bfa14e1c9 feat: new macos icon 2022-08-03 12:57:17 +02:00
88ba55ec02 build: added deb arm targets 2022-08-03 11:33:26 +02:00
aaff4cf4fe refactor: filter functions as composable 2022-08-03 11:10:16 +02:00
35c54aee84 chore: update README.md 2022-08-02 13:17:13 +02:00
be2e9b21f5 chore: update dependabot.yml 2022-08-02 10:55:19 +02:00
2262278393 Merge branch 'master' of https://github.com/antares-sql/antares 2022-08-02 10:51:43 +02:00
531e17889a chore: update dependabot.yml 2022-08-02 10:51:40 +02:00
a07ed58004 chore: update CONTRIBUTING.md 2022-08-02 10:41:05 +02:00
00dc59a76d ci: remove old ci configs 2022-08-02 10:37:27 +02:00
2f883bfeb2 ci: new ci config 2022-08-02 10:10:20 +02:00
7ff16fccce ci: minor change 2022-08-01 22:32:19 +02:00
dependabot[bot]
3625fbc1b0 build(deps): bump @mdi/font from 6.9.96 to 7.0.96
Bumps [@mdi/font](https://github.com/Templarian/MaterialDesign-Webfont) from 6.9.96 to 7.0.96.
- [Release notes](https://github.com/Templarian/MaterialDesign-Webfont/releases)
- [Commits](https://github.com/Templarian/MaterialDesign-Webfont/compare/v6.9.96...v7.0.96)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-01 19:10:35 +00:00
dependabot[bot]
deee0d637b build(deps): bump vue-i18n from 9.1.10 to 9.2.0
Bumps [vue-i18n](https://github.com/intlify/vue-i18n-next/tree/HEAD/packages/vue-i18n) from 9.1.10 to 9.2.0.
- [Release notes](https://github.com/intlify/vue-i18n-next/releases)
- [Changelog](https://github.com/intlify/vue-i18n-next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/intlify/vue-i18n-next/commits/v9.2.0/packages/vue-i18n)

---
updated-dependencies:
- dependency-name: vue-i18n
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-01 19:09:53 +00:00
8c6950cebd refactor: minor refactors 2022-08-01 18:13:14 +02:00
46167e4473 build: general ci config update 2022-08-01 18:06:57 +02:00
c32a4415d1 Update README.md 2022-07-31 10:43:32 +02:00
1c3d7aa30b feat: copy row as CSV, closes #394 2022-07-29 18:53:39 +02:00
664d18efc1 chore: update README.md 2022-07-26 17:21:43 +02:00
cc941dfc04 chore(release): 0.5.12 2022-07-26 14:59:06 +02:00
1d151e9349 fix: error on schema export 2022-07-26 13:33:57 +02:00
addd9fba28 build(deps): bump ace-builds from 1.4.14 to 1.8.1 2022-07-25 15:25:58 +02:00
a00c19d300 fix: prevent ctrl+a in console 2022-07-25 15:20:15 +02:00
9551afbd2d feat: ability to copy multiple selected rows 2022-07-25 14:56:00 +02:00
1ead76c028 fix: missing defaults on insert row window 2022-07-25 13:09:41 +02:00
d3da15aa13 feat: copy row as SQL INSERT 2022-07-25 12:42:22 +02:00
f3b5de38c4 feat: export table content as SQL INSERT 2022-07-25 12:19:58 +02:00
Askar Kanturin
d4b6d2e9d1 changed readme 2022-07-23 10:20:36 +02:00
Askar Kanturin
e2c106e4e0 fixed typo in readme
i'm no native speaker, but i feel this is a typo
2022-07-23 10:20:36 +02:00
eb60899e6e fix(MySQL): missing quoted identifier for column names in table filter, closes #380 2022-07-22 10:08:33 +02:00
1d367d468d Merge branch 'master' of https://github.com/antares-sql/antares 2022-07-21 11:01:13 +02:00
8ecaedbf6c fix: disable ctrl+alt+(left/right) shortcut on linux 2022-07-21 11:01:10 +02:00
dependabot[bot]
dd1eebd4ec build(deps): bump terser from 5.13.1 to 5.14.2
Bumps [terser](https://github.com/terser/terser) from 5.13.1 to 5.14.2.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/commits)

---
updated-dependencies:
- dependency-name: terser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-21 08:43:24 +02:00
8c83b3f144 fix: missing table on insert new records on session restored tabs 2022-07-20 10:41:44 +02:00
985e5d3527 feat: context menu option to duplicate a table row 2022-07-19 17:48:51 +02:00
78902639eb feat: execute selected query 2022-07-19 15:02:17 +02:00
cb038b374a fix: issue with logger on import/export 2022-07-19 10:10:24 +02:00
eafdb1cc3d chore(release): 0.5.11 2022-07-19 08:14:47 +02:00
91e0630513 fix: unable to edit table fields content on tables with datetime fields 2022-07-19 08:08:51 +02:00
bf768c3800 fix: filter persists switching temporary table tabs 2022-07-18 18:29:12 +02:00
0b1aa3dd29 fix: console events disabled in production 2022-07-18 17:21:34 +02:00
ec75f9546a chore(release): 0.5.10 2022-07-18 14:44:30 +02:00
9bc9adb7cf fix: exception on QueryEditor with null modelValue 2022-07-18 14:43:38 +02:00
b4d14d98db refactor: improved console with light theme 2022-07-18 11:58:22 +02:00
c21bd6075c feat: context menu to copy queries from console 2022-07-18 11:58:22 +02:00
44647f5b55 feat: open/close console on single connection 2022-07-18 11:58:22 +02:00
3f9e6d85ca perf: improved resize of text editor resizing console height 2022-07-18 11:58:22 +02:00
6a6f43a718 feat: initial console implementation 2022-07-18 11:58:22 +02:00
f12a04b052 feat: ipc event channel to send logs to renderer 2022-07-18 11:58:22 +02:00
abf829867e feat: Ctrl+PgUp & Ctrl+PgDn to navigate between tabs 2022-07-14 19:30:34 +02:00
b71f04e5aa feat: field names suggestion for tables in the query 2022-07-14 16:09:04 +02:00
7725fafe85 fix: unable to delete by select all in left bar search, closes #368 2022-07-13 18:50:16 +02:00
ed3d35f131 fix(Linux): ctrl+space shortcut not working 2022-07-13 18:25:42 +02:00
e0946f04f7 fix: unable to update data on tables missing primary or unique key 2022-07-13 09:30:30 +02:00
0891e7be8c refactor: disabled autofocus for scheduler timing modal 2022-07-11 11:35:30 +02:00
f312cf5f85 perf(UI): improved visibility of explore bar tooltips 2022-07-11 09:56:33 +02:00
05e0d310ec Merge pull request #363 from goYou/master
feat: Update zh-CN.ts
2022-07-10 09:13:35 +02:00
goYou
1e0b2b4cae Update zh-CN.ts 2022-07-10 14:49:30 +08:00
5ee728cfe4 Merge pull request #361 from antares-sql/dependabot/npm_and_yarn/moment-2.29.4
build(deps): bump moment from 2.29.3 to 2.29.4
2022-07-09 12:41:20 +02:00
a91fa8ff54 fix: fields content language detection not working properly 2022-07-09 12:39:44 +02:00
dependabot[bot]
2c13433900 build(deps): bump moment from 2.29.3 to 2.29.4
Bumps [moment](https://github.com/moment/moment) from 2.29.3 to 2.29.4.
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.29.3...2.29.4)

---
updated-dependencies:
- dependency-name: moment
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-09 10:38:14 +00:00
dcc2a4c51c refactor: replaced @vscode/vscode-languagedetection with custom language detection 2022-07-09 12:37:30 +02:00
7537dff401 chore(release): 0.5.9 2022-07-06 14:42:32 +02:00
853ee1f572 revert: revert to better-sqlite 7.5.1 due build issues 2022-07-06 10:43:32 +02:00
cf9c7c600a fix: error on export schema 2022-07-06 10:26:24 +02:00
73b88cad9e Merge branch 'master' of https://github.com/antares-sql/antares 2022-07-06 09:37:21 +02:00
d2eb31a63d perf(UI): improved focus visibility for buttons 2022-07-06 09:37:18 +02:00
d6b36b1f80 Merge pull request #353 from antares-sql/dependabot/npm_and_yarn/better-sqlite3-7.5.3
build(deps): bump better-sqlite3 from 7.5.1 to 7.5.3
2022-07-05 18:20:56 +02:00
dependabot[bot]
d66b932683 build(deps): bump better-sqlite3 from 7.5.1 to 7.5.3
Bumps [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) from 7.5.1 to 7.5.3.
- [Release notes](https://github.com/WiseLibs/better-sqlite3/releases)
- [Commits](https://github.com/WiseLibs/better-sqlite3/compare/v7.5.1...v7.5.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-05 06:59:47 +00:00
20f5497034 Merge pull request #341 from antares-sql/dependabot/npm_and_yarn/vue-3.2.37
build(deps): bump vue from 3.2.33 to 3.2.37
2022-07-05 08:54:40 +02:00
d4ed886489 Merge pull request #339 from antares-sql/dependabot/npm_and_yarn/mdi/font-6.9.96
build(deps): bump @mdi/font from 6.1.95 to 6.9.96
2022-07-05 08:54:30 +02:00
dependabot[bot]
aaa14f112a build(deps): bump vue from 3.2.33 to 3.2.37
Bumps [vue](https://github.com/vuejs/core) from 3.2.33 to 3.2.37.
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/compare/v3.2.33...v3.2.37)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-04 15:41:08 +00:00
dependabot[bot]
e0f3ff6939 build(deps): bump @mdi/font from 6.1.95 to 6.9.96
Bumps [@mdi/font](https://github.com/Templarian/MaterialDesign-Webfont) from 6.1.95 to 6.9.96.
- [Release notes](https://github.com/Templarian/MaterialDesign-Webfont/releases)
- [Commits](https://github.com/Templarian/MaterialDesign-Webfont/compare/v6.1.95...v6.9.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>
2022-07-04 15:41:01 +00:00
72a328785c Merge pull request #333 from antares-sql/new-connection-management
New connections management
2022-07-04 17:39:56 +02:00
4c2a1998a9 Merge branch 'master' of https://github.com/antares-sql/antares into new-connection-management 2022-07-04 17:25:37 +02:00
8e705706ae feat: ability to pin/unpin and delete connections from the "all connections" modal 2022-07-04 17:03:24 +02:00
56b0a4815c feat: option to disable scratchpad 2022-07-04 15:10:40 +02:00
a9a4344a71 feat: ctrl/cmd+space to open all connections modal 2022-07-04 12:42:33 +02:00
ec5ab73b19 feat: search form in all connections modal 2022-07-04 12:27:04 +02:00
71a5b5c828 fix: missing option for untrusted ssl connection on connections edit panel 2022-07-03 22:07:28 +02:00
a703dcc53e feat: modal with all connections 2022-07-02 15:30:36 +02:00
36e98e0742 feat: connections sorted by last usage by default and option to pin them 2022-06-30 18:20:14 +02:00
f632a0f189 initial implementation of new feature 2022-06-29 19:05:45 +02:00
185 changed files with 16254 additions and 23389 deletions

View File

@@ -174,8 +174,65 @@
"contributions": [ "contributions": [
"translation" "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"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,
"skipCi": true "skipCi": true,
"commitType": "docs"
} }

View File

@@ -6,6 +6,8 @@
version: 2 version: 2
updates: updates:
- package-ecosystem: "npm" - package-ecosystem: "npm"
allow:
- dependency-type: "production"
directory: "/" directory: "/"
schedule: schedule:
interval: "monthly" interval: "monthly"

View File

@@ -1,29 +0,0 @@
name: Build/release [LINUX]
on: push
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v2
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 14
- name: Install dependencies
run: npm i
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
with:
github_token: ${{ secrets.github_token }}
release: ${{ startsWith(github.ref, 'refs/tags/v') }}

View File

@@ -1,29 +0,0 @@
name: Build/release [MAC]
on: push
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v2
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 14
- name: Install dependencies
run: npm i
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
with:
github_token: ${{ secrets.github_token }}
release: ${{ startsWith(github.ref, 'refs/tags/v') }}

View File

@@ -1,29 +0,0 @@
name: Build/release [WINDOWS]
on: push
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-2019]
steps:
- name: Check out Git repository
uses: actions/checkout@v2
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 14
- name: Install dependencies
run: npm i
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
with:
github_token: ${{ secrets.github_token }}
release: ${{ startsWith(github.ref, 'refs/tags/v') }}

37
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Build & release
on:
push:
tags:
- "v*"
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-11, ubuntu-20.04, windows-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
- 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

@@ -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,15 +5,21 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: npm install & build - name: Install Node.js
run: | uses: actions/setup-node@v3
npm install with:
npm run build:local node-version: 16
- name: Install dependencies
run: npm i
- name: "Build"
run: npm run build
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3

View File

@@ -0,0 +1,31 @@
name: Create artifact [MAC]
on:
workflow_dispatch: {}
jobs:
build:
runs-on: macos-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: npm install & build
run: |
npm install
npm run build
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: macos-build
retention-days: 3
path: |
build
!build/*-unpacked
!build/.icon-ico

View File

@@ -1,4 +1,4 @@
name: Test end-to-end [LINUX] name: Test end-to-end [WINDOWS]
on: push on: push
@@ -8,16 +8,16 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest] os: [windows-latest]
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Install Node.js, NPM and Yarn - name: Install Node.js
uses: actions/setup-node@v1 uses: actions/setup-node@v3
with: with:
node-version: 14 node-version: 16
- name: Install dependencies - name: Install dependencies
run: npm i run: npm i

1
.nvmrc
View File

@@ -1 +0,0 @@
v16.13.0

View File

@@ -1,6 +1,7 @@
{ {
"extends": [ "extends": [
"stylelint-config-standard" "stylelint-config-standard",
"stylelint-config-recommended-vue"
], ],
"fix": true, "fix": true,
"formatter": "verbose", "formatter": "verbose",
@@ -10,6 +11,7 @@
"rules": { "rules": {
"at-rule-no-unknown": null, "at-rule-no-unknown": null,
"no-descending-specificity": null, "no-descending-specificity": null,
"font-family-no-missing-generic-family-keyword": null,
"declaration-colon-newline-after": "always-multi-line" "declaration-colon-newline-after": "always-multi-line"
}, },
"syntax": "scss" "syntax": "scss"

2
.vscode/launch.json vendored
View File

@@ -5,7 +5,6 @@
"name": "Electron: Main", "name": "Electron: Main",
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"port": 9222, "port": 9222,
"protocol": "inspector",
"request": "attach", "request": "attach",
"sourceMaps": true, "sourceMaps": true,
"type": "node", "type": "node",
@@ -23,7 +22,6 @@
"name": "Electron: Worker", "name": "Electron: Worker",
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"port": 9224, "port": 9224,
"protocol": "inspector",
"request": "attach", "request": "attach",
"sourceMaps": true, "sourceMaps": true,
"type": "node", "type": "node",

View File

@@ -5,9 +5,12 @@
"MySQL", "MySQL",
"PostgreSQL", "PostgreSQL",
"SQLite", "SQLite",
"Firebird SQL",
"Windows", "Windows",
"translation", "translation",
"Linux" "Linux",
"MacOS",
"deps"
], ],
"svg.preview.background": "transparent" "svg.preview.background": "transparent"
} }

View File

@@ -2,6 +2,406 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.7.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)
### Features
* **UI:** connections customization ([212b2bd](https://github.com/antares-sql/antares/commit/212b2bdba933db1af22967a057d64f41c96b9930))
* **UI:** folders customization ([72accb7](https://github.com/antares-sql/antares/commit/72accb7b0e62977dde2c82312aee5b9d025e5182))
* **UI:** folders implementation ([ece6c64](https://github.com/antares-sql/antares/commit/ece6c6401def4a9b42280f23efaa038b9ad98de8))
* **UI:** footer color based on folder color ([4fe9dfc](https://github.com/antares-sql/antares/commit/4fe9dfc4d7ca19a798b8a8a74d979ab88aebeaac))
* **UI:** new settimgbar tooltips ([6728964](https://github.com/antares-sql/antares/commit/672896414e901635c83ca037663e355a31ce013b))
### Bug Fixes
* deletion of connections inside folder ([b06bafe](https://github.com/antares-sql/antares/commit/b06bafe06c060233ebe901979b9fc4455a25cb89))
* missing sidebar data after update ([0a1f50a](https://github.com/antares-sql/antares/commit/0a1f50a9b92c9705784b93f14be40a01a78cb0da))
* **UI:** folder to folder drag glitches ([0fca70e](https://github.com/antares-sql/antares/commit/0fca70ebec1af3d594db4e1ce159e52e12224850))
* **UI:** wrong copnnection icons color with light theme ([d010d5a](https://github.com/antares-sql/antares/commit/d010d5aa8f07a5def1183567ee767fd144696ed3))
* wrong position moving elements outside folder ([7af178a](https://github.com/antares-sql/antares/commit/7af178a1e400876e6c45dbe31a198a12d29227a8))
## [0.6.0](https://github.com/antares-sql/antares/compare/v0.5.19...v0.6.0) (2022-11-18)
### Features
* **Firebird SQL:** connections pool ([76df631](https://github.com/antares-sql/antares/commit/76df6319c242ea42f93d4e5d811d96ec2c282aa8))
* **Firebird SQL:** display table content and query results ([95bb41e](https://github.com/antares-sql/antares/commit/95bb41e9db255a780aae1ae32ce4a53ee3bab20e))
* **Firebird SQL:** manual commit mode ([27566c1](https://github.com/antares-sql/antares/commit/27566c1dfae55f72d3f870c50410e5ecda256037))
* **Firebird SQL:** procedure add/edit/delete support ([ae312ef](https://github.com/antares-sql/antares/commit/ae312efbbc3a9941380477b9849bdd8edc5b9fbf))
* **Firebird SQL:** support to blob fields ([0827a04](https://github.com/antares-sql/antares/commit/0827a04d61af75b4366394e5f0289df461d02c98))
* **Firebird SQL:** support to indexes and foreign keys ([2c8509f](https://github.com/antares-sql/antares/commit/2c8509ff4173fbebeff92ab472a37edd3d80a2ac))
* **Firebird SQL:** table add/edit/delete support ([1b5cc31](https://github.com/antares-sql/antares/commit/1b5cc315dddca6b753fb6fe6e196e29441ffed79))
* **Firebird SQL:** trigger add/edit/delete support ([8e422e3](https://github.com/antares-sql/antares/commit/8e422e3f07323f388523621a05f0403a87f19e47))
* **Firebird SQL:** view add/edit/delete support ([7d1967a](https://github.com/antares-sql/antares/commit/7d1967a60977b2ce1095a37b7135f429a83f163d))
* support to text blob fields ([e6f6a02](https://github.com/antares-sql/antares/commit/e6f6a022d1a5bbc3f5303f635a2115813601c61a))
### Bug Fixes
* **Firebird SQL:** connection pool issue ([7ff8e21](https://github.com/antares-sql/antares/commit/7ff8e2149ef911a235b4a1dcc329775af1d2a72b))
* incomplete list of collations, fixes [#478](https://github.com/antares-sql/antares/issues/478) ([1c1403f](https://github.com/antares-sql/antares/commit/1c1403f58641f7b5f8a7c29fc430673ffa88f969))
* loss of precision updating BIGINT values, fixes [#467](https://github.com/antares-sql/antares/issues/467) ([d190a2d](https://github.com/antares-sql/antares/commit/d190a2dd61040d1748dfb97403f9d56015d938fe))
### [0.5.19](https://github.com/antares-sql/antares/compare/v0.5.18...v0.5.19) (2022-10-22)
### Features
* context menu option to fill cell with random values ([0a2124f](https://github.com/antares-sql/antares/commit/0a2124f2c2bdadc7c753db11d1e29f8acb9ddcac))
* uuid fill for string cells ([24edc82](https://github.com/antares-sql/antares/commit/24edc82b1be7299a09df18611b2a0ba361a6b46f))
### Bug Fixes
* app stuck inserting a random value if field length high ([440f74d](https://github.com/antares-sql/antares/commit/440f74dfc1f4942ba585b9bdae7517fe6ab04a81))
* error joining tables with different schema ([88408da](https://github.com/antares-sql/antares/commit/88408da745e45c70de977bc4270e5f61825be65f))
* **SQLite:** save boolean as integer to improve compativility, fixes [#463](https://github.com/antares-sql/antares/issues/463) ([d52b7af](https://github.com/antares-sql/antares/commit/d52b7af2978bc8beafd2d22078c72abb62e9e532))
* unable to edit text fields if value is NULL, fixes [#466](https://github.com/antares-sql/antares/issues/466) ([8621ca5](https://github.com/antares-sql/antares/commit/8621ca5333b5c51dc7a2ea1fcc0c5ec7f752a00a))
### [0.5.18](https://github.com/antares-sql/antares/compare/v0.5.17...v0.5.18) (2022-10-14)
### Features
* **PostgreSQL:** UUID random generation option on UUID fields, closes [#424](https://github.com/antares-sql/antares/issues/424) ([a521274](https://github.com/antares-sql/antares/commit/a521274d01031c1411bbbb136369802d43368089))
### Bug Fixes
* auto-scroll on sidebar not working, fixes [#447](https://github.com/antares-sql/antares/issues/447) ([dd9c089](https://github.com/antares-sql/antares/commit/dd9c089d27c61ab76d49887c7de2849cbb6e88a6))
* trackpad horizontal scroll on tabs not working properly ([ba5a1b6](https://github.com/antares-sql/antares/commit/ba5a1b68ab2d56777a5c94eede26e9bded5819e6))
### [0.5.17](https://github.com/antares-sql/antares/compare/v0.5.16...v0.5.17) (2022-09-22)
### Features
* added more editor font sizes, closes [#440](https://github.com/antares-sql/antares/issues/440) ([d114f8a](https://github.com/antares-sql/antares/commit/d114f8a65164f702b23175095e6fc2b021e0e038))
### Bug Fixes
* "run or reload" shortcut triggers on all connections open ([01f607c](https://github.com/antares-sql/antares/commit/01f607cd40c18ab0f9761b2a05705a966aaae43a))
* cant run procedures with parameters from leftbar ([efe134a](https://github.com/antares-sql/antares/commit/efe134a059700ca87333dc6e66166d6ec8d289e8))
* editor font size doesn't change on new tabs, fixes [#442](https://github.com/antares-sql/antares/issues/442) ([84168d1](https://github.com/antares-sql/antares/commit/84168d1d75460acc2c844bfece7d85f0c977e74c))
* empty definer when editing a view, fixes [#437](https://github.com/antares-sql/antares/issues/437) ([498a9b4](https://github.com/antares-sql/antares/commit/498a9b48e25ee061960f5f649c953cdaf6ff1a58))
* **MacOS:** empty options on macos menubar ([a142d3c](https://github.com/antares-sql/antares/commit/a142d3c4d77e31375dfbea148eec54ce1f635192))
### [0.5.16](https://github.com/antares-sql/antares/compare/v0.5.15...v0.5.16) (2022-08-26)
### Bug Fixes
* CTRL+Right/Left not working on text editor, closes [#427](https://github.com/antares-sql/antares/issues/427) ([ffc645b](https://github.com/antares-sql/antares/commit/ffc645ba5efb1c52670096e4f8c7f992b7335dae))
* issue updating datetime cells with null value, closes [#423](https://github.com/antares-sql/antares/issues/423) ([ebc325a](https://github.com/antares-sql/antares/commit/ebc325ae0c656dca2eb8f7544ab271beaee9b47e))
* ts exceptions ([df68114](https://github.com/antares-sql/antares/commit/df681147aaf0bfca69f3ffdc474cc1846541b1d8))
* **UI:** editor themes group not visible in select element ([9dc700e](https://github.com/antares-sql/antares/commit/9dc700e13ea65bb8c6feac4ff4ffeadd32053614))
* **UI:** wrong position of fields resizable area ([c90ab0e](https://github.com/antares-sql/antares/commit/c90ab0e8807ff30a9ab58e9aa3515cf427dd6e86))
* unable to set null or delete rows without primary key ([39326eb](https://github.com/antares-sql/antares/commit/39326eb52e038728b5419d4a8de8024c7ead3002))
### [0.5.15](https://github.com/antares-sql/antares/compare/v0.5.14...v0.5.15) (2022-08-17)
### Features
* ability to add new shortcuts ([d044a02](https://github.com/antares-sql/antares/commit/d044a02cb79a9d06aadc34cdbf6e81da84360559))
* ability to edit shortcuts ([8eb127e](https://github.com/antares-sql/antares/commit/8eb127e45838bc01ba12f0740fec077fcd975532))
* added more events in shortcuts setting ([5043faf](https://github.com/antares-sql/antares/commit/5043fafa934844ebc2f59cabcec830c6a4d5ca8e))
* delete shortcuts and restore defaults ([c22413f](https://github.com/antares-sql/antares/commit/c22413fde9dfe5501a5f220070cfe552a318c70b))
* dynamic shortcut suggestions on empty query tabs ([4df14c3](https://github.com/antares-sql/antares/commit/4df14c3693955bd7801b4b99103fca85f00f3e8c))
* list of available shortcuts in settings window ([44bb75b](https://github.com/antares-sql/antares/commit/44bb75bc60d7d31bbd99a9ba57f30fd354f7581c))
* **UI:** connection name on left bar, closes [#382](https://github.com/antares-sql/antares/issues/382) [#414](https://github.com/antares-sql/antares/issues/414) ([4887753](https://github.com/antares-sql/antares/commit/48877534d1a41d351b267c0dab925046ca984179))
* **UI:** shortcuts setting UI improved ([49b63bc](https://github.com/antares-sql/antares/commit/49b63bc6f28fc6031e6a892d0a48cd35ae2f26cd))
### Bug Fixes
* startup exception ([c50d17e](https://github.com/antares-sql/antares/commit/c50d17e82b7fd337d4037ddf646cd1a8fc765bae))
### Improvements
* improved keypress detector ([0f219cf](https://github.com/antares-sql/antares/commit/0f219cf9b796b4369c609fb0e8e3b84346a30b07))
* **translation:** updated italian translation ([c05be83](https://github.com/antares-sql/antares/commit/c05be8304f3cf299cf338f67c00184305e022919))
### [0.5.14](https://github.com/antares-sql/antares/compare/v0.5.13...v0.5.14) (2022-08-09)
### Bug Fixes
* unable to open settingbar context menu ([44eb507](https://github.com/antares-sql/antares/commit/44eb507a12bad028a4fa8a8bb0f6442a3e8dde91))
### [0.5.13](https://github.com/antares-sql/antares/compare/v0.5.12...v0.5.13) (2022-08-09)
### Features
* copy row as CSV, closes [#394](https://github.com/antares-sql/antares/issues/394) ([1c3d7aa](https://github.com/antares-sql/antares/commit/1c3d7aa30bb9c2bd900a764ee6b97960729e9263))
* new macos icon ([0bfa14e](https://github.com/antares-sql/antares/commit/0bfa14e1c90320578597df030941530b670a4131))
### Bug Fixes
* **MySQL:** error with ANSI sql_mode ([f64a12a](https://github.com/antares-sql/antares/commit/f64a12a8e9c5f764c3a692f1a032736e008058b5))
* set legacy: false ([104b7c9](https://github.com/antares-sql/antares/commit/104b7c928b9c2abfc056880f16c606a0b1fa7c67))
### [0.5.12](https://github.com/antares-sql/antares/compare/v0.5.11...v0.5.12) (2022-07-26)
### Features
* ability to copy multiple selected rows ([9551afb](https://github.com/antares-sql/antares/commit/9551afbd2d7e525c81f28e98e788b92609ce9de4))
* context menu option to duplicate a table row ([985e5d3](https://github.com/antares-sql/antares/commit/985e5d352793d1b3e1981d004b6f494bfbb049bf))
* copy row as SQL INSERT ([d3da15a](https://github.com/antares-sql/antares/commit/d3da15aa1377dcba73927047563f1d0c2d1284ca))
* execute selected query ([7890263](https://github.com/antares-sql/antares/commit/78902639ebb29a8c53f8aa0d2045c74e0646febc))
* export table content as SQL INSERT ([f3b5de3](https://github.com/antares-sql/antares/commit/f3b5de38c4abfd2c1d738e179fc22e6c8b6f9080))
### Bug Fixes
* disable ctrl+alt+(left/right) shortcut on linux ([8ecaedb](https://github.com/antares-sql/antares/commit/8ecaedbf6c2fc0dc56ff2177a87dd6ede74bdd22))
* error on schema export ([1d151e9](https://github.com/antares-sql/antares/commit/1d151e9349fd97576ccd8ef7f88ca789a1f28b65))
* issue with logger on import/export ([cb038b3](https://github.com/antares-sql/antares/commit/cb038b374a4fe85ad569e42eee7af123c925e775))
* missing defaults on insert row window ([1ead76c](https://github.com/antares-sql/antares/commit/1ead76c02889f48bd91cae702820b082ca2ff54b))
* missing table on insert new records on session restored tabs ([8c83b3f](https://github.com/antares-sql/antares/commit/8c83b3f1447354ec63b2a308db05ad4d54659aa7))
* **MySQL:** missing quoted identifier for column names in table filter, closes [#380](https://github.com/antares-sql/antares/issues/380) ([eb60899](https://github.com/antares-sql/antares/commit/eb60899e6e17879c79a7ee7108061e9aca8596f7))
* prevent ctrl+a in console ([a00c19d](https://github.com/antares-sql/antares/commit/a00c19d3003cd43d3ee6e3132728122bb2b24c97))
### [0.5.11](https://github.com/antares-sql/antares/compare/v0.5.10...v0.5.11) (2022-07-19)
### Bug Fixes
* console events disabled in production ([0b1aa3d](https://github.com/antares-sql/antares/commit/0b1aa3dd299db641df3d4c56c7ee56a187fc3ab3))
* filter persists switching temporary table tabs ([bf768c3](https://github.com/antares-sql/antares/commit/bf768c380087b65604b5b571a9858a7f07bd681d))
* unable to edit table fields content on tables with datetime fields ([91e0630](https://github.com/antares-sql/antares/commit/91e06305133c97ea02dcfdc4e739a4b0a7e7049d))
### [0.5.10](https://github.com/antares-sql/antares/compare/v0.5.9...v0.5.10) (2022-07-18)
### Features
* context menu to copy queries from console ([c21bd60](https://github.com/antares-sql/antares/commit/c21bd6075c1203607c05e45b76233d57e3008190))
* Ctrl+PgUp & Ctrl+PgDn to navigate between tabs ([abf8298](https://github.com/antares-sql/antares/commit/abf829867e567354e534cff3e02a6d43f4c7a262))
* field names suggestion for tables in the query ([b71f04e](https://github.com/antares-sql/antares/commit/b71f04e5aa3c37eaa160dfbc76d1b84789e3543e))
* initial console implementation ([6a6f43a](https://github.com/antares-sql/antares/commit/6a6f43a718561e0abd2cb89048b7fe45d08736ae))
* ipc event channel to send logs to renderer ([f12a04b](https://github.com/antares-sql/antares/commit/f12a04b0524f1172334c89afeb27675c19ff68d2))
* open/close console on single connection ([44647f5](https://github.com/antares-sql/antares/commit/44647f5b5508965bf5a7264add89e175c725e877))
### Bug Fixes
* exception on QueryEditor with null modelValue ([9bc9adb](https://github.com/antares-sql/antares/commit/9bc9adb7cff19b86a99491d968485a4cd7b47b99))
* fields content language detection not working properly ([a91fa8f](https://github.com/antares-sql/antares/commit/a91fa8ff54bbf1f8475666efd3a268a3a4f07f0c))
* **Linux:** ctrl+space shortcut not working ([ed3d35f](https://github.com/antares-sql/antares/commit/ed3d35f1319a1e2edcb8104f2045a71b9e9754a2))
* unable to delete by select all in left bar search, closes [#368](https://github.com/antares-sql/antares/issues/368) ([7725faf](https://github.com/antares-sql/antares/commit/7725fafe852479720fa619ced0970f2fa0099191))
* unable to update data on tables missing primary or unique key ([e0946f0](https://github.com/antares-sql/antares/commit/e0946f04f792d25c187ea56d4714bdacc016ada3))
### Improvements
* improved resize of text editor resizing console height ([3f9e6d8](https://github.com/antares-sql/antares/commit/3f9e6d85ca445eea1028effa32418eee4980f87d))
* **UI:** improved visibility of explore bar tooltips ([f312cf5](https://github.com/antares-sql/antares/commit/f312cf5f855deddd562c26d1835f78d16499b93b))
### [0.5.9](https://github.com/antares-sql/antares/compare/v0.5.8...v0.5.9) (2022-07-06)
### Features
* ability to pin/unpin and delete connections from the "all connections" modal ([8e70570](https://github.com/antares-sql/antares/commit/8e705706aecc5c9790329e63e61a1c02fa5d0342))
* connections sorted by last usage by default and option to pin them ([36e98e0](https://github.com/antares-sql/antares/commit/36e98e0742657e25df7768aa5b3b7cb350df5509))
* ctrl/cmd+space to open all connections modal ([a9a4344](https://github.com/antares-sql/antares/commit/a9a4344a71cc0f8f156b839733f6ddc200a26268))
* modal with all connections ([a703dcc](https://github.com/antares-sql/antares/commit/a703dcc53eb920117bc346a3c21f0c729c0ad96d))
* option to disable scratchpad ([56b0a48](https://github.com/antares-sql/antares/commit/56b0a4815c6f54eef164d849f6ca25af1e142b16))
* search form in all connections modal ([ec5ab73](https://github.com/antares-sql/antares/commit/ec5ab73b19d99e9971ae87e5f0a8d1bd1c34ef00))
### Bug Fixes
* error on export schema ([cf9c7c6](https://github.com/antares-sql/antares/commit/cf9c7c600aa915cef1ec3777866badb7ab1312ee))
* missing option for untrusted ssl connection on connections edit panel ([71a5b5c](https://github.com/antares-sql/antares/commit/71a5b5c8285fb777c43e7f6516006bfe9f52591c))
### Improvements
* **UI:** improved focus visibility for buttons ([d2eb31a](https://github.com/antares-sql/antares/commit/d2eb31a63d612323f8738eded1e1ce7b23554001))
### [0.5.8](https://github.com/antares-sql/antares/compare/v0.5.7...v0.5.8) (2022-07-02) ### [0.5.8](https://github.com/antares-sql/antares/compare/v0.5.7...v0.5.8) (2022-07-02)

View File

@@ -44,8 +44,7 @@ In this folder is located the structure of Vue frontend application.
## Build ## Build
The command to build Antares SQL locally is `npm run build:local`. The command to build Antares SQL locally is `npm run build`.
`build` command (without `:local`) is used exclusively by the GitHub Action.
## Conventions ## Conventions

View File

@@ -12,13 +12,17 @@
Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers. Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers.
Our target is to support as many databases as possible, and all major operating systems, including the ARM versions. Our target is to support as many databases as possible, and all major operating systems, including the ARM versions.
**At the moment this application is in development state, many features will come in future updates**, and supports only MySQL/MariaDB, PostgreSQL and SQLite. **At the moment this application is in development state, many features will come in future updates**, and supports only MySQL/MariaDB, PostgreSQL, SQLite and Firebird SQL.
At the moment, however, there are all the features necessary to have a pleasant database management experience, so give it a chance and send us your feedback, we would really appreciate it. However, there are all the features necessary to have a pleasant database management experience, so give it a chance and send us your feedback, we would really appreciate it.
We are actively working on it, hoping to provide new cool features, improvements and fixes as soon as possible. We are actively working on it, hoping to provide new cool features, improvements and fixes as soon as possible.
🔗 If you are curious to try Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases/latest). 🔗 If you are curious to try Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases/latest).
👁 To stay tuned for new releases [follow Antares SQL](https://twitter.com/AntaresSQL) on Twitter. 👁 To stay tuned for new releases [follow Antares SQL](https://twitter.com/AntaresSQL) on Twitter.
🌟 Don't forget to **leave a star** if you appreciate this project. 🌟 Don't forget to **leave a star** if you appreciate this project.
🗳️ Polls:
- **[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 ## Current key features
@@ -33,6 +37,7 @@ We are actively working on it, hoping to provide new cool features, improvements
- SSH tunnel support. - SSH tunnel support.
- Manual commit mode. - Manual commit mode.
- Import and export database dumps. - Import and export database dumps.
- Customizable keyboard shortcuts.
- Dark and light theme. - Dark and light theme.
- Editor themes. - Editor themes.
@@ -40,20 +45,20 @@ We are actively working on it, hoping to provide new cool features, improvements
Why are we developing an SQL client when there are a lot of them on the market? Why are we developing an SQL client when there are a lot of them on the market?
The main goal is to develop a **forever 100% free (without paid premium feature)**, full featured, as possible community driven, cross platform and open source alternative, empowered by JavaScript ecosystem. The main goal is to develop a **forever 100% free (without paid premium feature)**, full featured, as possible community driven, cross platform and open source alternative, empowered by JavaScript ecosystem.
A modern application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons, nested tabs or submenu; productivity comes first. A modern application created with minimalism and simplicity in mind, with features in the right places, not hundreds of tiny buttons, nested tabs or submenues; productivity comes first.
## Installation ## Installation
Based on your operating system you can have one or more distribution formats to choose based on your preferences. Based on your operating system you can have one or more distribution formats to choose based on your preferences.
Since Antares SQL is a free software we haven't a budget to spend in annual licenses or certificates. This can result that on some platforms you need some additional passages to install this app. Since Antares SQL is a free software we don't have a budget to spend on annual licenses or certificates. This can result that on some platforms you might need to put in some additional work to install this app.
### Linux ### Linux
On Linux you can simply download and run `.AppImage` distributions, install from Snap Store or from AUR. On Linux you can simply download and run the `.AppImage` distribution, install from Snap Store, from AUR or from our [PPA repository](https://github.com/antares-sql/antares-ppa).
### Windows ### Windows
On Windows you can choose between Microsoft Store and download `.exe` distribution. The latter lacks of a certificate, so to install you need to click on "More info" and then "Run anyway" on SmartScreen prompt. On Windows you can choose between downloading the app from Microsoft Store or downloading the `.exe` from our [website](https://antares-sql.app/downloads) or [this github repo](https://github.com/Fabio286/antares/releases/latest). Distributions that are not from Microsoft Store are not signed with a certificate, so to install you need to click on "More info" and then "Run anyway" on SmartScreen prompt.
### MacOS ### MacOS
@@ -61,7 +66,7 @@ On macOS you can run `.dmg` distribution following [this guide](https://support.
## Download ## 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/) [![Get it from Microsoft Store](https://raw.githubusercontent.com/Fabio286/antares/gh-pages/src/assets/ms-store.png)](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/) [<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)** 🚀 **[Other Downloads](https://github.com/Fabio286/antares/releases/latest)**
## Coming soon ## Coming soon
@@ -82,8 +87,8 @@ This is a roadmap with major features will come in near future.
- [x] MySQL/MariaDB - [x] MySQL/MariaDB
- [x] PostgreSQL - [x] PostgreSQL
- [x] SQLite - [x] SQLite
- [ ] MSSQL - [x] Firebird SQL
- [ ] OracleDB - [ ] SQL Server
- [ ] More... - [ ] More...
### Operating Systems ### Operating Systems
@@ -114,30 +119,40 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
<!-- markdownlint-disable --> <!-- markdownlint-disable -->
<table> <table>
<tr> <tbody>
<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> <tr>
<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" 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"><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" 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"><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" 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"><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" 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"><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" 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"><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://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>
</tr> <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> <tr>
<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" 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"><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" 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"><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" 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"><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" 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"><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://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"><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/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>
</tr> <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> <tr>
<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" 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"><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" 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"><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/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>
</tr> <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>
</tr>
</tbody>
</table> </table>
<!-- markdownlint-restore --> <!-- markdownlint-restore -->

Binary file not shown.

BIN
assets/linux/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
assets/linux/16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 889 B

BIN
assets/linux/256x256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
assets/linux/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
assets/linux/64x64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

26363
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "antares", "name": "antares",
"productName": "Antares", "productName": "Antares",
"version": "0.5.8", "version": "0.7.12",
"description": "A modern, fast and productivity driven SQL client with a focus in UX.", "description": "A modern, fast and productivity driven SQL client with a focus in UX.",
"license": "MIT", "license": "MIT",
"repository": "https://github.com/antares-sql/antares.git", "repository": "https://github.com/antares-sql/antares.git",
@@ -12,14 +12,13 @@
"compile:main": "webpack --mode=production --config webpack.main.config.js", "compile:main": "webpack --mode=production --config webpack.main.config.js",
"compile:workers": "webpack --mode=production --config webpack.workers.config.js", "compile:workers": "webpack --mode=production --config webpack.workers.config.js",
"compile:renderer": "webpack --mode=production --config webpack.renderer.config.js", "compile:renderer": "webpack --mode=production --config webpack.renderer.config.js",
"build": "cross-env NODE_ENV=production npm run compile", "build": "cross-env NODE_ENV=production npm run compile && electron-builder --publish never",
"build:local": "npm run build && electron-builder --publish never", "build:appx": "npm run build -- --win appx",
"build:appx": "npm run build:local -- --win appx", "rebuild:electron": "rimraf ./dist && npm run postinstall && npm run devtools:install",
"rebuild:electron": "rimraf ./dist && npm run postinstall",
"release": "standard-version", "release": "standard-version",
"release:pre": "npm run release -- --prerelease alpha", "release:pre": "npm run release -- --prerelease alpha",
"devtools:install": "node scripts/devtoolsInstaller", "devtools:install": "node scripts/devtoolsInstaller",
"postinstall": "electron-builder install-app-deps && npm run devtools:install", "postinstall": "electron-builder install-app-deps",
"test:e2e": "npm run compile && npm run test:e2e-dry", "test:e2e": "npm run compile && npm run test:e2e-dry",
"test:e2e-dry": "xvfb-maybe -- playwright test", "test:e2e-dry": "xvfb-maybe -- playwright test",
"lint": "eslint . --ext .js,.ts,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"", "lint": "eslint . --ext .js,.ts,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
@@ -65,17 +64,20 @@
"target": [ "target": [
{ {
"target": "deb", "target": "deb",
"arch": "x64" "arch": [
"x64",
"armv7l"
]
}, },
{ {
"target": "AppImage", "target": "AppImage",
"arch": [ "arch": [
"x64", "x64",
"armv7l", "armv7l"
"arm64"
] ]
} }
], ],
"icon": "assets/linux",
"category": "Development" "category": "Development"
}, },
"appImage": { "appImage": {
@@ -117,43 +119,48 @@
"dependencies": { "dependencies": {
"@electron/remote": "~2.0.1", "@electron/remote": "~2.0.1",
"@faker-js/faker": "~6.1.2", "@faker-js/faker": "~6.1.2",
"@mdi/font": "~6.1.95", "@mdi/font": "~7.1.96",
"@turf/helpers": "~6.5.0", "@turf/helpers": "~6.5.0",
"@vscode/vscode-languagedetection": "~1.0.21", "@vueuse/core": "~8.7.5",
"ace-builds": "~1.4.13", "ace-builds": "~1.14.0",
"better-sqlite3": "~7.5.0", "better-sqlite3": "~8.0.0",
"electron-log": "~4.4.1", "electron-log": "~4.4.1",
"electron-store": "~8.0.1", "electron-store": "~8.1.0",
"electron-updater": "~4.6.5", "electron-updater": "~4.6.5",
"electron-window-state": "~5.0.3", "electron-window-state": "~5.0.3",
"encoding": "~0.1.13", "encoding": "~0.1.13",
"floating-vue": "~2.0.0-beta.20",
"json2php": "~0.0.7",
"leaflet": "~1.7.1", "leaflet": "~1.7.1",
"marked": "~4.0.0", "marked": "~4.0.19",
"moment": "~2.29.1", "moment": "~2.29.4",
"mysql2": "~2.3.2", "mysql2": "~2.3.2",
"node-firebird": "~1.1.4",
"pg": "~8.7.1", "pg": "~8.7.1",
"pg-connection-string": "~2.5.0",
"pg-query-stream": "~4.2.3", "pg-query-stream": "~4.2.3",
"pgsql-ast-parser": "~7.2.1", "pgsql-ast-parser": "~7.2.1",
"pinia": "~2.0.13", "pinia": "~2.0.28",
"source-map-support": "~0.5.20", "source-map-support": "~0.5.20",
"spectre.css": "~0.5.9", "spectre.css": "~0.5.9",
"sql-formatter": "~4.0.2", "sql-formatter": "~12.2.0",
"ssh2-promise": "~1.0.2", "ssh2-promise": "~1.0.2",
"v-mask": "~2.3.0", "v-mask": "~2.3.0",
"vue": "~3.2.33", "vue": "~3.2.45",
"vue-i18n": "~9.1.9", "vue-i18n": "~9.2.2",
"vuedraggable": "~4.1.0" "vuedraggable": "~4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "~7.15.7", "@babel/eslint-parser": "~7.15.7",
"@babel/preset-env": "~7.15.8", "@babel/preset-env": "~7.15.8",
"@babel/preset-typescript": "~7.16.7", "@babel/preset-typescript": "~7.16.7",
"@playwright/test": "~1.21.1", "@playwright/test": "~1.28.1",
"@types/better-sqlite3": "~7.5.0", "@types/better-sqlite3": "~7.5.0",
"@types/leaflet": "~1.7.9", "@types/leaflet": "~1.7.9",
"@types/marked": "~4.0.3", "@types/marked": "~4.0.7",
"@types/node": "~17.0.23", "@types/node": "~17.0.23",
"@types/pg": "~8.6.5", "@types/pg": "~8.6.5",
"@types/ssh2": "~1.11.6",
"@typescript-eslint/eslint-plugin": "~5.18.0", "@typescript-eslint/eslint-plugin": "~5.18.0",
"@typescript-eslint/parser": "~5.18.0", "@typescript-eslint/parser": "~5.18.0",
"@vue/compiler-sfc": "~3.2.33", "@vue/compiler-sfc": "~3.2.33",
@@ -162,8 +169,8 @@
"chalk": "~4.1.2", "chalk": "~4.1.2",
"cross-env": "~7.0.2", "cross-env": "~7.0.2",
"css-loader": "~6.5.0", "css-loader": "~6.5.0",
"electron": "~19.0.5", "electron": "~22.0.3",
"electron-builder": "~23.0.3", "electron-builder": "~22.10.3",
"eslint": "~7.32.0", "eslint": "~7.32.0",
"eslint-config-standard": "~16.0.3", "eslint-config-standard": "~16.0.3",
"eslint-plugin-import": "~2.24.2", "eslint-plugin-import": "~2.24.2",
@@ -174,17 +181,19 @@
"html-webpack-plugin": "~5.5.0", "html-webpack-plugin": "~5.5.0",
"mini-css-extract-plugin": "~2.4.5", "mini-css-extract-plugin": "~2.4.5",
"node-loader": "~2.0.0", "node-loader": "~2.0.0",
"playwright": "~1.21.1", "playwright": "~1.28.1",
"playwright-core": "~1.21.1", "playwright-core": "~1.28.1",
"postcss-html": "~1.5.0",
"progress-webpack-plugin": "~1.0.12", "progress-webpack-plugin": "~1.0.12",
"rimraf": "~3.0.2", "rimraf": "~3.0.2",
"sass": "~1.42.1", "sass": "~1.42.1",
"sass-loader": "~12.3.0", "sass-loader": "~12.3.0",
"standard-version": "~9.3.1", "standard-version": "~9.3.1",
"style-loader": "~3.3.1", "style-loader": "~3.3.1",
"stylelint": "~13.13.1", "stylelint": "~14.9.1",
"stylelint-config-standard": "~22.0.0", "stylelint-config-recommended-vue": "~1.4.0",
"stylelint-scss": "~3.21.0", "stylelint-config-standard": "~26.0.0",
"stylelint-scss": "~4.3.0",
"tree-kill": "~1.2.2", "tree-kill": "~1.2.2",
"ts-loader": "~9.2.8", "ts-loader": "~9.2.8",
"typescript": "~4.6.3", "typescript": "~4.6.3",
@@ -193,7 +202,12 @@
"vue-loader": "~16.8.3", "vue-loader": "~16.8.3",
"webpack": "~5.72.0", "webpack": "~5.72.0",
"webpack-cli": "~4.9.1", "webpack-cli": "~4.9.1",
"webpack-dev-server": "~4.4.0", "webpack-dev-server": "~4.11.1",
"xvfb-maybe": "~0.2.1" "xvfb-maybe": "~0.2.1"
},
"overrides": {
"ssh2-promise": {
"ssh2": "github:Fabio286/ssh2"
}
} }
} }

View File

@@ -114,7 +114,6 @@ function startRenderer (callback) {
}); });
const server = new WebpackDevServer(compiler, { const server = new WebpackDevServer(compiler, {
hot: true,
port: 9080, port: 9080,
client: { client: {
overlay: true, overlay: true,

View File

@@ -1,4 +1,5 @@
// @ts-check // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const https = require('https'); const https = require('https');

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export default class { export default class {
static get _methods () { static get _methods () {
return [ return [
@@ -139,7 +140,7 @@ export default class {
{ name: 'arrayElement', group: 'random', types: ['string'] }, { name: 'arrayElement', group: 'random', types: ['string'] },
{ name: 'arrayElements', group: 'random', types: ['string'] }, { name: 'arrayElements', group: 'random', types: ['string'] },
{ name: 'objectElement', group: 'random', types: ['string'] }, { name: 'objectElement', group: 'random', types: ['string'] },
{ name: 'uuid', group: 'random', types: ['string'] }, { name: 'uuid', group: 'random', types: ['string', 'uuid'] },
{ name: 'boolean', group: 'random', types: ['string'] }, { name: 'boolean', group: 'random', types: ['string'] },
{ name: 'word', group: 'random', types: ['string'] }, { name: 'word', group: 'random', types: ['string'] },
{ name: 'words', group: 'random', types: ['string'] }, { name: 'words', group: 'random', types: ['string'] },
@@ -180,7 +181,7 @@ export default class {
acc[curr.group] = new Set(curr.types); acc[curr.group] = new Set(curr.types);
return acc; return acc;
}, {}); }, {} as any);
const groupsArr = []; const groupsArr = [];
@@ -198,12 +199,12 @@ export default class {
}); });
} }
static getGroupsByType (type) { static getGroupsByType (type: string) {
if (!type) return []; if (!type) return [];
return this.getGroups().filter(group => group.types.includes(type)); return this.getGroups().filter(group => group.types.includes(type));
} }
static getMethods ({ type, group }) { static getMethods ({ type, group }: {type: string; group: string}) {
return this._methods.filter(method => method.group === group && method.types.includes(type)).sort((a, b) => { return this._methods.filter(method => method.group === group && method.types.includes(type)).sort((a, b) => {
if (a.name < b.name) if (a.name < b.name)
return -1; return -1;

View File

@@ -1,10 +1,14 @@
import { Customizations } from '../interfaces/customizations'; import { Customizations } from '../interfaces/customizations';
// Everything OFF
export const defaults: Customizations = { export const defaults: Customizations = {
// Defaults // Defaults
defaultPort: null, defaultPort: null,
defaultUser: null, defaultUser: null,
defaultDatabase: null, defaultDatabase: null,
dataTypes: [],
indexTypes: [],
foreignActions: [],
// Core // Core
database: false, database: false,
collations: false, collations: false,
@@ -27,11 +31,12 @@ export const defaults: Customizations = {
routines: false, routines: false,
functions: false, functions: false,
schedulers: false, schedulers: false,
// Settings // Misc
elementsWrapper: '', elementsWrapper: '',
stringsWrapper: '"', stringsWrapper: '"',
tableAdd: false, tableAdd: false,
tableTruncateDisableFKCheck: false, tableTruncateDisableFKCheck: false,
tableDdl: false,
viewAdd: false, viewAdd: false,
triggerAdd: false, triggerAdd: false,
triggerFunctionAdd: false, triggerFunctionAdd: false,
@@ -45,9 +50,9 @@ export const defaults: Customizations = {
exportByChunks: false, exportByChunks: false,
schemaImport: false, schemaImport: false,
tableSettings: false, tableSettings: false,
tableOptions: false,
tableArray: false, tableArray: false,
tableRealCount: false, tableRealCount: false,
tableDuplicate: false,
viewSettings: false, viewSettings: false,
triggerSettings: false, triggerSettings: false,
triggerFunctionSettings: false, triggerFunctionSettings: false,
@@ -73,6 +78,7 @@ export const defaults: Customizations = {
procedureDataAccess: false, procedureDataAccess: false,
procedureSql: null, procedureSql: null,
procedureContext: false, procedureContext: false,
procedureContextValues: [],
procedureLanguage: false, procedureLanguage: false,
functionDeterministic: false, functionDeterministic: false,
functionDataAccess: false, functionDataAccess: false,

View File

@@ -0,0 +1,63 @@
import { Customizations } from '../interfaces/customizations';
import { defaults } from './defaults';
import firebirdTypes from '../data-types/firebird';
export const customizations: Customizations = {
...defaults,
// Defaults
defaultPort: 3050,
defaultUser: 'SYSDBA',
defaultDatabase: null,
dataTypes: firebirdTypes,
indexTypes: [
'PRIMARY',
// 'CHECK',
'UNIQUE'
],
foreignActions: [
'RESTRICT',
'NO ACTION',
'CASCADE',
'SET NULL',
'SET DEFAULT'
],
// Core
database: true,
collations: false,
engines: false,
connectionSchema: false,
sslConnection: false,
sshConnection: false,
fileConnection: false,
cancelQueries: false,
// Tools
processesList: false,
usersManagement: false,
variables: false,
// Structure
schemas: false,
tables: true,
views: true,
triggers: true,
routines: true,
functions: false,
// Settings
elementsWrapper: '"',
stringsWrapper: '\'',
tableAdd: true,
tableSettings: true,
tableRealCount: true,
viewAdd: true,
viewSettings: true,
triggerAdd: true,
triggerMultipleEvents: true,
triggerSql: 'BEGIN\r\n\r\nEND',
routineAdd: true,
procedureContext: true,
procedureContextValues: ['IN', 'OUT'],
procedureSql: 'BEGIN\r\n\r\nEND',
parametersLength: true,
indexes: true,
foreigns: true,
nullable: true
};

View File

@@ -1,16 +1,19 @@
import * as mysql from 'common/customizations/mysql'; import * as mysql from 'common/customizations/mysql';
import * as postgresql from 'common/customizations/postgresql'; import * as postgresql from 'common/customizations/postgresql';
import * as sqlite from 'common/customizations/sqlite'; import * as sqlite from 'common/customizations/sqlite';
import * as firebird from 'common/customizations/firebird';
import { Customizations } from 'common/interfaces/customizations'; import { Customizations } from 'common/interfaces/customizations';
export default { export default {
maria: mysql.customizations, maria: mysql.customizations,
mysql: mysql.customizations, mysql: mysql.customizations,
pg: postgresql.customizations, pg: postgresql.customizations,
sqlite: sqlite.customizations sqlite: sqlite.customizations,
firebird: firebird.customizations
} as { } as {
maria: Customizations; maria: Customizations;
mysql: Customizations; mysql: Customizations;
pg: Customizations; pg: Customizations;
sqlite: Customizations; sqlite: Customizations;
firebird: Customizations;
}; };

View File

@@ -1,5 +1,6 @@
import { Customizations } from '../interfaces/customizations'; import { Customizations } from '../interfaces/customizations';
import { defaults } from './defaults'; import { defaults } from './defaults';
import mysqlTypes from '../data-types/mysql';
export const customizations: Customizations = { export const customizations: Customizations = {
...defaults, ...defaults,
@@ -7,6 +8,19 @@ export const customizations: Customizations = {
defaultPort: 3306, defaultPort: 3306,
defaultUser: 'root', defaultUser: 'root',
defaultDatabase: null, defaultDatabase: null,
dataTypes: mysqlTypes,
indexTypes: [
'PRIMARY',
'INDEX',
'UNIQUE',
'FULLTEXT'
],
foreignActions: [
'RESTRICT',
'CASCADE',
'SET NULL',
'NO ACTION'
],
// Core // Core
connectionSchema: true, connectionSchema: true,
collations: true, collations: true,
@@ -25,10 +39,12 @@ export const customizations: Customizations = {
functions: true, functions: true,
schedulers: true, schedulers: true,
// Settings // Settings
elementsWrapper: '', elementsWrapper: '`',
stringsWrapper: '"', stringsWrapper: '"',
tableAdd: true, tableAdd: true,
tableTruncateDisableFKCheck: true, tableTruncateDisableFKCheck: true,
tableDuplicate: true,
tableDdl: true,
viewAdd: true, viewAdd: true,
triggerAdd: true, triggerAdd: true,
routineAdd: true, routineAdd: true,
@@ -51,7 +67,6 @@ export const customizations: Customizations = {
unsigned: true, unsigned: true,
nullable: true, nullable: true,
zerofill: true, zerofill: true,
tableOptions: true,
autoIncrement: true, autoIncrement: true,
comment: true, comment: true,
collation: true, collation: true,
@@ -64,6 +79,7 @@ export const customizations: Customizations = {
procedureDataAccess: true, procedureDataAccess: true,
procedureSql: 'BEGIN\r\n\r\nEND', procedureSql: 'BEGIN\r\n\r\nEND',
procedureContext: true, procedureContext: true,
procedureContextValues: ['IN', 'OUT', 'INOUT'],
triggerSql: 'BEGIN\r\n\r\nEND', triggerSql: 'BEGIN\r\n\r\nEND',
functionDeterministic: true, functionDeterministic: true,
functionDataAccess: true, functionDataAccess: true,

View File

@@ -1,5 +1,6 @@
import { Customizations } from '../interfaces/customizations'; import { Customizations } from '../interfaces/customizations';
import { defaults } from './defaults'; import { defaults } from './defaults';
import postgresqlTypes from '../data-types/postgresql';
export const customizations: Customizations = { export const customizations: Customizations = {
...defaults, ...defaults,
@@ -7,6 +8,18 @@ export const customizations: Customizations = {
defaultPort: 5432, defaultPort: 5432,
defaultUser: 'postgres', defaultUser: 'postgres',
defaultDatabase: 'postgres', defaultDatabase: 'postgres',
dataTypes: postgresqlTypes,
indexTypes: [
'PRIMARY',
'INDEX',
'UNIQUE'
],
foreignActions: [
'RESTRICT',
'CASCADE',
'SET NULL',
'NO ACTION'
],
// Core // Core
database: true, database: true,
sslConnection: true, sslConnection: true,
@@ -22,10 +35,12 @@ export const customizations: Customizations = {
triggerFunctions: true, triggerFunctions: true,
routines: true, routines: true,
functions: true, functions: true,
// Settings // Misc
elementsWrapper: '"', elementsWrapper: '"',
stringsWrapper: '\'', stringsWrapper: '\'',
tableAdd: true, tableAdd: true,
tableDuplicate: true,
tableDdl: true,
viewAdd: true, viewAdd: true,
triggerAdd: true, triggerAdd: true,
triggerFunctionAdd: true, triggerFunctionAdd: true,
@@ -47,6 +62,7 @@ export const customizations: Customizations = {
tableArray: true, tableArray: true,
procedureSql: '$procedure$\r\n\r\n$procedure$', procedureSql: '$procedure$\r\n\r\n$procedure$',
procedureContext: true, procedureContext: true,
procedureContextValues: ['IN', 'OUT', 'INOUT'],
procedureLanguage: true, procedureLanguage: true,
functionSql: '$function$\r\n\r\n$function$', functionSql: '$function$\r\n\r\n$function$',
triggerFunctionSql: '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$', triggerFunctionSql: '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$',

View File

@@ -1,8 +1,21 @@
import { Customizations } from '../interfaces/customizations'; import { Customizations } from '../interfaces/customizations';
import { defaults } from './defaults'; import { defaults } from './defaults';
import sqliteTypes from '../data-types/sqlite';
export const customizations: Customizations = { export const customizations: Customizations = {
...defaults, ...defaults,
dataTypes: sqliteTypes,
indexTypes: [
'PRIMARY',
'INDEX',
'UNIQUE'
],
foreignActions: [
'RESTRICT',
'CASCADE',
'SET NULL',
'NO ACTION'
],
// Core // Core
fileConnection: true, fileConnection: true,
// Structure // Structure
@@ -14,6 +27,7 @@ export const customizations: Customizations = {
elementsWrapper: '"', elementsWrapper: '"',
stringsWrapper: '\'', stringsWrapper: '\'',
tableAdd: true, tableAdd: true,
tableDuplicate: true,
viewAdd: true, viewAdd: true,
triggerAdd: true, triggerAdd: true,
schemaEdit: false, schemaEdit: false,

View File

@@ -0,0 +1,136 @@
import { TypesGroup } from 'common/interfaces/antares';
export default [
{
group: 'integer',
types: [
{
name: 'SMALLINT',
length: false,
collation: false,
unsigned: true,
zerofill: true
},
{
name: 'INTEGER',
length: false,
collation: false,
unsigned: true,
zerofill: true
},
{
name: 'BIGINT',
length: false,
collation: false,
unsigned: true,
zerofill: true
}
]
},
{
group: 'float',
types: [
{
name: 'DECIMAL',
length: true,
scale: true,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'NUMERIC',
length: true,
scale: true,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'FLOAT',
length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'DOUBLE PRECISION',
length: false,
collation: false,
unsigned: false,
zerofill: false
}
]
},
{
group: 'string',
types: [
{
name: 'CHAR',
length: true,
collation: true,
unsigned: false,
zerofill: false
},
{
name: 'VARCHAR',
length: true,
collation: true,
unsigned: false,
zerofill: false
},
{
name: 'BLOB-TEXT',
length: false,
collation: true,
unsigned: false,
zerofill: false
}
]
},
{
group: 'binary',
types: [
{
name: 'BLOB',
length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'CHAR-BINARY',
length: false,
collation: false,
unsigned: false,
zerofill: false
}
]
},
{
group: 'time',
types: [
{
name: 'DATE',
length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'TIME',
length: true,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'TIMESTAMP',
length: false,
collation: false,
unsigned: false,
zerofill: false
}
]
}
] as TypesGroup[];

View File

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

View File

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

View File

@@ -10,7 +10,8 @@ export const LONG_TEXT = [
'MEDIUMTEXT', 'MEDIUMTEXT',
'LONGTEXT', 'LONGTEXT',
'JSON', 'JSON',
'VARBINARY' 'VARBINARY',
'BLOB-TEXT'
]; ];
export const ARRAY = [ export const ARRAY = [
@@ -29,14 +30,14 @@ export const NUMBER = [
'SMALLINT', 'SMALLINT',
'MEDIUMINT', 'MEDIUMINT',
'BIGINT', 'BIGINT',
'DECIMAL',
'NUMERIC', 'NUMERIC',
'INTEGER', 'INTEGER',
'SMALLSERIAL', 'SMALLSERIAL',
'SERIAL', 'SERIAL',
'BIGSERIAL', 'BIGSERIAL',
'OID', 'OID',
'XID' 'XID',
'INT64'
]; ];
export const FLOAT = [ export const FLOAT = [
@@ -48,6 +49,12 @@ export const FLOAT = [
'MONEY' 'MONEY'
]; ];
export const IS_BIGINT = [
'BIGINT',
'BIGSERIAL',
'DOUBLE PRECISION'
];
export const BOOLEAN = [ export const BOOLEAN = [
'BOOL', 'BOOL',
'BOOLEAN' 'BOOLEAN'
@@ -78,7 +85,9 @@ export const BLOB = [
'TINYBLOB', 'TINYBLOB',
'MEDIUMBLOB', 'MEDIUMBLOB',
'LONGBLOB', 'LONGBLOB',
'BYTEA' 'LONG_BLOB',
'BYTEA',
'CHAR-BINARY'
]; ];
export const BIT = [ export const BIT = [
@@ -86,6 +95,14 @@ export const BIT = [
'BIT VARYING' 'BIT VARYING'
]; ];
export const BINARY = [
'BINARY'
];
export const UUID = [
'UUID'
];
export const SPATIAL = [ export const SPATIAL = [
'POINT', 'POINT',
'LINESTRING', 'LINESTRING',

View File

@@ -1,6 +0,0 @@
export default [
'PRIMARY',
'INDEX',
'UNIQUE',
'FULLTEXT'
];

View File

@@ -1,5 +0,0 @@
export default [
'PRIMARY',
'INDEX',
'UNIQUE'
];

View File

@@ -1,5 +0,0 @@
export default [
'PRIMARY',
'INDEX',
'UNIQUE'
];

View File

@@ -8,9 +8,10 @@ import SSHConfig from 'ssh2-promise/lib/sshConfig';
import { MySQLClient } from '../../main/libs/clients/MySQLClient'; import { MySQLClient } from '../../main/libs/clients/MySQLClient';
import { PostgreSQLClient } from '../../main/libs/clients/PostgreSQLClient'; import { PostgreSQLClient } from '../../main/libs/clients/PostgreSQLClient';
import { SQLiteClient } from '../../main/libs/clients/SQLiteClient'; import { SQLiteClient } from '../../main/libs/clients/SQLiteClient';
import { FirebirdSQLClient } from 'src/main/libs/clients/FirebirdSQLClient';
export type Client = MySQLClient | PostgreSQLClient | SQLiteClient export type Client = MySQLClient | PostgreSQLClient | SQLiteClient | FirebirdSQLClient
export type ClientCode = 'mysql' | 'maria' | 'pg' | 'sqlite' export type ClientCode = 'mysql' | 'maria' | 'pg' | 'sqlite' | 'firebird'
export type Exporter = MysqlExporter | PostgreSQLExporter export type Exporter = MysqlExporter | PostgreSQLExporter
export type Importer = MySQLImporter | PostgreSQLImporter export type Importer = MySQLImporter | PostgreSQLImporter
@@ -25,6 +26,7 @@ export interface IpcResponse<T = any> {
*/ */
export interface ClientParams { export interface ClientParams {
client: ClientCode; client: ClientCode;
uid?: string;
params: params:
mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean} mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean}
| pg.ClientConfig & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean} | pg.ClientConfig & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean}
@@ -83,13 +85,11 @@ export interface TableInfos {
name: string; name: string;
type: string; type: string;
rows: number; rows: number;
created: Date;
updated: Date;
engine: string; engine: string;
comment: string; comment: string;
size: number | false; size: number | false;
autoIncrement: number;
collation: string; collation: string;
autoIncrement?: boolean;
} }
export type TableOptions = Partial<TableInfos>; export type TableOptions = Partial<TableInfos>;

View File

@@ -1,8 +1,13 @@
import { TypesGroup } from './antares';
export interface Customizations { export interface Customizations {
// Defaults // Defaults
defaultPort?: number; defaultPort?: number;
defaultUser?: string; defaultUser?: string;
defaultDatabase?: string; defaultDatabase?: string;
dataTypes?: TypesGroup[];
indexTypes?: string[];
foreignActions?: string[];
// Core // Core
database?: boolean; database?: boolean;
collations?: boolean; collations?: boolean;
@@ -25,15 +30,16 @@ export interface Customizations {
routines?: boolean; routines?: boolean;
functions?: boolean; functions?: boolean;
schedulers?: boolean; schedulers?: boolean;
// Settings // Misc
elementsWrapper: string; elementsWrapper: string;
stringsWrapper: string; stringsWrapper: string;
tableAdd?: boolean; tableAdd?: boolean;
tableSettings?: boolean; tableSettings?: boolean;
tableOptions?: boolean; tableDuplicate?: boolean;
tableArray?: boolean; tableArray?: boolean;
tableRealCount?: boolean; tableRealCount?: boolean;
tableTruncateDisableFKCheck?: boolean; tableTruncateDisableFKCheck?: boolean;
tableDdl?: boolean;
viewAdd?: boolean; viewAdd?: boolean;
viewSettings?: boolean; viewSettings?: boolean;
triggerAdd?: boolean; triggerAdd?: boolean;
@@ -71,6 +77,7 @@ export interface Customizations {
procedureDataAccess?: boolean; procedureDataAccess?: boolean;
procedureSql?: string; procedureSql?: string;
procedureContext?: boolean; procedureContext?: boolean;
procedureContextValues?: string[];
procedureLanguage?: boolean; procedureLanguage?: boolean;
functionDeterministic?: boolean; functionDeterministic?: boolean;
functionDataAccess?: boolean; functionDataAccess?: boolean;

View File

@@ -7,6 +7,12 @@ export interface TableParams {
export interface ExportOptions { export interface ExportOptions {
schema: string; schema: string;
tables: {
table: string;
includeStructure: boolean;
includeContent: boolean;
includeDropStatement: boolean;
}[];
includes: {[key: string]: boolean}; includes: {[key: string]: boolean};
outputFormat: 'sql' | 'sql.zip'; outputFormat: 'sql' | 'sql.zip';
outputFile: string; outputFile: string;

View File

@@ -1,5 +1,5 @@
export function bufferToBase64 (buf: Buffer) { export function bufferToBase64 (buf: Buffer) {
const binstr = Array.prototype.map.call(buf, ch => { const binstr = Array.prototype.map.call(buf, (ch: number) => {
return String.fromCharCode(ch); return String.fromCharCode(ch);
}).join(''); }).join('');
return Buffer.from(binstr, 'binary').toString('base64'); return Buffer.from(binstr, 'binary').toString('base64');

View File

@@ -0,0 +1,192 @@
function isJSON (str: string) {
try {
if (!['{', '['].includes(str.trim()[0]))
return false;
JSON.parse(str);
return true;
}
catch (_) {
return false;
}
}
function isHTML (str: string) {
const tags = [
'a',
'abbr',
'address',
'area',
'article',
'aside',
'audio',
'b',
'base',
'bdi',
'bdo',
'blockquote',
'body',
'br',
'button',
'canvas',
'caption',
'cite',
'code',
'col',
'colgroup',
'data',
'datalist',
'dd',
'del',
'details',
'dfn',
'dialog',
'div',
'dl',
'dt',
'em',
'embed',
'fieldset',
'figcaption',
'figure',
'footer',
'form',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'head',
'header',
'hgroup',
'hr',
'html',
'i',
'iframe',
'img',
'input',
'ins',
'kbd',
'label',
'legend',
'li',
'link',
'main',
'map',
'mark',
'math',
'menu',
'menuitem',
'meta',
'meter',
'nav',
'noscript',
'object',
'ol',
'optgroup',
'option',
'output',
'p',
'param',
'picture',
'pre',
'progress',
'q',
'rb',
'rp',
'rt',
'rtc',
'ruby',
's',
'samp',
'script',
'section',
'select',
'slot',
'small',
'source',
'span',
'strong',
'style',
'sub',
'summary',
'sup',
'svg',
'table',
'tbody',
'td',
'template',
'textarea',
'tfoot',
'th',
'thead',
'time',
'title',
'tr',
'track',
'u',
'ul',
'var',
'video',
'wbr'
];
const doc = new DOMParser().parseFromString(str, 'text/html');
const lowerStr = str.toLowerCase();
if (Array.from(doc.body.childNodes).some(node => node.nodeType === 1))
return tags.some((tag) => lowerStr.includes(`<${tag}>`));
return false;
}
function isSVG (str: string) {
const doc = new DOMParser().parseFromString(str, 'text/xml');
const lowerStr = str.toLowerCase();
const errorNode = doc.querySelector('parsererror');
if (!errorNode)
return lowerStr.includes('<svg');
return false;
}
function isXML (str: string) {
const doc = new DOMParser().parseFromString(str, 'text/xml');
const errorNode = doc.querySelector('parsererror');
return !errorNode;
}
function isMD (str: string) {
const mdChecks = [
'# ',
'`',
'- ',
'+ ',
'* ',
'1. ',
'**',
'__',
'~~',
'>> ',
'](http',
'![',
'[ ]',
'[x]'
];
return mdChecks.some((tag) => str.includes(tag));
}
export function langDetector (str: string) {
if (!str || !str.trim().length)
return 'text';
if (isJSON(str))
return 'json';
if (isHTML(str))
return 'html';
if (isSVG(str))
return 'svg';
if (isXML(str))
return 'xml';
if (isMD(str))
return 'markdown';
return 'text';
}

View File

@@ -1,14 +0,0 @@
/* eslint-disable no-useless-escape */
// eslint-disable-next-line no-control-regex
const pattern = /[\0\x08\x09\x1a\n\r"'\\\%]/gm;
const regex = new RegExp(pattern);
function sqlEscaper (string: string) {
return string.replace(regex, char => {
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '\"', '\\', '\\\\', '%'];
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\\\'', '\\\"', '\\\\', '\\\\\\\\', '\%'];
return r[m.indexOf(char)] || char;
});
}
export { sqlEscaper };

198
src/common/libs/sqlUtils.ts Normal file
View File

@@ -0,0 +1,198 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable no-useless-escape */
import * as moment from 'moment';
import { lineString, point, polygon } from '@turf/helpers';
import customizations from '../customizations';
import { ClientCode } from '../interfaces/antares';
import { BLOB, BIT, DATE, DATETIME, FLOAT, SPATIAL, IS_MULTI_SPATIAL, NUMBER, TEXT_SEARCH } from 'common/fieldTypes';
import hexToBinary, { HexChar } from './hexToBinary';
import { getArrayDepth } from './getArrayDepth';
/**
* Escapes a string fo SQL use
*
* @param { String } string
* @returns { String } Escaped string
*/
export const sqlEscaper = (string: string): string => {
// eslint-disable-next-line no-control-regex
const pattern = /[\0\x08\x09\x1a\n\r"'\\\%]/gm;
const regex = new RegExp(pattern);
return string.replace(regex, char => {
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '\"', '\\', '\\\\', '%'];
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\\\'', '\\\"', '\\\\', '\\\\\\\\', '\%'];
return r[m.indexOf(char)] || char;
});
};
export const objectToGeoJSON = (val: any) => {
if (Array.isArray(val)) {
if (getArrayDepth(val) === 1)
return lineString(val.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []));
else
return polygon(val.map(arr => arr.reduce((acc: any, curr: any) => [...acc, [curr.x, curr.y]], [])));
}
else
return point([val.x, val.y]);
};
export const escapeAndQuote = (val: string, client: ClientCode) => {
const { stringsWrapper: sw } = customizations[client];
// eslint-disable-next-line no-control-regex
const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g;
const CHARS_ESCAPE_MAP: {[key: string]: string} = {
'\0': '\\0',
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\r': '\\r',
'\x1a': '\\Z',
'"': '\\"',
'\'': '\\\'',
'\\': '\\\\'
};
let chunkIndex = CHARS_TO_ESCAPE.lastIndex = 0;
let escapedVal = '';
let match;
while ((match = CHARS_TO_ESCAPE.exec(val))) {
escapedVal += val.slice(chunkIndex, match.index) + CHARS_ESCAPE_MAP[match[0]];
chunkIndex = CHARS_TO_ESCAPE.lastIndex;
}
if (chunkIndex === 0)
return `${sw}${val}${sw}`;
if (chunkIndex < val.length)
return `${sw}${escapedVal + val.slice(chunkIndex)}${sw}`;
return `${sw}${escapedVal}${sw}`;
};
export const valueToSqlString = (args: {
val: any;
client: ClientCode;
field: {type: string; datePrecision: number; isArray?: boolean};
}): string => {
let parsedValue;
const { val, client, field } = args;
const { stringsWrapper: sw } = customizations[client];
if (val === null)
parsedValue = 'NULL';
else if (DATE.includes(field.type)) {
parsedValue = moment(val).isValid()
? escapeAndQuote(moment(val).format('YYYY-MM-DD'), client)
: val;
}
else if (DATETIME.includes(field.type)) {
let datePrecision = '';
for (let i = 0; i < field.datePrecision; i++)
datePrecision += i === 0 ? '.S' : 'S';
parsedValue = moment(val).isValid()
? escapeAndQuote(moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`), client)
: escapeAndQuote(val, client);
}
else if ('isArray' in field && field.isArray) {
let localVal;
if (Array.isArray(val))
localVal = JSON.stringify(val).replaceAll('[', '{').replaceAll(']', '}');
else
localVal = typeof val === 'string' ? val.replaceAll('[', '{').replaceAll(']', '}') : '';
parsedValue = `'${localVal}'`;
}
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[])}'`;
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'${buffer.toString('hex').toUpperCase()}'`;
else if (client === 'pg')
parsedValue = `decode('${buffer.toString('hex').toUpperCase()}', 'hex')`;
}
else if (NUMBER.includes(field.type))
parsedValue = val;
else if (FLOAT.includes(field.type))
parsedValue = parseFloat(val);
else if (SPATIAL.includes(field.type)) {
let geoJson;
if (IS_MULTI_SPATIAL.includes(field.type)) {
const features = [];
for (const element of val)
features.push(objectToGeoJSON(element));
geoJson = {
type: 'FeatureCollection',
features
};
}
else
geoJson = objectToGeoJSON(val);
parsedValue = `ST_GeomFromGeoJSON('${JSON.stringify(geoJson)}')`;
}
else if (val === '') parsedValue = `${sw}${sw}`;
else {
parsedValue = typeof val === 'string'
? escapeAndQuote(val, client)
: typeof val === 'object'
? escapeAndQuote(JSON.stringify(val), client)
: val;
}
return parsedValue;
};
export const jsonToSqlInsert = (args: {
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, 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[0]).map(key => `${ew}${key}${ew}`);
let insertStmt = `INSERT INTO ${ew}${table}${ew} (${fieldNames.join(', ')}) VALUES `;
let insertsString = '';
let queryLength = 0;
let rowsWritten = 0;
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,3 +1,3 @@
export function uidGen (prefix?: string) { export function uidGen (prefix?: string) {
return (prefix ? `${prefix}:` : '') + Math.random().toString(36).substr(2, 9).toUpperCase(); return (prefix ? `${prefix}:` : '') + Math.random().toString(36).substring(2, 11).toUpperCase();
} }

138
src/common/shortcuts.ts Normal file
View File

@@ -0,0 +1,138 @@
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' }
};
interface ShortcutRecord {
event: string;
keys: Electron.Accelerator[] | string[];
/** Needed for default shortcuts */
os: NodeJS.Platform[];
}
/**
* Default shortcuts
*/
const shortcuts: ShortcutRecord[] = [
{
event: 'run-or-reload',
keys: ['F5'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'save-content',
keys: ['CommandOrControl+S'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'kill-query',
keys: ['CommandOrControl+K'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'format-query',
keys: ['CommandOrControl+B'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'clear-query',
keys: ['CommandOrControl+Alt+W'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'query-history',
keys: ['CommandOrControl+G'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'open-new-tab',
keys: ['CommandOrControl+T'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'close-tab',
keys: ['CommandOrControl+W'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'next-tab',
keys: ['Alt+CommandOrControl+Right'],
os: ['darwin', 'win32']
},
{
event: 'prev-tab',
keys: ['Alt+CommandOrControl+Left'],
os: ['darwin', 'win32']
},
{
event: 'next-tab',
keys: ['CommandOrControl+PageDown'],
os: ['linux', 'win32']
},
{
event: 'prev-tab',
keys: ['CommandOrControl+PageUp'],
os: ['linux', 'win32']
},
{
event: 'open-filter',
keys: ['CommandOrControl+F'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'next-page',
keys: ['CommandOrControl+Right'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'prev-page',
keys: ['CommandOrControl+Left'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'open-all-connections',
keys: ['Shift+CommandOrControl+Space'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'toggle-console',
keys: ['CommandOrControl+F12'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'toggle-console',
keys: ['CommandOrControl+`'],
os: ['darwin', 'linux', 'win32']
}
];
for (let i = 1; i <= 9; i++) {
shortcutEvents[`select-tab-${i}`] = {
l18n: 'message.selectTabNumber',
l18nParam: i
};
shortcuts.push({
event: `select-tab-${i}`,
keys: [`CommandOrControl+${i}`],
os: ['darwin', 'linux', 'win32']
});
}
export { shortcuts, ShortcutRecord };

View File

@@ -1,4 +1,5 @@
import { app, ipcMain, dialog } from 'electron'; import { app, ipcMain, dialog } from 'electron';
import { ShortcutRegister } from '../libs/ShortcutRegister';
export default () => { export default () => {
ipcMain.on('close-app', () => { ipcMain.on('close-app', () => {
@@ -12,4 +13,24 @@ export default () => {
ipcMain.handle('get-download-dir-path', () => { ipcMain.handle('get-download-dir-path', () => {
return app.getPath('downloads'); return app.getPath('downloads');
}); });
ipcMain.handle('resotre-default-shortcuts', () => {
const shortCutRegister = ShortcutRegister.getInstance();
shortCutRegister.restoreDefaults();
});
ipcMain.handle('reload-shortcuts', () => {
const shortCutRegister = ShortcutRegister.getInstance();
shortCutRegister.reload();
});
ipcMain.handle('update-shortcuts', (event, shortcuts) => {
const shortCutRegister = ShortcutRegister.getInstance();
shortCutRegister.updateShortcuts(shortcuts);
});
ipcMain.handle('unregister-shortcuts', () => {
const shortCutRegister = ShortcutRegister.getInstance();
shortCutRegister.unregister();
});
}; };

View File

@@ -55,12 +55,17 @@ export default (connections: {[key: string]: antares.Client}) => {
try { try {
const connection = await ClientsFactory.getClient({ const connection = await ClientsFactory.getClient({
uid: conn.uid,
client: conn.client, client: conn.client,
params params
}); });
await connection.connect(); await connection.connect();
await connection.select('1+1').run(); if (conn.client === 'firebird')
connection.raw('SELECT rdb$get_context(\'SYSTEM\', \'DB_NAME\') FROM rdb$database');
else
await connection.select('1+1').run();
connection.destroy(); connection.destroy();
return { status: 'success' }; return { status: 'success' };
@@ -128,6 +133,7 @@ export default (connections: {[key: string]: antares.Client}) => {
try { try {
const connection = ClientsFactory.getClient({ const connection = ClientsFactory.getClient({
uid: conn.uid,
client: conn.client, client: conn.client,
params, params,
poolSize: 5 poolSize: 5

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

View File

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

View File

@@ -4,8 +4,8 @@ import { InsertRowsParams } from 'common/interfaces/tableApis';
import { ipcMain } from 'electron'; import { ipcMain } from 'electron';
import { faker } from '@faker-js/faker'; import { faker } from '@faker-js/faker';
import * as moment from 'moment'; import * as moment from 'moment';
import { sqlEscaper } from 'common/libs/sqlEscaper'; import { sqlEscaper } from 'common/libs/sqlUtils';
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes'; import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME, BOOLEAN } from 'common/fieldTypes';
import customizations from 'common/customizations'; import customizations from 'common/customizations';
export default (connections: {[key: string]: antares.Client}) => { export default (connections: {[key: string]: antares.Client}) => {
@@ -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) => { ipcMain.handle('get-key-usage', async (event, params) => {
try { try {
const result = await connections[params.uid].getKeyUsage(params); const result = await connections[params.uid].getKeyUsage(params);
@@ -105,6 +116,7 @@ export default (connections: {[key: string]: antares.Client}) => {
break; break;
case 'pg': case 'pg':
case 'sqlite': case 'sqlite':
case 'firebird':
escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`; escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`;
break; break;
} }
@@ -124,6 +136,7 @@ export default (connections: {[key: string]: antares.Client}) => {
escapedParam = `0x${fileBlob.toString('hex')}`; escapedParam = `0x${fileBlob.toString('hex')}`;
break; break;
case 'pg': case 'pg':
case 'firebird':
fileBlob = fs.readFileSync(params.content); fileBlob = fs.readFileSync(params.content);
escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`; escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`;
break; break;
@@ -141,6 +154,7 @@ export default (connections: {[key: string]: antares.Client}) => {
escapedParam = '\'\''; escapedParam = '\'\'';
break; break;
case 'pg': case 'pg':
case 'firebird':
escapedParam = 'decode(\'\', \'hex\')'; escapedParam = 'decode(\'\', \'hex\')';
break; break;
case 'sqlite': case 'sqlite':
@@ -153,6 +167,19 @@ export default (connections: {[key: string]: antares.Client}) => {
escapedParam = `b'${sqlEscaper(params.content)}'`; escapedParam = `b'${sqlEscaper(params.content)}'`;
reload = true; reload = true;
} }
else if (BOOLEAN.includes(params.type)) {
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
case 'pg':
case 'firebird':
escapedParam = params.content;
break;
case 'sqlite':
escapedParam = Number(params.content === 'true');
break;
}
}
else if (params.content === null) else if (params.content === null)
escapedParam = 'NULL'; escapedParam = 'NULL';
else else
@@ -177,7 +204,10 @@ export default (connections: {[key: string]: antares.Client}) => {
if (typeof orgRow[key] === 'string') if (typeof orgRow[key] === 'string')
orgRow[key] = `'${orgRow[key]}'`; orgRow[key] = `'${orgRow[key]}'`;
orgRow[key] = `= ${orgRow[key]}`; if (orgRow[key] === null)
orgRow[key] = `IS ${orgRow[key]}`;
else
orgRow[key] = `= ${orgRow[key]}`;
} }
await connections[params.uid] await connections[params.uid]
@@ -208,10 +238,11 @@ export default (connections: {[key: string]: antares.Client}) => {
}).join(','); }).join(',');
try { try {
const result = await connections[params.uid] const result: unknown = await connections[params.uid]
.schema(params.schema) .schema(params.schema)
.delete(params.table) .delete(params.table)
.where({ [params.primary]: `IN (${idString})` }) .where({ [params.primary]: `IN (${idString})` })
.limit(params.rows.length)
.run(); .run();
return { status: 'success', response: result }; return { status: 'success', response: result };
@@ -227,7 +258,10 @@ export default (connections: {[key: string]: antares.Client}) => {
if (typeof row[key] === 'string') if (typeof row[key] === 'string')
row[key] = `'${row[key]}'`; row[key] = `'${row[key]}'`;
row[key] = `= ${row[key]}`; if (row[key] === null)
row[key] = 'IS NULL';
else
row[key] = `= ${row[key]}`;
} }
await connections[params.uid] await connections[params.uid]
@@ -270,6 +304,7 @@ export default (connections: {[key: string]: antares.Client}) => {
break; break;
case 'pg': case 'pg':
case 'sqlite': case 'sqlite':
case 'firebird':
escapedParam = `'${params.row[key].value.replaceAll('\'', '\'\'')}'`; escapedParam = `'${params.row[key].value.replaceAll('\'', '\'\'')}'`;
break; break;
} }
@@ -331,7 +366,18 @@ export default (connections: {[key: string]: antares.Client}) => {
if (typeof fakeValue === 'string') { if (typeof fakeValue === 'string') {
if (params.row[key].length) if (params.row[key].length)
fakeValue = fakeValue.substring(0, params.row[key].length); fakeValue = fakeValue.substring(0, params.row[key].length);
fakeValue = `'${sqlEscaper(fakeValue)}'`;
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)) else if ([...DATE, ...DATETIME].includes(type))
fakeValue = `'${moment(fakeValue).format('YYYY-MM-DD HH:mm:ss.SSSSSS')}'`; fakeValue = `'${moment(fakeValue).format('YYYY-MM-DD HH:mm:ss.SSSSSS')}'`;
@@ -367,7 +413,20 @@ export default (connections: {[key: string]: antares.Client}) => {
if (description) if (description)
query.select(`LEFT(${description}, 20) AS foreign_description`); query.select(`LEFT(${description}, 20) AS foreign_description`);
const results = await query.run(); const results = await query.run<{[key: string]: string}>();
const parsedResults: {[key: string]: string}[] = [];
for (const row of results.rows) {
const remappedRow: {[key: string]: string} = {};
for (const key in row)
remappedRow[key.toLowerCase()] = row[key];// Thanks Firebird -.-
parsedResults.push(remappedRow);
}
results.rows = parsedResults;
return { status: 'success', response: results }; return { status: 'success', response: results };
} }

View File

@@ -3,26 +3,32 @@ import mysql from 'mysql2/promise';
import * as pg from 'pg'; import * as pg from 'pg';
import SSH2Promise from 'ssh2-promise'; import SSH2Promise from 'ssh2-promise';
const queryLogger = (sql: string) => { const queryLogger = ({ sql, cUid }: {sql: string; cUid: string}) => {
// Remove comments, newlines and multiple spaces // Remove comments, newlines and multiple spaces
const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' '); const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' ');
console.log(escapedSql); if (process.type !== undefined) {
const mainWindow = require('electron').webContents.fromId(1);
mainWindow.send('query-log', { cUid, sql: escapedSql, date: new Date() });
}
if (process.env.NODE_ENV === 'development') console.log(escapedSql);
}; };
/** /**
* As Simple As Possible Query Builder Core * As Simple As Possible Query Builder Core
*/ */
export class AntaresCore { export abstract class AntaresCore {
_client: antares.ClientCode; _client: antares.ClientCode;
protected _cUid: string
protected _params: mysql.ConnectionOptions | pg.ClientConfig | { databasePath: string; readonly: boolean}; protected _params: mysql.ConnectionOptions | pg.ClientConfig | { databasePath: string; readonly: boolean};
protected _poolSize: number; protected _poolSize: number;
protected _ssh?: SSH2Promise; protected _ssh?: SSH2Promise;
protected _logger: (sql: string) => void; protected _logger: (args: {sql: string; cUid: string}) => void;
protected _queryDefaults: antares.QueryBuilderObject; protected _queryDefaults: antares.QueryBuilderObject;
protected _query: antares.QueryBuilderObject; protected _query: antares.QueryBuilderObject;
constructor (args: antares.ClientParams) { constructor (args: antares.ClientParams) {
this._client = args.client; this._client = args.client;
this._cUid = args.uid;
this._params = args.params; this._params = args.params;
this._poolSize = args.poolSize || undefined; this._poolSize = args.poolSize || undefined;
this._logger = args.logger || queryLogger; this._logger = args.logger || queryLogger;
@@ -156,6 +162,10 @@ export class AntaresCore {
throw new Error('Method "getDbConfig" not implemented'); throw new Error('Method "getDbConfig" not implemented');
} }
getDatabases () {
throw new Error('Method "getDatabases" not implemented');
}
createSchema (...args: any) { createSchema (...args: any) {
throw new Error('Method "createSchema" not implemented'); throw new Error('Method "createSchema" not implemented');
} }
@@ -168,6 +178,10 @@ export class AntaresCore {
throw new Error('Method "dropSchema" not implemented'); throw new Error('Method "dropSchema" not implemented');
} }
getTableDll (...args: any) {
throw new Error('Method "getTableDll" not implemented');
}
getDatabaseCollation (...args: any) { getDatabaseCollation (...args: any) {
throw new Error('Method "getDatabaseCollation" not implemented'); throw new Error('Method "getDatabaseCollation" not implemented');
} }

View File

@@ -2,6 +2,7 @@ import * as antares from 'common/interfaces/antares';
import { MySQLClient } from './clients/MySQLClient'; import { MySQLClient } from './clients/MySQLClient';
import { PostgreSQLClient } from './clients/PostgreSQLClient'; import { PostgreSQLClient } from './clients/PostgreSQLClient';
import { SQLiteClient } from './clients/SQLiteClient'; import { SQLiteClient } from './clients/SQLiteClient';
import { FirebirdSQLClient } from './clients/FirebirdSQLClient';
export class ClientsFactory { export class ClientsFactory {
static getClient (args: antares.ClientParams) { static getClient (args: antares.ClientParams) {
@@ -13,6 +14,8 @@ export class ClientsFactory {
return new PostgreSQLClient(args); return new PostgreSQLClient(args);
case 'sqlite': case 'sqlite':
return new SQLiteClient(args); return new SQLiteClient(args);
case 'firebird':
return new FirebirdSQLClient(args);
default: default:
throw new Error(`Unknown database client: ${args.client}`); throw new Error(`Unknown database client: ${args.client}`);
} }

View File

@@ -0,0 +1,139 @@
import { BrowserWindow, globalShortcut, Menu, MenuItem, MenuItemConstructorOptions } from 'electron';
import * as Store from 'electron-store';
import { ShortcutRecord, shortcuts } from 'common/shortcuts';
const shortcutsStore = new Store({ name: 'shortcuts' });
const isDevelopment = process.env.NODE_ENV !== 'production';
const defaultShortcuts = shortcuts.filter(s => s.os.includes(process.platform));
export type ShortcutMode = 'local' | 'global'
export type OsMenu = {
[key in NodeJS.Platform]?: MenuItemConstructorOptions[];
};
export class ShortcutRegister {
private _shortcuts: ShortcutRecord[];
private _mainWindow: BrowserWindow;
private _menu: Menu;
private _menuTemplate: OsMenu;
private _mode: ShortcutMode;
private static _instance: ShortcutRegister;
private constructor (args: { mainWindow: BrowserWindow; menuTemplate?: OsMenu; mode: ShortcutMode }) {
this._mainWindow = args.mainWindow;
this._menuTemplate = args.menuTemplate || {};
this._mode = args.mode;
this.shortcuts = shortcutsStore.get('shortcuts', defaultShortcuts) as ShortcutRecord[];
}
public static getInstance (args?: { mainWindow?: BrowserWindow; menuTemplate?: OsMenu; mode?: ShortcutMode }) {
if (!ShortcutRegister._instance && args.menuTemplate !== undefined && args.mode !== undefined) {
ShortcutRegister._instance = new ShortcutRegister({
mainWindow: args.mainWindow,
menuTemplate: args.menuTemplate,
mode: args.mode
});
}
return ShortcutRegister._instance;
}
get shortcuts () {
return this._shortcuts;
}
private set shortcuts (value: ShortcutRecord[]) {
this._shortcuts = value;
shortcutsStore.set('shortcuts', value);
}
init () {
this._mainWindow.webContents.send('update-shortcuts', this.shortcuts);
this.buildBaseMenu();
if (this._mode === 'global')
this.setGlobalShortcuts();
else if (this._mode === 'local')
this.setLocalShortcuts();
else
throw new Error(`Unknown mode "${this._mode}"`);
Menu.setApplicationMenu(this._menu);
}
private buildBaseMenu () {
if (Object.keys(this._menuTemplate).includes(process.platform))
this._menu = Menu.buildFromTemplate(this._menuTemplate[process.platform]);
else
this._menu = new Menu();
}
private setLocalShortcuts () {
for (const shortcut of this.shortcuts) {
if (shortcut.os.includes(process.platform)) {
for (const key of shortcut.keys) {
try {
this._menu.append(new MenuItem({
label: '.',
visible: false,
submenu: [{
label: String(key),
accelerator: key,
visible: false,
click: () => {
this._mainWindow.webContents.send(shortcut.event);
if (isDevelopment) console.log('LOCAL EVENT:', shortcut);
}
}]
}));
}
catch (error) {
if (isDevelopment) console.log(error);
this.restoreDefaults();
throw error;
}
}
}
}
}
private setGlobalShortcuts () {
for (const shortcut of this.shortcuts) {
if (shortcut.os.includes(process.platform)) {
for (const key of shortcut.keys) {
try {
globalShortcut.register(key, () => {
this._mainWindow.webContents.send(shortcut.event);
if (isDevelopment) console.log('GLOBAL EVENT:', shortcut);
});
}
catch (error) {
if (isDevelopment) console.log(error);
this.restoreDefaults();
throw error;
}
}
}
}
}
reload () {
this.unregister();
this.init();
}
updateShortcuts (shortcuts: ShortcutRecord[]) {
this.shortcuts = shortcuts;
this.reload();
}
restoreDefaults () {
this.shortcuts = defaultShortcuts;
this.reload();
}
unregister () {
if (this._mode === 'global') globalShortcut.unregisterAll();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,8 @@ export class MySQLClient extends AntaresCore {
private _schema?: string; private _schema?: string;
private _runningConnections: Map<string, number>; private _runningConnections: Map<string, number>;
private _connectionsToCommit: Map<string, mysql.Connection | mysql.PoolConnection>; private _connectionsToCommit: Map<string, mysql.Connection | mysql.PoolConnection>;
private _keepaliveTimer: NodeJS.Timer;
private _keepaliveMs: number;
_connection?: mysql.Connection | mysql.Pool; _connection?: mysql.Connection | mysql.Pool;
_params: mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean}; _params: mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean};
@@ -52,6 +54,7 @@ export class MySQLClient extends AntaresCore {
this._schema = null; this._schema = null;
this._runningConnections = new Map(); this._runningConnections = new Map();
this._connectionsToCommit = new Map(); this._connectionsToCommit = new Map();
this._keepaliveMs = 10*60*1000;
} }
private _getType (field: mysql.FieldPacket & { columnType?: number; columnLength?: number }) { private _getType (field: mysql.FieldPacket & { columnType?: number; columnLength?: number }) {
@@ -147,7 +150,14 @@ export class MySQLClient extends AntaresCore {
if (this._params.ssh) { if (this._params.ssh) {
try { 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({ const tunnel = await this._ssh.addTunnel({
remoteAddr: this._params.host, remoteAddr: this._params.host,
@@ -175,6 +185,8 @@ export class MySQLClient extends AntaresCore {
destroy () { destroy () {
this._connection.end(); this._connection.end();
clearInterval(this._keepaliveTimer);
this._keepaliveTimer = undefined;
if (this._ssh) this._ssh.close(); if (this._ssh) this._ssh.close();
} }
@@ -192,14 +204,14 @@ export class MySQLClient extends AntaresCore {
// ANSI_QUOTES check // ANSI_QUOTES check
const [response] = await connection.query<mysql.RowDataPacket[]>('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\''); const [response] = await connection.query<mysql.RowDataPacket[]>('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\'');
const sqlMode = response[0]?.Value?.split(','); const sqlMode: string[] = response[0]?.Value?.split(',');
const hasAnsiQuotes = sqlMode.includes('ANSI_QUOTES'); const hasAnsiQuotes = sqlMode.includes('ANSI') || sqlMode.includes('ANSI_QUOTES');
if (this._params.readonly) if (this._params.readonly)
await connection.query('SET SESSION TRANSACTION READ ONLY'); await connection.query('SET SESSION TRANSACTION READ ONLY');
if (hasAnsiQuotes) if (hasAnsiQuotes)
await connection.query(`SET SESSION sql_mode = "${sqlMode.filter((m: string) => m !== 'ANSI_QUOTES').join(',')}"`); await connection.query(`SET SESSION sql_mode = '${sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`);
return connection; return connection;
} }
@@ -219,23 +231,36 @@ export class MySQLClient extends AntaresCore {
// ANSI_QUOTES check // ANSI_QUOTES check
const [res] = await connection.query<mysql.RowDataPacket[]>('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\''); const [res] = await connection.query<mysql.RowDataPacket[]>('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\'');
const sqlMode = res[0]?.Value?.split(','); const sqlMode: string[] = res[0]?.Value?.split(',');
const hasAnsiQuotes = sqlMode.includes('ANSI_QUOTES'); const hasAnsiQuotes = sqlMode.includes('ANSI') || sqlMode.includes('ANSI_QUOTES');
if (hasAnsiQuotes) if (hasAnsiQuotes)
await connection.query(`SET SESSION sql_mode = "${sqlMode.filter((m: string) => m !== 'ANSI_QUOTES').join(',')}"`); 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 => { connection.on('connection', conn => {
if (this._params.readonly) if (this._params.readonly)
conn.query('SET SESSION TRANSACTION READ ONLY'); conn.query('SET SESSION TRANSACTION READ ONLY');
if (hasAnsiQuotes) if (hasAnsiQuotes)
conn.query(`SET SESSION sql_mode = "${sqlMode.filter((m: string) => m !== 'ANSI_QUOTES').join(',')}"`); 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; return connection;
} }
private async keepAlive () {
const connection = await (this._connection as mysql.Pool).getConnection();
await connection.ping();
connection.release();
}
use (schema: string) { use (schema: string) {
this._schema = schema; this._schema = schema;
return this.raw(`USE \`${schema}\``); return this.raw(`USE \`${schema}\``);
@@ -244,25 +269,15 @@ export class MySQLClient extends AntaresCore {
async getStructure (schemas: Set<string>) { async getStructure (schemas: Set<string>) {
/* eslint-disable camelcase */ /* eslint-disable camelcase */
interface ShowTableResult { interface ShowTableResult {
Db?: string; TABLE_SCHEMA?: string;
Name: string; TABLE_NAME: string;
Engine: string; TABLE_TYPE: string;
Version: number; TABLE_ROWS: number;
Row_format: string; ENGINE: string;
Rows: number; DATA_LENGTH: number;
Avg_row_length: number; INDEX_LENGTH: number;
Data_length: number; TABLE_COLLATION: string;
Max_data_length: number; TABLE_COMMENT: string;
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;
} }
interface ShowTriggersResult { interface ShowTriggersResult {
@@ -290,22 +305,69 @@ export class MySQLClient extends AntaresCore {
const { rows: functions } = await this.raw('SHOW FUNCTION STATUS'); const { rows: functions } = await this.raw('SHOW FUNCTION STATUS');
const { rows: procedures } = await this.raw('SHOW PROCEDURE 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 tablesArr: ShowTableResult[] = [];
const triggersArr: ShowTriggersResult[] = []; const triggersArr: ShowTriggersResult[] = [];
let schemaSize = 0; let schemaSize = 0;
const Store = require('electron-store');
Store.initRenderer();
const settingsStore = new Store({ name: 'settings' });
for (const db of filteredDatabases) { for (const db of filteredDatabases) {
if (!schemas.has(db.Database)) continue; 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 (tables.length) {
tables = tables.map(table => { if (showTableSize) {
table.Db = db.Database; let { rows: tables } = await this.raw<antares.QueryResult<ShowTableResult>>(`
return table; SELECT
}); TABLE_NAME,
tablesArr.push(...tables); 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.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}\``); let { rows: triggers } = await this.raw<antares.QueryResult<ShowTriggersResult>>(`SHOW TRIGGERS FROM \`${db.Database}\``);
@@ -321,9 +383,9 @@ export class MySQLClient extends AntaresCore {
return filteredDatabases.map(db => { return filteredDatabases.map(db => {
if (schemas.has(db.Database)) { if (schemas.has(db.Database)) {
// TABLES // 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; let tableType;
switch (table.Comment) { switch (table.TABLE_TYPE) {
case 'VIEW': case 'VIEW':
tableType = 'view'; tableType = 'view';
break; break;
@@ -332,20 +394,17 @@ export class MySQLClient extends AntaresCore {
break; break;
} }
const tableSize = Number(table.Data_length) + Number(table.Index_length); const tableSize = Number(table.DATA_LENGTH) + Number(table.INDEX_LENGTH);
schemaSize += tableSize; schemaSize += tableSize;
return { return {
name: table.Name, name: table.TABLE_NAME,
type: tableType, type: tableType,
rows: table.Rows, rows: table.TABLE_ROWS,
created: table.Create_time, engine: table.ENGINE,
updated: table.Update_time, comment: table.TABLE_COMMENT,
engine: table.Engine,
comment: table.Comment,
size: tableSize, size: tableSize,
autoIncrement: table.Auto_increment, collation: table.TABLE_COLLATION
collation: table.Collation
}; };
}); });
@@ -469,7 +528,12 @@ export class MySQLClient extends AntaresCore {
.orderBy({ ORDINAL_POSITION: 'ASC' }) .orderBy({ ORDINAL_POSITION: 'ASC' })
.run<TableColumnsResult>(); .run<TableColumnsResult>();
const { rows: fields } = await this.raw<antares.QueryResult<CreateTableResult>>(`SHOW CREATE TABLE \`${schema}\`.\`${table}\``); let fields: CreateTableResult[] = [];
try {
const { rows } = await this.raw<antares.QueryResult<CreateTableResult>>(`SHOW CREATE TABLE \`${schema}\`.\`${table}\``);
fields = rows;
}
catch (_) {}
const remappedFields = fields.map(row => { const remappedFields = fields.map(row => {
if (!row['Create Table']) return false; if (!row['Create Table']) return false;
@@ -625,11 +689,11 @@ export class MySQLClient extends AntaresCore {
return rows.map(row => { return rows.map(row => {
return { return {
unique: !row.Non_unique, unique: !Number(row.Non_unique),
name: row.Key_name, name: row.Key_name,
column: row.Column_name, column: row.Column_name,
indexType: row.Index_type, 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, cardinality: row.Cardinality,
comment: row.Comment, comment: row.Comment,
indexComment: row.Index_comment indexComment: row.Index_comment
@@ -637,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 }) { async getKeyUsage ({ schema, table }: { schema: string; table: string }) {
interface KeyResult { interface KeyResult {
TABLE_SCHEMA: string; TABLE_SCHEMA: string;
@@ -689,7 +764,7 @@ export class MySQLClient extends AntaresCore {
} }
async getUsers () { 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 rows.map(row => {
return { return {
@@ -940,7 +1015,7 @@ export class MySQLClient extends AntaresCore {
return { return {
algorithm: algorithm[0]['Create View'].match(/(?<=CREATE ALGORITHM=).*?(?=\s)/gs)[0], algorithm: algorithm[0]['Create View'].match(/(?<=CREATE ALGORITHM=).*?(?=\s)/gs)[0],
definer: viewInfo[0].DEFINER, definer: viewInfo[0].DEFINER.split('@').map((str: string) => `\`${str}\``).join('@'),
security: viewInfo[0].SECURITY_TYPE, security: viewInfo[0].SECURITY_TYPE,
updateOption: viewInfo[0].CHECK_OPTION === 'NONE' ? '' : viewInfo[0].CHECK_OPTION, updateOption: viewInfo[0].CHECK_OPTION === 'NONE' ? '' : viewInfo[0].CHECK_OPTION,
sql: viewInfo[0].VIEW_DEFINITION, sql: viewInfo[0].VIEW_DEFINITION,
@@ -1397,7 +1472,7 @@ export class MySQLClient extends AntaresCore {
} }
async getVersion () { async getVersion () {
const sql = 'SHOW VARIABLES LIKE "%vers%"'; const sql = 'SHOW VARIABLES LIKE \'%vers%\'';
const { rows } = await this.raw(sql); const { rows } = await this.raw(sql);
return rows.reduce((acc, curr) => { return rows.reduce((acc, curr) => {
@@ -1536,7 +1611,7 @@ export class MySQLClient extends AntaresCore {
} }
async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) { async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) {
if (process.env.NODE_ENV === 'development') this._logger(sql); this._logger({ cUid: this._cUid, sql });
args = { args = {
nest: false, nest: false,
@@ -1588,7 +1663,8 @@ export class MySQLClient extends AntaresCore {
let timeStop: Date; let timeStop: Date;
let keysArr: antares.QueryForeign[] = []; let keysArr: antares.QueryForeign[] = [];
const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any
const { rows, report, fields, keys, duration }: any = await new Promise((resolve, reject) => {
connection.query({ sql: query, nestTables }).then(async ([response, fields]) => { connection.query({ sql: query, nestTables }).then(async ([response, fields]) => {
timeStop = new Date(); timeStop = new Date();
const queryResult = response; const queryResult = response;
@@ -1598,7 +1674,7 @@ export class MySQLClient extends AntaresCore {
if (!field || Array.isArray(field)) if (!field || Array.isArray(field))
return undefined; return undefined;
const type = this._getType(field); const type = this._getType(field as undefined);
return { return {
name: field.orgName, name: field.orgName,

View File

@@ -1,6 +1,5 @@
import * as antares from 'common/interfaces/antares'; import * as antares from 'common/interfaces/antares';
import * as mysql from 'mysql2'; import * as mysql from 'mysql2';
import { builtinsTypes } from 'pg-types';
import * as pg from 'pg'; import * as pg from 'pg';
import * as pgAst from 'pgsql-ast-parser'; import * as pgAst from 'pgsql-ast-parser';
import { AntaresCore } from '../AntaresCore'; import { AntaresCore } from '../AntaresCore';
@@ -19,10 +18,74 @@ pg.types.setTypeParser(1114, pgToString); // timestamp
pg.types.setTypeParser(1184, pgToString); // timestamptz pg.types.setTypeParser(1184, pgToString); // timestamptz
pg.types.setTypeParser(1266, pgToString); // timetz pg.types.setTypeParser(1266, pgToString); // timetz
// from pg-types
type builtinsTypes =
'BOOL' |
'BYTEA' |
'CHAR' |
'INT8' |
'INT2' |
'INT4' |
'REGPROC' |
'TEXT' |
'OID' |
'TID' |
'XID' |
'CID' |
'JSON' |
'XML' |
'PG_NODE_TREE' |
'SMGR' |
'PATH' |
'POLYGON' |
'CIDR' |
'FLOAT4' |
'FLOAT8' |
'ABSTIME' |
'RELTIME' |
'TINTERVAL' |
'CIRCLE' |
'MACADDR8' |
'MONEY' |
'MACADDR' |
'INET' |
'ACLITEM' |
'BPCHAR' |
'VARCHAR' |
'DATE' |
'TIME' |
'TIMESTAMP' |
'TIMESTAMPTZ' |
'INTERVAL' |
'TIMETZ' |
'BIT' |
'VARBIT' |
'NUMERIC' |
'REFCURSOR' |
'REGPROCEDURE' |
'REGOPER' |
'REGOPERATOR' |
'REGCLASS' |
'REGTYPE' |
'UUID' |
'TXID_SNAPSHOT' |
'PG_LSN' |
'PG_NDISTINCT' |
'PG_DEPENDENCIES' |
'TSVECTOR' |
'TSQUERY' |
'GTSVECTOR' |
'REGCONFIG' |
'REGDICTIONARY' |
'JSONB' |
'REGNAMESPACE' |
'REGROLE';
export class PostgreSQLClient extends AntaresCore { export class PostgreSQLClient extends AntaresCore {
private _schema?: string; private _schema?: string;
private _runningConnections: Map<string, number>; private _runningConnections: Map<string, number>;
private _connectionsToCommit: Map<string, pg.Client | pg.PoolClient>; private _connectionsToCommit: Map<string, pg.Client | pg.PoolClient>;
private _keepaliveTimer: NodeJS.Timer;
private _keepaliveMs: number;
protected _connection?: pg.Client | pg.Pool; protected _connection?: pg.Client | pg.Pool;
private types: {[key: string]: string} = {}; private types: {[key: string]: string} = {};
private _arrayTypes: {[key: string]: string} = { private _arrayTypes: {[key: string]: string} = {
@@ -43,6 +106,7 @@ export class PostgreSQLClient extends AntaresCore {
this._schema = null; this._schema = null;
this._runningConnections = new Map(); this._runningConnections = new Map();
this._connectionsToCommit = new Map(); this._connectionsToCommit = new Map();
this._keepaliveMs = 10*60*1000;
for (const key in pg.types.builtins) { for (const key in pg.types.builtins) {
const builtinKey = key as builtinsTypes; const builtinKey = key as builtinsTypes;
@@ -90,7 +154,7 @@ export class PostgreSQLClient extends AntaresCore {
host: this._params.host, host: this._params.host,
port: this._params.port, port: this._params.port,
user: this._params.user, user: this._params.user,
database: undefined as string | undefined, database: 'postgres' as string,
password: this._params.password, password: this._params.password,
ssl: null as mysql.SslOptions ssl: null as mysql.SslOptions
}; };
@@ -101,7 +165,11 @@ export class PostgreSQLClient extends AntaresCore {
if (this._params.ssh) { if (this._params.ssh) {
try { 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({ const tunnel = await this._ssh.addTunnel({
remoteAddr: this._params.host, remoteAddr: this._params.host,
@@ -157,14 +225,26 @@ export class PostgreSQLClient extends AntaresCore {
}); });
} }
this._keepaliveTimer = setInterval(async () => {
await this.keepAlive();
}, this._keepaliveMs);
return connection; return connection;
} }
destroy () { destroy () {
this._connection.end(); this._connection.end();
clearInterval(this._keepaliveTimer);
this._keepaliveTimer = undefined;
if (this._ssh) this._ssh.close(); 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) { use (schema: string, connection?: pg.Client | pg.PoolClient) {
this._schema = schema; this._schema = schema;
@@ -182,6 +262,18 @@ export class PostgreSQLClient extends AntaresCore {
return []; 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>) { async getStructure (schemas: Set<string>) {
/* eslint-disable camelcase */ /* eslint-disable camelcase */
interface ShowTableResult { interface ShowTableResult {
@@ -479,11 +571,7 @@ export class PostgreSQLClient extends AntaresCore {
return { return {
name: row.constraint_name, name: row.constraint_name,
column: row.column_name, column: row.column_name,
indexType: null as null, type: row.constraint_type
type: row.constraint_type,
cardinality: null as null,
comment: '',
indexComment: ''
}; };
}); });
} }
@@ -506,6 +594,142 @@ export class PostgreSQLClient extends AntaresCore {
}, {} as {table: string; schema: string}[]); }, {} 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 }) { async getKeyUsage ({ schema, table }: { schema: string; table: string }) {
/* eslint-disable camelcase */ /* eslint-disable camelcase */
interface KeyResult { interface KeyResult {
@@ -1314,7 +1538,7 @@ export class PostgreSQLClient extends AntaresCore {
} }
async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) { async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) {
if (process.env.NODE_ENV === 'development') this._logger(sql); this._logger({ cUid: this._cUid, sql });
args = { args = {
nest: false, nest: false,
@@ -1365,7 +1589,8 @@ export class PostgreSQLClient extends AntaresCore {
let timeStop: Date; let timeStop: Date;
let keysArr: antares.QueryForeign[] = []; let keysArr: antares.QueryForeign[] = [];
const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any
const { rows, report, fields, keys, duration }: any = await new Promise((resolve, reject) => {
(async () => { (async () => {
try { try {
const res = await connection.query({ rowMode: args.nest ? 'array' : null, text: query }); const res = await connection.query({ rowMode: args.nest ? 'array' : null, text: query });

View File

@@ -217,11 +217,7 @@ export class SQLiteClient extends AntaresCore {
remappedIndexes.push({ remappedIndexes.push({
name: 'PRIMARY', name: 'PRIMARY',
column: key.name, column: key.name,
indexType: null as never, type: 'PRIMARY'
type: 'PRIMARY',
cardinality: null as never,
comment: '',
indexComment: ''
}); });
} }
@@ -348,6 +344,20 @@ export class SQLiteClient extends AntaresCore {
const tmpName = `Antares_${table}_tmp`; const tmpName = `Antares_${table}_tmp`;
await this.raw(`CREATE TABLE "${tmpName}" AS SELECT * FROM "${table}"`); 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); await this.dropTable(params);
const createTableParams = { const createTableParams = {
@@ -380,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.raw(`INSERT INTO "${createTableParams.options.name}" (${insertFields.join(',')}) SELECT ${selectFields.join(',')} FROM "${tmpName}"`);
await this.dropTable({ schema: schema, table: 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('PRAGMA foreign_keys = 1');
await this.raw('COMMIT'); await this.raw('COMMIT');
} }
@@ -586,7 +601,7 @@ export class SQLiteClient extends AntaresCore {
} }
async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) { async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) {
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder this._logger({ cUid: this._cUid, sql });// TODO: replace BLOB content with a placeholder
args = { args = {
nest: false, nest: false,
@@ -628,7 +643,8 @@ export class SQLiteClient extends AntaresCore {
let timeStop; let timeStop;
const keysArr: antares.QueryForeign[] = []; const keysArr: antares.QueryForeign[] = [];
const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any
const { rows, report, fields, keys, duration }: any = await new Promise((resolve, reject) => {
(async () => { (async () => {
let queryRunResult: sqlite.RunResult; let queryRunResult: sqlite.RunResult;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -675,8 +691,8 @@ export class SQLiteClient extends AntaresCore {
if ([...TIME, ...DATETIME].includes(parsedType)) { if ([...TIME, ...DATETIME].includes(parsedType)) {
const firstNotNull = queryAllResult.find(res => res[field.name] !== null); const firstNotNull = queryAllResult.find(res => res[field.name] !== null);
if (firstNotNull && firstNotNull[field.name].includes('.')) if (firstNotNull && String(firstNotNull[field.name]).includes('.'))
length = firstNotNull[field.name].split('.').pop().length; length = String(firstNotNull[field.name]).split('.').pop().length;
} }
return { return {

View File

@@ -1,12 +1,8 @@
import * as exporter from 'common/interfaces/exporter'; import * as exporter from 'common/interfaces/exporter';
import * as mysql from 'mysql2/promise'; import * as mysql from 'mysql2/promise';
import { SqlExporter } from './SqlExporter'; import { SqlExporter } from './SqlExporter';
import { BLOB, BIT, DATE, DATETIME, FLOAT, SPATIAL, IS_MULTI_SPATIAL, NUMBER } from 'common/fieldTypes';
import hexToBinary, { HexChar } from 'common/libs/hexToBinary';
import { getArrayDepth } from 'common/libs/getArrayDepth';
import * as moment from 'moment';
import { lineString, point, polygon } from '@turf/helpers';
import { MySQLClient } from '../../clients/MySQLClient'; import { MySQLClient } from '../../clients/MySQLClient';
import { valueToSqlString } from 'common/libs/sqlUtils';
export default class MysqlExporter extends SqlExporter { export default class MysqlExporter extends SqlExporter {
protected _client: MySQLClient; protected _client: MySQLClient;
@@ -122,54 +118,7 @@ ${footer}
const column = notGeneratedColumns[i]; const column = notGeneratedColumns[i];
const val = row[column.name]; const val = row[column.name];
if (val === null) sqlInsertString += 'NULL'; sqlInsertString += valueToSqlString({ val, client: 'mysql', field: column });
else if (DATE.includes(column.type)) {
sqlInsertString += moment(val).isValid()
? this.escapeAndQuote(moment(val).format('YYYY-MM-DD'))
: val;
}
else if (DATETIME.includes(column.type)) {
let datePrecision = '';
for (let i = 0; i < column.datePrecision; i++)
datePrecision += i === 0 ? '.S' : 'S';
sqlInsertString += moment(val).isValid()
? this.escapeAndQuote(moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`))
: this.escapeAndQuote(val);
}
else if (BIT.includes(column.type))
sqlInsertString += `b'${hexToBinary(Buffer.from(val).toString('hex') as undefined as HexChar[])}'`;
else if (BLOB.includes(column.type))
sqlInsertString += `X'${val.toString('hex').toUpperCase()}'`;
else if (NUMBER.includes(column.type))
sqlInsertString += val;
else if (FLOAT.includes(column.type))
sqlInsertString += parseFloat(val);
else if (SPATIAL.includes(column.type)) {
let geoJson;
if (IS_MULTI_SPATIAL.includes(column.type)) {
const features = [];
for (const element of val)
features.push(this._getGeoJSON(element));
geoJson = {
type: 'FeatureCollection',
features
};
}
else
geoJson = this._getGeoJSON(val);
sqlInsertString += `ST_GeomFromGeoJSON('${JSON.stringify(geoJson)}')`;
}
else if (val === '') sqlInsertString += '\'\'';
else {
sqlInsertString += typeof val === 'string'
? this.escapeAndQuote(val)
: typeof val === 'object'
? this.escapeAndQuote(JSON.stringify(val))
: val;
}
if (parseInt(i) !== notGeneratedColumns.length - 1) if (parseInt(i) !== notGeneratedColumns.length - 1)
sqlInsertString += ', '; sqlInsertString += ', ';
@@ -235,9 +184,9 @@ CREATE TABLE \`${view.Name}\`(
const { rows: triggers } = await this._client.raw( const { rows: triggers } = await this._client.raw(
`SHOW TRIGGERS FROM \`${this.schemaName}\`` `SHOW TRIGGERS FROM \`${this.schemaName}\``
); );
const generatedTables = this._tables // const generatedTables = this._tables
.filter(t => t.includeStructure) // .filter(t => t.includeStructure)
.map(t => t.table); // .map(t => t.table);
let sqlString = ''; let sqlString = '';
@@ -251,7 +200,7 @@ CREATE TABLE \`${view.Name}\`(
sql_mode: sqlMode sql_mode: sqlMode
} = trigger; } = 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); const definer = this.getEscapedDefiner(trigger.Definer);
sqlString += '/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;;\n'; sqlString += '/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;;\n';
@@ -435,17 +384,4 @@ CREATE TABLE \`${view.Name}\`(
return `'${escapedVal}'`; return `'${escapedVal}'`;
} }
/* eslint-disable @typescript-eslint/no-explicit-any */
_getGeoJSON (val: any) {
if (Array.isArray(val)) {
if (getArrayDepth(val) === 1)
return lineString(val.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []));
else
return polygon(val.map(arr => arr.reduce((acc: any, curr: any) => [...acc, [curr.x, curr.y]], [])));
}
else
return point([val.x, val.y]);
}
/* eslint-enable @typescript-eslint/no-explicit-any */
} }

View File

@@ -1,13 +1,11 @@
import * as antares from 'common/interfaces/antares'; import * as antares from 'common/interfaces/antares';
import * as exporter from 'common/interfaces/exporter'; import * as exporter from 'common/interfaces/exporter';
import { SqlExporter } from './SqlExporter'; import { SqlExporter } from './SqlExporter';
import { BLOB, BIT, DATE, DATETIME, FLOAT, NUMBER, TEXT_SEARCH } from 'common/fieldTypes';
import hexToBinary, { HexChar } from 'common/libs/hexToBinary';
import * as moment from 'moment';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
import * as QueryStream from 'pg-query-stream'; import * as QueryStream from 'pg-query-stream';
import { PostgreSQLClient } from '../../clients/PostgreSQLClient'; import { PostgreSQLClient } from '../../clients/PostgreSQLClient';
import { valueToSqlString } from 'common/libs/sqlUtils';
export default class PostgreSQLExporter extends SqlExporter { export default class PostgreSQLExporter extends SqlExporter {
constructor (client: PostgreSQLClient, tables: exporter.TableParams[], options: exporter.ExportOptions) { constructor (client: PostgreSQLClient, tables: exporter.TableParams[], options: exporter.ExportOptions) {
@@ -223,47 +221,7 @@ SET row_security = off;\n\n\n`;
const column = columns[i]; const column = columns[i];
const val = row[column.name]; const val = row[column.name];
if (val === null) sqlInsertString += 'NULL'; sqlInsertString += valueToSqlString({ val, client: 'pg', field: column });
else if (DATE.includes(column.type)) {
sqlInsertString += moment(val).isValid()
? this.escapeAndQuote(moment(val).format('YYYY-MM-DD'))
: val;
}
else if (DATETIME.includes(column.type)) {
let datePrecision = '';
for (let i = 0; i < column.datePrecision; i++)
datePrecision += i === 0 ? '.S' : 'S';
sqlInsertString += moment(val).isValid()
? this.escapeAndQuote(moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`))
: this.escapeAndQuote(val);
}
else if ('isArray' in column) {
let parsedVal;
if (Array.isArray(val))
parsedVal = JSON.stringify(val).replaceAll('[', '{').replaceAll(']', '}');
else
parsedVal = typeof val === 'string' ? val.replaceAll('[', '{').replaceAll(']', '}') : '';
sqlInsertString += `'${parsedVal}'`;
}
else if (TEXT_SEARCH.includes(column.type))
sqlInsertString += `'${val.replaceAll('\'', '\'\'')}'`;
else if (BIT.includes(column.type))
sqlInsertString += `b'${hexToBinary(Buffer.from(val).toString('hex') as undefined as HexChar[])}'`;
else if (BLOB.includes(column.type))
sqlInsertString += `decode('${val.toString('hex').toUpperCase()}', 'hex')`;
else if (NUMBER.includes(column.type))
sqlInsertString += val;
else if (FLOAT.includes(column.type))
sqlInsertString += parseFloat(val);
else if (val === '') sqlInsertString += '\'\'';
else {
sqlInsertString += typeof val === 'string'
? this.escapeAndQuote(val)
: typeof val === 'object'
? this.escapeAndQuote(JSON.stringify(val))
: val;
}
if (parseInt(i) !== columns.length - 1) if (parseInt(i) !== columns.length - 1)
sqlInsertString += ', '; sqlInsertString += ', ';

View File

@@ -1,15 +1,15 @@
import { app, BrowserWindow, /* session, */ nativeImage, Menu, ipcMain } from 'electron'; import { app, BrowserWindow, nativeImage, ipcMain } from 'electron';
import * as path from 'path'; import * as path from 'path';
import * as Store from 'electron-store'; import * as Store from 'electron-store';
import * as windowStateKeeper from 'electron-window-state'; import * as windowStateKeeper from 'electron-window-state';
import * as remoteMain from '@electron/remote/main'; import * as remoteMain from '@electron/remote/main';
import ipcHandlers from './ipc-handlers'; import ipcHandlers from './ipc-handlers';
import { OsMenu, ShortcutRegister } from './libs/ShortcutRegister';
Store.initRenderer(); Store.initRenderer();
const persistentStore = new Store({ name: 'settings' }); const settingsStore = new Store({ name: 'settings' });
const appTheme = settingsStore.get('application_theme');
const appTheme = persistentStore.get('application_theme');
const isDevelopment = process.env.NODE_ENV !== 'production'; const isDevelopment = process.env.NODE_ENV !== 'production';
const isMacOS = process.platform === 'darwin'; const isMacOS = process.platform === 'darwin';
const isLinux = process.platform === 'linux'; const isLinux = process.platform === 'linux';
@@ -37,6 +37,7 @@ async function createMainWindow () {
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,
contextIsolation: false, contextIsolation: false,
devTools: isDevelopment,
spellcheck: false spellcheck: false
}, },
autoHideMenuBar: true, autoHideMenuBar: true,
@@ -85,7 +86,7 @@ else {
ipcHandlers(); ipcHandlers();
ipcMain.on('refresh-theme-settings', () => { ipcMain.on('refresh-theme-settings', () => {
const appTheme = persistentStore.get('application_theme'); const appTheme = settingsStore.get('application_theme');
if (isWindows && mainWindow) { if (isWindows && mainWindow) {
mainWindow.setTitleBarOverlay({ mainWindow.setTitleBarOverlay({
color: appTheme === 'dark' ? '#3f3f3f' : '#fff', color: appTheme === 'dark' ? '#3f3f3f' : '#fff',
@@ -123,8 +124,8 @@ else {
if (isWindows) if (isWindows)
mainWindow.show(); mainWindow.show();
if (isDevelopment) // if (isDevelopment)
mainWindow.webContents.openDevTools(); // mainWindow.webContents.openDevTools();
process.on('uncaughtException', error => { process.on('uncaughtException', error => {
mainWindow.webContents.send('unhandled-exception', error); mainWindow.webContents.send('unhandled-exception', error);
@@ -145,10 +146,8 @@ else {
} }
function createAppMenu () { function createAppMenu () {
let menu: Electron.Menu = null; const menuTemplate: OsMenu = {
darwin: [
if (isMacOS) {
menu = Menu.buildFromTemplate([
{ {
label: app.name, label: app.name,
submenu: [ submenu: [
@@ -173,16 +172,14 @@ function createAppMenu () {
{ {
role: 'editMenu' role: 'editMenu'
}, },
{
role: 'viewMenu'
},
{ {
role: 'windowMenu' role: 'windowMenu'
} }
]); ]
} };
Menu.setApplicationMenu(menu); const shortCutRegister = ShortcutRegister.getInstance({ mainWindow, menuTemplate, mode: 'local' });
shortCutRegister.init();
} }
function saveWindowState () { function saveWindowState () {

View File

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

View File

@@ -6,72 +6,106 @@ import { PostgreSQLClient } from '../libs/clients/PostgreSQLClient';
import { ClientsFactory } from '../libs/ClientsFactory'; import { ClientsFactory } from '../libs/ClientsFactory';
import MySQLImporter from '../libs/importers/sql/MySQLlImporter'; import MySQLImporter from '../libs/importers/sql/MySQLlImporter';
import PostgreSQLImporter from '../libs/importers/sql/PostgreSQLImporter'; import PostgreSQLImporter from '../libs/importers/sql/PostgreSQLImporter';
import SSHConfig from 'ssh2-promise/lib/sshConfig';
import { ImportOptions } from 'common/interfaces/importer';
let importer: antares.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') { if (type === 'init') {
const connection = await ClientsFactory.getClient({ try {
client: options.type, const connection = await ClientsFactory.getClient({
params: { client: options.type,
...dbConfig, params: {
schema: options.schema ...dbConfig,
}, schema: options.schema
poolSize: 1 },
}) as MySQLClient | PostgreSQLClient; poolSize: 1
}) as MySQLClient | PostgreSQLClient;
const pool = await connection.getConnectionPool(); const pool = await connection.getConnectionPool();
switch (options.type) { switch (options.type) {
case 'mysql': case 'mysql':
case 'maria': case 'maria':
importer = new MySQLImporter(pool as unknown as mysql.Pool, options); importer = new MySQLImporter(pool as unknown as mysql.Pool, options);
break; break;
case 'pg': case 'pg':
importer = new PostgreSQLImporter(pool as unknown as pg.PoolClient, options); importer = new PostgreSQLImporter(pool as unknown as pg.PoolClient, options);
break; break;
default: default:
process.send({
type: 'error',
payload: `"${options.type}" importer not aviable`
});
return;
}
importer.once('error', err => {
console.error(err);
process.send({ process.send({
type: 'error', type: 'error',
payload: `"${options.type}" importer not aviable` payload: err.toString()
}); });
return; });
}
importer.once('error', err => { importer.once('end', () => {
process.send({
type: 'end',
payload: { cancelled: importer.isCancelled }
});
});
importer.once('cancel', () => {
process.send({ type: 'cancel' });
});
importer.on('progress', state => {
process.send({
type: 'import-progress',
payload: state
});
});
importer.on('query-error', state => {
process.send({
type: 'query-error',
payload: state
});
});
importer.run();
}
catch (err) {
console.error(err); console.error(err);
process.send({ process.send({
type: 'error', type: 'error',
payload: err.toString() payload: err.toString()
}); });
}); }
importer.once('end', () => {
process.send({
type: 'end',
payload: { cancelled: importer.isCancelled }
});
});
importer.once('cancel', () => {
process.send({ type: 'cancel' });
});
importer.on('progress', state => {
process.send({
type: 'import-progress',
payload: state
});
});
importer.on('query-error', state => {
process.send({
type: 'query-error',
payload: state
});
});
importer.run();
} }
else if (type === 'cancel') else if (type === 'cancel')
importer.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

@@ -2,7 +2,7 @@
<div id="wrapper" :class="[`theme-${applicationTheme}`, !disableBlur || 'no-blur']"> <div id="wrapper" :class="[`theme-${applicationTheme}`, !disableBlur || 'no-blur']">
<TheTitleBar /> <TheTitleBar />
<div id="window-content"> <div id="window-content">
<TheSettingBar /> <TheSettingBar @show-connections-modal="isAllConnectionsModal = true" />
<div id="main-content" class="container"> <div id="main-content" class="container">
<div class="columns col-gapless"> <div class="columns col-gapless">
<Workspace <Workspace
@@ -10,7 +10,7 @@
:key="connection.uid" :key="connection.uid"
:connection="connection" :connection="connection"
/> />
<div class="connection-panel-wrapper"> <div class="connection-panel-wrapper p-relative">
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" /> <WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
</div> </div>
</div> </div>
@@ -21,13 +21,15 @@
<BaseTextEditor class="d-none" value="" /> <BaseTextEditor class="d-none" value="" />
</div> </div>
</div> </div>
<ModalAllConnections v-if="isAllConnectionsModal" @close="isAllConnectionsModal = false" />
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineAsyncComponent } from 'vue'; import { defineAsyncComponent, onMounted, Ref, ref } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import { useI18n } from 'vue-i18n';
import { Menu, getCurrentWindow } from '@electron/remote'; import { Menu, getCurrentWindow } from '@electron/remote';
import { useApplicationStore } from '@/stores/application'; import { useApplicationStore } from '@/stores/application';
import { useConnectionsStore } from '@/stores/connections'; import { useConnectionsStore } from '@/stores/connections';
@@ -35,98 +37,106 @@ import { useSettingsStore } from '@/stores/settings';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import TheSettingBar from '@/components/TheSettingBar.vue'; import TheSettingBar from '@/components/TheSettingBar.vue';
export default { const { t } = useI18n();
name: 'App',
components: {
TheTitleBar: defineAsyncComponent(() => import(/* webpackChunkName: "TheTitleBar" */'@/components/TheTitleBar.vue')),
TheSettingBar,
TheFooter: defineAsyncComponent(() => import(/* webpackChunkName: "TheFooter" */'@/components/TheFooter.vue')),
TheNotificationsBoard: defineAsyncComponent(() => import(/* webpackChunkName: "TheNotificationsBoard" */'@/components/TheNotificationsBoard.vue')),
Workspace: defineAsyncComponent(() => import(/* webpackChunkName: "Workspace" */'@/components/Workspace.vue')),
WorkspaceAddConnectionPanel: defineAsyncComponent(() => import(/* webpackChunkName: "WorkspaceAddConnectionPanel" */'@/components/WorkspaceAddConnectionPanel.vue')),
ModalSettings: defineAsyncComponent(() => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings.vue')),
TheScratchpad: defineAsyncComponent(() => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad.vue')),
BaseTextEditor: defineAsyncComponent(() => import(/* webpackChunkName: "BaseTextEditor" */'@/components/BaseTextEditor.vue'))
},
setup () {
const applicationStore = useApplicationStore();
const connectionsStore = useConnectionsStore();
const settingsStore = useSettingsStore();
const workspacesStore = useWorkspacesStore();
const { const TheTitleBar = defineAsyncComponent(() => import(/* webpackChunkName: "TheTitleBar" */'@/components/TheTitleBar.vue'));
isLoading, const TheFooter = defineAsyncComponent(() => import(/* webpackChunkName: "TheFooter" */'@/components/TheFooter.vue'));
isSettingModal, const TheNotificationsBoard = defineAsyncComponent(() => import(/* webpackChunkName: "TheNotificationsBoard" */'@/components/TheNotificationsBoard.vue'));
isScratchpad const Workspace = defineAsyncComponent(() => import(/* webpackChunkName: "Workspace" */'@/components/Workspace.vue'));
} = storeToRefs(applicationStore); const WorkspaceAddConnectionPanel = defineAsyncComponent(() => import(/* webpackChunkName: "WorkspaceAddConnectionPanel" */'@/components/WorkspaceAddConnectionPanel.vue'));
const { connections } = storeToRefs(connectionsStore); const ModalSettings = defineAsyncComponent(() => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings.vue'));
const { applicationTheme, disableBlur } = storeToRefs(settingsStore); const ModalAllConnections = defineAsyncComponent(() => import(/* webpackChunkName: "ModalAllConnections" */'@/components/ModalAllConnections.vue'));
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const TheScratchpad = defineAsyncComponent(() => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad.vue'));
const BaseTextEditor = defineAsyncComponent(() => import(/* webpackChunkName: "BaseTextEditor" */'@/components/BaseTextEditor.vue'));
const { checkVersionUpdate } = applicationStore; const applicationStore = useApplicationStore();
const { changeApplicationTheme } = settingsStore; const connectionsStore = useConnectionsStore();
const settingsStore = useSettingsStore();
const workspacesStore = useWorkspacesStore();
document.addEventListener('DOMContentLoaded', () => { const {
setTimeout(() => { isSettingModal,
changeApplicationTheme(applicationTheme.value);// Forces persistentStore to save on file and mail process isScratchpad
}, 1000); } = storeToRefs(applicationStore);
}); const { connections } = storeToRefs(connectionsStore);
const { applicationTheme, disableBlur } = storeToRefs(settingsStore);
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
return { const { checkVersionUpdate } = applicationStore;
isLoading, const { changeApplicationTheme } = settingsStore;
isSettingModal,
isScratchpad,
checkVersionUpdate,
changeApplicationTheme,
connections,
applicationTheme,
disableBlur,
selectedWorkspace
};
},
mounted () {
ipcRenderer.send('check-for-updates');
this.checkVersionUpdate();
const InputMenu = Menu.buildFromTemplate([ const isAllConnectionsModal: Ref<boolean> = ref(false);
{
label: this.$t('word.cut'), document.addEventListener('DOMContentLoaded', () => {
role: 'cut' setTimeout(() => {
}, changeApplicationTheme(applicationTheme.value);// Forces persistentStore to save on file and mail process
{ }, 1000);
label: this.$t('word.copy'), });
role: 'copy'
}, onMounted(() => {
{ ipcRenderer.on('open-all-connections', () => {
label: this.$t('word.paste'), isAllConnectionsModal.value = true;
role: 'paste' });
},
{ ipcRenderer.on('open-scratchpad', () => {
type: 'separator' isScratchpad.value = true;
}, });
{
label: this.$t('message.selectAll'), ipcRenderer.on('open-settings', () => {
role: 'selectAll' isSettingModal.value = true;
});
ipcRenderer.on('create-connection', () => {
workspacesStore.selectWorkspace('NEW');
});
ipcRenderer.send('check-for-updates');
checkVersionUpdate();
const InputMenu = Menu.buildFromTemplate([
{
label: t('word.cut'),
role: 'cut'
},
{
label: t('word.copy'),
role: 'copy'
},
{
label: t('word.paste'),
role: 'paste'
},
{
type: 'separator'
},
{
label: t('message.selectAll'),
role: 'selectAll'
}
]);
document.body.addEventListener('contextmenu', (e) => {
e.preventDefault();
e.stopPropagation();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let node: any = e.target;
while (node) {
if (node.nodeName.match(/^(input|textarea)$/i) || node.isContentEditable) {
InputMenu.popup({ window: getCurrentWindow() });
break;
} }
]); node = node.parentNode;
}
});
document.body.addEventListener('contextmenu', (e) => { document.addEventListener('keydown', e => {
if (e.altKey && e.key === 'Alt') { // Prevent Alt key to trigger hidden shortcut menu
e.preventDefault(); e.preventDefault();
e.stopPropagation(); }
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any });
let node: any = e.target;
while (node) {
if (node.nodeName.match(/^(input|textarea)$/i) || node.isContentEditable) {
InputMenu.popup({ window: getCurrentWindow() });
break;
}
node = node.parentNode;
}
});
}
};
</script> </script>
<style lang="scss"> <style lang="scss">
@@ -159,7 +169,7 @@ export default {
.connection-panel-wrapper { .connection-panel-wrapper {
height: calc(100vh - #{$excluding-size}); height: calc(100vh - #{$excluding-size});
width: 100%; width: 100%;
padding-top: 15vh; padding-top: 10vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: flex-start; align-items: flex-start;

View File

@@ -31,13 +31,13 @@
class="btn btn-primary mr-2" class="btn btn-primary mr-2"
@click.stop="confirmModal" @click.stop="confirmModal"
> >
{{ confirmText || $t('word.confirm') }} {{ confirmText || t('word.confirm') }}
</button> </button>
<button <button
class="btn btn-link" class="btn btn-link"
@click="hideModal" @click="hideModal"
> >
{{ cancelText || $t('word.cancel') }} {{ cancelText || t('word.cancel') }}
</button> </button>
</div> </div>
</div> </div>
@@ -49,6 +49,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { useFocusTrap } from '@/composables/useFocusTrap'; import { useFocusTrap } from '@/composables/useFocusTrap';
import { computed, onBeforeUnmount, PropType, useSlots } from 'vue'; import { computed, onBeforeUnmount, PropType, useSlots } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({ const props = defineProps({
size: { size: {
@@ -65,6 +68,10 @@ const props = defineProps({
disableAutofocus: { disableAutofocus: {
type: Boolean, type: Boolean,
default: false default: false
},
closeOnConfirm: {
type: Boolean,
default: true
} }
}); });
const emit = defineEmits(['confirm', 'hide']); const emit = defineEmits(['confirm', 'hide']);
@@ -87,7 +94,7 @@ const modalSizeClass = computed(() => {
const confirmModal = () => { const confirmModal = () => {
emit('confirm'); emit('confirm');
hideModal(); if (props.closeOnConfirm) hideModal();
}; };
const hideModal = () => { const hideModal = () => {

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="context" :class="{'bottom': isBottom}"> <div class="context" :class="[{ 'bottom': isBottom }, { 'right': isRight }]">
<a <a
class="context-overlay" class="context-overlay"
@click="close" @click="close"
@@ -19,9 +19,10 @@
import { computed, onBeforeUnmount, onMounted, Ref, ref } from 'vue'; import { computed, onBeforeUnmount, onMounted, Ref, ref } from 'vue';
const contextContent: Ref<HTMLDivElement> = ref(null); 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 isBottom: Ref<boolean> = ref(false);
const props = defineProps<{contextEvent: MouseEvent}>(); const isRight: Ref<boolean> = ref(false);
const props = defineProps<{ contextEvent: MouseEvent }>();
const emit = defineEmits(['close-context']); const emit = defineEmits(['close-context']);
const position = computed(() => { const position = computed(() => {
@@ -39,8 +40,16 @@ const position = computed(() => {
isBottom.value = true; 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`; 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); window.addEventListener('keydown', onKey);
onMounted(() => { onMounted(() => {
if (contextContent.value) if (contextContent.value) {
contextSize.value = contextContent.value.getBoundingClientRect(); 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(() => { onBeforeUnmount(() => {
@@ -73,88 +94,94 @@ onBeforeUnmount(() => {
<style lang="scss"> <style lang="scss">
.context { .context {
display: flex; display: flex;
font-size: 16px; font-size: 16px;
z-index: 400; z-index: 400;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
overflow: hidden; overflow: hidden;
position: fixed; position: fixed;
height: 100vh; height: 100vh;
right: 0; right: 0;
top: 0; top: 0;
left: 0; left: 0;
bottom: 0; bottom: 0;
&:not(.bottom) .context-submenu { &:not(.bottom) .context-submenu {
top: -0.2rem; top: -0.2rem;
} }
&.bottom .context-submenu { &.bottom .context-submenu {
bottom: -0.2rem; bottom: -0.2rem;
} }
.context-container { &:not(.right) .context-submenu {
min-width: 100px; left: 100%;
z-index: 10; }
padding: 0;
background: #1d1d1d;
border-radius: $border-radius;
border: 1px solid $bg-color-light-dark;
display: flex;
flex-direction: column;
position: absolute;
pointer-events: initial;
.context-element { &.right .context-submenu {
display: flex; right: 100%;
align-items: center; }
margin: 0.2rem;
padding: 0.1rem 0.3rem; .context-container {
min-width: 100px;
z-index: 10;
padding: 0;
background: #1d1d1d;
border-radius: $border-radius; border-radius: $border-radius;
cursor: pointer; border: 1px solid $bg-color-light-dark;
justify-content: space-between; display: flex;
position: relative; flex-direction: column;
white-space: nowrap; position: absolute;
pointer-events: initial;
.context-submenu { .context-element {
border-radius: $border-radius; display: flex;
border: 1px solid $bg-color-light-dark; align-items: center;
opacity: 0; margin: 0.2rem;
visibility: hidden; padding: 0.1rem 0.3rem;
transition: opacity 0.2s; border-radius: $border-radius;
position: absolute; cursor: pointer;
left: 100%; justify-content: space-between;
min-width: 100px; position: relative;
background: #1d1d1d; white-space: nowrap;
.context-submenu {
border-radius: $border-radius;
border: 1px solid $bg-color-light-dark;
opacity: 0;
visibility: hidden;
transition: opacity 0.2s;
position: absolute;
min-width: 100px;
background: #1d1d1d;
}
&:hover {
.context-submenu {
display: block;
visibility: visible;
opacity: 1;
}
}
} }
}
&:hover { .context-overlay {
.context-submenu { background: transparent;
display: block; bottom: 0;
visibility: visible; cursor: default;
opacity: 1; display: block;
} left: 0;
} position: absolute;
} right: 0;
} top: 0;
}
.context-overlay {
background: transparent;
bottom: 0;
cursor: default;
display: block;
left: 0;
position: absolute;
right: 0;
top: 0;
}
} }
.disabled { .disabled {
pointer-events: none; pointer-events: none;
filter: grayscale(100%); filter: grayscale(100%);
opacity: 0.5; opacity: 0.5;
} }
</style> </style>

View File

@@ -431,6 +431,12 @@ export default defineComponent({
width: 100%; width: 100%;
} }
&__item-text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
&__list-wrapper { &__list-wrapper {
cursor: pointer; cursor: pointer;
position: fixed; position: fixed;

View File

@@ -44,6 +44,11 @@ watch(() => props.mode, () => {
editor.session.setMode(`ace/mode/${props.mode}`); editor.session.setMode(`ace/mode/${props.mode}`);
}); });
watch(() => props.modelValue, () => {
if (editor && props.readOnly)
editor.session.setValue(props.modelValue);
});
watch(editorTheme, () => { watch(editorTheme, () => {
if (editor) if (editor)
editor.setTheme(`ace/theme/${editorTheme.value}`); editor.setTheme(`ace/theme/${editorTheme.value}`);
@@ -51,14 +56,17 @@ watch(editorTheme, () => {
watch(editorFontSize, () => { watch(editorFontSize, () => {
const sizes = { const sizes = {
small: 12, xsmall: '10px',
medium: 14, small: '12px',
large: 16 medium: '14px',
large: '16px',
xlarge: '18px',
xxlarge: '20px'
}; };
if (editor) { if (editor) {
editor.setOptions({ editor.setOptions({
fontSize: sizes[editorFontSize.value as undefined as 'small' | 'medium' | 'large'] fontSize: sizes[editorFontSize.value]
}); });
} }
}); });
@@ -110,6 +118,8 @@ onMounted(() => {
}, 20); }, 20);
} }
editor.commands.removeCommand('showSettingsMenu');
setTimeout(() => { setTimeout(() => {
editor.resize(); editor.resize();
}, 20); }, 20);

View File

@@ -4,7 +4,7 @@
<i class="mdi mdi-folder-open mr-1" />{{ message }} <i class="mdi mdi-folder-open mr-1" />{{ message }}
</span> </span>
<span class="text-ellipsis file-uploader-value"> <span class="text-ellipsis file-uploader-value">
{{ lastPart(modelValue) }} {{ lastPart(modelValue, 19) }}
</span> </span>
<i <i
v-if="modelValue" v-if="modelValue"
@@ -24,6 +24,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
import { useFilters } from '@/composables/useFilters';
const { lastPart } = useFilters();
defineProps({ defineProps({
message: { message: {
@@ -43,15 +46,6 @@ const id = uidGen();
const clear = () => { const clear = () => {
emit('clear'); emit('clear');
}; };
const lastPart = (string: string) => {
if (!string) return '';
string = string.split(/[/\\]+/).pop();
if (string.length >= 19)
string = `...${string.slice(-19)}`;
return string;
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -4,10 +4,10 @@
v-model="selectedGroup" v-model="selectedGroup"
class="form-select" class="form-select"
:options="[{name: 'manual'}, ...fakerGroups]" :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('message.manualValue') : t(`faker.${opt.name}`)"
option-track-by="name" option-track-by="name"
:disabled="!isChecked" :disabled="!isChecked"
style="flex-grow: 0;" :style="'flex-grow: 0;'"
@change="onChange" @change="onChange"
/> />
@@ -15,7 +15,7 @@
v-if="selectedGroup !== 'manual'" v-if="selectedGroup !== 'manual'"
v-model="selectedMethod" v-model="selectedMethod"
:options="fakerMethods" :options="fakerMethods"
:option-label="(opt: any) => $t(`faker.${opt.name}`)" :option-label="(opt: any) => t(`faker.${opt.name}`)"
option-track-by="name" option-track-by="name"
class="form-select" class="form-select"
:disabled="!isChecked" :disabled="!isChecked"
@@ -41,7 +41,7 @@
<BaseUploadInput <BaseUploadInput
v-else-if="inputProps().type === 'file'" v-else-if="inputProps().type === 'file'"
:model-value="selectedValue" :model-value="selectedValue"
:message="$t('word.browse')" :message="t('word.browse')"
@clear="clearValue" @clear="clearValue"
@change="filesChange($event)" @change="filesChange($event)"
/> />
@@ -87,11 +87,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, PropType, Ref, ref, watch } from 'vue'; import { computed, PropType, Ref, ref, watch } from 'vue';
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes'; import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT, UUID, IS_BIGINT } from 'common/fieldTypes';
import BaseUploadInput from '@/components/BaseUploadInput.vue'; import BaseUploadInput from '@/components/BaseUploadInput.vue';
import ForeignKeySelect from '@/components/ForeignKeySelect.vue'; import ForeignKeySelect from '@/components/ForeignKeySelect.vue';
import FakerMethods from 'common/FakerMethods'; import FakerMethods from 'common/FakerMethods';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({ const props = defineProps({
type: String, type: String,
@@ -123,6 +126,8 @@ const fakerGroups = computed(() => {
localType.value = 'datetime'; localType.value = 'datetime';
else if (TIME.includes(props.type)) else if (TIME.includes(props.type))
localType.value = 'time'; localType.value = 'time';
else if (UUID.includes(props.type))
localType.value = 'uuid';
else else
localType.value = 'none'; localType.value = 'none';
@@ -141,8 +146,12 @@ const inputProps = () => {
if ([...TEXT, ...LONG_TEXT].includes(props.type)) if ([...TEXT, ...LONG_TEXT].includes(props.type))
return { type: 'text', mask: false }; return { type: 'text', mask: false };
if ([...NUMBER, ...FLOAT].includes(props.type)) if ([...NUMBER, ...FLOAT].includes(props.type)) {
return { type: 'number', mask: false }; if (IS_BIGINT.includes(props.type))
return { type: 'text', mask: false };
else
return { type: 'number', mask: false };
}
if (TIME.includes(props.type)) { if (TIME.includes(props.type)) {
let timeMask = '##:##:##'; let timeMask = '##:##:##';

View File

@@ -13,7 +13,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, Ref, ref } from 'vue'; import { computed, Ref, ref, watch } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import Tables from '@/ipc-api/Tables'; import Tables from '@/ipc-api/Tables';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
@@ -21,6 +21,7 @@ import { useWorkspacesStore } from '@/stores/workspaces';
import { TEXT, LONG_TEXT } from 'common/fieldTypes'; import { TEXT, LONG_TEXT } from 'common/fieldTypes';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { TableField } from 'common/interfaces/antares'; import { TableField } from 'common/interfaces/antares';
import { useFilters } from '@/composables/useFilters';
const props = defineProps({ const props = defineProps({
modelValue: [String, Number], modelValue: [String, Number],
@@ -35,17 +36,18 @@ const emit = defineEmits(['update:modelValue', 'blur']);
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { cutText } = useFilters();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const editField: Ref<HTMLSelectElement> = ref(null); const editField: Ref<HTMLSelectElement> = ref(null);
const foreignList = ref([]); const foreignList = ref([]);
const currentValue = ref(props.modelValue); const currentValue = ref(null);
const isValidDefault = computed(() => { const isValidDefault = computed(() => {
if (!foreignList.value.length) return true; if (!foreignList.value.length) return true;
if (props.modelValue === null) return false; if (props.modelValue === null) return false;
return foreignList.value.some(foreign => foreign.foreign_column.toString() === props.modelValue.toString()); return foreignList.value.some(foreign => foreign.foreign_column.toString() === props.modelValue?.toString());
}); });
const foreigns = computed(() => { const foreigns = computed(() => {
@@ -53,7 +55,7 @@ const foreigns = computed(() => {
if (!isValidDefault.value) if (!isValidDefault.value)
list.push({ value: props.modelValue, label: props.modelValue === null ? 'NULL' : props.modelValue }); list.push({ value: props.modelValue, label: props.modelValue === null ? 'NULL' : props.modelValue });
for (const row of foreignList.value) for (const row of foreignList.value)
list.push({ value: row.foreign_column, label: `${row.foreign_column} ${cutText('foreign_description' in row ? ` - ${row.foreign_description}` : '')}` }); list.push({ value: row.foreign_column, label: `${row.foreign_column} ${cutText('foreign_description' in row ? ` - ${row.foreign_description}` : '', 15)}` });
return list; return list;
}); });
@@ -61,10 +63,9 @@ const onChange = (opt: HTMLSelectElement) => {
emit('update:modelValue', opt.value); emit('update:modelValue', opt.value);
}; };
const cutText = (val: string) => { watch(() => props.modelValue, () => {
if (typeof val !== 'string') return val; currentValue.value = props.modelValue;
return val.length > 15 ? `${val.substring(0, 15)}...` : val; });
};
let foreignDesc: string | false; let foreignDesc: string | false;
const params = { const params = {

View File

@@ -0,0 +1,122 @@
<template>
<div class="form-group has-icon-right m-0">
<input
class="form-input"
type="text"
:value="pressedKeys"
:placeholder="t('message.registerAShortcut')"
@focus="isFocus = true"
@blur="isFocus = false"
@keydown.prevent.stop="onKey"
>
<i class="form-icon mdi mdi-keyboard-outline mdi-24px" />
</div>
</template>
<script setup lang="ts">
import { computed, PropType, Ref, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import Application from '@/ipc-api/Application';
const { t } = useI18n();
const emit = defineEmits(['update:modelValue']);
const isMacOS = process.platform === 'darwin';
const props = defineProps({
modelValue: String as PropType<string | Electron.Accelerator>
});
const isFocus = ref(false);
const keyboardEvent: Ref<KeyboardEvent> = ref(null);
const pressedKeys = computed(() => {
const keys: string[] = [];
const singleKeysToIgnore = ['Dead', 'Backspace', 'ArrotLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
const specialKeys = ['Control', 'Alt', 'AltGraph', 'Shift', 'Meta', 'CapsLock', 'ContextMenu', 'Escape'];
const keysFromCode = ['Space', 'Minus', 'Equal', 'Slash', 'Quote', 'Semicolon', 'Comma', 'Period', 'Backslash', 'BracketLeft', 'BracketRight'];
if (props.modelValue && !keyboardEvent.value)
return props.modelValue;
else if (keyboardEvent.value) {
if (keyboardEvent.value.altKey)
keys.push('Alt');
if (keyboardEvent.value.ctrlKey)
keys.push('Control');
if (keyboardEvent.value.metaKey && isMacOS)
keys.push('Command');
if (keyboardEvent.value.shiftKey && keys.length)
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');
else if (!specialKeys.includes(keyboardEvent.value.key)) {
if (keyboardEvent.value.key === 'Dead') {
keys.push(keyboardEvent.value.code
.replace('Digit', '')
.replace('Key', '')
.replace('Quote', '\'')
.replace('Backquote', '`'));
}
else if (keysFromCode.includes(keyboardEvent.value.code) || keyboardEvent.value.code.includes('Digit')) {
keys.push(keyboardEvent.value.code
.replace('Quote', '\'')
.replace('Semicolon', ';')
.replace('Slash', '/')
.replace('Backslash', '\\')
.replace('BracketLeft', '[')
.replace('BracketRight', ']')
.replace('Comma', ',')
.replace('Period', '.')
.replace('Minus', '-')
.replace('Equal', '=')
.replace('Digit', '')
.replace('Key', ''));
}
else {
keys.push(keyboardEvent.value.key.length === 1
? keyboardEvent.value.key.toUpperCase()
: keyboardEvent.value.key
.replace('Arrow', '')
);
}
}
else
return t('message.invalidShortcutMessage');
}
}
return keys.join('+');
});
const onKey = (e: KeyboardEvent) => {
e.stopPropagation();
e.preventDefault();
keyboardEvent.value = e;
};
watch(pressedKeys, (value) => {
if (value !== t('message.invalidShortcutMessage'))
emit('update:modelValue', pressedKeys.value);
});
watch(isFocus, (val) => {
if (val)
Application.unregisterShortcuts();
else
Application.reloadShortcuts();
});
</script>
<style lang="scss" scoped>
.has-icon-right {
.form-input {
padding-right: 1.8rem;
overflow: hidden;
text-overflow: ellipsis;
caret-color: transparent;
}
.form-icon {
right: 0.4rem;
}
}
</style>

View File

@@ -0,0 +1,337 @@
<template>
<Teleport to="#window-content">
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div ref="trapRef" class="modal-container p-0 pb-4">
<div class="modal-header pl-2">
<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>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body py-0">
<div class="columns">
<div class="connections-search column col-12 columns col-gapless">
<div class="column col-12 mt-2">
<div ref="searchForm" class="form-group has-icon-right p-2 m-0">
<input
v-model="searchTerm"
class="form-input"
type="text"
:placeholder="t('message.searchForConnections')"
@keypress.esc="searchTerm = ''"
>
<i v-if="!searchTerm" class="form-icon mdi mdi-magnify mdi-18px pr-4" />
<i
v-else
class="form-icon c-hand mdi mdi-backspace mdi-18px pr-4"
@click="searchTerm = ''"
/>
</div>
</div>
</div>
<TransitionGroup name="fade" :duration="{ enter: 200, leave: 200 }">
<div
v-for="connection in filteredConnections"
:key="connection.uid"
class="connection-block column col-md-6 col-lg-4 col-3 p-3"
tabindex="0"
@click.stop="selectConnection(connection.uid)"
@keypress.stop.enter="selectConnection(connection.uid)"
@mouseover="connectionHover = connection.uid"
@mouseleave="connectionHover = null"
>
<div class="panel">
<div class="panel-header p-2 text-center p-relative">
<figure class="avatar avatar-lg pt-1 mb-1">
<i class="settingbar-element-icon dbi" :class="[`dbi-${connection.client}`]" />
</figure>
<div class="panel-title h6 text-ellipsis">
{{ getConnectionName(connection.uid) }}
</div>
<div class="panel-subtitle">
{{ clients.get(connection.client) || connection.client }}
</div>
<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')"
@click.stop="askToDelete(connection)"
/>
</div>
</div>
<div class="panel-body text-center">
<div v-if="connection.databasePath">
<div class="text-ellipsis" :title="connection.databasePath">
<i class="mdi mdi-database d-inline" /> <span class="text-bold">{{
connection.databasePath
}}</span>
</div>
</div>
<div v-else>
<div class="text-ellipsis" :title="`${connection.host}:${connection.port}`">
<i class="mdi mdi-server d-inline" /> <span class="text-bold">{{ connection.host
}}:{{ connection.port }}</span>
</div>
</div>
<div v-if="connection.user">
<div class="text-ellipsis">
<i class="mdi mdi-account d-inline" /> <span class="text-bold">{{ connection.user
}}</span>
</div>
</div>
<div v-if="connection.schema">
<div class="text-ellipsis">
<i class="mdi mdi-database d-inline" /> <span class="text-bold">{{ connection.schema
}}</span>
</div>
</div>
<div v-if="connection.database">
<div class="text-ellipsis">
<i class="mdi mdi-database d-inline" /> <span class="text-bold">{{
connection.database
}}</span>
</div>
</div>
</div>
<div class="panel-footer text-center py-0">
<div v-if="connection.ssl" class="chip bg-success mt-2">
<i class="mdi mdi-shield-key mdi-18px mr-1" />
SSL
</div>
<div v-if="connection.ssh" class="chip bg-success mt-2">
<i class="mdi mdi-console-network mdi-18px mr-1" />
SSH
</div>
</div>
</div>
</div>
<input
key="trick"
readonly
class="p-absolute"
:style="'width: 1px; height: 1px; opacity: 0;'"
type="text"
>
<!-- workaround for useFocusTrap $lastFocusable -->
</TransitionGroup>
</div>
</div>
</div>
</div>
<ConfirmModal
v-if="isConfirmModal"
@confirm="confirmDeleteConnection"
@hide="isConfirmModal = false"
>
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-server-remove mr-1" /> {{ t('message.deleteConnection') }}
</div>
</template>
<template #body>
<div class="mb-2">
{{ t('message.deleteConfirm') }} <b>{{ selectedConnectionName }}</b>?
</div>
</template>
</ConfirmModal>
</Teleport>
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, Ref, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { useFocusTrap } from '@/composables/useFocusTrap';
import { useConnectionsStore } from '@/stores/connections';
import { useWorkspacesStore } from '@/stores/workspaces';
import ConfirmModal from '@/components/BaseConfirmModal.vue';
import { ConnectionParams } from 'common/interfaces/antares';
const { t } = useI18n();
const connectionsStore = useConnectionsStore();
const workspacesStore = useWorkspacesStore();
const { connections,
lastConnections
} = storeToRefs(connectionsStore);
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getConnectionName,
deleteConnection
} = connectionsStore;
const { selectWorkspace } = workspacesStore;
const { trapRef } = useFocusTrap();
const emit = defineEmits(['close']);
const clients = new Map([
['mysql', 'MySQL'],
['maria', 'MariaDB'],
['pg', 'PostgreSQL'],
['sqlite', 'SQLite']
]);
const searchTerm = ref('');
const isConfirmModal = ref(false);
const connectionHover: Ref<string> = ref(null);
const selectedConnection: Ref<ConnectionParams> = ref(null);
const sortedConnections = computed(() => {
return connections.value
.map(c => {
const connTime = lastConnections.value.find((lc) => lc.uid === c.uid)?.time || 0;
return {
...c,
time: connTime
};
})
.sort((a, b) => {
if (a.time < b.time) return 1;
if (a.time > b.time) return -1;
return 0;
});
});
const filteredConnections = computed(() => {
return sortedConnections.value.filter(connection => {
return connection.name?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
connection.host?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
connection.database?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
connection.databasePath?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
connection.schema?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
connection.user?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
String(connection.port)?.includes(searchTerm.value);
});
});
const selectedConnectionName = computed(() => getConnectionName(selectedConnection.value?.uid));
const closeModal = () => emit('close');
const selectConnection = (uid: string) => {
selectWorkspace(uid);
closeModal();
};
const askToDelete = (connection: ConnectionParams) => {
selectedConnection.value = connection;
isConfirmModal.value = true;
};
const confirmDeleteConnection = () => {
if (selectedWorkspace.value === selectedConnection.value.uid)
selectWorkspace(null);
deleteConnection(selectedConnection.value);
};
const onKey = (e: KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape') {
if ((e.target as HTMLInputElement).tagName === 'INPUT' && searchTerm.value.length > 0)
searchTerm.value = '';
else
closeModal();
}
};
window.addEventListener('keydown', onKey);
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script>
<style lang="scss" scoped>
.vscroll {
height: 1000px;
overflow: auto;
overflow-anchor: none;
}
.column-resizable {
&:hover,
&:active {
resize: horizontal;
overflow: hidden;
}
}
.table-column-title {
display: flex;
align-items: center;
}
.sort-icon {
font-size: 0.7rem;
line-height: 1;
margin-left: 0.2rem;
}
.modal {
align-items: flex-start;
.modal-container {
max-width: 75vw;
margin-top: 10vh;
.modal-body {
height: 80vh;
}
}
}
.connections-search {
display: flex;
justify-content: space-around;
}
.connection-block {
cursor: pointer;
transition: all 0.2s;
border-radius: $border-radius;
outline: none;
&:focus {
box-shadow: 0 0 3px 0.1rem rgba($primary-color, 80%);
}
&:hover {
.all-connections-buttons {
.all-connections-delete,
.all-connections-pinned,
.all-connections-pin {
opacity: 0.5;
}
}
}
.all-connections-buttons {
.all-connections-pinned {
opacity: 0.3;
transition: opacity 0.2s;
&:hover {
opacity: 1;
}
}
.all-connections-delete,
.all-connections-pin {
opacity: 0;
transition: opacity 0.2s;
&:hover {
opacity: 1;
}
}
}
}
</style>

View File

@@ -6,7 +6,7 @@
<div class="modal-header pl-2"> <div class="modal-header pl-2">
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <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('word.credentials') }}
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -16,7 +16,7 @@
<form class="form-horizontal"> <form class="form-horizontal">
<div class="form-group"> <div class="form-group">
<div class="col-3"> <div class="col-3">
<label class="form-label">{{ $t('word.user') }}</label> <label class="form-label">{{ t('word.user') }}</label>
</div> </div>
<div class="col-9"> <div class="col-9">
<input <input
@@ -29,7 +29,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-3"> <div class="col-3">
<label class="form-label">{{ $t('word.password') }}</label> <label class="form-label">{{ t('word.password') }}</label>
</div> </div>
<div class="col-9"> <div class="col-9">
<input <input
@@ -44,10 +44,10 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn btn-primary mr-2" @click.stop="sendCredentials"> <button class="btn btn-primary mr-2" @click.stop="sendCredentials">
{{ $t('word.send') }} {{ t('word.send') }}
</button> </button>
<button class="btn btn-link" @click.stop="closeModal"> <button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }} {{ t('word.close') }}
</button> </button>
</div> </div>
</div> </div>
@@ -58,6 +58,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { Ref, ref } from 'vue'; import { Ref, ref } from 'vue';
import { useFocusTrap } from '@/composables/useFocusTrap'; import { useFocusTrap } from '@/composables/useFocusTrap';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const { trapRef } = useFocusTrap(); const { trapRef } = useFocusTrap();

View File

@@ -1,7 +1,7 @@
<template> <template>
<ConfirmModal <ConfirmModal
:confirm-text="$t('word.run')" :confirm-text="t('word.run')"
:cancel-text="$t('word.cancel')" :cancel-text="t('word.cancel')"
size="400" size="400"
@confirm="runRoutine" @confirm="runRoutine"
@hide="closeModal" @hide="closeModal"
@@ -9,7 +9,7 @@
<template #header> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-play mr-1" /> <i class="mdi mdi-24px mdi-play mr-1" />
<span class="cut-text">{{ $t('word.parameters') }}: {{ localRoutine.name }}</span> <span class="cut-text">{{ t('word.parameters') }}: {{ localRoutine.name }}</span>
</div> </div>
</template> </template>
<template #body> <template #body>
@@ -52,6 +52,12 @@ import { computed, PropType, Ref, ref } from 'vue';
import { NUMBER, FLOAT } from 'common/fieldTypes'; import { NUMBER, FLOAT } from 'common/fieldTypes';
import { FunctionInfos, RoutineInfos } from 'common/interfaces/antares'; import { FunctionInfos, RoutineInfos } from 'common/interfaces/antares';
import ConfirmModal from '@/components/BaseConfirmModal.vue'; import ConfirmModal from '@/components/BaseConfirmModal.vue';
import { useFilters } from '@/composables/useFilters';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const { wrapNumber } = useFilters();
const props = defineProps({ const props = defineProps({
localRoutine: Object as PropType<RoutineInfos | FunctionInfos>, localRoutine: Object as PropType<RoutineInfos | FunctionInfos>,
@@ -106,11 +112,6 @@ const onKey = (e: KeyboardEvent) => {
closeModal(); closeModal();
}; };
const wrapNumber = (num: number) => {
if (!num) return '';
return `(${num})`;
};
window.addEventListener('keydown', onKey); window.addEventListener('keydown', onKey);
setTimeout(() => { setTimeout(() => {

View File

@@ -0,0 +1,195 @@
<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-brush-variant mr-1" />
<span class="cut-text">{{ t('message.editConnectionAppearance') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body pb-0">
<div class="content">
<form class="form-horizontal">
<div class="form-group mb-4">
<div class="col-3">
<label class="form-label">{{ t('word.label') }}</label>
</div>
<div class="col-9">
<input
ref="firstInput"
v-model="localConnection.name"
class="form-input"
type="text"
:placeholder="getConnectionName(localConnection.uid)"
>
</div>
</div>
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ t('word.icon') }}</label>
</div>
<div class="col-9 icons-wrapper">
<div
v-for="icon in icons"
:key="icon.name"
class="icon-box"
:title="icon.name"
:class="[icon.code ? `mdi ${icon.code} mdi-36px` : `dbi dbi-${connection.client}`, {'selected': localConnection.icon === icon.code}]"
@click="localConnection.icon = icon.code"
/>
</div>
</div>
</form>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary mr-2" @click.stop="editFolderAppearance">
{{ t('word.update') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ t('word.close') }}
</button>
</div>
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { onBeforeUnmount, PropType, Ref, ref } from 'vue';
import { useFocusTrap } from '@/composables/useFocusTrap';
import { useI18n } from 'vue-i18n';
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
import { unproxify } from '@/libs/unproxify';
const connectionsStore = useConnectionsStore();
const { t } = useI18n();
const props = defineProps({
connection: {
type: Object as PropType<SidebarElement>,
required: true
}
});
const emit = defineEmits(['close']);
const { updateConnectionOrder, getConnectionName } = connectionsStore;
const icons = [
{ name: 'default', code: null },
// Symbols
{ name: 'account-group', code: 'mdi-account-group-outline' },
{ name: 'cloud', code: 'mdi-cloud-outline' },
{ name: 'key-chain', code: 'mdi-key-chain-variant' },
{ name: 'lightning-bolt', code: 'mdi-lightning-bolt' },
{ name: 'map-marker', code: 'mdi-map-marker-radius-outline' },
{ name: 'api', code: 'mdi-api' },
{ name: 'chart-line', code: 'mdi-chart-line' },
{ name: 'chat', code: 'mdi-chat-outline' },
{ name: 'bug', code: 'mdi-bug-outline' },
{ name: 'shield', code: 'mdi-shield-outline' },
{ name: 'cart', code: 'mdi-cart-variant' },
{ name: 'bank', code: 'mdi-bank-outline' },
{ name: 'receipt', code: 'mdi-receipt-text-outline' },
{ name: 'raspberry-pi', code: 'mdi-raspberry-pi' },
{ name: 'book', code: 'mdi-book-outline' },
{ name: 'web', code: 'mdi-web' },
{ name: 'multimedia', code: 'mdi-multimedia' },
{ name: 'qr-code', code: 'mdi-qrcode' },
{ name: 'flask', code: 'mdi-flask-outline' },
{ name: 'memory', code: 'mdi-memory' },
{ name: 'cube', code: 'mdi-cube-outline' },
{ name: 'weather', code: 'mdi-weather-partly-snowy-rainy' },
{ name: 'controller', code: 'mdi-controller' },
{ name: 'home-group', code: 'mdi-home-group' },
// Vehicles
{ name: 'truck', code: 'mdi-truck-outline' },
{ name: 'car', code: 'mdi-car' },
{ name: 'motorbike', code: 'mdi-atv' },
{ name: 'train', code: 'mdi-train' },
{ name: 'airplane', code: 'mdi-airplane' },
{ name: 'ferry', code: 'mdi-ferry' },
// Brand
{ name: 'docker', code: 'mdi-docker' },
{ name: 'open-source', code: 'mdi-open-source-initiative' },
{ name: 'aws', code: 'mdi-aws' },
{ name: 'google-cloud', code: 'mdi-google-cloud' },
{ name: 'microsoft-azure', code: 'mdi-microsoft-azure' },
{ name: 'linux', code: 'mdi-linux' },
{ name: 'microsoft-windows', code: 'mdi-microsoft-windows' },
{ name: 'apple', code: 'mdi-apple' },
{ name: 'android', code: 'mdi-android' }
];
const { trapRef } = useFocusTrap();
const firstInput: Ref<HTMLInputElement> = ref(null);
const localConnection: Ref<SidebarElement> = ref(unproxify(props.connection));
const editFolderAppearance = () => {
updateConnectionOrder(localConnection.value);
closeModal();
};
const closeModal = () => emit('close');
const onKey =(e: KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape')
closeModal();
};
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script>
<style scoped lang="scss">
.modal-container {
max-width: 360px;
}
.icons-wrapper{
display: grid;
grid-template-columns: repeat(auto-fill, 40px);
gap: 5px;
.icon-box {
height: 40px;
width: 40px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&.selected {
outline: 2px solid $primary-color;
border-radius: 8px;
}
}
}
.theme-light {
.icons-wrapper {
.dbi {
filter: invert(100%) opacity(.8);
&.selected {
outline-color: #1c96d6;
}
}
}
}
</style>

View File

@@ -1,18 +1,18 @@
<template> <template>
<ConfirmModal <ConfirmModal
:confirm-text="$t('word.discard')" :confirm-text="t('word.discard')"
:cancel-text="$t('word.stay')" :cancel-text="t('word.stay')"
@confirm="emit('confirm')" @confirm="emit('confirm')"
@hide="emit('close')" @hide="emit('close')"
> >
<template #header> <template #header>
<div class="d-flex"> <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('message.unsavedChanges') }}
</div> </div>
</template> </template>
<template #body> <template #body>
<div> <div>
{{ $t('message.discardUnsavedChanges') }} {{ t('message.discardUnsavedChanges') }}
</div> </div>
</template> </template>
</ConfirmModal> </ConfirmModal>
@@ -21,6 +21,9 @@
<script setup lang="ts"> <script setup lang="ts">
import ConfirmModal from '@/components/BaseConfirmModal.vue'; import ConfirmModal from '@/components/BaseConfirmModal.vue';
import { onBeforeUnmount } from 'vue'; import { onBeforeUnmount } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const emit = defineEmits(['confirm', 'close']); const emit = defineEmits(['confirm', 'close']);

View File

@@ -7,7 +7,7 @@
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-database-edit mr-1" /> <i class="mdi mdi-24px mdi-database-edit mr-1" />
<span class="cut-text">{{ $t('message.editSchema') }}</span> <span class="cut-text">{{ t('message.editSchema') }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -17,7 +17,7 @@
<form class="form-horizontal"> <form class="form-horizontal">
<div class="form-group"> <div class="form-group">
<div class="col-3"> <div class="col-3">
<label class="form-label">{{ $t('word.name') }}</label> <label class="form-label">{{ t('word.name') }}</label>
</div> </div>
<div class="col-9"> <div class="col-9">
<input <input
@@ -26,24 +26,25 @@
class="form-input" class="form-input"
type="text" type="text"
required required
:placeholder="$t('message.schemaName')" :placeholder="t('message.schemaName')"
readonly readonly
> >
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-3"> <div class="col-3">
<label class="form-label">{{ $t('word.collation') }}</label> <label class="form-label">{{ t('word.collation') }}</label>
</div> </div>
<div class="col-9"> <div class="col-9">
<BaseSelect <BaseSelect
v-model="database.collation" v-model="database.collation"
class="form-select" class="form-select"
:options="collations" :options="collations"
:max-visible-options="1000"
option-label="collation" option-label="collation"
option-track-by="collation" option-track-by="collation"
/> />
<small>{{ $t('message.serverDefault') }}: {{ defaultCollation }}</small> <small>{{ t('message.serverDefault') }}: {{ defaultCollation }}</small>
</div> </div>
</div> </div>
</form> </form>
@@ -51,10 +52,10 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn btn-primary mr-2" @click.stop="updateSchema"> <button class="btn btn-primary mr-2" @click.stop="updateSchema">
{{ $t('word.update') }} {{ t('word.update') }}
</button> </button>
<button class="btn btn-link" @click.stop="closeModal"> <button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }} {{ t('word.close') }}
</button> </button>
</div> </div>
</div> </div>
@@ -70,6 +71,9 @@ import { useWorkspacesStore } from '@/stores/workspaces';
import { useFocusTrap } from '@/composables/useFocusTrap'; import { useFocusTrap } from '@/composables/useFocusTrap';
import Schema from '@/ipc-api/Schema'; import Schema from '@/ipc-api/Schema';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({ const props = defineProps({
selectedSchema: String selectedSchema: String
@@ -160,7 +164,7 @@ onBeforeUnmount(() => {
</script> </script>
<style scoped> <style scoped lang="scss">
.modal-container { .modal-container {
max-width: 360px; max-width: 360px;
} }

View File

@@ -6,8 +6,8 @@
<div class="modal-header pl-2"> <div class="modal-header pl-2">
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-database-arrow-down mr-1" /> <i class="mdi mdi-24px mdi-database-export mr-1" />
<span class="cut-text">{{ $t('message.exportSchema') }}</span> <span class="cut-text">{{ t('message.exportSchema') }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -16,7 +16,7 @@
<div class="container"> <div class="container">
<div class="columns"> <div class="columns">
<div class="col-3"> <div class="col-3">
<label class="form-label">{{ $t('message.directoryPath') }}</label> <label class="form-label">{{ t('message.directoryPath') }}</label>
</div> </div>
<div class="col-9"> <div class="col-9">
<fieldset class="input-group"> <fieldset class="input-group">
@@ -26,14 +26,14 @@
type="text" type="text"
required required
readonly readonly
:placeholder="$t('message.schemaName')" :placeholder="t('message.schemaName')"
> >
<button <button
type="button" type="button"
class="btn btn-primary input-group-btn" class="btn btn-primary input-group-btn"
@click.prevent="openPathDialog" @click.prevent="openPathDialog"
> >
{{ $t('word.change') }} {{ t('word.change') }}
</button> </button>
</fieldset> </fieldset>
</div> </div>
@@ -51,14 +51,14 @@
<div class="column col-auto col-ml-auto "> <div class="column col-auto col-ml-auto ">
<button <button
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:title="$t('word.refresh')" :title="t('word.refresh')"
@click="refresh" @click="refresh"
> >
<i class="mdi mdi-database-refresh" /> <i class="mdi mdi-database-refresh" />
</button> </button>
<button <button
class="btn btn-dark btn-sm mx-1" class="btn btn-dark btn-sm mx-1"
:title="$t('message.uncheckAllTables')" :title="t('message.uncheckAllTables')"
:disabled="isRefreshing" :disabled="isRefreshing"
@click="uncheckAllTables" @click="uncheckAllTables"
> >
@@ -66,7 +66,7 @@
</button> </button>
<button <button
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:title="$t('message.checkAllTables')" :title="t('message.checkAllTables')"
:disabled="isRefreshing" :disabled="isRefreshing"
@click="checkAllTables" @click="checkAllTables"
> >
@@ -78,7 +78,7 @@
<div ref="table" class="table table-hover"> <div ref="table" class="table table-hover">
<div class="thead"> <div class="thead">
<div class="tr text-center"> <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"> <div class="th no-border">
<label <label
class="form-checkbox m-0 px-2 form-inline" class="form-checkbox m-0 px-2 form-inline"
@@ -120,24 +120,24 @@
</div> </div>
</div> </div>
<div class="tr"> <div class="tr">
<div class="th" style="width: 50%;"> <div class="th" :style="'width: 50%;'">
<div class="table-column-title"> <div class="table-column-title">
<span>{{ $t('word.table') }}</span> <span>{{ t('word.table') }}</span>
</div> </div>
</div> </div>
<div class="th text-center"> <div class="th text-center">
<div class="table-column-title"> <div class="table-column-title">
<span>{{ $t('word.structure') }}</span> <span>{{ t('word.structure') }}</span>
</div> </div>
</div> </div>
<div class="th text-center"> <div class="th text-center">
<div class="table-column-title"> <div class="table-column-title">
<span>{{ $t('word.content') }}</span> <span>{{ t('word.content') }}</span>
</div> </div>
</div> </div>
<div class="th text-center"> <div class="th text-center">
<div class="table-column-title"> <div class="table-column-title">
<span>{{ $t('word.drop') }}</span> <span>{{ t('word.drop') }}</span>
</div> </div>
</div> </div>
</div> </div>
@@ -183,19 +183,19 @@
</div> </div>
<div class="column col-4"> <div class="column col-4">
<h5 class="h5"> <h5 class="h5">
{{ $t('word.options') }} {{ t('word.options') }}
</h5> </h5>
<span class="h6">{{ $t('word.includes') }}:</span> <span class="h6">{{ t('word.includes') }}:</span>
<label <label
v-for="(_, key) in options.includes" v-for="(_, key) in options.includes"
:key="key" :key="key"
class="form-checkbox" class="form-checkbox"
> >
<input v-model="options.includes[key]" type="checkbox"><i class="form-icon" /> {{ $tc(`word.${key}`, 2) }} <input v-model="options.includes[key]" type="checkbox"><i class="form-icon" /> {{ t(`word.${key}`, 2) }}
</label> </label>
<div v-if="clientCustoms.exportByChunks"> <div v-if="clientCustoms.exportByChunks">
<div class="h6 mt-4 mb-2"> <div class="h6 mt-4 mb-2">
{{ $t('message.newInserStmtEvery') }}: {{ t('message.newInsertStmtEvery') }}:
</div> </div>
<div class="columns"> <div class="columns">
<div class="column col-6"> <div class="column col-6">
@@ -209,21 +209,21 @@
<BaseSelect <BaseSelect
v-model="options.sqlInsertDivider" v-model="options.sqlInsertDivider"
class="form-select" class="form-select"
:options="[{value: 'bytes', label: 'KiB'}, {value: 'rows', label: $tc('word.row', 2)}]" :options="[{value: 'bytes', label: 'KiB'}, {value: 'rows', label: t('word.row', 2)}]"
/> />
</div> </div>
</div> </div>
</div> </div>
<div class="h6 mb-2 mt-4"> <div class="h6 mb-2 mt-4">
{{ $t('message.ourputFormat') }}: {{ t('message.outputFormat') }}:
</div> </div>
<div class="columns"> <div class="columns">
<div class="column h5 mb-4"> <div class="column h5 mb-4">
<BaseSelect <BaseSelect
v-model="options.outputFormat" v-model="options.outputFormat"
class="form-select" 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('message.singleFile', {ext: '.sql'})}, {value: 'sql.zip', label: t('message.zipCompressedFile', {ext: '.sql'})}]"
/> />
</div> </div>
</div> </div>
@@ -245,7 +245,7 @@
</div> </div>
<div class="column col-auto px-0"> <div class="column col-auto px-0">
<button class="btn btn-link" @click.stop="closeModal"> <button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }} {{ t('word.close') }}
</button> </button>
<button <button
class="btn btn-primary mr-2" class="btn btn-primary mr-2"
@@ -254,7 +254,7 @@
autofocus autofocus
@click.prevent="startExport" @click.prevent="startExport"
> >
{{ $t('word.export') }} {{ t('word.export') }}
</button> </button>
</div> </div>
</div> </div>
@@ -269,8 +269,8 @@ import * as moment from 'moment';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { SchemaInfos } from 'common/interfaces/antares'; import { ClientCode, SchemaInfos } from 'common/interfaces/antares';
import { ExportOptions, ExportState, TableParams } from 'common/interfaces/exporter'; import { ExportOptions, ExportState } from 'common/interfaces/exporter';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import { useFocusTrap } from '@/composables/useFocusTrap'; import { useFocusTrap } from '@/composables/useFocusTrap';
@@ -302,11 +302,15 @@ const isExporting = ref(false);
const isRefreshing = ref(false); const isRefreshing = ref(false);
const progressPercentage = ref(0); const progressPercentage = ref(0);
const progressStatus = ref(''); const progressStatus = ref('');
const tables: Ref<TableParams[]> = ref([]); const tables: Ref<{
const options: Ref<ExportOptions> = ref({ table: string;
includeStructure: boolean;
includeContent: boolean;
includeDropStatement: boolean;
}[]> = ref([]);
const options: Ref<Partial<ExportOptions>> = ref({
schema: props.selectedSchema, schema: props.selectedSchema,
includes: {} as {[key: string]: boolean}, includes: {} as {[key: string]: boolean},
outputFile: '',
outputFormat: 'sql' as 'sql' | 'sql.zip', outputFormat: 'sql' as 'sql' | 'sql.zip',
sqlInsertAfter: 250, sqlInsertAfter: 250,
sqlInsertDivider: 'bytes' as 'bytes' | 'rows' sqlInsertDivider: 'bytes' as 'bytes' | 'rows'
@@ -353,7 +357,7 @@ const startExport = async () => {
outputFile: dumpFilePath.value, outputFile: dumpFilePath.value,
tables: [...tables.value], tables: [...tables.value],
...options.value ...options.value
}; } as ExportOptions & { uid: string; type: ClientCode };
try { try {
const { status, response } = await Schema.export(params); const { status, response } = await Schema.export(params);
@@ -378,7 +382,7 @@ const updateProgress = (event: Event, state: ExportState) => {
progressStatus.value = t('message.processingTableExport', { table: state.currentItem }); progressStatus.value = t('message.processingTableExport', { table: state.currentItem });
break; break;
case 'FETCH': case 'FETCH':
progressStatus.value = t('message.fechingTableExport', { table: state.currentItem }); progressStatus.value = t('message.fetchingTableExport', { table: state.currentItem });
break; break;
case 'WRITE': case 'WRITE':
progressStatus.value = t('message.writingTableExport', { table: state.currentItem }); progressStatus.value = t('message.writingTableExport', { table: state.currentItem });

View File

@@ -7,7 +7,7 @@
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-playlist-plus mr-1" /> <i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span class="cut-text">{{ $tc('message.insertRow', 2) }}</span> <span class="cut-text">{{ t('message.insertRow', 2) }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <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)"> <span class="input-group-addon field-type" :class="typeClass(field.type)">
{{ field.type }} {{ wrapNumber(fieldLength(field)) }} {{ field.type }} {{ wrapNumber(fieldLength(field)) }}
</span> </span>
<label class="form-checkbox ml-3" :title="$t('word.insert')"> <label class="form-checkbox ml-3" :title="t('word.insert')">
<input <input
type="checkbox" type="checkbox"
:checked="!fieldsToExclude.includes(field.name)" :checked="!fieldsToExclude.includes(field.name)"
@@ -55,7 +55,7 @@
</div> </div>
<div class="modal-footer columns"> <div class="modal-footer columns">
<div class="column d-flex" :class="hasFakes ? 'col-4' : 'col-2'"> <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('message.numberOfInserts')">
<input <input
v-model="nInserts" v-model="nInserts"
type="number" type="number"
@@ -70,7 +70,7 @@
<div <div
v-if="hasFakes" v-if="hasFakes"
class="tooltip tooltip-right ml-2" class="tooltip tooltip-right ml-2"
:data-tooltip="$t('message.fakeDataLanguage')" :data-tooltip="t('message.fakeDataLanguage')"
> >
<BaseSelect <BaseSelect
v-model="fakerLocale" v-model="fakerLocale"
@@ -85,10 +85,10 @@
:class="{'loading': isInserting}" :class="{'loading': isInserting}"
@click.stop="insertRows" @click.stop="insertRows"
> >
{{ $t('word.insert') }} {{ t('word.insert') }}
</button> </button>
<button class="btn btn-link" @click.stop="closeModal"> <button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }} {{ t('word.close') }}
</button> </button>
</div> </div>
</div> </div>
@@ -109,10 +109,20 @@ import { useFocusTrap } from '@/composables/useFocusTrap';
import Tables from '@/ipc-api/Tables'; import Tables from '@/ipc-api/Tables';
import FakerSelect from '@/components/FakerSelect.vue'; import FakerSelect from '@/components/FakerSelect.vue';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { useFilters } from '@/composables/useFilters';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const { wrapNumber } = useFilters();
const props = defineProps({ const props = defineProps({
tabUid: [String, Number], tabUid: [String, Number],
schema: String,
table: String,
fields: Array as Prop<TableField[]>, fields: Array as Prop<TableField[]>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
rowToDuplicate: Object as Prop<any>,
keyUsage: Array as Prop<TableForeign[]> keyUsage: Array as Prop<TableForeign[]>
}); });
@@ -123,8 +133,6 @@ const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { getWorkspace } = workspacesStore;
const { trapRef } = useFocusTrap({ disableAutofocus: true }); const { trapRef } = useFocusTrap({ disableAutofocus: true });
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -134,7 +142,6 @@ const nInserts = ref(1);
const isInserting = ref(false); const isInserting = ref(false);
const fakerLocale = ref('en'); const fakerLocale = ref('en');
const workspace = computed(() => getWorkspace(selectedWorkspace.value));
const foreignKeys = computed(() => props.keyUsage.map(key => key.field)); const foreignKeys = computed(() => props.keyUsage.map(key => key.field));
const hasFakes = computed(() => Object.keys(localRow.value).some(field => 'group' in localRow.value[field] && localRow.value[field].group !== 'manual')); const hasFakes = computed(() => Object.keys(localRow.value).some(field => 'group' in localRow.value[field] && localRow.value[field].group !== 'manual'));
@@ -220,8 +227,8 @@ const insertRows = async () => {
try { try {
const { status, response } = await Tables.insertTableFakeRows({ const { status, response } = await Tables.insertTableFakeRows({
uid: selectedWorkspace.value, uid: selectedWorkspace.value,
schema: workspace.value.breadcrumbs.schema, schema: props.schema,
table: workspace.value.breadcrumbs.table, table: props.table,
row: rowToInsert, row: rowToInsert,
repeat: nInserts.value, repeat: nInserts.value,
fields: fieldTypes, fields: fieldTypes,
@@ -266,11 +273,6 @@ const onKey = (e: KeyboardEvent) => {
closeModal(); closeModal();
}; };
const wrapNumber = (num: number) => {
if (!num) return '';
return `(${num})`;
};
window.addEventListener('keydown', onKey); window.addEventListener('keydown', onKey);
onMounted(() => { onMounted(() => {
@@ -284,44 +286,57 @@ onMounted(() => {
const rowObj: {[key: string]: unknown} = {}; const rowObj: {[key: string]: unknown} = {};
for (const field of props.fields) { if (!props.rowToDuplicate) {
let fieldDefault; // Set default values
for (const field of props.fields) {
let fieldDefault;
if (field.default === 'NULL') fieldDefault = null; if (field.default === 'NULL') fieldDefault = null;
else { else {
if ([...NUMBER, ...FLOAT].includes(field.type)) if ([...NUMBER, ...FLOAT].includes(field.type))
fieldDefault = !field.default || Number.isNaN(+field.default.replaceAll('\'', '')) ? null : +field.default.replaceAll('\'', ''); fieldDefault = !field.default || Number.isNaN(+field.default.replaceAll('\'', '')) ? null : +field.default.replaceAll('\'', '');
else if ([...TEXT, ...LONG_TEXT].includes(field.type)) { else if ([...TEXT, ...LONG_TEXT].includes(field.type)) {
fieldDefault = field.default fieldDefault = field.default
? field.default.includes('\'') ? field.default.includes('\'')
? field.default.split('\'')[1] ? field.default.split('\'')[1]
: field.default : field.default
: ''; : '';
}
else if ([...TIME, ...DATE].includes(field.type))
fieldDefault = field.default;
else if (BIT.includes(field.type))
fieldDefault = field.default?.replaceAll('\'', '').replaceAll('b', '');
else if (DATETIME.includes(field.type)) {
if (field.default && ['current_timestamp', 'now()'].some(term => field.default.toLowerCase().includes(term))) {
let datePrecision = '';
for (let i = 0; i < field.datePrecision; i++)
datePrecision += i === 0 ? '.S' : 'S';
fieldDefault = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
} }
else if ([...TIME, ...DATE].includes(field.type))
fieldDefault = field.default;
else if (BIT.includes(field.type))
fieldDefault = field.default?.replaceAll('\'', '').replaceAll('b', '');
else if (DATETIME.includes(field.type)) {
if (field.default && ['current_timestamp', 'now()'].some(term => field.default.toLowerCase().includes(term))) {
let datePrecision = '';
for (let i = 0; i < field.datePrecision; i++)
datePrecision += i === 0 ? '.S' : 'S';
fieldDefault = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
}
else
fieldDefault = field.default;
}
else if (field.enumValues)
fieldDefault = field.enumValues.replaceAll('\'', '').split(',');
else else
fieldDefault = field.default; fieldDefault = field.default;
} }
else if (field.enumValues)
fieldDefault = field.enumValues.replaceAll('\'', '').split(','); rowObj[field.name] = { value: fieldDefault };
else
fieldDefault = field.default; if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields
fieldsToExclude.value = [...fieldsToExclude.value, field.name];
} }
}
else {
// Set values to duplicate
for (const field of props.fields) {
if (typeof props.rowToDuplicate[field.name] !== 'object')
rowObj[field.name] = { value: props.rowToDuplicate[field.name] };
rowObj[field.name] = { value: fieldDefault }; if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields
fieldsToExclude.value = [...fieldsToExclude.value, field.name];
if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields }
fieldsToExclude.value = [...fieldsToExclude.value, field.name];
} }
localRow.value = { ...rowObj }; localRow.value = { ...rowObj };

View File

@@ -0,0 +1,152 @@
<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-folder-edit mr-1" />
<span class="cut-text">{{ t('message.editFolder') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body pb-0">
<div class="content">
<form class="form-horizontal">
<div class="form-group mb-4">
<div class="col-3">
<label class="form-label">{{ t('word.name') }}</label>
</div>
<div class="col-9">
<input
ref="firstInput"
v-model="localFolder.name"
class="form-input"
type="text"
required
:placeholder="t('message.folderName')"
>
</div>
</div>
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ t('word.color') }}</label>
</div>
<div class="col-9 color-wrapper">
<div
v-for="color in colorPalette"
:key="color.name"
class="color-box"
:title="color.name"
:style="`background-color: ${color.hex}`"
@click="localFolder.color = color.hex"
>
<i v-if="localFolder.color === color.hex" class="mdi mdi-check" />
</div>
</div>
</div>
</form>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary mr-2" @click.stop="editFolderAppearance">
{{ t('word.update') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ t('word.close') }}
</button>
</div>
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { onBeforeUnmount, PropType, Ref, ref } from 'vue';
import { useFocusTrap } from '@/composables/useFocusTrap';
import { useI18n } from 'vue-i18n';
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
import { unproxify } from '@/libs/unproxify';
const connectionsStore = useConnectionsStore();
const { t } = useI18n();
const props = defineProps({
folder: {
type: Object as PropType<SidebarElement>,
required: true
}
});
const emit = defineEmits(['close']);
const { updateConnectionOrder } = connectionsStore;
const colorPalette = [
{ name: 'default', hex: '#E36929' },
{ name: 'grape-fruit', hex: '#ED5565' },
{ name: 'rose', hex: '#E3242B' },
{ name: 'fire', hex: '#FDA50F' },
{ name: 'sunflower', hex: '#FFCE54' },
{ name: 'moss', hex: '#8A985E' },
{ name: 'grass', hex: '#6DCD05' },
{ name: 'emerald', hex: '#038835' },
{ name: 'mint', hex: '#48CFAD' },
{ name: 'aqua', hex: '#4FC1E9' },
{ name: 'royal-lblue', hex: '#4169E1' },
{ name: 'blue-jeans', hex: '#5D9CEC' },
{ name: 'stone', hex: '#59788E' },
{ name: 'lavander', hex: '#AC92EC' },
{ name: 'pink-rose', hex: '#EC87C0' },
{ name: 'smoke', hex: '#BEBDB8' },
{ name: 'slate', hex: '#757C88' }
];
const { trapRef } = useFocusTrap();
const firstInput: Ref<HTMLInputElement> = ref(null);
const localFolder: Ref<SidebarElement> = ref(unproxify(props.folder));
const editFolderAppearance = () => {
updateConnectionOrder(localFolder.value);
closeModal();
};
const closeModal = () => emit('close');
const onKey =(e: KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape')
closeModal();
};
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script>
<style scoped lang="scss">
.modal-container {
max-width: 360px;
}
.color-wrapper{
display: grid;
grid-template-columns: repeat(auto-fill, 20px);
gap: 5px;
.color-box {
height: 20px;
width: 20px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
}
</style>

View File

@@ -100,14 +100,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { Component, computed, ComputedRef, onBeforeUnmount, onMounted, onUpdated, Prop, Ref, ref, watch } from 'vue'; import { Component, computed, ComputedRef, onBeforeUnmount, onMounted, onUpdated, Prop, Ref, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import * as moment from 'moment';
import { ConnectionParams } from 'common/interfaces/antares'; import { ConnectionParams } from 'common/interfaces/antares';
import { HistoryRecord, useHistoryStore } from '@/stores/history'; import { HistoryRecord, useHistoryStore } from '@/stores/history';
import { useConnectionsStore } from '@/stores/connections'; import { useConnectionsStore } from '@/stores/connections';
import { useFocusTrap } from '@/composables/useFocusTrap'; import { useFocusTrap } from '@/composables/useFocusTrap';
import { useFilters } from '@/composables/useFilters';
import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue'; import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue';
const { t } = useI18n(); const { t } = useI18n();
const { formatDate } = useFilters();
const { getHistoryByWorkspace, deleteQueryFromHistory } = useHistoryStore(); const { getHistoryByWorkspace, deleteQueryFromHistory } = useHistoryStore();
const { getConnectionName } = useConnectionsStore(); const { getConnectionName } = useConnectionsStore();
@@ -164,7 +165,6 @@ const resizeResults = () => {
} }
}; };
const formatDate = (date: Date) => moment(date).isValid() ? moment(date).format('HH:mm:ss - YYYY/MM/DD') : date;
const refreshScroller = () => resizeResults(); const refreshScroller = () => resizeResults();
const closeModal = () => emit('close'); const closeModal = () => emit('close');

View File

@@ -6,8 +6,8 @@
<div class="modal-header pl-2"> <div class="modal-header pl-2">
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-database-arrow-up mr-1" /> <i class="mdi mdi-24px mdi-database-import mr-1" />
<span class="cut-text">{{ $t('message.importSchema') }}</span> <span class="cut-text">{{ t('message.importSchema') }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -15,7 +15,7 @@
<div class="modal-body pb-0"> <div class="modal-body pb-0">
{{ sqlFile }} {{ sqlFile }}
<div v-if="queryErrors.length > 0" class="mt-2"> <div v-if="queryErrors.length > 0" class="mt-2">
<label>{{ $tc('message.importQueryErrors', queryErrors.length) }}</label> <label>{{ t('message.importQueryErrors', queryErrors.length) }}</label>
<textarea <textarea
v-model="formattedQueryErrors" v-model="formattedQueryErrors"
class="form-input" class="form-input"
@@ -28,7 +28,7 @@
<div class="column col modal-progress-wrapper text-left"> <div class="column col modal-progress-wrapper text-left">
<div class="import-progress"> <div class="import-progress">
<span class="progress-status"> <span class="progress-status">
{{ progressPercentage }}% - {{ progressStatus }} - {{ $tc('message.executedQueries', queryCount) }} {{ progressPercentage }}% - {{ progressStatus }} - {{ t('message.executedQueries', queryCount) }}
</span> </span>
<progress <progress
class="progress d-block" class="progress d-block"
@@ -39,7 +39,7 @@
</div> </div>
<div class="column col-auto px-0"> <div class="column col-auto px-0">
<button class="btn btn-link" @click.stop="closeModal"> <button class="btn btn-link" @click.stop="closeModal">
{{ completed ? $t('word.close') : $t('word.cancel') }} {{ completed ? t('word.close') : t('word.cancel') }}
</button> </button>
</div> </div>
</div> </div>
@@ -57,8 +57,8 @@ import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import Schema from '@/ipc-api/Schema'; import Schema from '@/ipc-api/Schema';
import { useI18n } from 'vue-i18n';
import { ImportState } from 'common/interfaces/importer'; import { ImportState } from 'common/interfaces/importer';
import { useI18n } from 'vue-i18n';
const { t } = useI18n(); const { t } = useI18n();

View File

@@ -7,7 +7,7 @@
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-database-plus mr-1" /> <i class="mdi mdi-24px mdi-database-plus mr-1" />
<span class="cut-text">{{ $t('message.createNewSchema') }}</span> <span class="cut-text">{{ t('message.createNewSchema') }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -17,7 +17,7 @@
<form class="form-horizontal" @submit.prevent="createSchema"> <form class="form-horizontal" @submit.prevent="createSchema">
<div class="form-group"> <div class="form-group">
<div class="col-3"> <div class="col-3">
<label class="form-label">{{ $t('word.name') }}</label> <label class="form-label">{{ t('word.name') }}</label>
</div> </div>
<div class="col-9"> <div class="col-9">
<input <input
@@ -26,23 +26,24 @@
class="form-input" class="form-input"
type="text" type="text"
required required
:placeholder="$t('message.schemaName')" :placeholder="t('message.schemaName')"
> >
</div> </div>
</div> </div>
<div v-if="customizations.collations" class="form-group"> <div v-if="customizations.collations" class="form-group">
<div class="col-3"> <div class="col-3">
<label class="form-label">{{ $t('word.collation') }}</label> <label class="form-label">{{ t('word.collation') }}</label>
</div> </div>
<div class="col-9"> <div class="col-9">
<BaseSelect <BaseSelect
v-model="database.collation" v-model="database.collation"
class="form-select" class="form-select"
:options="collations" :options="collations"
:max-visible-options="1000"
option-label="collation" option-label="collation"
option-track-by="collation" option-track-by="collation"
/> />
<small>{{ $t('message.serverDefault') }}: {{ defaultCollation }}</small> <small>{{ t('message.serverDefault') }}: {{ defaultCollation }}</small>
</div> </div>
</div> </div>
</form> </form>
@@ -54,10 +55,10 @@
:class="{'loading': isLoading}" :class="{'loading': isLoading}"
@click.stop="createSchema" @click.stop="createSchema"
> >
{{ $t('word.add') }} {{ t('word.add') }}
</button> </button>
<button class="btn btn-link" @click.stop="closeModal"> <button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }} {{ t('word.close') }}
</button> </button>
</div> </div>
</div> </div>
@@ -73,6 +74,9 @@ import { useWorkspacesStore } from '@/stores/workspaces';
import { useFocusTrap } from '@/composables/useFocusTrap'; import { useFocusTrap } from '@/composables/useFocusTrap';
import Schema from '@/ipc-api/Schema'; import Schema from '@/ipc-api/Schema';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
@@ -139,7 +143,7 @@ onBeforeUnmount(() => {
}); });
</script> </script>
<style scoped> <style scoped lang="scss">
.modal-container { .modal-container {
max-width: 360px; max-width: 360px;
} }

View File

@@ -17,7 +17,7 @@
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-memory mr-1" /> <i class="mdi mdi-24px mdi-memory mr-1" />
<span class="cut-text">{{ $t('message.processesList') }}: {{ connectionName }}</span> <span class="cut-text">{{ t('message.processesList') }}: {{ connectionName }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -29,7 +29,7 @@
<button <button
class="btn btn-dark btn-sm mr-0 pr-1 d-flex" class="btn btn-dark btn-sm mr-0 pr-1 d-flex"
:class="{'loading':isQuering}" :class="{'loading':isQuering}"
:title="`${$t('word.refresh')} (F5)`" :title="`${t('word.refresh')}`"
@click="getProcessesList" @click="getProcessesList"
> >
<i v-if="!+autorefreshTimer" class="mdi mdi-24px mdi-refresh mr-1" /> <i v-if="!+autorefreshTimer" class="mdi mdi-24px mdi-refresh mr-1" />
@@ -39,7 +39,7 @@
<i class="mdi mdi-24px mdi-menu-down" /> <i class="mdi mdi-24px mdi-menu-down" />
</div> </div>
<div class="menu px-3"> <div class="menu px-3">
<span>{{ $t('word.autoRefresh') }}: <b>{{ +autorefreshTimer ? `${autorefreshTimer}s` : 'OFF' }}</b></span> <span>{{ t('word.autoRefresh') }}: <b>{{ +autorefreshTimer ? `${autorefreshTimer}s` : 'OFF' }}</b></span>
<input <input
v-model="autorefreshTimer" v-model="autorefreshTimer"
class="slider no-border" class="slider no-border"
@@ -59,7 +59,7 @@
tabindex="0" tabindex="0"
> >
<i class="mdi mdi-24px mdi-file-export mr-1" /> <i class="mdi mdi-24px mdi-file-export mr-1" />
<span>{{ $t('word.export') }}</span> <span>{{ t('word.export') }}</span>
<i class="mdi mdi-24px mdi-menu-down" /> <i class="mdi mdi-24px mdi-menu-down" />
</button> </button>
<ul class="menu text-left"> <ul class="menu text-left">
@@ -74,7 +74,7 @@
</div> </div>
<div class="workspace-query-info"> <div class="workspace-query-info">
<div v-if="sortedResults.length"> <div v-if="sortedResults.length">
{{ $t('word.processes') }}: <b>{{ sortedResults.length.toLocaleString() }}</b> {{ t('word.processes') }}: <b>{{ sortedResults.length.toLocaleString() }}</b>
</div> </div>
</div> </div>
</div> </div>
@@ -135,8 +135,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { Component, computed, onBeforeUnmount, onMounted, onUpdated, Prop, Ref, ref } from 'vue'; import { Component, computed, onBeforeUnmount, onMounted, onUpdated, Prop, Ref, ref } from 'vue';
import { ipcRenderer } from 'electron';
import { ConnectionParams } from 'common/interfaces/antares'; import { ConnectionParams } from 'common/interfaces/antares';
import { arrayToFile } from '../libs/arrayToFile'; import { exportRows } from '../libs/exportRows';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useFocusTrap } from '@/composables/useFocusTrap'; import { useFocusTrap } from '@/composables/useFocusTrap';
import Schema from '@/ipc-api/Schema'; import Schema from '@/ipc-api/Schema';
@@ -144,6 +145,9 @@ import { useConnectionsStore } from '@/stores/connections';
import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue'; import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue';
import ModalProcessesListRow from '@/components/ModalProcessesListRow.vue'; import ModalProcessesListRow from '@/components/ModalProcessesListRow.vue';
import ModalProcessesListContext from '@/components/ModalProcessesListContext.vue'; import ModalProcessesListContext from '@/components/ModalProcessesListContext.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const { getConnectionName } = useConnectionsStore(); const { getConnectionName } = useConnectionsStore();
@@ -312,10 +316,10 @@ const closeModal = () => emit('close');
const downloadTable = (format: 'csv' | 'json') => { const downloadTable = (format: 'csv' | 'json') => {
if (!sortedResults.value) return; if (!sortedResults.value) return;
arrayToFile({ exportRows({
type: format, type: format,
content: sortedResults.value, content: sortedResults.value,
filename: 'processes' table: 'processes'
}); });
}; };
@@ -323,10 +327,10 @@ const onKey = (e:KeyboardEvent) => {
e.stopPropagation(); e.stopPropagation();
if (e.key === 'Escape') if (e.key === 'Escape')
closeModal(); closeModal();
if (e.key === 'F5')
getProcessesList();
}; };
ipcRenderer.on('run-or-reload', getProcessesList);
window.addEventListener('keydown', onKey, { capture: true }); window.addEventListener('keydown', onKey, { capture: true });
onMounted(() => { onMounted(() => {
@@ -345,6 +349,8 @@ onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey, { capture: true }); window.removeEventListener('keydown', onKey, { capture: true });
window.removeEventListener('resize', resizeResults); window.removeEventListener('resize', resizeResults);
clearInterval(refreshInterval.value); clearInterval(refreshInterval.value);
ipcRenderer.removeListener('run-or-reload', getProcessesList);
}); });
defineExpose({ refreshScroller }); defineExpose({ refreshScroller });

View File

@@ -4,7 +4,7 @@
@close-context="closeContext" @close-context="closeContext"
> >
<div v-if="props.selectedRow" class="context-element"> <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('word.copy') }}</span>
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" /> <i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
<div class="context-submenu"> <div class="context-submenu">
<div <div
@@ -13,7 +13,7 @@
@click="copyCell" @click="copyCell"
> >
<span class="d-flex"> <span class="d-flex">
<i class="mdi mdi-18px mdi-numeric-0 mdi-rotate-90 text-light pr-1" /> {{ $tc('word.cell', 1) }} <i class="mdi mdi-18px mdi-numeric-0 mdi-rotate-90 text-light pr-1" /> {{ t('word.cell', 1) }}
</span> </span>
</div> </div>
<div <div
@@ -22,7 +22,7 @@
@click="copyRow" @click="copyRow"
> >
<span class="d-flex"> <span class="d-flex">
<i class="mdi mdi-18px mdi-table-row text-light pr-1" /> {{ $tc('word.row', 1) }} <i class="mdi mdi-18px mdi-table-row text-light pr-1" /> {{ t('word.row', 1) }}
</span> </span>
</div> </div>
</div> </div>
@@ -33,7 +33,7 @@
@click="killProcess" @click="killProcess"
> >
<span class="d-flex"> <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('message.killProcess') }}
</span> </span>
</div> </div>
</BaseContextMenu> </BaseContextMenu>
@@ -41,6 +41,9 @@
<script setup lang="ts"> <script setup lang="ts">
import BaseContextMenu from '@/components/BaseContextMenu.vue'; import BaseContextMenu from '@/components/BaseContextMenu.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({ const props = defineProps({
contextEvent: MouseEvent, contextEvent: MouseEvent,

View File

@@ -12,19 +12,19 @@
class="cell-content" class="cell-content"
:class="`${isNull(col)} type-${typeof col === 'number' ? 'int' : 'varchar'}`" :class="`${isNull(col)} type-${typeof col === 'number' ? 'int' : 'varchar'}`"
@dblclick="dblClick(cKey)" @dblclick="dblClick(cKey)"
>{{ cutText(col) }}</span> >{{ cutText(col, 250) }}</span>
</div> </div>
<ConfirmModal <ConfirmModal
v-if="isInfoModal" v-if="isInfoModal"
:confirm-text="$t('word.update')" :confirm-text="t('word.update')"
:cancel-text="$t('word.close')" :cancel-text="t('word.close')"
size="medium" size="medium"
:hide-footer="true" :hide-footer="true"
@hide="hideInfoModal" @hide="hideInfoModal"
> >
<template #header> <template #header>
<div class="d-flex"> <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('message.processInfo') }}
</div> </div>
</template> </template>
<template #body> <template #body>
@@ -48,6 +48,12 @@
import { Ref, ref } from 'vue'; import { Ref, ref } from 'vue';
import ConfirmModal from '@/components/BaseConfirmModal.vue'; import ConfirmModal from '@/components/BaseConfirmModal.vue';
import TextEditor from '@/components/BaseTextEditor.vue'; import TextEditor from '@/components/BaseTextEditor.vue';
import { useFilters } from '@/composables/useFilters';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const { cutText } = useFilters();
const props = defineProps({ const props = defineProps({
row: Object row: Object
@@ -79,11 +85,6 @@ const dblClick = (col: string) => {
isInfoModal.value = true; isInfoModal.value = true;
}; };
const cutText = (val: string | number) => {
if (typeof val !== 'string') return val;
return val.length > 250 ? `${val.substring(0, 250)}[...]` : val;
};
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@@ -30,6 +30,13 @@
> >
<a class="tab-link">{{ t('word.themes') }}</a> <a class="tab-link">{{ t('word.themes') }}</a>
</li> </li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'shortcuts'}"
@click="selectTab('shortcuts')"
>
<a class="tab-link">{{ t('word.shortcuts') }}</a>
</li>
<li <li
v-if="updateStatus !== 'disabled'" v-if="updateStatus !== 'disabled'"
class="tab-item c-hand" class="tab-item c-hand"
@@ -79,7 +86,7 @@
/> />
</div> </div>
<div class="col-4 col-sm-12 px-2 p-vcentered"> <div class="col-4 col-sm-12 px-2 p-vcentered">
<small class="d-block" style="line-height: 1.1; font-size: 70%;"> <small class="d-block" :style="'line-height: 1.1; font-size: 70%;'">
{{ t('message.missingOrIncompleteTranslation') }}<br> {{ 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> <a class="text-bold c-hand" @click="openOutside('https://github.com/antares-sql/antares/wiki/Translate-Antares')">{{ t('message.findOutHowToContribute') }}</a>
</small> </small>
@@ -103,7 +110,7 @@
<div class="form-group column col-12 mb-0"> <div class="form-group column col-12 mb-0">
<div class="col-5 col-sm-12"> <div class="col-5 col-sm-12">
<label class="form-label"> <label class="form-label">
{{ t('message.restorePreviourSession') }} {{ t('message.restorePreviousSession') }}
</label> </label>
</div> </div>
<div class="col-3 col-sm-12"> <div class="col-3 col-sm-12">
@@ -113,6 +120,24 @@
</label> </label>
</div> </div>
</div> </div>
<div class="form-group column col-12 mb-0">
<div class="col-5 col-sm-12">
<label class="form-label">
{{ t('message.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('message.showTableSizeDescription') }}
</small>
</div>
</div>
<div class="form-group column col-12 mb-0"> <div class="form-group column col-12 mb-0">
<div class="col-5 col-sm-12"> <div class="col-5 col-sm-12">
<label class="form-label"> <label class="form-label">
@@ -126,6 +151,19 @@
</label> </label>
</div> </div>
</div> </div>
<div class="form-group column col-12 mb-0">
<div class="col-5 col-sm-12">
<label class="form-label">
{{ t('message.disableScratchpad') }}
</label>
</div>
<div class="col-3 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleDisableScratchpad">
<input type="checkbox" :checked="disableScratchpad">
<i class="form-icon" />
</label>
</div>
</div>
<div class="form-group column col-12"> <div class="form-group column col-12">
<div class="col-5 col-sm-12"> <div class="col-5 col-sm-12">
<label class="form-label"> <label class="form-label">
@@ -176,6 +214,41 @@
</label> </label>
</div> </div>
</div> </div>
<div class="form-group column col-12 mb-0">
<div class="col-5 col-sm-12">
<label class="form-label">
{{ t('message.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('word.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('message.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> </div>
</form> </form>
</div> </div>
@@ -219,7 +292,7 @@
<div class="column col-12 h6 text-uppercase mb-2 mt-4"> <div class="column col-12 h6 text-uppercase mb-2 mt-4">
{{ t('message.editorTheme') }} {{ t('message.editorTheme') }}
</div> </div>
<div class="column col-6 h5 mb-4"> <div class="column col-5 h5 mb-4">
<BaseSelect <BaseSelect
v-model="localEditorTheme" v-model="localEditorTheme"
class="form-select" class="form-select"
@@ -231,28 +304,49 @@
@change="changeEditorTheme(localEditorTheme)" @change="changeEditorTheme(localEditorTheme)"
/> />
</div> </div>
<div class="column col-6 mb-4"> <div class="column col-7 mb-4">
<div class="btn-group btn-group-block"> <div class="btn-group btn-group-block">
<button
class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'xsmall'}"
@click="changeEditorFontSize('xsmall')"
>
10px
</button>
<button <button
class="btn btn-dark cut-text" class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'small'}" :class="{'active': editorFontSize === 'small'}"
@click="changeEditorFontSize('small')" @click="changeEditorFontSize('small')"
> >
{{ t('word.small') }} 12px
</button> </button>
<button <button
class="btn btn-dark cut-text" class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'medium'}" :class="{'active': editorFontSize === 'medium'}"
@click="changeEditorFontSize('medium')" @click="changeEditorFontSize('medium')"
> >
{{ t('word.medium') }} 14px
</button> </button>
<button <button
class="btn btn-dark cut-text" class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'large'}" :class="{'active': editorFontSize === 'large'}"
@click="changeEditorFontSize('large')" @click="changeEditorFontSize('large')"
> >
{{ t('word.large') }} 16px
</button>
<button
class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'xlarge'}"
@click="changeEditorFontSize('xlarge')"
>
18px
</button>
<button
class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'xxlarge'}"
@click="changeEditorFontSize('xxlarge')"
>
20px
</button> </button>
</div> </div>
</div> </div>
@@ -269,6 +363,9 @@
</div> </div>
</div> </div>
<div v-show="selectedTab === 'shortcuts'" class="panel-body py-4">
<ModalSettingsShortcuts />
</div>
<div v-show="selectedTab === 'update'" class="panel-body py-4"> <div v-show="selectedTab === 'update'" class="panel-body py-4">
<ModalSettingsUpdate /> <ModalSettingsUpdate />
</div> </div>
@@ -302,7 +399,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onBeforeUnmount, Ref, ref } from 'vue'; import { onBeforeUnmount, Ref, ref, computed } from 'vue';
import { shell } from 'electron'; import { shell } from 'electron';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@@ -313,11 +410,12 @@ import { useFocusTrap } from '@/composables/useFocusTrap';
import { localesNames } from '@/i18n/supported-locales'; import { localesNames } from '@/i18n/supported-locales';
import ModalSettingsUpdate from '@/components/ModalSettingsUpdate.vue'; import ModalSettingsUpdate from '@/components/ModalSettingsUpdate.vue';
import ModalSettingsChangelog from '@/components/ModalSettingsChangelog.vue'; import ModalSettingsChangelog from '@/components/ModalSettingsChangelog.vue';
import ModalSettingsShortcuts from '@/components/ModalSettingsShortcuts.vue';
import BaseTextEditor from '@/components/BaseTextEditor.vue'; import BaseTextEditor from '@/components/BaseTextEditor.vue';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { computed } from '@vue/reactivity'; import { AvailableLocale } from '@/i18n';
const { t, availableLocales } = useI18n(); const { t } = useI18n();
const applicationStore = useApplicationStore(); const applicationStore = useApplicationStore();
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
@@ -334,9 +432,13 @@ const {
dataTabLimit: pageSize, dataTabLimit: pageSize,
autoComplete: selectedAutoComplete, autoComplete: selectedAutoComplete,
lineWrap: selectedLineWrap, lineWrap: selectedLineWrap,
executeSelected: selectedExecuteSelected,
defaultCopyType: selectedCopyType,
notificationsTimeout, notificationsTimeout,
restoreTabs, restoreTabs,
showTableSize,
disableBlur, disableBlur,
disableScratchpad,
applicationTheme, applicationTheme,
editorTheme, editorTheme,
editorFontSize editorFontSize
@@ -349,12 +451,16 @@ const {
changePageSize, changePageSize,
changeRestoreTabs, changeRestoreTabs,
changeDisableBlur, changeDisableBlur,
changeDisableScratchpad,
changeAutoComplete, changeAutoComplete,
changeLineWrap, changeLineWrap,
changeExecuteSelected,
changeApplicationTheme, changeApplicationTheme,
changeEditorTheme, changeEditorTheme,
changeEditorFontSize, changeEditorFontSize,
updateNotificationsTimeout updateNotificationsTimeout,
changeDefaultCopyType,
changeShowTableSize
} = settingsStore; } = settingsStore;
const { const {
hideSettingModal: closeModal, hideSettingModal: closeModal,
@@ -369,7 +475,30 @@ const contributors = process.env.APP_CONTRIBUTORS;
const appLogo = require('../images/logo.svg'); const appLogo = require('../images/logo.svg');
const darkPreview = require('../images/dark.png'); const darkPreview = require('../images/dark.png');
const lightPreview = require('../images/light.png'); const lightPreview = require('../images/light.png');
const editorThemes= [ const exampleQuery = `-- This is an example
SELECT
employee.id,
employee.first_name,
employee.last_name,
SUM(DATEDIFF("SECOND", call.start, call.end)) AS call_duration
FROM call
INNER JOIN employee ON call.employee_id = employee.id
GROUP BY
employee.id,
employee.first_name,
employee.last_name
ORDER BY
employee.id ASC;
`;
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);
const selectedTab: Ref<string> = ref('general');
const editorThemes = computed(() => [
{ {
group: t('word.light'), group: t('word.light'),
themes: [ themes: [
@@ -417,37 +546,24 @@ const editorThemes= [
{ code: 'vibrant_ink', name: 'Vibrant Ink' } { code: 'vibrant_ink', name: 'Vibrant Ink' }
] ]
} }
]; ]);
const exampleQuery = `-- This is an example
SELECT
employee.id,
employee.first_name,
employee.last_name,
SUM(DATEDIFF("SECOND", call.start, call.end)) AS call_duration
FROM call
INNER JOIN employee ON call.employee_id = employee.id
GROUP BY
employee.id,
employee.first_name,
employee.last_name
ORDER BY
employee.id ASC;
`;
const localLocale: Ref<string> = ref(null);
const localPageSize: Ref<number> = ref(null);
const localTimeout: Ref<number> = ref(null);
const localEditorTheme: Ref<string> = ref(null);
const selectedTab: Ref<string> = ref('general');
const locales = computed(() => { const locales = computed(() => {
const locales = []; const locales = [];
for (const locale of availableLocales) for (const locale of Object.keys(localesNames))
locales.push({ code: locale, name: localesNames[locale] }); 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('word.cell') },
{ code: 'html', name: t('word.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 hasUpdates = computed(() => ['available', 'downloading', 'downloaded', 'link'].includes(updateStatus.value));
const workspace = computed(() => { const workspace = computed(() => {
@@ -486,10 +602,18 @@ const toggleRestoreSession = () => {
changeRestoreTabs(!restoreTabs.value); changeRestoreTabs(!restoreTabs.value);
}; };
const toggleShowTableSize = () => {
changeShowTableSize(!showTableSize.value);
};
const toggleDisableBlur = () => { const toggleDisableBlur = () => {
changeDisableBlur(!disableBlur.value); changeDisableBlur(!disableBlur.value);
}; };
const toggleDisableScratchpad = () => {
changeDisableScratchpad(!disableScratchpad.value);
};
const toggleAutoComplete = () => { const toggleAutoComplete = () => {
changeAutoComplete(!selectedAutoComplete.value); changeAutoComplete(!selectedAutoComplete.value);
}; };
@@ -498,7 +622,12 @@ const toggleLineWrap = () => {
changeLineWrap(!selectedLineWrap.value); changeLineWrap(!selectedLineWrap.value);
}; };
localLocale.value = selectedLocale.value as string; const toggleExecuteSelected = () => {
changeExecuteSelected(!selectedExecuteSelected.value);
};
localLocale.value = selectedLocale.value;
defaultCopyType.value = selectedCopyType.value;
localPageSize.value = pageSize.value as number; localPageSize.value = pageSize.value as number;
localTimeout.value = notificationsTimeout.value as number; localTimeout.value = notificationsTimeout.value as number;
localEditorTheme.value = editorTheme.value as string; localEditorTheme.value = editorTheme.value as string;
@@ -519,6 +648,12 @@ onBeforeUnmount(() => {
.modal-body { .modal-body {
overflow: hidden; overflow: hidden;
.tab-link {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.panel-body { .panel-body {
min-height: calc(25vh - 70px); min-height: calc(25vh - 70px);
max-height: 65vh; max-height: 65vh;

View File

@@ -0,0 +1,313 @@
<template>
<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>
</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>
</button>
</div>
<div class="container workspace-query-results">
<div class="table table-hover">
<div class="thead">
<div class="tr text-uppercase">
<div class="th no-border">
<div>
{{ t('word.event') }}
</div>
</div>
<div class="th no-border" style="width: 100%;">
<div>
{{ t('word.key', 2) }}
</div>
</div>
<div class="th no-border" />
</div>
</div>
<div class="tbody">
<div
v-for="(shortcut, i) in shortcuts"
:key="i"
class="tr"
tabindex="0"
>
<div class="td py-1">
{{ t(shortcutEvents[shortcut.event].l18n, {param: shortcutEvents[shortcut.event].l18nParam}) }}
</div>
<div
class="td py-1"
style="border-right: 0;"
v-html="parseKeys(shortcut.keys)"
/>
<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" />
</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" />
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<Teleport to="#window-content">
<ConfirmModal
v-if="isConfirmAddModal"
:disable-autofocus="true"
:confirm-text="t('word.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') }}
</div>
</template>
<template #body>
<div class="mb-2">
<div class="form-group">
<label class="form-label">{{ t('word.event') }}</label>
<BaseSelect
v-model="shortcutToAdd.event"
class="form-select"
:options="eventOptions"
/>
</div>
</div>
<div class="mb-2">
<div class="form-group">
<label class="form-label">{{ t('word.key', 2) }}</label>
<KeyPressDetector v-model="typedShortcut" />
</div>
</div>
<small v-if="doesShortcutExists" class="text-warning">{{ t('message.shortcutAlreadyExists') }}</small>
</template>
</ConfirmModal>
<ConfirmModal
v-if="isConfirmEditModal"
:disable-autofocus="true"
:confirm-text="t('word.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') }}
</div>
</template>
<template #body>
<div class="mb-2">
<div class="form-group">
<label class="form-label">{{ t('word.event') }}</label>
<BaseSelect
v-model="shortcutToEdit.event"
class="form-select"
:options="eventOptions"
:disabled="true"
/>
</div>
</div>
<div class="mb-2">
<div class="form-group">
<label class="form-label">{{ t('word.key', 2) }}</label>
<KeyPressDetector v-model="shortcutToEdit.keys[0]" />
</div>
</div>
<small v-if="doesShortcutExists" class="text-warning">{{ t('message.shortcutAlreadyExists') }}</small>
</template>
</ConfirmModal>
<ConfirmModal
v-if="isConfirmDeleteModal"
:disable-autofocus="true"
@confirm="deleteShortcut"
@hide="isConfirmDeleteModal = false"
>
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-delete mr-1" /> {{ t('message.deleteShortcut') }}
</div>
</template>
<template #body>
<div class="mb-2">
{{ t('message.deleteConfirm') }} <b>{{ t(shortcutEvents[shortcutToDelete.event].l18n, {param: shortcutEvents[shortcutToDelete.event].l18nParam}) }} (<span v-html="parseKeys(shortcutToDelete.keys)" />)</b>?
</div>
</template>
</ConfirmModal>
<ConfirmModal
v-if="isConfirmRestoreModal"
:disable-autofocus="true"
@confirm="restoreDefaults"
@hide="isConfirmRestoreModal = false"
>
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-undo mr-1" /> {{ t('message.restoreDefaults') }}
</div>
</template>
<template #body>
<div class="mb-2">
{{ t('message.restoreDefaultsQuestion') }}
</div>
</template>
</ConfirmModal>
</Teleport>
</template>
<script setup lang="ts">
import { Ref, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { useSettingsStore } from '@/stores/settings';
import KeyPressDetector from './KeyPressDetector.vue';
import Application from '@/ipc-api/Application';
import { shortcutEvents, ShortcutRecord } from 'common/shortcuts';
import ConfirmModal from '@/components/BaseConfirmModal.vue';
import BaseSelect from '@/components/BaseSelect.vue';
import { computed } from '@vue/reactivity';
import { useFilters } from '@/composables/useFilters';
const { parseKeys } = useFilters();
const { t } = useI18n();
const isMacOS = process.platform === 'darwin';
const isConfirmRestoreModal = ref(false);
const isConfirmAddModal = ref(false);
const isConfirmEditModal = ref(false);
const isConfirmDeleteModal = ref(false);
const doesShortcutExists = ref(false);
const shortcutToAdd: Ref<ShortcutRecord> = ref({ event: undefined, keys: [], os: [process.platform] });
const shortcutToEdit: Ref<ShortcutRecord & { index: number }> = ref(null);
const shortcutToDelete: Ref<ShortcutRecord> = ref(null);
const typedShortcut = ref('');
const settingsStore = useSettingsStore();
const { shortcuts } = storeToRefs(settingsStore);
const eventOptions = computed(() => {
return Object.keys(shortcutEvents)
.map(key => {
return { value: key, label: t(shortcutEvents[key].l18n, { param: shortcutEvents[key].l18nParam }) };
})
.sort((a, b) => {
if (a.label < b.label) return -1;
if (a.label > b.label) return 1;
return 0;
});
});
const restoreDefaults = () => {
isConfirmRestoreModal.value = false;
return Application.restoreDefaultShortcuts();
};
const showAddModal = () => {
shortcutToAdd.value.event = eventOptions.value[0].value;
isConfirmAddModal.value = true;
};
const closeAddModal = () => {
typedShortcut.value = '';
doesShortcutExists.value = false;
shortcutToAdd.value = { event: undefined, keys: [], os: [process.platform] };
isConfirmAddModal.value = false;
};
const showEditModal = (shortcut: ShortcutRecord & { index: number }) => {
shortcut = {
...shortcut,
keys: [shortcut.keys[0].replaceAll('CommandOrControl', isMacOS ? 'Command' : 'Control')]
};
shortcutToEdit.value = shortcut;
isConfirmEditModal.value = true;
};
const editShortcut = () => {
const index = shortcutToEdit.value.index;
delete shortcutToEdit.value.index;
shortcutToEdit.value.index = undefined;
shortcuts.value[index] = shortcutToEdit.value;
isConfirmEditModal.value = false;
return Application.updateShortcuts(shortcuts.value);
};
const closeEditModal = () => {
typedShortcut.value = '';
doesShortcutExists.value = false;
shortcutToEdit.value = null;
isConfirmEditModal.value = false;
};
const addShortcut = () => {
if (!typedShortcut.value.length || doesShortcutExists.value) return;
shortcutToAdd.value.keys = [typedShortcut.value.replaceAll(isMacOS ? 'Command' : 'Control', 'CommandOrControl')];
const filteredShortcuts = [shortcutToAdd.value, ...shortcuts.value];
isConfirmAddModal.value = false;
return Application.updateShortcuts(filteredShortcuts);
};
const showDeleteModal = (shortcut: ShortcutRecord) => {
isConfirmDeleteModal.value = true;
shortcutToDelete.value = shortcut;
};
const deleteShortcut = () => {
const filteredShortcuts = shortcuts.value.filter(s => (
shortcutToDelete.value.keys.toString() !== s.keys.toString()
));
isConfirmDeleteModal.value = false;
return Application.updateShortcuts(filteredShortcuts);
};
watch(typedShortcut, () => {
doesShortcutExists.value = shortcuts.value.some(s => (
s.keys.some(k => (
k.replaceAll('CommandOrControl', isMacOS ? 'Command' : 'Control') === typedShortcut.value
))
));
});
</script>
<style lang="scss" scoped>
.table {
.tr {
.td {
border-right: 3px solid;
border-bottom: 3px solid;
}
&:hover {
.shortcut-button {
opacity: 1;
}
}
.shortcut-button {
font-size: 0.7rem;
height: 1rem;
line-height: 1rem;
display: inline-flex;
align-items: center;
justify-content: center;
opacity: 0;
}
}
}
.shortcuts-tools {
display: flex;
justify-content: flex-end;
}
</style>

View File

@@ -26,27 +26,27 @@
:class="{'loading': updateStatus === 'checking'}" :class="{'loading': updateStatus === 'checking'}"
@click="checkForUpdates" @click="checkForUpdates"
> >
{{ $t('message.checkForUpdates') }} {{ t('message.checkForUpdates') }}
</button> </button>
<button <button
v-else-if="updateStatus === 'downloaded'" v-else-if="updateStatus === 'downloaded'"
class="btn btn-primary" class="btn btn-primary"
@click="restartToUpdate" @click="restartToUpdate"
> >
{{ $t('message.restartToInstall') }} {{ t('message.restartToInstall') }}
</button> </button>
<button <button
v-else-if="updateStatus === 'link'" v-else-if="updateStatus === 'link'"
class="btn btn-primary" class="btn btn-primary"
@click="openOutside('https://antares-sql.app/download.html')" @click="openOutside('https://antares-sql.app/download.html')"
> >
{{ $t('message.goToDownloadPage') }} {{ t('message.goToDownloadPage') }}
</button> </button>
</div> </div>
<div class="form-group mt-4"> <div class="form-group mt-4">
<label class="form-switch d-inline-block disabled" @click.prevent="toggleAllowPrerelease"> <label class="form-switch d-inline-block disabled" @click.prevent="toggleAllowPrerelease">
<input type="checkbox" :checked="allowPrerelease"> <input type="checkbox" :checked="allowPrerelease">
<i class="form-icon" /> {{ $t('message.includeBetaUpdates') }} <i class="form-icon" /> {{ t('message.includeBetaUpdates') }}
</label> </label>
</div> </div>
</div> </div>

View File

@@ -34,6 +34,15 @@ const {
lineWrap lineWrap
} = storeToRefs(settingsStore); } = storeToRefs(settingsStore);
const sizes = {
xsmall: '10px',
small: '12px',
medium: '14px',
large: '16px',
xlarge: '18px',
xxlarge: '20px'
};
const props = defineProps({ const props = defineProps({
modelValue: String, modelValue: String,
workspace: Object as Prop<Workspace>, workspace: Object as Prop<Workspace>,
@@ -47,10 +56,11 @@ const props = defineProps({
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
const cursorPosition = ref(0); const cursorPosition = ref(0);
const fields = ref([]); const lastTableFields = ref([]);
const customCompleter = ref([]); const customCompleter = ref([]);
const id = ref(uidGen()); const id = ref(uidGen());
const lastSchema: Ref<string> = ref(null); const lastSchema: Ref<string> = ref(null);
const fields: Ref<{name: string; type: string}[]> = ref([]);
const tables = computed(() => { const tables = computed(() => {
return props.workspace return props.workspace
@@ -61,13 +71,27 @@ const tables = computed(() => {
}, []).map(table => { }, []).map(table => {
return { return {
name: table.name as string, name: table.name as string,
type: table.type as string, type: table.type as string
fields: []
}; };
}) })
: []; : [];
}); });
const tablesInQuery = computed(() => {
if (!props.modelValue) return [];
const words = props.modelValue
.replaceAll(/[.'"`]/g, ' ')
.split(' ')
.filter(Boolean);
const includedTables = tables.value.reduce((acc, curr) => {
acc.push(curr.name);
return acc;
}, [] as string[]).filter((t) => words.includes(t));
return includedTables;
});
const triggers = computed(() => { const triggers = computed(() => {
return props.workspace return props.workspace
? props.workspace.structure.filter(schema => schema.name === props.schema) ? props.workspace.structure.filter(schema => schema.name === props.schema)
@@ -150,11 +174,11 @@ const lastWord = computed(() => {
const isLastWordATable = computed(() => /\w+\.\w*/gm.test(lastWord.value)); const isLastWordATable = computed(() => /\w+\.\w*/gm.test(lastWord.value));
const fieldsCompleter = computed(() => { const tableFieldsCompleter = computed(() => {
return { return {
getCompletions: (editor: never, session: never, pos: never, prefix: never, callback: (err: null, response: ace.Ace.Completion[]) => void) => { getCompletions: (editor: never, session: never, pos: never, prefix: never, callback: (err: null, response: ace.Ace.Completion[]) => void) => {
const completions: ace.Ace.Completion[] = []; const completions: ace.Ace.Completion[] = [];
fields.value.forEach(field => { lastTableFields.value.forEach(field => {
completions.push({ completions.push({
value: field, value: field,
meta: 'column', meta: 'column',
@@ -175,7 +199,8 @@ const setCustomCompleter = () => {
...triggers.value, ...triggers.value,
...procedures.value, ...procedures.value,
...functions.value, ...functions.value,
...schedulers.value ...schedulers.value,
...fields.value
].forEach(el => { ].forEach(el => {
completions.push({ completions.push({
value: el.name, value: el.name,
@@ -195,18 +220,37 @@ watch(() => props.modelValue, () => {
cursorPosition.value = (editor.value.session as any).doc.positionToIndex(editor.value.getCursorPosition()); cursorPosition.value = (editor.value.session as any).doc.positionToIndex(editor.value.getCursorPosition());
}); });
watch(() => tablesInQuery.value.length, () => {
const localFields: {name: string; type: string}[] = [];
tablesInQuery.value.forEach(async table => {
const params = {
uid: props.workspace.uid,
schema: props.schema,
table: table
};
const { response } = await Tables.getTableColumns(params);
response.forEach((field: { name: string }) => {
localFields.push({
name: field.name,
type: 'column'
});
});
});
fields.value = localFields;
setTimeout(() => {
setCustomCompleter();
}, 100);
});
watch(editorTheme, () => { watch(editorTheme, () => {
if (editor.value) if (editor.value)
editor.value.setTheme(`ace/theme/${editorTheme.value}`); editor.value.setTheme(`ace/theme/${editorTheme.value}`);
}); });
watch(editorFontSize, () => { watch(editorFontSize, () => {
const sizes = {
small: '12px',
medium: '14px',
large: '16px'
};
if (editor.value) { if (editor.value) {
editor.value.setOptions({ editor.value.setOptions({
fontSize: sizes[editorFontSize.value] fontSize: sizes[editorFontSize.value]
@@ -266,7 +310,8 @@ onMounted(() => {
enableBasicAutocompletion: true, enableBasicAutocompletion: true,
wrap: lineWrap.value, wrap: lineWrap.value,
enableSnippets: true, enableSnippets: true,
enableLiveAutocompletion: autoComplete.value enableLiveAutocompletion: autoComplete.value,
fontSize: sizes[editorFontSize.value]
}); });
if (!baseCompleter.value.length) if (!baseCompleter.value.length)
@@ -289,8 +334,8 @@ onMounted(() => {
Tables.getTableColumns(params).then(res => { Tables.getTableColumns(params).then(res => {
if (res.response.length) if (res.response.length)
fields.value = res.response.map((field: { name: string }) => field.name); lastTableFields.value = res.response.map((field: { name: string }) => field.name);
editor.value.completers = [fieldsCompleter.value]; editor.value.completers = [tableFieldsCompleter.value];
editor.value.execCommand('startAutocomplete'); editor.value.execCommand('startAutocomplete');
}).catch(console.log); }).catch(console.log);
} }
@@ -328,6 +373,8 @@ onMounted(() => {
e.stop(); e.stop();
}); });
editor.value.commands.removeCommand('showSettingsMenu');
if (props.autoFocus) { if (props.autoFocus) {
setTimeout(() => { setTimeout(() => {
editor.value.focus(); editor.value.focus();

View File

@@ -0,0 +1,218 @@
<template>
<Draggable
:list="localList"
item-key="'uid'"
ghost-class="ghost"
:group="{ name: 'connections', pull: 'clone' }"
:swap-threshold="0.3"
@start="emit('start', $event)"
@end="emit('end', $event)"
@change="emit('update:modelValue', localList)"
>
<template #item="{ element }">
<li
v-if="element.isFolder || !folderedConnections.includes(element.uid)"
:draggable="true"
:class="{'folder': element.isFolder}"
@dragstart="draggedElement = element.uid"
@dragend="dragEnd"
@contextmenu.prevent="emit('context', $event, element)"
>
<div
v-if="!element.isFolder && !folderedConnections.includes(element.uid)"
v-tooltip="{
strategy: 'fixed',
placement: 'right',
content: getConnectionName(element.uid)
}"
class="settingbar-element btn btn-link"
:class="{ 'selected': element.uid === selectedWorkspace && coveredElement !== element.uid }"
placement="right"
strategy="fixed"
@click.stop="selectWorkspace(element.uid)"
>
<!-- Creates a new folder -->
<SettingBarConnections
v-if="draggedElement && !foldersUid.includes(draggedElement)"
class="drag-area"
:class="[{'folder-preview': coveredElement === element.uid && draggedElement !== coveredElement}]"
:list="dummyNested"
@dragenter="coveredElement = element.uid"
@dragleave="coveredElement = false"
@change="createFolder"
/>
<i v-if="coveredElement === element.uid && draggedElement !== coveredElement" class="settingbar-element-icon mdi mdi-folder-plus mdi-36px" />
<template v-else>
<div class="settingbar-element-icon-wrapper">
<i
class="settingbar-element-icon dbi"
:class="[element.icon ? `mdi ${element.icon} mdi-36px`: `dbi-${element.client}`, getStatusBadge(element.uid)]"
/>
<small class="settingbar-element-name">{{ element.name || getConnectionName(element.uid) }}</small>
</div>
</template>
</div>
<SettingBarConnectionsFolder
v-else-if="element.isFolder"
:key="`${element.uid}-${element.connections.length}`"
:folder="element"
:covered-element="coveredElement"
:dragged-element="draggedElement"
:folder-drag="folderDrag"
@select-workspace="selectWorkspace"
@covered="coveredElement = element.uid"
@uncovered="coveredElement = false"
@folder-sort="emit('update:modelValue', localList)"
@folder-drag="folderDrag = $event"
@context="emit('context', $event.event, $event.content)"
/>
</li>
</template>
</Draggable>
</template>
<script setup lang="ts">
import { computed, PropType, Ref, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import * as Draggable from 'vuedraggable';
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
import { useWorkspacesStore } from '@/stores/workspaces';
import SettingBarConnectionsFolder from '@/components/SettingBarConnectionsFolder.vue';
const workspacesStore = useWorkspacesStore();
const connectionsStore = useConnectionsStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { getFolders: folders } = storeToRefs(connectionsStore);
const { getWorkspace, selectWorkspace } = workspacesStore;
const { getConnectionName, addFolder } = connectionsStore;
const props = defineProps({
modelValue: {
type: Array as PropType<SidebarElement[]>,
default: () => []
}
});
const emit = defineEmits(['start', 'end', 'move', 'context', 'update:modelValue']);
const localList = ref(props.modelValue);
const dummyNested = ref([]);
const draggedElement: Ref<string | false> = ref(false);
const coveredElement: Ref<string | false> = ref(false);
const folderDrag = ref(false);
const foldersUid = computed(() => folders.value.reduce<string[]>((acc, curr) => {
acc.push(curr.uid);
return acc;
}, []));
const folderedConnections = computed(() => {
return folders.value.reduce<string[]>((acc, curr) => {
acc = [...acc, ...curr.connections];
return acc;
}, []);
});
const dragEnd = () => {
coveredElement.value = false;
draggedElement.value = false;
};
const createFolder = ({ added }: {added: { element: SidebarElement }}) => {
if (typeof coveredElement.value === 'string' && !added.element.isFolder) {
// Create folder
addFolder({
after: coveredElement.value,
connections: [coveredElement.value, added.element.uid]
});
coveredElement.value = false;
}
};
const getStatusBadge = (uid: string) => {
if (getWorkspace(uid)) {
const status = getWorkspace(uid).connectionStatus;
switch (status) {
case 'connected':
return 'badge badge-connected';
case 'connecting':
return 'badge badge-connecting';
case 'failed':
return 'badge badge-failed';
default:
return '';
}
}
};
watch(() => dummyNested.value.length, () => {
dummyNested.value = [];
});
watch(() => props.modelValue, (value) => {
localList.value = value;
folderDrag.value = false;// Prenent some edge cases
});
</script>
<style lang="scss">
.drag-area {
background-color: transparent;
z-index: 10;
position: absolute;
left: 20px;
top: 20px;
right: 20px;
bottom: 20px;
transition: all .2s;
&.folder-preview {
border: 2px dotted;
border-radius: 15px;
left: 5px;
top: 5px;
right: 5px;
bottom: 5px;
}
li {
display: none!important;
}
}
.ghost:not(.folder) {
height: $settingbar-width;
display: flex;
align-items: center;
justify-content: center;
padding: 4px;
position: relative;
transition: opacity .2s;
.settingbar-element {
border-radius: 15px!important;
background: rgba($color: #fff, $alpha: 0.1);
.settingbar-element-name {
position: absolute;
bottom: 5px;
left: 3px!important;
text-align: center!important;
font-size: 65%;
font-style: normal;
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 1;
}
}
}
.settingbar-element-icon {
display: flex;
color: $body-font-color-dark;
}
</style>

View File

@@ -0,0 +1,451 @@
<template>
<div
v-tooltip="{
strategy: 'fixed',
placement: 'right',
content: folder.name,
disabled: isOpen || !folder.name
}"
class="settingbar-element folder btn btn-link p-1"
:class="[{ 'selected-inside': hasSelectedInside && !isOpen }]"
:style="isOpen ? `height: auto; opacity: 1;` : null"
>
<Draggable
class="folder-container"
:item-key="((item: string) => localList.indexOf(item))"
:class="[{'opened': isOpen}]"
:style="[
`background: ${folder.color};`,
isOpen ? `max-height: ${60*(folder.connections.length+1)}px` : 'max-height: 60px',
!isOpen || folderDrag ? `overflow: hidden;` : ''
]"
:list="localList"
ghost-class="ghost"
:group="{ name: 'connections', put: folderDrag ? undefined : 'clone' }"
@start="dragStart"
@end="dragStop"
>
<template #header>
<div
v-if="!isOpen"
class="folder-overlay"
@click="openFolder"
@contextmenu.stop="emit('context', {event: $event, content: folder})"
/>
<div
v-if="isOpen"
class="folder-icon"
:style="`color: ${folder.color};`"
@click="closeFolder"
>
<i class="folder-icon-open mdi mdi-folder-open mdi-36px" />
<i class="folder-icon-close mdi mdi-folder mdi-36px" />
</div>
</template>
<template #item="{ element }">
<div
:key="element"
v-tooltip="{
strategy: 'fixed',
placement: 'right',
content: getConnectionName(element)
}"
class="folder-element"
:class="{ 'selected': element === selectedWorkspace }"
@click="emit('select-workspace', element)"
@contextmenu.stop="emit('context', {event: $event, content: getConnectionOrderByUid(element)})"
>
<i
class="folder-element-icon dbi"
:class="[getConnectionOrderByUid(element)?.icon ? `mdi ${getConnectionOrderByUid(element).icon}`: `dbi-${getConnectionOrderByUid(element)?.client}`, getStatusBadge(element)]"
/>
<small v-if="isOpen" class="folder-element-name">{{ getConnectionOrderByUid(element)?.name || getConnectionName(element) }}</small>
</div>
</template>
</Draggable>
<SettingBarConnections
v-if="draggedElement && !foldersUid.includes(draggedElement)"
class="drag-area"
:class="[{'folder-preview': coveredElement === folder.uid && draggedElement !== coveredElement}]"
:list="dummyNested"
:swap-threshold="1"
@dragenter="emit('covered')"
@dragleave="emit('uncovered')"
@change="addIntoFolder"
/>
</div>
</template>
<script setup lang="ts">
import { computed, PropType, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import * as Draggable from 'vuedraggable';
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
import { useWorkspacesStore } from '@/stores/workspaces';
import SettingBarConnections from '@/components/SettingBarConnections.vue';
const workspacesStore = useWorkspacesStore();
const connectionsStore = useConnectionsStore();
const { getFolders: folders } = storeToRefs(connectionsStore);
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { getWorkspace } = workspacesStore;
const { getConnectionOrderByUid, getConnectionName, addToFolder } = connectionsStore;
const foldersOpened = JSON.parse(localStorage.getItem('opened-folders')) || [];
const props = defineProps({
folder: {
type: Object as PropType<SidebarElement>,
required: true
},
folderDrag: {
type: Boolean,
default: false
},
draggedElement: {
type: [String, Boolean] as PropType<string | false>,
required: true
},
coveredElement: {
type: [String, Boolean] as PropType<string | false>,
required: true
}
});
const emit = defineEmits(['context', 'covered', 'uncovered', 'select-workspace', 'folder-sort', 'folder-drag']);
const isOpen = ref(foldersOpened.includes(props.folder.uid));
const localList = ref(props.folder.connections);
const dummyNested = ref([]);
const hasSelectedInside = computed(() => localList.value.includes(selectedWorkspace.value));
const foldersUid = computed(() => folders.value.reduce<string[]>((acc, curr) => {
acc.push(curr.uid);
return acc;
}, []));
const addIntoFolder = ({ added }: {added: { element: SidebarElement }}) => {
if (typeof props.coveredElement === 'string' && !added.element.isFolder) {
addToFolder({
folder: props.coveredElement,
connection: added.element.uid
});
emit('uncovered');
}
};
const getStatusBadge = (uid: string) => {
if (getWorkspace(uid)) {
const status = getWorkspace(uid).connectionStatus;
switch (status) {
case 'connected':
return 'badge badge-connected';
case 'connecting':
return 'badge badge-connecting';
case 'failed':
return 'badge badge-failed';
default:
return '';
}
}
};
const openFolder = () => {
isOpen.value = true;
const opened: string[] = JSON.parse(localStorage.getItem('opened-folders')) || [];
opened.push(props.folder.uid);
localStorage.setItem('opened-folders', JSON.stringify(opened));
};
const closeFolder = () => {
isOpen.value = false;
let opened: string[] = JSON.parse(localStorage.getItem('opened-folders')) || [];
opened = opened.filter(uid => uid !== props.folder.uid);
localStorage.setItem('opened-folders', JSON.stringify(opened));
};
const dragStart = () => {
emit('folder-drag', true);
};
const dragStop = () => {
emit('folder-drag', false);
};
watch(() => dummyNested.value.length, () => {
dummyNested.value = [];
});
emit('folder-sort');// To apply changes on component key change
</script>
<style lang="scss" scoped>
.folder {
position: relative;
&.selected-inside {
opacity: 1!important;
&::after {
height: 2.5rem;
position: absolute;
}
}
&::after {
content: "";
height: 0;
width: 3px;
transition: height 0.2s;
background-color: rgba($color: #fff, $alpha: 0.8);
border-radius: $border-radius;
position: absolute;
left: 0;
}
}
.folder-container {
display: grid;
grid-template-columns: auto auto;
grid-template-rows: 50%;
gap: 4px;
padding: 4px;
height: 100%;
width: 100%;
border-radius: 15px;
transition: background .3s;
color: $body-font-color-dark;
&::before {
content: "";
height: 0;
width: 3px;
transition: height 0.2s;
background-color: rgba($color: #fff, $alpha: 0.8);
border-radius: $border-radius;
position: absolute;
left: -11px;
}
.folder-element-icon {
margin: 0 auto;
&.badge::after {
top: 10px;
right: 0px;
position: absolute;
display: none;
}
&.badge-update::after {
bottom: initial;
}
}
&.opened {
gap: 4px 6px;
grid-template-columns: auto;
grid-template-rows: auto;
background: rgba($color: #fff, $alpha: 0.1)!important;
transition: max-height .1s;
.folder-element {
opacity: .6;
height: 2.5rem;
max-width: initial;
max-height: initial;
background: transparent;
&.ghost {
background: $bg-color-light-dark;
&.selected::before {
height: 0;
position: absolute;
}
}
&.selected {
opacity: 1;
&::before {
height: 2.5rem;
position: absolute;
}
}
&::before {
content: "";
height: 0;
width: 3px;
transition: height 0.2s;
background-color: rgba($color: #fff, $alpha: 0.8);
border-radius: $border-radius;
position: absolute;
left: -11px;
}
.folder-element-icon {
margin: 0 auto;
font-size: 36px;
display: flex;
&.badge::after {
top: 14px;
right: -4px;
position: absolute;
display: block;
}
&.badge-update::after {
bottom: initial;
}
}
}
.folder-icon {
height: 2.5rem;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 3px;
position: relative;
transition: opacity .2s;
.folder-icon-open {
display: block;
}
.folder-icon-close {
display: none;
}
&:hover {
opacity: 1;
.folder-icon-open {
display: none;
}
.folder-icon-close {
display: block;
}
}
}
}
.folder-overlay {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 10;
}
.folder-element {
display: flex;
align-items: flex-start;
justify-content: center;
max-width: 23px;
max-height: 23px;
margin-bottom: 3px;
position: relative;
transition: opacity .2s;
background: $bg-color-light-dark;
border-radius: 8px;
&.ghost {
margin:0 ;
margin-bottom: 3px;
height: 2.5rem;
}
&:hover, &.selected {
opacity: 1;
}
.folder-element-icon {
transition: margin .2s;
}
.folder-element-name {
position: absolute;
max-width: 90%;
bottom: 0;
font-size: 65%;
font-style: normal;
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 1.02;
transition: bottom .2s;
}
}
&:not(.opened){
.folder-element {
.folder-element-icon {
width: 21px;
height: 21px;
font-size: 16px;
}
}
}
}
.ghost {
border-radius: 15px!important;
background: rgba($color: #fff, $alpha: 0.1);
&.folder-element {
height: $settingbar-width;
border-radius: 15px;
display: flex;
align-items: center;
justify-content: center;
margin: 4px;
position: relative;
transition: opacity .2s;
&:hover, &.selected {
opacity: 1;
}
.folder-element-icon {
margin: 0 auto;
font-size: 36px;
display: flex;
align-items: center;
justify-content: center;
&.badge::after {
top: 5px;
right: -4px;
position: absolute;
}
&.badge-update::after {
bottom: initial;
}
}
.folder-element-name {
position: absolute;
max-width: 90%;
bottom: 5px;
text-align: center;
font-size: 65%;
font-style: normal;
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 1.02;
}
}
}
</style>

View File

@@ -8,13 +8,20 @@
class="context-element" class="context-element"
@click="disconnect" @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('word.disconnect') }}</span>
</div> </div>
<div class="context-element" @click="duplicateConnection"> <div
<span class="d-flex"><i class="mdi mdi-18px mdi-content-duplicate text-light pr-1" /> {{ $t('word.duplicate') }}</span> 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>
</div>
<div class="context-element" @click.stop="showAppearanceModal">
<span class="d-flex"><i class="mdi mdi-18px mdi-brush-variant text-light pr-1" /> {{ t('word.appearance') }}</span>
</div> </div>
<div class="context-element" @click="showConfirmModal"> <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('word.delete') }}</span>
</div> </div>
<ConfirmModal <ConfirmModal
@@ -24,15 +31,25 @@
> >
<template #header> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-server-remove mr-1" /> {{ $t('message.deleteConnection') }} <i class="mdi mdi-24px mr-1" :class="[contextConnection.isFolder ? 'mdi-folder-remove' : 'mdi-server-remove']" /> {{ t(contextConnection.isFolder ? 'message.deleteFolder' : 'message.deleteConnection') }}
</div> </div>
</template> </template>
<template #body> <template #body>
<div class="mb-2"> <div class="mb-2">
{{ $t('message.deleteCorfirm') }} <b>{{ connectionName }}</b>? {{ t('message.deleteConfirm') }} <b>{{ connectionName }}</b>?
</div> </div>
</template> </template>
</ConfirmModal> </ConfirmModal>
<ModalFolderAppearance
v-if="isFolderEdit"
:folder="contextConnection"
@close="hideAppearanceModal"
/>
<ModalConnectionAppearance
v-if="isConnectionEdit"
:connection="contextConnection"
@close="hideAppearanceModal"
/>
</BaseContextMenu> </BaseContextMenu>
</template> </template>
@@ -40,17 +57,25 @@
import { computed, Prop, ref } from 'vue'; import { computed, Prop, ref } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
import { useConnectionsStore } from '@/stores/connections'; import { useI18n } from 'vue-i18n';
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import BaseContextMenu from '@/components/BaseContextMenu.vue'; import BaseContextMenu from '@/components/BaseContextMenu.vue';
import ConfirmModal from '@/components/BaseConfirmModal.vue'; import ConfirmModal from '@/components/BaseConfirmModal.vue';
import { ConnectionParams } from 'common/interfaces/antares'; import ModalFolderAppearance from '@/components/ModalFolderAppearance.vue';
import ModalConnectionAppearance from '@/components/ModalConnectionAppearance.vue';
const { t } = useI18n();
const connectionsStore = useConnectionsStore();
const { const {
getConnectionByUid,
getConnectionName, getConnectionName,
addConnection, addConnection,
deleteConnection deleteConnection
} = useConnectionsStore(); } = connectionsStore;
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
@@ -62,15 +87,17 @@ const {
const props = defineProps({ const props = defineProps({
contextEvent: MouseEvent, contextEvent: MouseEvent,
contextConnection: Object as Prop<ConnectionParams> contextConnection: Object as Prop<SidebarElement>
}); });
const emit = defineEmits(['close-context']); const emit = defineEmits(['close-context']);
const isConfirmModal = ref(false); const isConfirmModal = ref(false);
const isFolderEdit = ref(false);
const isConnectionEdit = ref(false);
const connectionName = computed(() => getConnectionName(props.contextConnection.uid)); const connectionName = computed(() => props.contextConnection.name || getConnectionName(props.contextConnection.uid) || t('word.folder', 1));
const isConnected = computed(() => getWorkspace(props.contextConnection.uid).connectionStatus === 'connected'); const isConnected = computed(() => getWorkspace(props.contextConnection.uid)?.connectionStatus === 'connected');
const confirmDeleteConnection = () => { const confirmDeleteConnection = () => {
if (selectedWorkspace.value === props.contextConnection.uid) if (selectedWorkspace.value === props.contextConnection.uid)
@@ -80,7 +107,7 @@ const confirmDeleteConnection = () => {
}; };
const duplicateConnection = () => { const duplicateConnection = () => {
let connectionCopy = Object.assign({}, props.contextConnection); let connectionCopy = getConnectionByUid(props.contextConnection.uid);
connectionCopy = { connectionCopy = {
...connectionCopy, ...connectionCopy,
uid: uidGen('C'), uid: uidGen('C'),
@@ -91,6 +118,19 @@ const duplicateConnection = () => {
closeContext(); closeContext();
}; };
const showAppearanceModal = () => {
if (props.contextConnection.isFolder)
isFolderEdit.value = true;
else
isConnectionEdit.value = true;
};
const hideAppearanceModal = () => {
isConnectionEdit.value = false;
isFolderEdit.value = false;
closeContext();
};
const showConfirmModal = () => { const showConfirmModal = () => {
isConfirmModal.value = true; isConfirmModal.value = true;
}; };

View File

@@ -1,24 +1,56 @@
<template> <template>
<div id="footer" class="text-light"> <div
id="footer"
:class="[lightColors.includes(footerColor) ? 'text-dark' : 'text-light']"
:style="`background-color: ${footerColor};`"
>
<div class="footer-left-elements"> <div class="footer-left-elements">
<ul class="footer-elements"> <ul class="footer-elements">
<li class="footer-element"> <li class="footer-element">
<i class="mdi mdi-18px mdi-database mr-1" /> <i class="mdi mdi-18px mdi-database mr-1" />
<small>{{ versionString }}</small> <small>{{ versionString }}</small>
</li> </li>
<li v-if="connectionInfos && connectionInfos.readonly" class="footer-element">
<i class="mdi mdi-18px mdi-lock mr-1" />
<small>{{ t('message.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> </ul>
</div> </div>
<div class="footer-right-elements"> <div class="footer-right-elements">
<ul class="footer-elements"> <ul class="footer-elements">
<li
v-if="workspace?.connectionStatus === 'connected'"
class="footer-element footer-link"
@click="toggleConsole()"
>
<i class="mdi mdi-18px mdi-console-line mr-1" />
<small>{{ t('word.console') }}</small>
</li>
<li class="footer-element footer-link" @click="openOutside('https://www.paypal.com/paypalme/fabiodistasio')"> <li class="footer-element footer-link" @click="openOutside('https://www.paypal.com/paypalme/fabiodistasio')">
<i class="mdi mdi-18px mdi-coffee mr-1" /> <i class="mdi mdi-18px mdi-coffee mr-1" />
<small>{{ $t('word.donate') }}</small> <small>{{ t('word.donate') }}</small>
</li> </li>
<li class="footer-element footer-link" @click="openOutside('https://github.com/antares-sql/antares/issues')"> <li
class="footer-element footer-link"
:title="t('message.reportABug')"
@click="openOutside('https://github.com/antares-sql/antares/issues')"
>
<i class="mdi mdi-18px mdi-bug" /> <i class="mdi mdi-18px mdi-bug" />
</li> </li>
<li class="footer-element footer-link" @click="showSettingModal('about')"> <li
class="footer-element footer-link"
:title="t('word.about')"
@click="showSettingModal('about')"
>
<i class="mdi mdi-18px mdi-information-outline" /> <i class="mdi mdi-18px mdi-information-outline" />
</li> </li>
</ul> </ul>
@@ -29,9 +61,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { shell } from 'electron'; import { shell } from 'electron';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { useApplicationStore } from '@/stores/application'; import { useApplicationStore } from '@/stores/application';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import { computed, ComputedRef } from 'vue'; import { computed, ComputedRef } from 'vue';
import { useConsoleStore } from '@/stores/console';
import { useConnectionsStore } from '@/stores/connections';
const { t } = useI18n();
interface DatabaseInfos {// TODO: temp interface DatabaseInfos {// TODO: temp
name: string; name: string;
@@ -42,14 +79,22 @@ interface DatabaseInfos {// TODO: temp
const applicationStore = useApplicationStore(); const applicationStore = useApplicationStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const connectionsStore = useConnectionsStore();
const { getSelected: workspace } = storeToRefs(workspacesStore); const lightColors = ['#FFCE54', '#FDA50F', '#BEBDB8', '#48CFAD'];
const { getSelected: workspaceUid } = storeToRefs(workspacesStore);
const { toggleConsole } = useConsoleStore();
const { showSettingModal } = applicationStore; const { showSettingModal } = applicationStore;
const { getWorkspace } = workspacesStore; const { getWorkspace } = workspacesStore;
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(() => { const version: ComputedRef<DatabaseInfos> = computed(() => {
return getWorkspace(workspace.value) ? getWorkspace(workspace.value).version : null; return getWorkspace(workspaceUid.value) ? workspace.value.version : null;
}); });
const versionString = computed(() => { const versionString = computed(() => {

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