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

Compare commits

...

303 Commits

Author SHA1 Message Date
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
4519829aa2 chore(release): 0.5.8 2022-07-02 15:32:02 +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
a45d76e8b4 fix: exception on new scheduler tab 2022-06-30 16:42:29 +02:00
7702ca025f fix: error on modals missing focusable elements 2022-06-30 10:05:35 +02:00
f632a0f189 initial implementation of new feature 2022-06-29 19:05:45 +02:00
e97da37103 feat: context shortcut to disconnect from left bar 2022-06-29 13:17:33 +02:00
6573fe69ac fix: connection string field doesn't appear switching to postgre when editing a connection 2022-06-29 11:20:08 +02:00
902c29ffa5 feat(MySQL): option to disable foreign key checks when empty a table 2022-06-29 10:48:21 +02:00
5f57a9f60d fix: ctrl+a on results doesn't work properly 2022-06-28 17:55:18 +02:00
822af44a47 refactor: minor improvements 2022-06-27 18:28:04 +02:00
e42c424a13 fix: focus goes outside modals with tab key navigation 2022-06-26 15:07:37 +02:00
0a3a4827dd fix: result table cells/rows not loses focus clicking outside 2022-06-26 10:21:17 +02:00
a80d227400 fix(Windows): white window buttons with dark theme 2022-06-24 18:17:37 +02:00
cfd82c8f41 fix: editor gutter pin not working 2022-06-24 17:26:28 +02:00
91d0735a5f fix: double context menu on table settings rows 2022-06-23 23:11:43 +02:00
93ce619782 fix(Windows): Windows 7 style window frame at startup 2022-06-23 11:57:25 +02:00
8f01740475 fix(UI): wrong tables scrollable height after switching tabs 2022-06-22 18:47:32 +02:00
869c75f654 Merge pull request #324 from antares-sql/dependabot/npm_and_yarn/electron-19.0.5
build(deps-dev): bump electron from 17.4.3 to 19.0.5
2022-06-21 21:44:06 +02:00
dependabot[bot]
14aff67d2d build(deps-dev): bump electron from 17.4.3 to 19.0.5
Bumps [electron](https://github.com/electron/electron) from 17.4.3 to 19.0.5.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v17.4.3...v19.0.5)

---
updated-dependencies:
- dependency-name: electron
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-21 16:11:11 +00:00
174579bf8c Merge pull request #248 from antares-sql/ts-renderer
TypeScript in renderer process
2022-06-21 18:09:42 +02:00
0eab0a7140 Merge branch 'master' of https://github.com/antares-sql/antares into ts-renderer 2022-06-21 18:08:43 +02:00
a103617ce8 refactor: ts and composition api on missing components 2022-06-21 17:54:47 +02:00
61ec3cbd4e Merge branch 'master' of https://github.com/antares-sql/antares 2022-06-20 08:49:29 +02:00
1765d9fd0b chore: dependabot monthly interval 2022-06-20 08:49:26 +02:00
329656ff1d Merge pull request #309 from toriphes/feat/base-select-max-visible-options
feat: add max visible options prop
2022-06-20 08:46:47 +02:00
Giulio Ganci
067a6f3507 feat: add max visible options prop 2022-06-19 16:58:52 +02:00
89e8d9fcdb refactor: ts and composition api on WorkspaceTabNew* components 2022-06-14 20:02:17 +02:00
33a4663694 Merge branch 'master' of https://github.com/antares-sql/antares into ts-renderer 2022-06-13 09:29:05 +02:00
bd46d17424 refactor: ts and composition api on WorkspaceExplorebar* components 2022-06-09 20:08:32 +02:00
be70b5be7f refactor: ts on ipc api 2022-06-05 17:57:44 +02:00
7fc01227e7 refactor: ts and composition api on more components 2022-06-04 18:37:16 +02:00
2007305ff0 refactor: ts on pinia store 2022-05-28 18:43:56 +02:00
e97401e27d Merge branch 'master' of https://github.com/antares-sql/antares into ts-renderer 2022-05-25 14:41:15 +02:00
62f6fd16d5 refactor: ts on i18n 2022-05-24 23:02:40 +02:00
cdca6eaa35 refactor: ts and composition api for modals 2022-05-24 23:02:14 +02:00
84826ff4c0 refactor: ts and composition api on more elements 2022-05-17 19:11:31 +02:00
5a50ba88e8 Merge branch 'master' of https://github.com/antares-sql/antares into ts-renderer 2022-05-15 18:23:09 +02:00
8a55b36527 refactor: ts and composition api for single instance components 2022-05-14 11:15:42 +02:00
45b2eb2934 fix: reactivity problem on BaseVirtualScroll component 2022-05-11 11:27:29 +02:00
d494b17df7 fix: 2022-05-10 13:22:26 +02:00
ae377a6c3c refactor: ts and composition api for base components 2022-05-10 13:02:01 +02:00
cc5910b88f refactor: common to ts 2022-05-10 12:57:25 +02:00
d1bfa282c3 build: ts config for renderer 2022-05-09 11:48:30 +02:00
231 changed files with 43207 additions and 13757 deletions

View File

@@ -174,6 +174,34 @@
"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"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

View File

@@ -1,4 +1,5 @@
node_modules node_modules
assets assets
out out
dist dist
build

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: "weekly" 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

@@ -5,15 +5,20 @@ 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: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 16
- name: npm install & build - name: npm install & build
run: | run: |
npm install npm install
npm run build:local 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
.gitignore vendored
View File

@@ -7,5 +7,4 @@ node_modules
thumbs.db thumbs.db
NOTES.md NOTES.md
*.txt *.txt
package-lock.json
*.heapsnapshot *.heapsnapshot

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,328 @@
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.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)
### Features
* add max visible options prop ([067a6f3](https://github.com/antares-sql/antares/commit/067a6f350757c1e6b4df51f801ae832b47bd3484))
* context shortcut to disconnect from left bar ([e97da37](https://github.com/antares-sql/antares/commit/e97da3710385690b85391938e40145a1591bc2e8))
* **MySQL:** option to disable foreign key checks when empty a table ([902c29f](https://github.com/antares-sql/antares/commit/902c29ffa551bc3489fa1d9136ee926d135ea14f))
### Bug Fixes
* connection string field doesn't appear switching to postgre when editing a connection ([6573fe6](https://github.com/antares-sql/antares/commit/6573fe69aca2b99c7a700879fb0d0930e864cbe6))
* ctrl+a on results doesn't work properly ([5f57a9f](https://github.com/antares-sql/antares/commit/5f57a9f60d281e24e5bee4330c081fa5d8651b36))
* double context menu on table settings rows ([91d0735](https://github.com/antares-sql/antares/commit/91d0735a5f4861bc6ad13b9285ea7a9bd7be9538))
* editor gutter pin not working ([cfd82c8](https://github.com/antares-sql/antares/commit/cfd82c8f419952879b386187eb146847098263fe))
* error on modals missing focusable elements ([7702ca0](https://github.com/antares-sql/antares/commit/7702ca025fcae6209ae3851d0ccd25579f93e243))
* exception on new scheduler tab ([a45d76e](https://github.com/antares-sql/antares/commit/a45d76e8b4ecdecf53438fe174f61ea32f4e10ac))
* focus goes outside modals with tab key navigation ([e42c424](https://github.com/antares-sql/antares/commit/e42c424a13a6901414a1a1c4e2f68cb4ddef7d59))
* reactivity problem on BaseVirtualScroll component ([45b2eb2](https://github.com/antares-sql/antares/commit/45b2eb2934b9f7a08f379ad4d7a44b1c89585449))
* result table cells/rows not loses focus clicking outside ([0a3a482](https://github.com/antares-sql/antares/commit/0a3a4827dd75539666fa2c827415af3bfa224543))
* **UI:** wrong tables scrollable height after switching tabs ([8f01740](https://github.com/antares-sql/antares/commit/8f01740475ea6d5d9b5eefabdbf27099df76f2cf))
* **Windows:** white window buttons with dark theme ([a80d227](https://github.com/antares-sql/antares/commit/a80d22740045a61fd14fd5da401c0d123d54f4de))
* **Windows:** Windows 7 style window frame at startup ([93ce619](https://github.com/antares-sql/antares/commit/93ce619782d58cfb8fb1ecce2ca2137a61ec6181))
### [0.5.7](https://github.com/antares-sql/antares/compare/v0.5.4...v0.5.7) (2022-06-19) ### [0.5.7](https://github.com/antares-sql/antares/compare/v0.5.4...v0.5.7) (2022-06-19)

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,14 @@
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.
🗳️ Poll: **[Which is the main OS you use Antares on?](https://github.com/antares-sql/antares/discussions/379)**
## Current key features ## Current key features
@@ -33,6 +34,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 +42,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 +63,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 +84,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 +116,35 @@ 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>
</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

23642
package-lock.json generated Normal file

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.7", "version": "0.7.5",
"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,18 +12,17 @@
"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,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"", "lint": "eslint . --ext .js,.ts,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
"lint:fix": "eslint . --ext .js,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix", "lint:fix": "eslint . --ext .js,.ts,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
"contributors:add": "all-contributors add", "contributors:add": "all-contributors add",
"contributors:generate": "all-contributors generate" "contributors:generate": "all-contributors generate"
}, },
@@ -65,7 +64,11 @@
"target": [ "target": [
{ {
"target": "deb", "target": "deb",
"arch": "x64" "arch": [
"x64",
"armv7l",
"arm64"
]
}, },
{ {
"target": "AppImage", "target": "AppImage",
@@ -76,6 +79,7 @@
] ]
} }
], ],
"icon": "assets/linux",
"category": "Development" "category": "Development"
}, },
"appImage": { "appImage": {
@@ -117,41 +121,47 @@
"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",
"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.0.3",
"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/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",
@@ -160,8 +170,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": "~17.4.3", "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",
@@ -172,26 +182,33 @@
"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",
"unzip-crx-3": "~0.2.0", "unzip-crx-3": "~0.2.0",
"vue-eslint-parser": "~8.3.0", "vue-eslint-parser": "~8.3.0",
"vue-loader": "~16.8.3", "vue-loader": "~16.8.3",
"webpack": "~5.60.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

@@ -59,7 +59,7 @@ async function restartElectron () {
console.error(chalk.red(data.toString())); console.error(chalk.red(data.toString()));
}); });
electronProcess.on('exit', (code, signal) => { electronProcess.on('exit', () => {
if (!manualRestart) process.exit(0); if (!manualRestart) process.exit(0);
}); });
} }
@@ -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,8 +1,14 @@
module.exports = { import { Customizations } from '../interfaces/customizations';
// Everything OFF
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,
@@ -29,6 +35,7 @@ module.exports = {
elementsWrapper: '', elementsWrapper: '',
stringsWrapper: '"', stringsWrapper: '"',
tableAdd: false, tableAdd: false,
tableTruncateDisableFKCheck: false,
viewAdd: false, viewAdd: false,
triggerAdd: false, triggerAdd: false,
triggerFunctionAdd: false, triggerFunctionAdd: false,
@@ -42,9 +49,9 @@ module.exports = {
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,
@@ -68,24 +75,25 @@ module.exports = {
viewUpdateOption: false, viewUpdateOption: false,
procedureDeterministic: false, procedureDeterministic: false,
procedureDataAccess: false, procedureDataAccess: false,
procedureSql: false, procedureSql: null,
procedureContext: false, procedureContext: false,
procedureContextValues: [],
procedureLanguage: false, procedureLanguage: false,
functionDeterministic: false, functionDeterministic: false,
functionDataAccess: false, functionDataAccess: false,
functionSql: false, functionSql: null,
functionContext: false, functionContext: false,
functionLanguage: false, functionLanguage: false,
triggerSql: false, triggerSql: null,
triggerStatementInCreation: false, triggerStatementInCreation: false,
triggerMultipleEvents: false, triggerMultipleEvents: false,
triggerTableInName: false, triggerTableInName: false,
triggerUpdateColumns: false, triggerUpdateColumns: false,
triggerOnlyRename: false, triggerOnlyRename: false,
triggerEnableDisable: false, triggerEnableDisable: false,
triggerFunctionSql: false, triggerFunctionSql: null,
triggerFunctionlanguages: false, triggerFunctionlanguages: null,
parametersLength: false, parametersLength: false,
languages: false, languages: null,
readOnlyMode: false readOnlyMode: 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,6 +0,0 @@
module.exports = {
maria: require('./mysql'),
mysql: require('./mysql'),
pg: require('./postgresql'),
sqlite: require('./sqlite')
};

View File

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

View File

@@ -1,11 +1,26 @@
const defaults = require('./defaults'); import { Customizations } from '../interfaces/customizations';
import { defaults } from './defaults';
import mysqlTypes from '../data-types/mysql';
module.exports = { export const customizations: Customizations = {
...defaults, ...defaults,
// Defaults // Defaults
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,
@@ -24,9 +39,11 @@ module.exports = {
functions: true, functions: true,
schedulers: true, schedulers: true,
// Settings // Settings
elementsWrapper: '', elementsWrapper: '`',
stringsWrapper: '"', stringsWrapper: '"',
tableAdd: true, tableAdd: true,
tableTruncateDisableFKCheck: true,
tableDuplicate: true,
viewAdd: true, viewAdd: true,
triggerAdd: true, triggerAdd: true,
routineAdd: true, routineAdd: true,
@@ -49,7 +66,6 @@ module.exports = {
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,
@@ -62,6 +78,7 @@ module.exports = {
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,11 +1,25 @@
const defaults = require('./defaults'); import { Customizations } from '../interfaces/customizations';
import { defaults } from './defaults';
import postgresqlTypes from '../data-types/postgresql';
module.exports = { export const customizations: Customizations = {
...defaults, ...defaults,
// Defaults // Defaults
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,
@@ -25,6 +39,7 @@ module.exports = {
elementsWrapper: '"', elementsWrapper: '"',
stringsWrapper: '\'', stringsWrapper: '\'',
tableAdd: true, tableAdd: true,
tableDuplicate: true,
viewAdd: true, viewAdd: true,
triggerAdd: true, triggerAdd: true,
triggerFunctionAdd: true, triggerFunctionAdd: true,
@@ -46,6 +61,7 @@ module.exports = {
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,4 +1,21 @@
module.exports = { import { Customizations } from '../interfaces/customizations';
import { defaults } from './defaults';
import sqliteTypes from '../data-types/sqlite';
export const customizations: Customizations = {
...defaults,
dataTypes: sqliteTypes,
indexTypes: [
'PRIMARY',
'INDEX',
'UNIQUE'
],
foreignActions: [
'RESTRICT',
'CASCADE',
'SET NULL',
'NO ACTION'
],
// Core // Core
fileConnection: true, fileConnection: true,
// Structure // Structure
@@ -10,6 +27,7 @@ module.exports = {
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

@@ -1,4 +1,6 @@
module.exports = [ import { TypesGroup } from 'common/interfaces/antares';
export default [
{ {
group: 'integer', group: 'integer',
types: [ types: [
@@ -306,4 +308,4 @@ module.exports = [
} }
] ]
} }
]; ] as TypesGroup[];

View File

@@ -1,4 +1,6 @@
module.exports = [ import { TypesGroup } from 'common/interfaces/antares';
export default [
{ {
group: 'integer', group: 'integer',
types: [ types: [
@@ -290,4 +292,4 @@ module.exports = [
} }
] ]
} }
]; ] as TypesGroup[];

View File

@@ -1,10 +1,12 @@
module.exports = [ import { TypesGroup } from 'common/interfaces/antares';
export default [
{ {
group: 'integer', group: 'integer',
types: [ types: [
{ {
name: 'INT', name: 'INT',
length: true, length: 10,
collation: false, collation: false,
unsigned: true, unsigned: true,
zerofill: true zerofill: true
@@ -16,6 +18,13 @@ module.exports = [
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,
@@ -134,4 +143,4 @@ module.exports = [
} }
] ]
} }
]; ] as TypesGroup[];

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 @@
module.exports = [
'PRIMARY',
'INDEX',
'UNIQUE',
'FULLTEXT'
];

View File

@@ -1,5 +0,0 @@
module.exports = [
'PRIMARY',
'INDEX',
'UNIQUE'
];

View File

@@ -1,5 +0,0 @@
module.exports = [
'PRIMARY',
'INDEX',
'UNIQUE'
];

View File

@@ -8,17 +8,25 @@ 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
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export interface IpcResponse<T = any> {
status: 'success' | 'error';
response?: T;
}
/** /**
* Pasameters needed to create a new Antares connection to a database * Pasameters needed to create a new Antares connection to a database
*/ */
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}
@@ -67,12 +75,32 @@ export interface TypeInformations {
zerofill: boolean; zerofill: boolean;
} }
export interface TypesGroup {
group: string;
types: TypeInformations[];
}
// Tables // Tables
export interface TableField { export interface TableInfos {
name: string;
type: string;
rows: number;
engine: string;
comment: string;
size: number | false;
collation: string;
autoIncrement?: boolean;
}
export type TableOptions = Partial<TableInfos>;
export interface TableField {
// eslint-disable-next-line camelcase
_antares_id?: string;
name: string; name: string;
key: string;
type: string; type: string;
schema: string; schema: string;
table?: string;
numPrecision?: number; numPrecision?: number;
numLength?: number; numLength?: number;
datePrecision?: number; datePrecision?: number;
@@ -82,7 +110,8 @@ export interface TableField {
unsigned?: boolean; unsigned?: boolean;
zerofill?: boolean; zerofill?: boolean;
order?: number; order?: number;
default?: number | string; default?: string;
defaultType?: string;
enumValues?: string; enumValues?: string;
charset?: string; charset?: string;
collation?: string; collation?: string;
@@ -92,9 +121,16 @@ export interface TableField {
comment?: string; comment?: string;
after?: string; after?: string;
orgName?: string; orgName?: string;
length?: number | false;
alias: string;
tableAlias: string;
orgTable: string;
key?: 'pri' | 'uni' | '';
} }
export interface TableIndex { export interface TableIndex {
// eslint-disable-next-line camelcase
_antares_id?: string;
name: string; name: string;
fields: string[]; fields: string[];
type: string; type: string;
@@ -107,6 +143,8 @@ export interface TableIndex {
} }
export interface TableForeign { export interface TableForeign {
// eslint-disable-next-line camelcase
_antares_id?: string;
constraintName: string; constraintName: string;
refSchema: string; refSchema: string;
table: string; table: string;
@@ -118,15 +156,6 @@ export interface TableForeign {
oldName?: string; oldName?: string;
} }
export interface TableOptions {
name: string;
type?: 'table' | 'view';
engine?: string;
comment?: string;
collation?: string;
autoIncrement?: number;
}
export interface CreateTableParams { export interface CreateTableParams {
/** Connection UID */ /** Connection UID */
uid?: string; uid?: string;
@@ -165,6 +194,7 @@ export interface AlterTableParams {
} }
// Views // Views
export type ViewInfos = TableInfos
export interface CreateViewParams { export interface CreateViewParams {
schema: string; schema: string;
name: string; name: string;
@@ -180,6 +210,19 @@ export interface AlterViewParams extends CreateViewParams {
} }
// Triggers // Triggers
export interface TriggerInfos {
name: string;
statement: string;
timing: string;
definer: string;
event: string;
table: string;
sqlMode: string;
created: Date;
charset: string;
enabled?: boolean;
}
export interface CreateTriggerParams { export interface CreateTriggerParams {
definer?: string; definer?: string;
schema: string; schema: string;
@@ -195,13 +238,38 @@ export interface AlterTriggerParams extends CreateTriggerParams {
} }
// Routines & Functions // Routines & Functions
export interface FunctionParam { export interface FunctionParam {
// eslint-disable-next-line camelcase
_antares_id: string;
context: string; context: string;
name: string; name: string;
type: string; type: string;
length: number; length: number;
} }
export interface RoutineInfos {
name: string;
type?: string;
definer: string;
created?: string;
sql?: string;
updated?: string;
comment?: string;
charset?: string;
security?: string;
language?: string;
dataAccess?: string;
deterministic?: boolean;
parameters?: FunctionParam[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
returns?: any;
returnsLength?: number;
}
export type FunctionInfos = RoutineInfos
export type TriggerFunctionInfos = FunctionInfos
export interface CreateRoutineParams { export interface CreateRoutineParams {
name: string; name: string;
parameters?: FunctionParam[]; parameters?: FunctionParam[];
@@ -239,7 +307,7 @@ export interface AlterFunctionParams extends CreateFunctionParams {
} }
// Events // Events
export interface CreateEventParams { export interface EventInfos {
definer?: string; definer?: string;
schema: string; schema: string;
name: string; name: string;
@@ -248,16 +316,39 @@ export interface CreateEventParams {
starts: string; starts: string;
ends: string; ends: string;
at: string; at: string;
preserve: string; preserve: boolean;
state: string; state: string;
comment: string; comment: string;
enabled?: boolean;
sql: string; sql: string;
} }
export type CreateEventParams = EventInfos;
export interface AlterEventParams extends CreateEventParams { export interface AlterEventParams extends CreateEventParams {
oldName?: string; oldName?: string;
} }
// Schema
export interface SchemaInfos {
name: string;
size: number;
tables: TableInfos[];
functions: FunctionInfos[];
procedures: RoutineInfos[];
triggers: TriggerInfos[];
schedulers: EventInfos[];
}
export interface CollationInfos {
charset: string;
collation: string;
compiled: boolean;
default: boolean;
id: string | number;
sortLen: number;
}
// Query // Query
export interface QueryBuilderObject { export interface QueryBuilderObject {
schema: string; schema: string;
@@ -285,17 +376,10 @@ export interface QueryParams {
tabUid?: string; tabUid?: string;
} }
export interface QueryField { /**
name: string; * @deprecated Use TableFIeld
alias: string; */
orgName: string; export type QueryField = TableField
schema: string;
table: string;
tableAlias: string;
orgTable: string;
type: string;
length: number;
}
export interface QueryForeign { export interface QueryForeign {
schema: string; schema: string;

View File

@@ -0,0 +1,98 @@
import { TypesGroup } from './antares';
export interface Customizations {
// Defaults
defaultPort?: number;
defaultUser?: string;
defaultDatabase?: string;
dataTypes?: TypesGroup[];
indexTypes?: string[];
foreignActions?: string[];
// Core
database?: boolean;
collations?: boolean;
engines?: boolean;
connectionSchema?: boolean;
sslConnection?: boolean;
sshConnection?: boolean;
fileConnection?: boolean;
cancelQueries?: boolean;
// Tools
processesList?: boolean;
usersManagement?: boolean;
variables?: boolean;
// Structure
schemas?: boolean;
tables?: boolean;
views?: boolean;
triggers?: boolean;
triggerFunctions?: boolean;
routines?: boolean;
functions?: boolean;
schedulers?: boolean;
// Settings
elementsWrapper: string;
stringsWrapper: string;
tableAdd?: boolean;
tableSettings?: boolean;
tableDuplicate?: boolean;
tableArray?: boolean;
tableRealCount?: boolean;
tableTruncateDisableFKCheck?: boolean;
viewAdd?: boolean;
viewSettings?: boolean;
triggerAdd?: boolean;
triggerFunctionAdd?: boolean;
routineAdd?: boolean;
functionAdd?: boolean;
schedulerAdd?: boolean;
databaseEdit?: boolean;
schemaEdit?: boolean;
schemaDrop?: boolean;
schemaExport?: boolean;
exportByChunks?: boolean;
schemaImport?: boolean;
triggerSettings?: boolean;
triggerFunctionSettings?: boolean;
routineSettings?: boolean;
functionSettings?: boolean;
schedulerSettings?: boolean;
indexes?: boolean;
foreigns?: boolean;
sortableFields?: boolean;
unsigned?: boolean;
nullable?: boolean;
nullablePrimary?: boolean;
zerofill?: boolean;
autoIncrement?: boolean;
comment?: boolean;
collation?: boolean;
definer?: boolean;
onUpdate?: boolean;
viewAlgorithm?: boolean;
viewSqlSecurity?: boolean;
viewUpdateOption?: boolean;
procedureDeterministic?: boolean;
procedureDataAccess?: boolean;
procedureSql?: string;
procedureContext?: boolean;
procedureContextValues?: string[];
procedureLanguage?: boolean;
functionDeterministic?: boolean;
functionDataAccess?: boolean;
functionSql?: string;
functionContext?: boolean;
functionLanguage?: boolean;
triggerSql?: string;
triggerStatementInCreation?: boolean;
triggerMultipleEvents?: boolean;
triggerTableInName?: boolean;
triggerUpdateColumns?: boolean;
triggerOnlyRename?: boolean;
triggerEnableDisable?: boolean;
triggerFunctionSql?: string;
triggerFunctionlanguages?: string[];
parametersLength?: boolean;
languages?: string[];
readOnlyMode?: boolean;
}

View File

@@ -7,13 +7,13 @@ export interface TableParams {
export interface ExportOptions { export interface ExportOptions {
schema: string; schema: string;
includes: { tables: {
functions: boolean; table: string;
views: boolean; includeStructure: boolean;
triggers: boolean; includeContent: boolean;
routines: boolean; includeDropStatement: boolean;
schedulers: boolean; }[];
}; includes: {[key: string]: boolean};
outputFormat: 'sql' | 'sql.zip'; outputFormat: 'sql' | 'sql.zip';
outputFile: string; outputFile: string;
sqlInsertAfter: number; sqlInsertAfter: number;

View File

@@ -1,5 +1,34 @@
import { UsableLocale } from '@faker-js/faker'; import { UsableLocale } from '@faker-js/faker';
export interface TableUpdateParams {
uid: string;
schema: string;
table: string;
primary?: string;
id: number | string;
content: number | string | boolean | Date | Blob | null;
type: string;
field: string;
}
export interface TableDeleteParams {
uid: string;
schema: string;
table: string;
primary?: string;
field: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
rows: {[key: string]: any};
}
export interface TableFilterClausole {
active: boolean;
field: string;
op: '=' | '!=' | '>' | '<' | '>=' | '<=' | 'IN' | 'NOT IN' | 'LIKE' | 'BETWEEN' | 'IS NULL' | 'IS NOT NULL';
value: '';
value2: '';
}
export interface InsertRowsParams { export interface InsertRowsParams {
uid: string; uid: string;
schema: string; schema: string;

View File

@@ -1,7 +0,0 @@
'use strict';
export function bufferToBase64 (buf) {
const binstr = Array.prototype.map.call(buf, ch => {
return String.fromCharCode(ch);
}).join('');
return btoa(binstr);
}

View File

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

View File

@@ -1,5 +1,4 @@
'use strict'; export function formatBytes (bytes: number, decimals = 2) {
export function formatBytes (bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes'; if (bytes === 0) return '0 Bytes';
const k = 1024; const k = 1024;

View File

@@ -1,10 +0,0 @@
/**
*
* @param {any[]} array
* @returns {number}
*/
export function getArrayDepth (array) {
return Array.isArray(array)
? 1 + Math.max(0, ...array.map(getArrayDepth))
: 0;
}

View File

@@ -0,0 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export function getArrayDepth (array: any[]): number {
return Array.isArray(array)
? 1 + Math.max(0, ...array.map(getArrayDepth))
: 0;
}

View File

@@ -1,5 +1,3 @@
'use strict';
const lookup = { const lookup = {
0: '0000', 0: '0000',
1: '0001', 1: '0001',
@@ -23,15 +21,11 @@ const lookup = {
D: '1101', D: '1101',
E: '1110', E: '1110',
F: '1111' F: '1111'
}; } as const;
/** export type HexChar = keyof typeof lookup
* Converts hexadecimal string to binary string
* export default function hexToBinary (hex: HexChar[]) {
* @param {string} hex Hexadecimal string
* @returns {string} Binary string
*/
export default function hexToBinary (hex) {
let binary = ''; let binary = '';
for (let i = 0; i < hex.length; i++) for (let i = 0; i < hex.length; i++)
binary += lookup[hex[i]]; binary += lookup[hex[i]];

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,5 +1,4 @@
'use strict'; export function mimeFromHex (hex: string) {
export function mimeFromHex (hex) {
switch (hex.substring(0, 4)) { // 2 bytes switch (hex.substring(0, 4)) { // 2 bytes
case '424D': case '424D':
return { ext: 'bmp', mime: 'image/bmp' }; return { ext: 'bmp', mime: 'image/bmp' };
@@ -23,7 +22,7 @@ export function mimeFromHex (hex) {
case '425A68': case '425A68':
return { ext: 'bz2', mime: 'application/x-bzip2' }; return { ext: 'bz2', mime: 'application/x-bzip2' };
default: default:
switch (hex) { // 4 bytes switch (hex) { // 4 bites
case '89504E47': case '89504E47':
return { ext: 'png', mime: 'image/png' }; return { ext: 'png', mime: 'image/png' };
case '47494638': case '47494638':

View File

@@ -1,20 +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);
/**
* Escapes a string
*
* @param {String} string
* @returns {String}
*/
function sqlEscaper (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 };

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

@@ -0,0 +1,162 @@
/* 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};
}): 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) {
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)) {
if (['mysql', 'maria'].includes(client))
parsedValue = `X'${val.toString('hex').toUpperCase()}'`;
else if (client === 'pg')
parsedValue = `decode('${val.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;
}) => {
const { client, json, fields, table } = args;
const { elementsWrapper: ew } = customizations[client];
const fieldNames = Object.keys(json).map(key => `${ew}${key}${ew}`);
const values = Object.keys(json).map(key => (
valueToSqlString({ val: json[key], client, field: fields[key] })
));
return `INSERT INTO ${ew}${table}${ew} (${fieldNames.join(', ')}) VALUES (${values.join(', ')});`;
};

View File

@@ -1,8 +0,0 @@
/**
* @export
* @param {String} [prefix]
* @returns {String} Unique ID
*/
export function uidGen (prefix) {
return (prefix ? `${prefix}:` : '') + Math.random().toString(36).substr(2, 9).toUpperCase();
}

View File

@@ -0,0 +1,3 @@
export function uidGen (prefix?: string) {
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,15 +1,11 @@
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', () => {
app.exit(); app.exit();
}); });
ipcMain.on('get-key', async event => {
const key = false;
event.returnValue = key;
});
ipcMain.handle('show-open-dialog', (event, options) => { ipcMain.handle('show-open-dialog', (event, options) => {
return dialog.showOpenDialog(options); return dialog.showOpenDialog(options);
}); });
@@ -17,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

@@ -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 };
@@ -172,7 +171,10 @@ export default (connections: {[key: string]: antares.Client}) => {
}); });
ipcMain.handle('export', (event, { uid, type, tables, ...rest }) => { ipcMain.handle('export', (event, { uid, type, tables, ...rest }) => {
if (exporter !== null) return; if (exporter !== null) {
exporter.kill();
return;
}
return new Promise((resolve/*, reject */) => { return new Promise((resolve/*, reject */) => {
(async () => { (async () => {
@@ -265,7 +267,10 @@ export default (connections: {[key: string]: antares.Client}) => {
}); });
ipcMain.handle('import-sql', async (event, options) => { ipcMain.handle('import-sql', async (event, options) => {
if (importer !== null) return; if (importer !== null) {
importer.kill();
return;
}
return new Promise((resolve/*, reject */) => { return new Promise((resolve/*, reject */) => {
(async () => { (async () => {

View File

@@ -1,12 +1,12 @@
import * as fs from 'fs';
import * as antares from 'common/interfaces/antares'; import * as antares from 'common/interfaces/antares';
import { InsertRowsParams } from 'common/interfaces/tableApis'; 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 * as customizations from 'common/customizations'; import customizations from 'common/customizations';
import fs from 'fs';
export default (connections: {[key: string]: antares.Client}) => { export default (connections: {[key: string]: antares.Client}) => {
ipcMain.handle('get-table-columns', async (event, params) => { ipcMain.handle('get-table-columns', async (event, params) => {
@@ -105,6 +105,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 +125,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 +143,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 +156,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 +193,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 +227,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 };
@@ -246,84 +266,12 @@ export default (connections: {[key: string]: antares.Client}) => {
} }
}); });
ipcMain.handle('insert-table-rows', async (event, params) => {
try { // TODO: move to client classes
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const insertObj: {[key: string]: any} = {};
for (const key in params.row) {
const type = params.fields[key];
let escapedParam;
if (params.row[key] === null)
escapedParam = 'NULL';
else if ([...NUMBER, ...FLOAT].includes(type))
escapedParam = +params.row[key];
else if ([...TEXT, ...LONG_TEXT].includes(type)) {
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
escapedParam = `"${sqlEscaper(params.row[key].value)}"`;
break;
case 'pg':
escapedParam = `'${params.row[key].value.replaceAll('\'', '\'\'')}'`;
break;
}
}
else if (BLOB.includes(type)) {
if (params.row[key].value) {
let fileBlob;
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
fileBlob = fs.readFileSync(params.row[key].value);
escapedParam = `0x${fileBlob.toString('hex')}`;
break;
case 'pg':
fileBlob = fs.readFileSync(params.row[key].value);
escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`;
break;
}
}
else {
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
escapedParam = '""';
break;
case 'pg':
escapedParam = 'decode(\'\', \'hex\')';
break;
}
}
}
insertObj[key] = escapedParam;
}
const rows = new Array(+params.repeat).fill(insertObj);
await connections[params.uid]
.schema(params.schema)
.into(params.table)
.insert(rows)
.run();
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('insert-table-fake-rows', async (event, params: InsertRowsParams) => { ipcMain.handle('insert-table-fake-rows', async (event, params: InsertRowsParams) => {
try { // TODO: move to client classes try { // TODO: move to client classes
// eslint-disable-next-line @typescript-eslint/no-explicit-any const rows: {[key: string]: string | number | boolean | Date | Buffer}[] = [];
const rows: {[key: string]: any}[] = [];
for (let i = 0; i < +params.repeat; i++) { for (let i = 0; i < +params.repeat; i++) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any const insertObj: {[key: string]: string | number | boolean | Date | Buffer} = {};
const insertObj: {[key: string]: any} = {};
for (const key in params.row) { for (const key in params.row) {
const type = params.fields[key]; const type = params.fields[key];
@@ -342,6 +290,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;
} }
@@ -382,8 +331,7 @@ export default (connections: {[key: string]: antares.Client}) => {
insertObj[key] = escapedParam; insertObj[key] = escapedParam;
} }
else { // Faker value else { // Faker value
// eslint-disable-next-line @typescript-eslint/no-explicit-any const parsedParams: {[key: string]: string | number | boolean | Date | Buffer} = {};
const parsedParams: {[key: string]: any} = {};
let fakeValue; let fakeValue;
if (params.locale) if (params.locale)
@@ -403,8 +351,19 @@ 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.substr(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')}'`;
@@ -440,7 +399,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

@@ -1,7 +1,7 @@
import { ipcMain } from 'electron'; import { ipcMain } from 'electron';
import { autoUpdater } from 'electron-updater'; import { autoUpdater } from 'electron-updater';
import Store from 'electron-store'; import * as Store from 'electron-store';
const persistentStore = new Store({ name: 'settings' }); const persistentStore = new Store({ name: 'settings', clearInvalidConfig: true });
const isMacOS = process.platform === 'darwin'; const isMacOS = process.platform === 'darwin';
let mainWindow: Electron.IpcMainEvent; let mainWindow: Electron.IpcMainEvent;

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;

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: 'Shortcuts',
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

@@ -1,10 +1,14 @@
import * as antares from 'common/interfaces/antares'; import * as antares from 'common/interfaces/antares';
import * as mysql from 'mysql2/promise'; import * as mysql from 'mysql2/promise';
import * as Store from 'electron-store';
import { AntaresCore } from '../AntaresCore'; import { AntaresCore } from '../AntaresCore';
import * as dataTypes from 'common/data-types/mysql'; import dataTypes from 'common/data-types/mysql';
import SSH2Promise = require('ssh2-promise'); import SSH2Promise = require('ssh2-promise');
import SSHConfig from 'ssh2-promise/lib/sshConfig'; import SSHConfig from 'ssh2-promise/lib/sshConfig';
Store.initRenderer();
const settingsStore = new Store({ name: 'settings' });
export class MySQLClient extends AntaresCore { export class MySQLClient extends AntaresCore {
private _schema?: string; private _schema?: string;
private _runningConnections: Map<string, number>; private _runningConnections: Map<string, number>;
@@ -147,7 +151,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,
@@ -192,14 +203,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,18 +230,21 @@ 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(',')}'`);
}); });
return connection; return connection;
@@ -244,25 +258,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,7 +294,16 @@ 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[] = [];
@@ -299,13 +312,46 @@ export class MySQLClient extends AntaresCore {
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');
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 +367,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 = 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,25 +378,22 @@ 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
}; };
}); });
// PROCEDURES // PROCEDURES
const remappedProcedures = procedures.filter(procedure => procedure.Db === db.Database).map(procedure => { const remappedProcedures: antares.RoutineInfos[] = procedures.filter(procedure => procedure.Db === db.Database).map(procedure => {
return { return {
name: procedure.Name, name: procedure.Name,
type: procedure.Type, type: procedure.Type,
@@ -364,7 +407,7 @@ export class MySQLClient extends AntaresCore {
}); });
// FUNCTIONS // FUNCTIONS
const remappedFunctions = functions.filter(func => func.Db === db.Database).map(func => { const remappedFunctions: antares.FunctionInfos[] = functions.filter(func => func.Db === db.Database).map(func => {
return { return {
name: func.Name, name: func.Name,
type: func.Type, type: func.Type,
@@ -378,33 +421,26 @@ export class MySQLClient extends AntaresCore {
}); });
// SCHEDULERS // SCHEDULERS
const remappedSchedulers = schedulers.filter(scheduler => scheduler.Db === db.Database).map(scheduler => { const remappedSchedulers: antares.EventInfos[] = schedulers.filter(scheduler => scheduler.Db === db.Database).map(scheduler => {
return { return {
name: scheduler.EVENT_NAME, name: scheduler.EVENT_NAME,
definition: scheduler.EVENT_DEFINITION, schema: scheduler.Db,
type: scheduler.EVENT_TYPE, sql: scheduler.EVENT_DEFINITION,
execution: scheduler.EVENT_TYPE === 'RECURRING' ? 'EVERY' : 'ONCE',
definer: scheduler.DEFINER, definer: scheduler.DEFINER,
body: scheduler.EVENT_BODY,
starts: scheduler.STARTS, starts: scheduler.STARTS,
ends: scheduler.ENDS, ends: scheduler.ENDS,
state: scheduler.STATUS === 'ENABLED' ? 'ENABLE' : scheduler.STATE === 'DISABLED' ? 'DISABLE' : 'DISABLE ON SLAVE',
enabled: scheduler.STATUS === 'ENABLED', enabled: scheduler.STATUS === 'ENABLED',
executeAt: scheduler.EXECUTE_AT, at: scheduler.EXECUTE_AT,
intervalField: scheduler.INTERVAL_FIELD, every: [scheduler.INTERVAL_FIELD, scheduler.INTERVAL_VALUE],
intervalValue: scheduler.INTERVAL_VALUE, preserve: scheduler.ON_COMPLETION.includes('PRESERVE'),
onCompletion: scheduler.ON_COMPLETION, comment: scheduler.EVENT_COMMENT
originator: scheduler.ORIGINATOR,
sqlMode: scheduler.SQL_MODE,
created: scheduler.CREATED,
updated: scheduler.LAST_ALTERED,
lastExecuted: scheduler.LAST_EXECUTED,
comment: scheduler.EVENT_COMMENT,
charset: scheduler.CHARACTER_SET_CLIENT,
timezone: scheduler.TIME_ZONE
}; };
}); });
// TRIGGERS // TRIGGERS
const remappedTriggers = triggersArr.filter(trigger => trigger.Db === db.Database).map(trigger => { const remappedTriggers: antares.TriggerInfos[] = triggersArr.filter(trigger => trigger.Db === db.Database).map(trigger => {
return { return {
name: trigger.Trigger, name: trigger.Trigger,
statement: trigger.Statement, statement: trigger.Statement,
@@ -476,7 +512,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;
@@ -919,8 +960,15 @@ export class MySQLClient extends AntaresCore {
return await this.raw(sql); return await this.raw(sql);
} }
async truncateTable (params: { schema: string; table: string }) { async truncateTable (params: { schema: string; table: string; force: boolean }) {
const sql = `TRUNCATE TABLE \`${params.schema}\`.\`${params.table}\``; let sql = `TRUNCATE TABLE \`${params.schema}\`.\`${params.table}\`;`;
if (params.force) {
sql = `
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
${sql}
SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1);
`;
}
return await this.raw(sql); return await this.raw(sql);
} }
@@ -930,19 +978,22 @@ export class MySQLClient extends AntaresCore {
} }
async getViewInformations ({ schema, view }: { schema: string; view: string }) { async getViewInformations ({ schema, view }: { schema: string; view: string }) {
const sql = `SHOW CREATE VIEW \`${schema}\`.\`${view}\``; const { rows: algorithm } = await this.raw(`SHOW CREATE VIEW \`${schema}\`.\`${view}\``);
const results = await this.raw(sql); const { rows: viewInfo } = await this.raw(`
SELECT *
FROM INFORMATION_SCHEMA.VIEWS
WHERE TABLE_SCHEMA = '${schema}'
AND TABLE_NAME = '${view}'
`);
return results.rows.map(row => { return {
return { algorithm: algorithm[0]['Create View'].match(/(?<=CREATE ALGORITHM=).*?(?=\s)/gs)[0],
algorithm: row['Create View'].match(/(?<=CREATE ALGORITHM=).*?(?=\s)/gs)[0], definer: viewInfo[0].DEFINER.split('@').map((str: string) => `\`${str}\``).join('@'),
definer: row['Create View'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0], security: viewInfo[0].SECURITY_TYPE,
security: row['Create View'].match(/(?<=SQL SECURITY ).*?(?=\s)/gs)[0], updateOption: viewInfo[0].CHECK_OPTION === 'NONE' ? '' : viewInfo[0].CHECK_OPTION,
updateOption: row['Create View'].match(/(?<=WITH ).*?(?=\s)/gs) ? row['Create View'].match(/(?<=WITH ).*?(?=\s)/gs)[0] : '', sql: viewInfo[0].VIEW_DEFINITION,
sql: row['Create View'].match(/(?<=AS ).*?$/gs)[0], name: viewInfo[0].TABLE_NAME
name: row.View };
};
})[0];
} }
async dropView (params: { schema: string; view: string }) { async dropView (params: { schema: string; view: string }) {
@@ -955,7 +1006,7 @@ export class MySQLClient extends AntaresCore {
USE \`${view.schema}\`; USE \`${view.schema}\`;
ALTER ALGORITHM = ${view.algorithm}${view.definer ? ` DEFINER=${view.definer}` : ''} ALTER ALGORITHM = ${view.algorithm}${view.definer ? ` DEFINER=${view.definer}` : ''}
SQL SECURITY ${view.security} SQL SECURITY ${view.security}
params \`${view.schema}\`.\`${view.oldName}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''} VIEW \`${view.schema}\`.\`${view.oldName}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''}
`; `;
if (view.name !== view.oldName) if (view.name !== view.oldName)
@@ -1381,12 +1432,20 @@ export class MySQLClient extends AntaresCore {
xa: row.XA, xa: row.XA,
savepoints: row.Savepoints, savepoints: row.Savepoints,
isDefault: row.Support.includes('DEFAULT') isDefault: row.Support.includes('DEFAULT')
} as {
name: string;
support: string;
comment: string;
transactions: string;
xa: string;
savepoints: string;
isDefault: boolean;
}; };
}); });
} }
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) => {
@@ -1405,7 +1464,12 @@ export class MySQLClient extends AntaresCore {
break; break;
} }
return acc; return acc;
}, {}); }, {}) as {
number: string;
name: string;
arch: string;
os: string;
};
} }
async getProcesses () { async getProcesses () {
@@ -1423,6 +1487,15 @@ export class MySQLClient extends AntaresCore {
time: row.TIME, time: row.TIME,
state: row.STATE, state: row.STATE,
info: row.INFO info: row.INFO
} as {
id: number;
user: string;
host: string;
db: string;
command: string;
time: number;
state: string;
info: string;
}; };
}); });
} }
@@ -1511,7 +1584,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,
@@ -1563,7 +1636,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;
@@ -1573,7 +1647,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,10 +1,9 @@
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';
import * as dataTypes from 'common/data-types/postgresql'; import dataTypes from 'common/data-types/postgresql';
import SSH2Promise = require('ssh2-promise'); import SSH2Promise = require('ssh2-promise');
import SSHConfig from 'ssh2-promise/lib/sshConfig'; import SSHConfig from 'ssh2-promise/lib/sshConfig';
@@ -19,6 +18,68 @@ 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>;
@@ -101,7 +162,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,
@@ -479,11 +544,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: ''
}; };
}); });
} }
@@ -1314,7 +1375,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 +1426,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

@@ -1,7 +1,7 @@
import * as antares from 'common/interfaces/antares'; import * as antares from 'common/interfaces/antares';
import * as sqlite from 'better-sqlite3'; import * as sqlite from 'better-sqlite3';
import { AntaresCore } from '../AntaresCore'; import { AntaresCore } from '../AntaresCore';
import * as dataTypes from 'common/data-types/sqlite'; import dataTypes from 'common/data-types/sqlite';
import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes'; import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes';
export class SQLiteClient extends AntaresCore { export class SQLiteClient extends AntaresCore {
@@ -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 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'))}'`;
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 += ', ';
@@ -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 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'))}'`;
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,6 +1,6 @@
import * as pg from 'pg'; import * as pg from 'pg';
import * as importer from 'common/interfaces/importer'; import * as importer from 'common/interfaces/importer';
import fs from 'fs/promises'; import * as fs from 'fs/promises';
import PostgreSQLParser from '../../parsers/PostgreSQLParser'; import PostgreSQLParser from '../../parsers/PostgreSQLParser';
import { BaseImporter } from '../BaseImporter'; import { BaseImporter } from '../BaseImporter';

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';
@@ -31,11 +31,13 @@ async function createMainWindow () {
y: mainWindowState.y, y: mainWindowState.y,
minWidth: 900, minWidth: 900,
minHeight: 550, minHeight: 550,
show: !isWindows,
title: 'Antares SQL', title: 'Antares SQL',
icon: nativeImage.createFromDataURL(icon.default), icon: nativeImage.createFromDataURL(icon.default),
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,
contextIsolation: false, contextIsolation: false,
devTools: isDevelopment,
spellcheck: false spellcheck: false
}, },
autoHideMenuBar: true, autoHideMenuBar: true,
@@ -84,8 +86,8 @@ 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) { if (isWindows && mainWindow) {
mainWindow.setTitleBarOverlay({ mainWindow.setTitleBarOverlay({
color: appTheme === 'dark' ? '#3f3f3f' : '#fff', color: appTheme === 'dark' ? '#3f3f3f' : '#fff',
symbolColor: appTheme === 'dark' ? '#fff' : '#000' symbolColor: appTheme === 'dark' ? '#fff' : '#000'
@@ -93,7 +95,7 @@ else {
} }
}); });
ipcMain.on('change-window-title', (event, title: string) => { ipcMain.on('change-window-title', (_, title: string) => {
if (mainWindow) mainWindow.setTitle(title); if (mainWindow) mainWindow.setTitle(title);
}); });
@@ -119,8 +121,11 @@ else {
mainWindow = await createMainWindow(); mainWindow = await createMainWindow();
createAppMenu(); createAppMenu();
if (isDevelopment) if (isWindows)
mainWindow.webContents.openDevTools(); mainWindow.show();
// if (isDevelopment)
// mainWindow.webContents.openDevTools();
process.on('uncaughtException', error => { process.on('uncaughtException', error => {
mainWindow.webContents.send('unhandled-exception', error); mainWindow.webContents.send('unhandled-exception', error);
@@ -141,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: [
@@ -175,10 +178,11 @@ function createAppMenu () {
{ {
role: 'windowMenu' role: 'windowMenu'
} }
]); ]
} };
Menu.setApplicationMenu(menu); const shortCutRegister = ShortcutRegister.getInstance({ mainWindow, menuTemplate, mode: 'local' });
shortCutRegister.init();
} }
function saveWindowState () { function saveWindowState () {

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,103 +21,116 @@
<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> <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';
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import TheSettingBar from '@/components/TheSettingBar'; import TheSettingBar from '@/components/TheSettingBar.vue';
export default { const { t } = useI18n();
name: 'App',
components: {
TheTitleBar: defineAsyncComponent(() => import(/* webpackChunkName: "TheTitleBar" */'@/components/TheTitleBar')),
TheSettingBar,
TheFooter: defineAsyncComponent(() => import(/* webpackChunkName: "TheFooter" */'@/components/TheFooter')),
TheNotificationsBoard: defineAsyncComponent(() => import(/* webpackChunkName: "TheNotificationsBoard" */'@/components/TheNotificationsBoard')),
Workspace: defineAsyncComponent(() => import(/* webpackChunkName: "Workspace" */'@/components/Workspace')),
WorkspaceAddConnectionPanel: defineAsyncComponent(() => import(/* webpackChunkName: "WorkspaceAddConnectionPanel" */'@/components/WorkspaceAddConnectionPanel')),
ModalSettings: defineAsyncComponent(() => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings')),
TheScratchpad: defineAsyncComponent(() => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad')),
BaseTextEditor: defineAsyncComponent(() => import(/* webpackChunkName: "BaseTextEditor" */'@/components/BaseTextEditor'))
},
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 connectionsStore = useConnectionsStore();
const settingsStore = useSettingsStore();
const workspacesStore = useWorkspacesStore();
return { const {
isLoading, isSettingModal,
isSettingModal, isScratchpad
isScratchpad, } = storeToRefs(applicationStore);
checkVersionUpdate, const { connections } = storeToRefs(connectionsStore);
connections, const { applicationTheme, disableBlur } = storeToRefs(settingsStore);
applicationTheme, const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
disableBlur,
selectedWorkspace
};
},
mounted () {
ipcRenderer.send('check-for-updates');
this.checkVersionUpdate();
const InputMenu = Menu.buildFromTemplate([ const { checkVersionUpdate } = applicationStore;
{ const { changeApplicationTheme } = settingsStore;
label: this.$t('word.cut'),
role: 'cut' const isAllConnectionsModal: Ref<boolean> = ref(false);
},
{ document.addEventListener('DOMContentLoaded', () => {
label: this.$t('word.copy'), setTimeout(() => {
role: 'copy' changeApplicationTheme(applicationTheme.value);// Forces persistentStore to save on file and mail process
}, }, 1000);
{ });
label: this.$t('word.paste'),
role: 'paste' onMounted(() => {
}, ipcRenderer.on('open-all-connections', () => {
{ isAllConnectionsModal.value = true;
type: 'separator' });
},
{ ipcRenderer.on('open-scratchpad', () => {
label: this.$t('message.selectAll'), isScratchpad.value = true;
role: 'selectall' });
ipcRenderer.on('open-settings', () => {
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) => { });
e.preventDefault(); });
e.stopPropagation();
let node = e.target;
while (node) {
if (node.nodeName.match(/^(input|textarea)$/i) || node.isContentEditable) {
InputMenu.popup(getCurrentWindow());
break;
}
node = node.parentNode;
}
});
}
};
</script> </script>
<style lang="scss"> <style lang="scss">
@@ -147,14 +160,14 @@ export default {
height: calc(100vh - #{$footer-height}); height: calc(100vh - #{$footer-height});
} }
.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;
overflow: auto; overflow: auto;
} }
} }
</style> </style>

View File

@@ -3,7 +3,7 @@
<Teleport to="#window-content"> <Teleport to="#window-content">
<div class="modal active" :class="modalSizeClass"> <div class="modal active" :class="modalSizeClass">
<a class="modal-overlay" @click="hideModal" /> <a class="modal-overlay" @click="hideModal" />
<div class="modal-container"> <div ref="trapRef" class="modal-container">
<div v-if="hasHeader" class="modal-header pl-2"> <div v-if="hasHeader" class="modal-header pl-2">
<div class="modal-title h6"> <div class="modal-title h6">
<slot name="header" /> <slot name="header" />
@@ -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>
@@ -46,65 +46,72 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
export default { import { useFocusTrap } from '@/composables/useFocusTrap';
name: 'BaseConfirmModal', import { computed, onBeforeUnmount, PropType, useSlots } from 'vue';
props: { import { useI18n } from 'vue-i18n';
size: {
type: String,
validator: prop => ['small', 'medium', '400', 'large'].includes(prop),
default: 'small'
},
hideFooter: {
type: Boolean,
default: false
},
confirmText: String,
cancelText: String
},
emits: ['confirm', 'hide'],
computed: {
hasHeader () {
return !!this.$slots.header;
},
hasBody () {
return !!this.$slots.body;
},
hasDefault () {
return !!this.$slots.default;
},
modalSizeClass () {
if (this.size === 'small')
return 'modal-sm';
if (this.size === '400')
return 'modal-400';
else if (this.size === 'large')
return 'modal-lg';
else return '';
}
},
created () {
window.addEventListener('keydown', this.onKey);
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
confirmModal () {
this.$emit('confirm');
this.hideModal();
},
hideModal () { const { t } = useI18n();
this.$emit('hide');
}, const props = defineProps({
onKey (e) { size: {
e.stopPropagation(); type: String as PropType<'small' | 'medium' | '400' | 'large'>,
if (e.key === 'Escape') validator: (prop: string) => ['small', 'medium', '400', 'large'].includes(prop),
this.hideModal(); default: 'small'
} },
hideFooter: {
type: Boolean,
default: false
},
confirmText: String,
cancelText: String,
disableAutofocus: {
type: Boolean,
default: false
},
closeOnConfirm: {
type: Boolean,
default: true
} }
});
const emit = defineEmits(['confirm', 'hide']);
const slots = useSlots();
const { trapRef } = useFocusTrap({ disableAutofocus: props.disableAutofocus });
const hasHeader = computed(() => !!slots.header);
const hasBody = computed(() => !!slots.body);
const hasDefault = computed(() => !!slots.default);
const modalSizeClass = computed(() => {
if (props.size === 'small')
return 'modal-sm';
if (props.size === '400')
return 'modal-400';
else if (props.size === 'large')
return 'modal-lg';
else return '';
});
const confirmModal = () => {
emit('confirm');
if (props.closeOnConfirm) hideModal();
}; };
const hideModal = () => {
emit('hide');
};
const onKey = (e: KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape')
hideModal();
};
window.addEventListener('keydown', onKey);
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script> </script>
<style scoped> <style scoped>

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"
@@ -15,153 +15,173 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
export default { import { computed, onBeforeUnmount, onMounted, Ref, ref } from 'vue';
name: 'BaseContextMenu',
props: {
contextEvent: MouseEvent
},
emits: ['close-context'],
data () {
return {
contextSize: null,
isBottom: false
};
},
computed: {
position () {
let topCord = 0;
let leftCord = 0;
if (this.contextEvent) { const contextContent: Ref<HTMLDivElement> = ref(null);
const { clientY, clientX } = this.contextEvent; const contextSize: Ref<{height: number; width: number; subHeight?: number; subWidth?: number}> = ref(null);
topCord = `${clientY + 2}px`; const isBottom: Ref<boolean> = ref(false);
leftCord = `${clientX + 5}px`; const isRight: Ref<boolean> = ref(false);
const props = defineProps<{ contextEvent: MouseEvent }>();
const emit = defineEmits(['close-context']);
if (this.contextSize) { const position = computed(() => {
if (clientY + (this.contextSize.height < 200 ? 200 : this.contextSize.height) + 5 >= window.innerHeight) { let topCord = '0px';
topCord = `${clientY + 3 - this.contextSize.height}px`; let leftCord = '0px';
this.isBottom = true;
}
if (clientX + this.contextSize.width + 5 >= window.innerWidth) if (props.contextEvent) {
leftCord = `${clientX - this.contextSize.width}px`; const { clientY, clientX } = props.contextEvent;
} topCord = `${clientY + 2}px`;
leftCord = `${clientX + 5}px`;
if (contextSize.value) {
if (clientY + (contextSize.value.height < 200 ? 200 : contextSize.value.height) + 5 >= window.innerHeight) {
topCord = `${clientY + 3 - contextSize.value.height}px`;
isBottom.value = true;
} }
return { if (clientY + contextSize.value.subHeight + contextSize.value.height >= window.innerHeight)
top: topCord, isBottom.value = true;
left: leftCord
}; if (clientX + contextSize.value.width + 5 >= window.innerWidth) {
} leftCord = `${clientX - contextSize.value.width}px`;
}, isRight.value = true;
created () { }
window.addEventListener('keydown', this.onKey);
}, if (clientX + contextSize.value.subWidth + contextSize.value.width >= window.innerWidth)
mounted () { isRight.value = true;
if (this.$refs.contextContent)
this.contextSize = this.$refs.contextContent.getBoundingClientRect();
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
close () {
this.$emit('close-context');
},
onKey (e) {
e.stopPropagation();
if (e.key === 'Escape')
this.close();
} }
} }
return {
top: topCord,
left: leftCord
};
});
const close = () => {
emit('close-context');
}; };
const onKey = (e: KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape')
close();
};
window.addEventListener('keydown', onKey);
onMounted(() => {
if (contextContent.value) {
contextSize.value = contextContent.value.getBoundingClientRect();
const submenus = contextContent.value.querySelectorAll<HTMLDivElement>('.context-submenu');
for (const submenu of submenus) {
const submenuSize = submenu.getBoundingClientRect();
if (!contextSize.value.subHeight || submenuSize.height > contextSize.value.subHeight)
contextSize.value.subHeight = submenuSize.height;
if (!contextSize.value.subWidth || submenuSize.width > contextSize.value.subWidth)
contextSize.value.subWidth = submenuSize.width;
}
}
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script> </script>
<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

@@ -4,11 +4,6 @@
</div> </div>
</template> </template>
<script>
export default {
name: 'BaseLoader'
};
</script>
<style scoped> <style scoped>
.empty { .empty {
position: absolute; position: absolute;

View File

@@ -1,95 +1,93 @@
<template> <template>
<div id="map" class="map" /> <div id="map" class="map" />
</template> </template>
<script>
import L from 'leaflet'; <script setup lang="ts">
import { onMounted, PropType, Ref, ref } from 'vue';
import * as L from 'leaflet';
import { import {
point, point,
lineString, lineString,
polygon polygon
} from '@turf/helpers'; } from '@turf/helpers';
import { GeoJsonObject } from 'geojson';
import { getArrayDepth } from 'common/libs/getArrayDepth'; import { getArrayDepth } from 'common/libs/getArrayDepth';
export default { interface Coordinates { x: number; y: number }
name: 'BaseMap',
props: {
points: [Object, Array],
isMultiSpatial: Boolean
},
data () {
return {
map: null,
markers: [],
center: null
};
},
mounted () {
if (this.isMultiSpatial) {
for (const element of this.points)
this.markers.push(this.getMarkers(element));
}
else {
this.markers = this.getMarkers(this.points);
if (!Array.isArray(this.points)) const props = defineProps({
this.center = [this.points.y, this.points.x]; points: [Object, Array] as PropType<Coordinates | Coordinates[]>,
} isMultiSpatial: Boolean
});
const map: Ref<L.Map> = ref(null);
const markers: Ref<GeoJsonObject | GeoJsonObject[]> = ref(null);
const center: Ref<[number, number]> = ref(null);
this.map = L.map('map', { const getMarkers = (points: Coordinates) => {
center: this.center || [0, 0], if (Array.isArray(points)) {
zoom: 15, if (getArrayDepth(points) === 1)
minZoom: 1, return lineString(points.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []));
attributionControl: false else
}); return polygon(points.map(arr => arr.reduce((acc: Coordinates[], curr: Coordinates) => [...acc, [curr.x, curr.y]], [])));
L.control.attribution({ prefix: '<b>Leaflet</b>' }).addTo(this.map);
const geoJsonObj = L.geoJSON(this.markers, {
style: function () {
return {
weight: 2,
fillColor: '#ff7800',
color: '#ff7800',
opacity: 0.8,
fillOpacity: 0.4
};
},
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {
radius: 7,
weight: 2,
fillColor: '#ff7800',
color: '#ff7800',
opacity: 0.8,
fillOpacity: 0.4
});
}
}).addTo(this.map);
const southWest = L.latLng(-90, -180);
const northEast = L.latLng(90, 180);
const bounds = L.latLngBounds(southWest, northEast);
this.map.setMaxBounds(bounds);
if (!this.center) this.map.fitBounds(geoJsonObj.getBounds());
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <b>OpenStreetMap</b>'
}).addTo(this.map);
},
methods: {
getMarkers (points) {
if (Array.isArray(points)) {
if (getArrayDepth(points) === 1)
return lineString(points.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []));
else
return polygon(points.map(arr => arr.reduce((acc, curr) => [...acc, [curr.x, curr.y]], [])));
}
else
return point([points.x, points.y]);
}
} }
else
return point([points.x, points.y]);
}; };
onMounted(() => {
if (props.isMultiSpatial) {
for (const element of props.points as Coordinates[])
(markers.value as GeoJsonObject[]).push(getMarkers(element));
}
else {
markers.value = getMarkers(props.points as Coordinates);
if (!Array.isArray(props.points))
center.value = [props.points.y, props.points.x];
}
map.value = L.map('map', {
center: center.value || [0, 0],
zoom: 15,
minZoom: 1,
attributionControl: false
});
L.control.attribution({ prefix: '<b>Leaflet</b>' }).addTo(map.value);
const geoJsonObj = L.geoJSON((markers.value as GeoJsonObject), {
style: function () {
return {
weight: 2,
fillColor: '#ff7800',
color: '#ff7800',
opacity: 0.8,
fillOpacity: 0.4
};
},
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {
radius: 7,
weight: 2,
fillColor: '#ff7800',
color: '#ff7800',
opacity: 0.8,
fillOpacity: 0.4
});
}
}).addTo(map.value);
const southWest = L.latLng(-90, -180);
const northEast = L.latLng(90, 180);
const bounds = L.latLngBounds(southWest, northEast);
map.value.setMaxBounds(bounds);
if (!center.value) map.value.fitBounds(geoJsonObj.getBounds());
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <b>OpenStreetMap</b>'
}).addTo(map.value);
});
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@@ -14,64 +14,58 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
export default { import { computed, ref } from 'vue';
name: 'BaseNotification',
props: {
message: {
type: String,
default: ''
},
status: {
type: String,
default: ''
}
},
emits: ['close'],
data () {
return {
isExpanded: false
};
},
computed: {
notificationStatus () {
let className = '';
let iconName = '';
switch (this.status) {
case 'success':
className = 'toast-success';
iconName = 'mdi-check';
break;
case 'error':
className = 'toast-error';
iconName = 'mdi-alert-rhombus';
break;
case 'warning':
className = 'toast-warning';
iconName = 'mdi-alert';
break;
case 'primary':
className = 'toast-primary';
iconName = 'mdi-information-outline';
break;
}
return { className, iconName }; const props = defineProps({
}, message: {
isExpandable () { type: String,
return this.message.length > 80; default: ''
}
}, },
methods: { status: {
hideToast () { type: String,
this.$emit('close'); default: ''
},
toggleExpand () {
this.isExpanded = !this.isExpanded;
}
} }
});
const isExpanded = ref(false);
const emit = defineEmits(['close']);
const notificationStatus = computed(() => {
let className = '';
let iconName = '';
switch (props.status) {
case 'success':
className = 'toast-success';
iconName = 'mdi-check';
break;
case 'error':
className = 'toast-error';
iconName = 'mdi-alert-rhombus';
break;
case 'warning':
className = 'toast-warning';
iconName = 'mdi-alert';
break;
case 'primary':
className = 'toast-primary';
iconName = 'mdi-information-outline';
break;
}
return { className, iconName };
});
const isExpandable = computed(() => props.message.length > 80);
const hideToast = () => {
emit('close');
};
const toggleExpand = () => {
isExpanded.value = !isExpanded.value;
}; };
</script> </script>
<style scoped> <style scoped>
.toast { .toast {
display: flex; display: flex;

View File

@@ -132,6 +132,10 @@ export default defineComponent({
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false default: false
},
maxVisibleOptions: {
type: Number,
default: 100
} }
}, },
emits: ['select', 'open', 'close', 'update:modelValue', 'change', 'blur'], emits: ['select', 'open', 'close', 'update:modelValue', 'change', 'blur'],
@@ -139,7 +143,7 @@ export default defineComponent({
const hightlightedIndex = ref(0); const hightlightedIndex = ref(0);
const isOpen = ref(false); const isOpen = ref(false);
const isMouseDown = ref(false); const isMouseDown = ref(false);
const internalValue = ref(props.modelValue || props.value); const internalValue = ref(props.modelValue !== false ? props.modelValue : props.value);
const el = ref(null); const el = ref(null);
const searchInput = ref(null); const searchInput = ref(null);
const optionList = ref(null); const optionList = ref(null);
@@ -147,7 +151,7 @@ export default defineComponent({
const searchText = ref(''); const searchText = ref('');
const getOptionValue = (opt) => _guess('optionTrackBy', opt); const getOptionValue = (opt) => _guess('optionTrackBy', opt);
const getOptionLabel = (opt) => _guess('optionLabel', opt); const getOptionLabel = (opt) => _guess('optionLabel', opt) + '';
const getOptionDisabled = (opt) => _guess('optionDisabled', opt); const getOptionDisabled = (opt) => _guess('optionDisabled', opt);
const _guess = (name, item) => { const _guess = (name, item) => {
const prop = props[name]; const prop = props[name];
@@ -200,11 +204,30 @@ export default defineComponent({
}); });
const filteredOptions = computed(() => { const filteredOptions = computed(() => {
const normalizedSearch = (searchText.value || '').toLowerCase().trim(); const searchTerms = (searchText.value || '').toLowerCase().trim();
return normalizedSearch let options = searchTerms
? flattenOptions.value.filter(opt => opt.$type === 'group' || opt.label.trim().toLowerCase().indexOf(normalizedSearch) !== -1) ? flattenOptions.value.filter(opt => opt.$type === 'group' || opt.label.trim().toLowerCase().indexOf(searchTerms) !== -1)
: flattenOptions.value; : flattenOptions.value;
if (options.length > props.maxVisibleOptions) {
let sliceStart = 0;
let sliceEnd = sliceStart + props.maxVisibleOptions;
// if no search active try to open the dropdown showing options around the selected one
if (searchTerms === '') {
const index = internalValue.value ? flattenOptions.value.findIndex(el => el.value === internalValue.value) : -1;
if (index < options.length -1) {
sliceStart = Math.max(0, index - Math.floor(props.maxVisibleOptions / 2));
sliceEnd = Math.min(sliceStart + sliceEnd, options.length -1);
}
}
options = options.slice(sliceStart, sliceEnd);
}
return options;
}); });
const searchInputStyle = computed(() => { const searchInputStyle = computed(() => {
@@ -253,7 +276,7 @@ export default defineComponent({
const activate = () => { const activate = () => {
if (isOpen.value || props.disabled) return; if (isOpen.value || props.disabled) return;
isOpen.value = true; isOpen.value = true;
hightlightedIndex.value = flattenOptions.value.findIndex(el => el.value === internalValue.value) || 0; hightlightedIndex.value = filteredOptions.value.findIndex(el => el.value === internalValue.value) || 0;
if (props.searchable) if (props.searchable)
searchInput.value.focus(); searchInput.value.focus();
@@ -285,6 +308,8 @@ export default defineComponent({
}; };
const adjustListPosition = () => { const adjustListPosition = () => {
if (!optionList.value) return;
const element = el.value; const element = el.value;
let { left, top } = element.getBoundingClientRect(); let { left, top } = element.getBoundingClientRect();
const { left: offsetLeft = 0, top: offsetTop = 0 } = props.dropdownOffsets; const { left: offsetLeft = 0, top: offsetTop = 0 } = props.dropdownOffsets;
@@ -380,7 +405,8 @@ export default defineComponent({
optionList, optionList,
optionRefs, optionRefs,
handleBlurEvent, handleBlurEvent,
handleMouseUpEvent handleMouseUpEvent,
internalValue
}; };
} }
}); });
@@ -405,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

@@ -9,121 +9,114 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { onMounted, watch } from 'vue';
import * as ace from 'ace-builds'; import * as ace from 'ace-builds';
import { storeToRefs } from 'pinia';
import 'ace-builds/webpack-resolver'; import 'ace-builds/webpack-resolver';
import { storeToRefs } from 'pinia';
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
export default { const props = defineProps({
name: 'BaseTextEditor', modelValue: String,
props: { mode: { type: String, default: 'text' },
modelValue: String, editorClass: { type: String, default: '' },
mode: { type: String, default: 'text' }, autoFocus: { type: Boolean, default: false },
editorClass: { type: String, default: '' }, readOnly: { type: Boolean, default: false },
autoFocus: { type: Boolean, default: false }, showLineNumbers: { type: Boolean, default: true },
readOnly: { type: Boolean, default: false }, height: { type: Number, default: 200 }
showLineNumbers: { type: Boolean, default: true }, });
height: { type: Number, default: 200 } const emit = defineEmits(['update:modelValue']);
}, const settingsStore = useSettingsStore();
emits: ['update:modelValue'],
setup () {
const settingsStore = useSettingsStore();
const { const {
editorTheme, editorTheme,
editorFontSize, editorFontSize,
autoComplete, autoComplete,
lineWrap lineWrap
} = storeToRefs(settingsStore); } = storeToRefs(settingsStore);
return { let editor: ace.Ace.Editor;
editorTheme, const id = uidGen();
editorFontSize,
autoComplete,
lineWrap
};
},
data () {
return {
editor: null,
id: uidGen()
};
},
watch: {
mode () {
if (this.editor)
this.editor.session.setMode(`ace/mode/${this.mode}`);
},
editorTheme () {
if (this.editor)
this.editor.setTheme(`ace/theme/${this.editorTheme}`);
},
editorFontSize () {
const sizes = {
small: '12px',
medium: '14px',
large: '16px'
};
if (this.editor) { watch(() => props.mode, () => {
this.editor.setOptions({ if (editor)
fontSize: sizes[this.editorFontSize] editor.session.setMode(`ace/mode/${props.mode}`);
}); });
}
}, watch(editorTheme, () => {
autoComplete () { if (editor)
if (this.editor) { editor.setTheme(`ace/theme/${editorTheme.value}`);
this.editor.setOptions({ });
enableLiveAutocompletion: this.autoComplete
}); watch(editorFontSize, () => {
} const sizes = {
}, xsmall: '10px',
lineWrap () { small: '12px',
if (this.editor) { medium: '14px',
this.editor.setOptions({ large: '16px',
wrap: this.lineWrap xlarge: '18px',
}); xxlarge: '20px'
} };
}
}, if (editor) {
mounted () { editor.setOptions({
this.editor = ace.edit(`editor-${this.id}`, { fontSize: sizes[editorFontSize.value]
mode: `ace/mode/${this.mode}`,
theme: `ace/theme/${this.editorTheme}`,
value: this.modelValue || '',
fontSize: '14px',
printMargin: false,
readOnly: this.readOnly,
showLineNumbers: this.showLineNumbers,
showGutter: this.showLineNumbers
}); });
}
});
this.editor.setOptions({ watch(autoComplete, () => {
enableBasicAutocompletion: false, if (editor) {
wrap: this.lineWrap, editor.setOptions({
enableSnippets: false, enableLiveAutocompletion: autoComplete.value
enableLiveAutocompletion: false
}); });
}
});
this.editor.session.on('change', () => { watch(lineWrap, () => {
const content = this.editor.getValue(); if (editor) {
this.$emit('update:modelValue', content); editor.setOptions({
wrap: lineWrap.value
}); });
}
});
if (this.autoFocus) { onMounted(() => {
setTimeout(() => { editor = ace.edit(`editor-${id}`, {
this.editor.focus(); mode: `ace/mode/${props.mode}`,
this.editor.resize(); theme: `ace/theme/${editorTheme.value}`,
}, 20); value: props.modelValue || '',
} fontSize: 14,
printMargin: false,
readOnly: props.readOnly,
showLineNumbers: props.showLineNumbers,
showGutter: props.showLineNumbers
});
editor.setOptions({
enableBasicAutocompletion: false,
wrap: lineWrap,
enableSnippets: false,
enableLiveAutocompletion: false
});
(editor.session as unknown as ace.Ace.Editor).on('change', () => {
const content = editor.getValue();
emit('update:modelValue', content);
});
if (props.autoFocus) {
setTimeout(() => { setTimeout(() => {
this.editor.resize(); editor.focus();
editor.resize();
}, 20); }, 20);
} }
};
setTimeout(() => {
editor.resize();
}, 20);
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -9,67 +9,63 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
export default { import { computed, ref, watch } from 'vue';
name: 'BaseToast',
props: {
message: {
type: String,
default: ''
},
status: {
type: String,
default: ''
}
},
emits: ['close'],
data () {
return {
isVisible: false
};
},
computed: {
toastStatus () {
let className = '';
let iconName = '';
switch (this.status) {
case 'success':
className = 'toast-success';
iconName = 'mdi-check';
break;
case 'error':
className = 'toast-error';
iconName = 'mdi-alert-rhombus';
break;
case 'warning':
className = 'toast-warning';
iconName = 'mdi-alert';
break;
case 'primary':
className = 'toast-primary';
iconName = 'mdi-information-outline';
break;
}
return { className, iconName }; const props = defineProps({
} message: {
type: String,
default: ''
}, },
watch: { status: {
message: function () { type: String,
if (this.message) default: ''
this.isVisible = true;
else
this.isVisible = false;
}
},
methods: {
hideToast () {
this.isVisible = false;
this.$emit('close');
}
} }
});
const isVisible = ref(false);
const message = ref(props.message);
const emit = defineEmits(['close']);
const toastStatus = computed(() => {
let className = '';
let iconName = '';
switch (props.status) {
case 'success':
className = 'toast-success';
iconName = 'mdi-check';
break;
case 'error':
className = 'toast-error';
iconName = 'mdi-alert-rhombus';
break;
case 'warning':
className = 'toast-warning';
iconName = 'mdi-alert';
break;
case 'primary':
className = 'toast-primary';
iconName = 'mdi-information-outline';
break;
}
return { className, iconName };
});
watch(message, () => {
if (message.value)
isVisible.value = true;
else
isVisible.value = false;
});
const hideToast = () => {
isVisible.value = false;
emit('close');
}; };
</script> </script>
<style scoped> <style scoped>
.toast { .toast {
display: flex; display: flex;

View File

@@ -4,10 +4,10 @@
<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.length" v-if="modelValue"
class="file-uploader-reset mdi mdi-close" class="file-uploader-reset mdi mdi-close"
@click.prevent="clear" @click.prevent="clear"
/> />
@@ -22,40 +22,29 @@
</label> </label>
</template> </template>
<script> <script setup lang="ts">
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
import { useFilters } from '@/composables/useFilters';
export default { const { lastPart } = useFilters();
name: 'BaseUploadInput',
props: {
message: {
default: 'Browse',
type: String
},
modelValue: {
default: '',
type: String
}
},
emits: ['change', 'clear'],
data () {
return {
id: uidGen()
};
},
methods: {
clear () {
this.$emit('clear');
},
lastPart (string) {
if (!string) return '';
string = string.split(/[/\\]+/).pop(); defineProps({
if (string.length >= 19) message: {
string = `...${string.slice(-19)}`; default: 'Browse',
return string; type: String
} },
modelValue: {
default: '',
type: String
} }
});
const emit = defineEmits(['change', 'clear']);
const id = uidGen();
const clear = () => {
emit('clear');
}; };
</script> </script>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="vscroll-holder"> <div ref="root" class="vscroll-holder">
<div <div
class="vscroll-spacer" class="vscroll-spacer"
:style="{ :style="{
@@ -20,71 +20,76 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
export default { import { onBeforeUnmount, onMounted, Ref, ref, watch } from 'vue';
name: 'BaseVirtualScroll',
props: {
items: Array,
itemHeight: Number,
visibleHeight: Number,
scrollElement: {
type: HTMLDivElement,
default: null
}
},
data () {
return {
topHeight: 0,
bottomHeight: 0,
visibleItems: [],
renderTimeout: null,
localScrollElement: null
};
},
watch: {
scrollElement () {
this.setScrollElement();
}
},
mounted () {
this.setScrollElement();
},
beforeUnmount () {
this.localScrollElement.removeEventListener('scroll', this.checkScrollPosition);
},
methods: {
checkScrollPosition (e) {
clearTimeout(this.renderTimeout);
this.renderTimeout = setTimeout(() => { const props = defineProps({
this.updateWindow(e); items: Array,
}, 200); itemHeight: Number,
}, visibleHeight: Number,
updateWindow () { scrollElement: {
const visibleItemsCount = Math.ceil(this.visibleHeight / this.itemHeight); type: HTMLDivElement,
const totalScrollHeight = this.items.length * this.itemHeight; default: null
const offset = 50;
const scrollTop = this.localScrollElement.scrollTop;
const firstVisibleIndex = Math.floor(scrollTop / this.itemHeight);
const lastVisibleIndex = firstVisibleIndex + visibleItemsCount;
const firstCutIndex = Math.max(firstVisibleIndex - offset, 0);
const lastCutIndex = lastVisibleIndex + offset;
this.visibleItems = this.items.slice(firstCutIndex, lastCutIndex);
this.topHeight = firstCutIndex * this.itemHeight;
this.bottomHeight = totalScrollHeight - this.visibleItems.length * this.itemHeight - this.topHeight;
},
setScrollElement () {
if (this.localScrollElement)
this.localScrollElement.removeEventListener('scroll', this.checkScrollPosition);
this.localScrollElement = this.scrollElement ? this.scrollElement : this.$el;
this.updateWindow();
this.localScrollElement.addEventListener('scroll', this.checkScrollPosition);
}
} }
});
const root = ref(null);
const topHeight: Ref<number> = ref(0);
const bottomHeight: Ref<number> = ref(0);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const visibleItems: Ref<any[]> = ref([]);
const renderTimeout: Ref<NodeJS.Timeout> = ref(null);
const localScrollElement: Ref<HTMLDivElement> = ref(null);
const checkScrollPosition = () => {
clearTimeout(renderTimeout.value);
renderTimeout.value = setTimeout(() => {
updateWindow();
}, 200);
}; };
const updateWindow = () => {
const visibleItemsCount = Math.ceil(props.visibleHeight / props.itemHeight);
const totalScrollHeight = props.items.length * props.itemHeight;
const offset = 50;
const scrollTop = localScrollElement.value.scrollTop;
const firstVisibleIndex = Math.floor(scrollTop / props.itemHeight);
const lastVisibleIndex = firstVisibleIndex + visibleItemsCount;
const firstCutIndex = Math.max(firstVisibleIndex - offset, 0);
const lastCutIndex = lastVisibleIndex + offset;
visibleItems.value = props.items.slice(firstCutIndex, lastCutIndex);
topHeight.value = firstCutIndex * props.itemHeight;
bottomHeight.value = totalScrollHeight - visibleItems.value.length * props.itemHeight - topHeight.value;
};
const setScrollElement = () => {
if (localScrollElement.value)
localScrollElement.value.removeEventListener('scroll', checkScrollPosition);
localScrollElement.value = props.scrollElement ? props.scrollElement : root.value;
updateWindow();
localScrollElement.value.addEventListener('scroll', checkScrollPosition);
};
watch(() => props.scrollElement, () => {
setScrollElement();
});
onMounted(() => {
setScrollElement();
});
onBeforeUnmount(() => {
localScrollElement.value.removeEventListener('scroll', checkScrollPosition);
});
defineExpose({
updateWindow
});
</script> </script>

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) => 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) => $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)"
/> />
@@ -85,153 +85,158 @@
</fieldset> </fieldset>
</template> </template>
<script> <script setup lang="ts">
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes'; import { computed, PropType, Ref, ref, watch } from 'vue';
import BaseUploadInput from '@/components/BaseUploadInput'; import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT, UUID, IS_BIGINT } from 'common/fieldTypes';
import ForeignKeySelect from '@/components/ForeignKeySelect'; import BaseUploadInput from '@/components/BaseUploadInput.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';
export default { const { t } = useI18n();
name: 'FakerSelect',
components: {
ForeignKeySelect,
BaseUploadInput,
BaseSelect
},
props: {
type: String,
field: Object,
isChecked: Boolean,
foreignKeys: Array,
keyUsage: Array,
fieldLength: Number,
fieldObj: Object
},
emits: ['update:modelValue'],
data () {
return {
localType: null,
selectedGroup: 'manual',
selectedMethod: '',
selectedValue: '',
debounceTimeout: null,
methodParams: {},
enumArray: null
};
},
computed: {
fakerGroups () {
if ([...TEXT, ...LONG_TEXT].includes(this.type))
this.localType = 'string';
else if (NUMBER.includes(this.type))
this.localType = 'number';
else if (FLOAT.includes(this.type))
this.localType = 'float';
else if ([...DATE, ...DATETIME].includes(this.type))
this.localType = 'datetime';
else if (TIME.includes(this.type))
this.localType = 'time';
else
this.localType = 'none';
return FakerMethods.getGroupsByType(this.localType); const props = defineProps({
}, type: String,
fakerMethods () { field: Object,
return FakerMethods.getMethods({ type: this.localType, group: this.selectedGroup }); isChecked: Boolean,
}, foreignKeys: Array,
methodData () { keyUsage: Array as PropType<{field: string}[]>,
return this.fakerMethods.find(method => method.name === this.selectedMethod); fieldLength: Number,
} fieldObj: Object
}, });
watch: { const emit = defineEmits(['update:modelValue']);
fieldObj () {
if (this.fieldObj) {
if (Array.isArray(this.fieldObj.value)) {
this.enumArray = this.fieldObj.value;
this.selectedValue = this.fieldObj.value[0];
}
else
this.selectedValue = this.fieldObj.value;
}
},
selectedGroup () {
if (this.fakerMethods.length)
this.selectedMethod = this.fakerMethods[0].name;
else
this.selectedMethod = '';
},
selectedMethod () {
this.onChange();
},
selectedValue () {
clearTimeout(this.debounceTimeout);
this.debounceTimeout = null;
this.debounceTimeout = setTimeout(() => {
this.onChange();
}, 200);
}
},
methods: {
inputProps () {
if ([...TEXT, ...LONG_TEXT].includes(this.type))
return { type: 'text', mask: false };
if ([...NUMBER, ...FLOAT].includes(this.type)) const localType: Ref<string> = ref(null);
return { type: 'number', mask: false }; const selectedGroup: Ref<string> = ref('manual');
const selectedMethod: Ref<string> = ref('');
const selectedValue: Ref<string> = ref('');
const debounceTimeout: Ref<NodeJS.Timeout> = ref(null);
const methodParams: Ref<{[key: string]: string}> = ref({});
const enumArray: Ref<string[]> = ref(null);
if (TIME.includes(this.type)) { const fakerGroups = computed(() => {
let timeMask = '##:##:##'; if ([...TEXT, ...LONG_TEXT].includes(props.type))
const precision = this.fieldLength; localType.value = 'string';
else if (NUMBER.includes(props.type))
localType.value = 'number';
else if (FLOAT.includes(props.type))
localType.value = 'float';
else if ([...DATE, ...DATETIME].includes(props.type))
localType.value = 'datetime';
else if (TIME.includes(props.type))
localType.value = 'time';
else if (UUID.includes(props.type))
localType.value = 'uuid';
else
localType.value = 'none';
for (let i = 0; i < precision; i++) return FakerMethods.getGroupsByType(localType.value);
timeMask += i === 0 ? '.#' : '#'; });
return { type: 'text', mask: timeMask }; const fakerMethods = computed(() => {
} return FakerMethods.getMethods({ type: localType.value, group: selectedGroup.value });
});
if (DATE.includes(this.type)) const methodData = computed(() => {
return { type: 'text', mask: '####-##-##' }; return fakerMethods.value.find(method => method.name === selectedMethod.value);
});
if (DATETIME.includes(this.type)) { const inputProps = () => {
let datetimeMask = '####-##-## ##:##:##'; if ([...TEXT, ...LONG_TEXT].includes(props.type))
const precision = this.fieldLength; return { type: 'text', mask: false };
for (let i = 0; i < precision; i++)
datetimeMask += i === 0 ? '.#' : '#';
return { type: 'text', mask: datetimeMask };
}
if (BLOB.includes(this.type))
return { type: 'file', mask: false };
if (BIT.includes(this.type))
return { type: 'text', mask: false };
if ([...NUMBER, ...FLOAT].includes(props.type)) {
if (IS_BIGINT.includes(props.type))
return { type: 'text', mask: false }; return { type: 'text', mask: false };
}, else
getKeyUsage (keyName) { return { type: 'number', mask: false };
return this.keyUsage.find(key => key.field === keyName);
},
filesChange (event) {
const { files } = event.target;
if (!files.length) return;
this.selectedValue = files[0].path;
},
clearValue () {
this.selectedValue = '';
},
onChange () {
this.$emit('update:modelValue', {
group: this.selectedGroup,
method: this.selectedMethod,
params: this.methodParams,
value: this.selectedValue,
length: this.fieldLength
});
}
} }
if (TIME.includes(props.type)) {
let timeMask = '##:##:##';
const precision = props.fieldLength;
for (let i = 0; i < precision; i++)
timeMask += i === 0 ? '.#' : '#';
return { type: 'text', mask: timeMask };
}
if (DATE.includes(props.type))
return { type: 'text', mask: '####-##-##' };
if (DATETIME.includes(props.type)) {
let datetimeMask = '####-##-## ##:##:##';
const precision = props.fieldLength;
for (let i = 0; i < precision; i++)
datetimeMask += i === 0 ? '.#' : '#';
return { type: 'text', mask: datetimeMask };
}
if (BLOB.includes(props.type))
return { type: 'file', mask: false };
if (BIT.includes(props.type))
return { type: 'text', mask: false };
return { type: 'text', mask: false };
}; };
const getKeyUsage = (keyName: string) => {
return props.keyUsage.find(key => key.field === keyName);
};
const filesChange = ({ target } : {target: HTMLInputElement }) => {
const { files } = target;
if (!files.length) return;
selectedValue.value = files[0].path;
};
const clearValue = () => {
selectedValue.value = '';
};
const onChange = () => {
emit('update:modelValue', {
group: selectedGroup.value,
method: selectedMethod.value,
params: methodParams.value,
value: selectedValue.value,
length: props.fieldLength
});
};
watch(() => props.fieldObj, () => {
if (props.fieldObj) {
if (Array.isArray(props.fieldObj.value)) {
enumArray.value = props.fieldObj.value;
selectedValue.value = props.fieldObj.value[0];
}
else
selectedValue.value = props.fieldObj.value;
}
});
watch(selectedGroup, () => {
if (fakerMethods.value.length)
selectedMethod.value = fakerMethods.value[0].name;
else
selectedMethod.value = '';
});
watch(selectedMethod, () => {
onChange();
});
watch(selectedValue, () => {
clearTimeout(debounceTimeout.value);
debounceTimeout.value = null;
debounceTimeout.value = setTimeout(() => {
onChange();
}, 200);
});
</script> </script>

View File

@@ -8,107 +8,101 @@
dropdown-class="select-sm" dropdown-class="select-sm"
dropdown-container=".workspace-query-results > .vscroll" dropdown-container=".workspace-query-results > .vscroll"
@change="onChange" @change="onChange"
@blur="$emit('blur')" @blur="emit('blur')"
/> />
</template> </template>
<script> <script setup lang="ts">
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';
import { useWorkspacesStore } from '@/stores/workspaces'; 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 { useFilters } from '@/composables/useFilters';
export default { const props = defineProps({
name: 'ForeignKeySelect', modelValue: [String, Number],
components: { BaseSelect }, keyUsage: Object,
props: { size: {
modelValue: [String, Number], type: String,
keyUsage: Object, default: ''
size: {
type: String,
default: ''
}
},
emits: ['update:modelValue', 'blur'],
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
return { addNotification, selectedWorkspace };
},
data () {
return {
foreignList: [],
currentValue: this.modelValue || null
};
},
computed: {
isValidDefault () {
if (!this.foreignList.length) return true;
if (this.modelValue === null) return false;
return this.foreignList.some(foreign => foreign.foreign_column.toString() === this.modelValue.toString());
},
foreigns () {
const list = [];
if (!this.isValidDefault)
list.push({ value: this.modelValue, label: this.modelValue === null ? 'NULL' : this.modelValue });
for (const row of this.foreignList)
list.push({ value: row.foreign_column, label: `${row.foreign_column} ${this.cutText('foreign_description' in row ? ` - ${row.foreign_description}` : '')}` });
return list;
}
},
async created () {
let foreignDesc;
const params = {
uid: this.selectedWorkspace,
schema: this.keyUsage.refSchema,
table: this.keyUsage.refTable
};
try { // Field data
const { status, response } = await Tables.getTableColumns(params);
if (status === 'success') {
const textField = response.find(field => [...TEXT, ...LONG_TEXT].includes(field.type) && field.name !== this.keyUsage.refField);
foreignDesc = textField ? textField.name : false;
}
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
try { // Foregn list
const { status, response } = await Tables.getForeignList({
...params,
column: this.keyUsage.refField,
description: foreignDesc
});
if (status === 'success')
this.foreignList = response.rows;
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
},
methods: {
onChange (opt) {
this.$emit('update:modelValue', opt.value);
},
cutText (val) {
if (typeof val !== 'string') return val;
return val.length > 15 ? `${val.substring(0, 15)}...` : val;
}
} }
});
const emit = defineEmits(['update:modelValue', 'blur']);
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { cutText } = useFilters();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const editField: Ref<HTMLSelectElement> = ref(null);
const foreignList = ref([]);
const currentValue = ref(null);
const isValidDefault = computed(() => {
if (!foreignList.value.length) return true;
if (props.modelValue === null) return false;
return foreignList.value.some(foreign => foreign.foreign_column.toString() === props.modelValue?.toString());
});
const foreigns = computed(() => {
const list = [];
if (!isValidDefault.value)
list.push({ value: props.modelValue, label: props.modelValue === null ? 'NULL' : props.modelValue });
for (const row of foreignList.value)
list.push({ value: row.foreign_column, label: `${row.foreign_column} ${cutText('foreign_description' in row ? ` - ${row.foreign_description}` : '', 15)}` });
return list;
});
const onChange = (opt: HTMLSelectElement) => {
emit('update:modelValue', opt.value);
}; };
watch(() => props.modelValue, () => {
currentValue.value = props.modelValue;
});
let foreignDesc: string | false;
const params = {
uid: selectedWorkspace.value,
schema: props.keyUsage.refSchema,
table: props.keyUsage.refTable
};
(async () => {
try { // Field data
const { status, response } = await Tables.getTableColumns(params);
if (status === 'success') {
const textField = (response as TableField[]).find((field: {type: string; name: string}) => [...TEXT, ...LONG_TEXT].includes(field.type) && field.name !== props.keyUsage.refField);
foreignDesc = textField ? textField.name : false;
}
else
addNotification({ status: 'error', message: response });
}
catch (err) {
addNotification({ status: 'error', message: err.stack });
}
try { // Foregn list
const { status, response } = await Tables.getForeignList({
...params,
column: props.keyUsage.refField,
description: foreignDesc
});
if (status === 'success')
foreignList.value = response.rows;
else
addNotification({ status: 'error', message: response });
}
catch (err) {
addNotification({ status: 'error', message: err.stack });
}
})();
</script> </script>

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.deleteCorfirm') }} <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

@@ -2,11 +2,11 @@
<Teleport to="#window-content"> <Teleport to="#window-content">
<div class="modal active modal-sm"> <div class="modal active modal-sm">
<a class="modal-overlay" /> <a class="modal-overlay" />
<div class="modal-container p-0"> <div ref="trapRef" class="modal-container p-0">
<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>
@@ -55,30 +55,31 @@
</Teleport> </Teleport>
</template> </template>
<script> <script setup lang="ts">
export default { import { Ref, ref } from 'vue';
name: 'ModalAskCredentials', import { useFocusTrap } from '@/composables/useFocusTrap';
emits: ['close-asking', 'credentials'], import { useI18n } from 'vue-i18n';
data () {
return { const { t } = useI18n();
credentials: {
user: '', const { trapRef } = useFocusTrap();
password: ''
} const credentials = ref({
}; user: '',
}, password: ''
created () { });
setTimeout(() => { const firstInput: Ref<HTMLInputElement> = ref(null);
this.$refs.firstInput.focus(); const emit = defineEmits(['close-asking', 'credentials']);
}, 20);
}, const closeModal = () => {
methods: { emit('close-asking');
closeModal () {
this.$emit('close-asking');
},
sendCredentials () {
this.$emit('credentials', this.credentials);
}
}
}; };
const sendCredentials = () => {
emit('credentials', credentials.value);
};
setTimeout(() => {
firstInput.value.focus();
}, 20);
</script> </script>

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>
@@ -47,83 +47,76 @@
</ConfirmModal> </ConfirmModal>
</template> </template>
<script> <script setup lang="ts">
import { computed, PropType, Ref, ref } from 'vue';
import { NUMBER, FLOAT } from 'common/fieldTypes'; import { NUMBER, FLOAT } from 'common/fieldTypes';
import ConfirmModal from '@/components/BaseConfirmModal'; import { FunctionInfos, RoutineInfos } from 'common/interfaces/antares';
import ConfirmModal from '@/components/BaseConfirmModal.vue';
import { useFilters } from '@/composables/useFilters';
import { useI18n } from 'vue-i18n';
export default { const { t } = useI18n();
name: 'ModalAskParameters',
components: {
ConfirmModal
},
props: {
localRoutine: Object,
client: String
},
emits: ['confirm', 'close'],
data () {
return {
values: {}
};
},
computed: {
inParameters () {
return this.localRoutine.parameters.filter(param => param.context === 'IN');
}
},
created () {
window.addEventListener('keydown', this.onKey);
setTimeout(() => { const { wrapNumber } = useFilters();
this.$refs.firstInput[0].focus();
}, 20);
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
typeClass (type) {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return '';
},
runRoutine () {
const valArr = Object.keys(this.values).reduce((acc, curr, i) => {
let qc;
switch (this.client) {
case 'maria':
case 'mysql':
qc = '"';
break;
case 'pg':
qc = '\'';
break;
default:
qc = '"';
}
const param = this.localRoutine.parameters.find(param => `${i}-${param.name}` === curr); const props = defineProps({
localRoutine: Object as PropType<RoutineInfos | FunctionInfos>,
client: String
});
const value = [...NUMBER, ...FLOAT].includes(param.type) ? this.values[curr] : `${qc}${this.values[curr]}${qc}`; const emit = defineEmits(['confirm', 'close']);
acc.push(value);
return acc; const firstInput: Ref<HTMLInputElement[]> = ref(null);
}, []); const values: Ref<{[key: string]: string}> = ref({});
this.$emit('confirm', valArr);
}, const inParameters = computed(() => {
closeModal () { return props.localRoutine.parameters.filter(param => param.context === 'IN');
this.$emit('close'); });
},
onKey (e) { const typeClass = (type: string) => {
e.stopPropagation(); if (type)
if (e.key === 'Escape') return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
this.closeModal(); return '';
},
wrapNumber (num) {
if (!num) return '';
return `(${num})`;
}
}
}; };
const runRoutine = () => {
const valArr = Object.keys(values.value).reduce((acc, curr, i) => {
let qc;
switch (props.client) {
case 'maria':
case 'mysql':
qc = '"';
break;
case 'pg':
qc = '\'';
break;
default:
qc = '"';
}
const param = props.localRoutine.parameters.find(param => `${i}-${param.name}` === curr);
const value = [...NUMBER, ...FLOAT].includes(param.type) ? values.value[curr] : `${qc}${values.value[curr]}${qc}`;
acc.push(value);
return acc;
}, []);
emit('confirm', valArr);
};
const closeModal = () => emit('close');
const onKey = (e: KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape')
closeModal();
};
window.addEventListener('keydown', onKey);
setTimeout(() => {
firstInput.value[0].focus();
}, 20);
</script> </script>
<style scoped> <style scoped>

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.editConnectionAppearence') }}</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="editFolderAppearence">
{{ 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 editFolderAppearence = () => {
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,46 +1,43 @@
<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>
</template> </template>
<script> <script setup lang="ts">
import ConfirmModal from '@/components/BaseConfirmModal'; import ConfirmModal from '@/components/BaseConfirmModal.vue';
import { onBeforeUnmount } from 'vue';
import { useI18n } from 'vue-i18n';
export default { const { t } = useI18n();
name: 'ModalDiscardChanges',
components: { const emit = defineEmits(['confirm', 'close']);
ConfirmModal
}, const onKey = (e: KeyboardEvent) => {
emits: ['confirm', 'close'], e.stopPropagation();
created () { if (e.key === 'Escape')
window.addEventListener('keydown', this.onKey); emit('close');
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
onKey (e) {
e.stopPropagation();
if (e.key === 'Escape')
this.closeModal();
}
}
}; };
window.addEventListener('keydown', onKey);
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script> </script>
<style scoped> <style scoped>

View File

@@ -2,12 +2,12 @@
<Teleport to="#window-content"> <Teleport to="#window-content">
<div class="modal active"> <div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" /> <a class="modal-overlay" @click.stop="closeModal" />
<div class="modal-container p-0"> <div ref="trapRef" class="modal-container p-0">
<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-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>
@@ -62,119 +63,108 @@
</Teleport> </Teleport>
</template> </template>
<script> <script setup lang="ts">
import { computed, onBeforeUnmount, Ref, ref } from 'vue';
import { storeToRefs } from 'pinia'; 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 { 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';
export default { const { t } = useI18n();
name: 'ModalEditSchema',
components: {
BaseSelect
},
props: {
selectedSchema: String
},
emits: ['close'],
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const props = defineProps({
selectedSchema: String
});
const { getWorkspace, getDatabaseVariable } = workspacesStore; const emit = defineEmits(['close']);
return { const { addNotification } = useNotificationsStore();
addNotification, const workspacesStore = useWorkspacesStore();
selectedWorkspace,
getWorkspace, const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
getDatabaseVariable
}; const { getWorkspace, getDatabaseVariable } = workspacesStore;
},
data () { const { trapRef } = useFocusTrap();
return {
database: { const firstInput: Ref<HTMLInputElement> = ref(null);
name: '', const database = ref({
prevName: '', name: '',
collation: '' prevName: '',
} collation: '',
}; prevCollation: null
}, });
computed: {
collations () { const collations = computed(() => getWorkspace(selectedWorkspace.value).collations);
return this.getWorkspace(this.selectedWorkspace).collations; const defaultCollation = computed(() => (getDatabaseVariable(selectedWorkspace.value, 'collation_server').value || ''));
},
defaultCollation () { const updateSchema = async () => {
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value || ''; if (database.value.collation !== database.value.prevCollation) {
}
},
async created () {
let actualCollation;
try { try {
const { status, response } = await Schema.getDatabaseCollation({ uid: this.selectedWorkspace, database: this.selectedSchema }); const { status, response } = await Schema.updateSchema({
uid: selectedWorkspace.value,
...database.value
});
if (status === 'success') if (status === 'success')
actualCollation = response; closeModal();
else else
this.addNotification({ status: 'error', message: response }); addNotification({ status: 'error', message: response });
} }
catch (err) { catch (err) {
this.addNotification({ status: 'error', message: err.stack }); addNotification({ status: 'error', message: err.stack });
}
this.database = {
name: this.selectedSchema,
prevName: this.selectedSchema,
collation: actualCollation || this.defaultCollation,
prevCollation: actualCollation || this.defaultCollation
};
window.addEventListener('keydown', this.onKey);
setTimeout(() => {
this.$refs.firstInput.focus();
}, 20);
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
async updateSchema () {
if (this.database.collation !== this.database.prevCollation) {
try {
const { status, response } = await Schema.updateSchema({
uid: this.selectedWorkspace,
...this.database
});
if (status === 'success')
this.closeModal();
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
}
else
this.closeModal();
},
closeModal () {
this.$emit('close');
},
onKey (e) {
e.stopPropagation();
if (e.key === 'Escape')
this.closeModal();
} }
} }
else closeModal();
}; };
const closeModal = () => emit('close');
const onKey =(e: KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape')
closeModal();
};
(async () => {
let actualCollation;
try {
const { status, response } = await Schema.getDatabaseCollation({ uid: selectedWorkspace.value, database: props.selectedSchema });
if (status === 'success')
actualCollation = response;
else
addNotification({ status: 'error', message: response });
}
catch (err) {
addNotification({ status: 'error', message: err.stack });
}
database.value = {
name: props.selectedSchema,
prevName: props.selectedSchema,
collation: actualCollation || defaultCollation.value,
prevCollation: actualCollation || defaultCollation.value
};
window.addEventListener('keydown', onKey);
setTimeout(() => {
firstInput.value.focus();
}, 20);
})();
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script> </script>
<style scoped> <style scoped lang="scss">
.modal-container { .modal-container {
max-width: 360px; max-width: 360px;
} }

View File

@@ -2,12 +2,12 @@
<Teleport to="#window-content"> <Teleport to="#window-content">
<div class="modal active"> <div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" /> <a class="modal-overlay" @click.stop="closeModal" />
<div class="modal-container p-0"> <div ref="trapRef" class="modal-container p-0">
<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-arrow-down 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"
> >
@@ -122,22 +122,22 @@
<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>
@@ -146,7 +146,7 @@
<div class="tbody"> <div class="tbody">
<div <div
v-for="item in tables" v-for="item in tables"
:key="item.name" :key="item.table"
class="tr" class="tr"
> >
<div class="td"> <div class="td">
@@ -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="customizations.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.newInserStmtEvery') }}:
</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.ourputFormat') }}:
</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>
@@ -263,211 +263,213 @@
</Teleport> </Teleport>
</template> </template>
<script> <script setup lang="ts">
import moment from 'moment'; import { computed, onBeforeUnmount, Ref, ref } from 'vue';
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 { ClientCode, SchemaInfos } from 'common/interfaces/antares';
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 customizations from 'common/customizations'; import { useFocusTrap } from '@/composables/useFocusTrap';
import Application from '@/ipc-api/Application'; import Application from '@/ipc-api/Application';
import Schema from '@/ipc-api/Schema'; import Schema from '@/ipc-api/Schema';
import { Customizations } from 'common/interfaces/customizations';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
export default { const props = defineProps({
name: 'ModalExportSchema', selectedSchema: String
components: { });
BaseSelect
},
props: {
selectedSchema: String
},
emits: ['close'],
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const emit = defineEmits(['close']);
const { t } = useI18n();
const { const { addNotification } = useNotificationsStore();
getWorkspace, const workspacesStore = useWorkspacesStore();
getDatabaseVariable,
refreshSchema
} = workspacesStore;
return { const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
addNotification,
selectedWorkspace,
getWorkspace,
getDatabaseVariable,
refreshSchema
};
},
data () {
return {
isExporting: false,
isRefreshing: false,
progressPercentage: 0,
progressStatus: '',
tables: [],
options: {
includes: {},
outputFormat: 'sql',
sqlInsertAfter: 250,
sqlInsertDivider: 'bytes'
},
basePath: ''
};
},
computed: {
currentWorkspace () {
return this.getWorkspace(this.selectedWorkspace);
},
customizations () {
return this.currentWorkspace.customizations;
},
schemaItems () {
const db = this.currentWorkspace.structure.find(db => db.name === this.selectedSchema);
if (db)
return db.tables.filter(table => table.type === 'table');
return []; const { trapRef } = useFocusTrap();
},
filename () {
const date = moment().format('YYYY-MM-DD');
return `${this.selectedSchema}_${date}.${this.options.outputFormat}`;
},
dumpFilePath () {
return `${this.basePath}/${this.filename}`;
},
includeStructureStatus () {
if (this.tables.every(item => item.includeStructure)) return 1;
else if (this.tables.some(item => item.includeStructure)) return 2;
else return 0;
},
includeContentStatus () {
if (this.tables.every(item => item.includeContent)) return 1;
else if (this.tables.some(item => item.includeContent)) return 2;
else return 0;
},
includeDropStatementStatus () {
if (this.tables.every(item => item.includeDropStatement)) return 1;
else if (this.tables.some(item => item.includeDropStatement)) return 2;
else return 0;
}
},
async created () {
if (!this.schemaItems.length) await this.refresh();
window.addEventListener('keydown', this.onKey); const {
getWorkspace,
refreshSchema
} = workspacesStore;
this.basePath = await Application.getDownloadPathDirectory(); const isExporting = ref(false);
this.tables = this.schemaItems.map(item => ({ const isRefreshing = ref(false);
table: item.name, const progressPercentage = ref(0);
includeStructure: true, const progressStatus = ref('');
includeContent: true, const tables: Ref<{
includeDropStatement: true table: string;
})); includeStructure: boolean;
includeContent: boolean;
includeDropStatement: boolean;
}[]> = ref([]);
const options: Ref<Partial<ExportOptions>> = ref({
schema: props.selectedSchema,
includes: {} as {[key: string]: boolean},
outputFormat: 'sql' as 'sql' | 'sql.zip',
sqlInsertAfter: 250,
sqlInsertDivider: 'bytes' as 'bytes' | 'rows'
});
const basePath = ref('');
const structure = ['functions', 'views', 'triggers', 'routines', 'schedulers']; const currentWorkspace = computed(() => getWorkspace(selectedWorkspace.value));
const clientCustoms: Ref<Customizations> = computed(() => currentWorkspace.value.customizations);
const schemaItems = computed(() => {
const db: SchemaInfos = currentWorkspace.value.structure.find((db: SchemaInfos) => db.name === props.selectedSchema);
if (db)
return db.tables.filter(table => table.type === 'table');
structure.forEach(feat => { return [];
const val = customizations[this.currentWorkspace.client][feat]; });
if (val) const filename = computed(() => {
this.options.includes[feat] = true; const date = moment().format('YYYY-MM-DD');
}); return `${props.selectedSchema}_${date}.${options.value.outputFormat}`;
});
const dumpFilePath = computed(() => `${basePath.value}/${filename.value}`);
const includeStructureStatus = computed(() => {
if (tables.value.every(item => item.includeStructure)) return 1;
else if (tables.value.some(item => item.includeStructure)) return 2;
else return 0;
});
const includeContentStatus = computed(() => {
if (tables.value.every(item => item.includeContent)) return 1;
else if (tables.value.some(item => item.includeContent)) return 2;
else return 0;
});
const includeDropStatementStatus = computed(() => {
if (tables.value.every(item => item.includeDropStatement)) return 1;
else if (tables.value.some(item => item.includeDropStatement)) return 2;
else return 0;
});
ipcRenderer.on('export-progress', this.updateProgress); const startExport = async () => {
}, isExporting.value = true;
beforeUnmount () { const { uid, client } = currentWorkspace.value;
window.removeEventListener('keydown', this.onKey); const params = {
ipcRenderer.off('export-progress', this.updateProgress); uid,
}, type: client,
methods: { schema: props.selectedSchema,
async startExport () { outputFile: dumpFilePath.value,
this.isExporting = true; tables: [...tables.value],
const { uid, client } = this.currentWorkspace; ...options.value
const params = { } as ExportOptions & { uid: string; type: ClientCode };
uid,
type: client,
schema: this.selectedSchema,
outputFile: this.dumpFilePath,
tables: [...this.tables],
...this.options
};
try { try {
const { status, response } = await Schema.export(params); const { status, response } = await Schema.export(params);
if (status === 'success') if (status === 'success')
this.progressStatus = response.cancelled ? this.$t('word.aborted') : this.$t('word.completed'); progressStatus.value = response.cancelled ? t('word.aborted') : t('word.completed');
else { else {
this.progressStatus = response; progressStatus.value = response;
this.addNotification({ status: 'error', message: response }); addNotification({ status: 'error', message: response });
}
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isExporting = false;
},
updateProgress (event, state) {
this.progressPercentage = Number((state.currentItemIndex / state.totalItems * 100).toFixed(1));
switch (state.op) {
case 'PROCESSING':
this.progressStatus = this.$t('message.processingTableExport', { table: state.currentItem });
break;
case 'FETCH':
this.progressStatus = this.$t('message.fechingTableExport', { table: state.currentItem });
break;
case 'WRITE':
this.progressStatus = this.$t('message.writingTableExport', { table: state.currentItem });
break;
}
},
async closeModal () {
let willClose = true;
if (this.isExporting) {
willClose = false;
const { response } = await Schema.abortExport();
willClose = response.willAbort;
}
if (willClose)
this.$emit('close');
},
onKey (e) {
e.stopPropagation();
if (e.key === 'Escape')
this.closeModal();
},
checkAllTables () {
this.tables = this.tables.map(item => ({ ...item, includeStructure: true, includeContent: true, includeDropStatement: true }));
},
uncheckAllTables () {
this.tables = this.tables.map(item => ({ ...item, includeStructure: false, includeContent: false, includeDropStatement: false }));
},
toggleAllTablesOption (option) {
const options = ['includeStructure', 'includeContent', 'includeDropStatement'];
if (!options.includes(option)) return;
if (this[`${option}Status`] !== 1)
this.tables = this.tables.map(item => ({ ...item, [option]: true }));
else
this.tables = this.tables.map(item => ({ ...item, [option]: false }));
},
async refresh () {
this.isRefreshing = true;
await this.refreshSchema({ uid: this.currentWorkspace.uid, schema: this.selectedSchema });
this.isRefreshing = false;
},
async openPathDialog () {
const result = await Application.showOpenDialog({ properties: ['openDirectory'] });
if (result && !result.canceled)
this.basePath = result.filePaths[0];
} }
} }
catch (err) {
addNotification({ status: 'error', message: err.stack });
}
isExporting.value = false;
}; };
const updateProgress = (event: Event, state: ExportState) => {
progressPercentage.value = Number((state.currentItemIndex / state.totalItems * 100).toFixed(1));
switch (state.op) {
case 'PROCESSING':
progressStatus.value = t('message.processingTableExport', { table: state.currentItem });
break;
case 'FETCH':
progressStatus.value = t('message.fechingTableExport', { table: state.currentItem });
break;
case 'WRITE':
progressStatus.value = t('message.writingTableExport', { table: state.currentItem });
break;
}
};
const closeModal = async () => {
let willClose = true;
if (isExporting.value) {
willClose = false;
const { response } = await Schema.abortExport();
willClose = response.willAbort;
}
if (willClose)
emit('close');
};
const onKey = (e: KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape')
closeModal();
};
const checkAllTables = () => {
tables.value = tables.value.map(item => ({ ...item, includeStructure: true, includeContent: true, includeDropStatement: true }));
};
const uncheckAllTables = () => {
tables.value = tables.value.map(item => ({ ...item, includeStructure: false, includeContent: false, includeDropStatement: false }));
};
const toggleAllTablesOption = (option: 'includeStructure' | 'includeContent' |'includeDropStatement') => {
const options = {
includeStructure: includeStructureStatus.value,
includeContent: includeContentStatus.value,
includeDropStatement: includeDropStatementStatus.value
};
if (options[option] !== 1)
tables.value = tables.value.map(item => ({ ...item, [option]: true }));
else
tables.value = tables.value.map(item => ({ ...item, [option]: false }));
};
const refresh = async () => {
isRefreshing.value = true;
await refreshSchema({ uid: currentWorkspace.value.uid, schema: props.selectedSchema });
isRefreshing.value = false;
};
const openPathDialog = async () => {
const result = await Application.showOpenDialog({ properties: ['openDirectory'] });
if (result && !result.canceled)
basePath.value = result.filePaths[0];
};
(async () => {
if (!schemaItems.value.length) await refresh();
window.addEventListener('keydown', onKey);
basePath.value = await Application.getDownloadPathDirectory();
tables.value = schemaItems.value.map(item => ({
table: item.name,
includeStructure: true,
includeContent: true,
includeDropStatement: true
}));
const structure = ['functions', 'views', 'triggers', 'routines', 'schedulers'];
structure.forEach((feat: keyof Customizations) => {
const val = clientCustoms.value[feat];
if (val)
options.value.includes[feat] = true;
});
ipcRenderer.on('export-progress', updateProgress);
})();
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
ipcRenderer.off('export-progress', updateProgress);
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -476,14 +478,15 @@ export default {
overflow: hidden; overflow: hidden;
.left { .left {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1; flex: 1;
} }
} }
.workspace-query-results { .workspace-query-results {
flex: 1 0 1px; flex: 1 0 1px;
.table { .table {
width: 100% !important; width: 100% !important;
} }
@@ -499,25 +502,24 @@ export default {
} }
.modal { .modal {
.modal-container { .modal-container {
max-width: 800px; max-width: 800px;
} }
.modal-body { .modal-body {
max-height: 60vh; max-height: 60vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.modal-footer { .modal-footer {
display: flex; display: flex;
} }
} }
.progress-status { .progress-status {
font-style: italic; font-style: italic;
font-size: 80%; font-size: 80%;
} }
</style> </style>

View File

@@ -2,12 +2,12 @@
<Teleport to="#window-content"> <Teleport to="#window-content">
<div class="modal active"> <div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" /> <a class="modal-overlay" @click.stop="closeModal" />
<div class="modal-container p-0"> <div ref="trapRef" class="modal-container p-0">
<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-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>
@@ -97,126 +97,198 @@
</Teleport> </Teleport>
</template> </template>
<script> <script setup lang="ts">
import moment from 'moment'; import { computed, onBeforeMount, onMounted, Prop, Ref, ref, watch } from 'vue';
import * as moment from 'moment';
import { TableField, TableForeign } from 'common/interfaces/antares';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
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 } from 'common/fieldTypes';
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 Tables from '@/ipc-api/Tables'; import Tables from '@/ipc-api/Tables';
import FakerSelect from '@/components/FakerSelect'; 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';
export default { const { t } = useI18n();
name: 'ModalFakerRows',
components: {
FakerSelect,
BaseSelect
},
props: {
tabUid: [String, Number],
fields: Array,
keyUsage: Array
},
emits: ['reload', 'hide'],
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { wrapNumber } = useFilters();
const { getWorkspace, getWorkspaceTab } = workspacesStore;
const locales = [
{ value: 'ar', label: 'Arabic' },
{ value: 'az', label: 'Azerbaijani' },
{ value: 'zh_CN', label: 'Chinese' },
{ value: 'zh_TW', label: 'Chinese (Taiwan)' },
{ value: 'cz', label: 'Czech' },
{ value: 'nl', label: 'Dutch' },
{ value: 'nl_BE', label: 'Dutch (Belgium)' },
{ value: 'en', label: 'English' },
{ value: 'en_AU_ocker', label: 'English (Australia Ocker)' },
{ value: 'en_AU', label: 'English (Australia)' },
{ value: 'en_BORK', label: 'English (Bork)' },
{ value: 'en_CA', label: 'English (Canada)' },
{ value: 'en_GB', label: 'English (Great Britain)' },
{ value: 'en_IND', label: 'English (India)' },
{ value: 'en_IE', label: 'English (Ireland)' },
{ value: 'en_ZA', label: 'English (South Africa)' },
{ value: 'en_US', label: 'English (United States)' },
{ value: 'fa', label: 'Farsi' },
{ value: 'fi', label: 'Finnish' },
{ value: 'fr', label: 'French' },
{ value: 'fr_CA', label: 'French (Canada)' },
{ value: 'fr_CH', label: 'French (Switzerland)' },
{ value: 'ge', label: 'Georgian' },
{ value: 'de', label: 'German' },
{ value: 'de_AT', label: 'German (Austria)' },
{ value: 'de_CH', label: 'German (Switzerland)' },
{ value: 'hr', label: 'Hrvatski' },
{ value: 'id_ID', label: 'Indonesia' },
{ value: 'it', label: 'Italian' },
{ value: 'ja', label: 'Japanese' },
{ value: 'ko', label: 'Korean' },
{ value: 'nep', label: 'Nepalese' },
{ value: 'nb_NO', label: 'Norwegian' },
{ value: 'pl', label: 'Polish' },
{ value: 'pt_BR', label: 'Portuguese (Brazil)' },
{ value: 'pt_PT', label: 'Portuguese (Portugal)' },
{ value: 'ro', label: 'Romanian' },
{ value: 'ru', label: 'Russian' },
{ value: 'sk', label: 'Slovakian' },
{ value: 'es', label: 'Spanish' },
{ value: 'es_MX', label: 'Spanish (Mexico)' },
{ value: 'sv', label: 'Swedish' },
{ value: 'tr', label: 'Turkish' },
{ value: 'uk', label: 'Ukrainian' },
{ value: 'vi', label: 'Vietnamese' }
]; const props = defineProps({
tabUid: [String, Number],
schema: String,
table: String,
fields: Array as Prop<TableField[]>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
rowToDuplicate: Object as Prop<any>,
keyUsage: Array as Prop<TableForeign[]>
});
return { const emit = defineEmits(['reload', 'hide']);
addNotification,
selectedWorkspace, const { addNotification } = useNotificationsStore();
getWorkspace, const workspacesStore = useWorkspacesStore();
getWorkspaceTab,
locales const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
};
}, const { trapRef } = useFocusTrap({ disableAutofocus: true });
data () {
return { // eslint-disable-next-line @typescript-eslint/no-explicit-any
localRow: {}, const localRow: Ref<{[key: string]: any}> = ref({});
fieldsToExclude: [], const fieldsToExclude = ref([]);
nInserts: 1, const nInserts = ref(1);
isInserting: false, const isInserting = ref(false);
fakerLocale: 'en' const fakerLocale = ref('en');
};
}, const foreignKeys = computed(() => props.keyUsage.map(key => key.field));
computed: { const hasFakes = computed(() => Object.keys(localRow.value).some(field => 'group' in localRow.value[field] && localRow.value[field].group !== 'manual'));
workspace () {
return this.getWorkspace(this.selectedWorkspace); const locales = [
}, { value: 'ar', label: 'Arabic' },
foreignKeys () { { value: 'az', label: 'Azerbaijani' },
return this.keyUsage.map(key => key.field); { value: 'zh_CN', label: 'Chinese' },
}, { value: 'zh_TW', label: 'Chinese (Taiwan)' },
hasFakes () { { value: 'cz', label: 'Czech' },
return Object.keys(this.localRow).some(field => 'group' in this.localRow[field] && this.localRow[field].group !== 'manual'); { value: 'nl', label: 'Dutch' },
{ value: 'nl_BE', label: 'Dutch (Belgium)' },
{ value: 'en', label: 'English' },
{ value: 'en_AU_ocker', label: 'English (Australia Ocker)' },
{ value: 'en_AU', label: 'English (Australia)' },
{ value: 'en_BORK', label: 'English (Bork)' },
{ value: 'en_CA', label: 'English (Canada)' },
{ value: 'en_GB', label: 'English (Great Britain)' },
{ value: 'en_IND', label: 'English (India)' },
{ value: 'en_IE', label: 'English (Ireland)' },
{ value: 'en_ZA', label: 'English (South Africa)' },
{ value: 'en_US', label: 'English (United States)' },
{ value: 'fa', label: 'Farsi' },
{ value: 'fi', label: 'Finnish' },
{ value: 'fr', label: 'French' },
{ value: 'fr_CA', label: 'French (Canada)' },
{ value: 'fr_CH', label: 'French (Switzerland)' },
{ value: 'ge', label: 'Georgian' },
{ value: 'de', label: 'German' },
{ value: 'de_AT', label: 'German (Austria)' },
{ value: 'de_CH', label: 'German (Switzerland)' },
{ value: 'hr', label: 'Hrvatski' },
{ value: 'id_ID', label: 'Indonesia' },
{ value: 'it', label: 'Italian' },
{ value: 'ja', label: 'Japanese' },
{ value: 'ko', label: 'Korean' },
{ value: 'nep', label: 'Nepalese' },
{ value: 'nb_NO', label: 'Norwegian' },
{ value: 'pl', label: 'Polish' },
{ value: 'pt_BR', label: 'Portuguese (Brazil)' },
{ value: 'pt_PT', label: 'Portuguese (Portugal)' },
{ value: 'ro', label: 'Romanian' },
{ value: 'ru', label: 'Russian' },
{ value: 'sk', label: 'Slovakian' },
{ value: 'es', label: 'Spanish' },
{ value: 'es_MX', label: 'Spanish (Mexico)' },
{ value: 'sv', label: 'Swedish' },
{ value: 'tr', label: 'Turkish' },
{ value: 'uk', label: 'Ukrainian' },
{ value: 'vi', label: 'Vietnamese' }
];
watch(nInserts, (val) => {
if (!val || val < 1)
nInserts.value = 1;
else if (val > 1000)
nInserts.value = 1000;
});
const typeClass = (type: string) => {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return '';
};
const insertRows = async () => {
isInserting.value = true;
const rowToInsert = localRow.value;
Object.keys(rowToInsert).forEach(key => {
if (fieldsToExclude.value.includes(key))
delete rowToInsert[key];
if (typeof rowToInsert[key] === 'undefined')
delete rowToInsert[key];
});
const fieldTypes: {[key: string]: string} = {};
props.fields.forEach(field => {
fieldTypes[field.name] = field.type;
});
try {
const { status, response } = await Tables.insertTableFakeRows({
uid: selectedWorkspace.value,
schema: props.schema,
table: props.table,
row: rowToInsert,
repeat: nInserts.value,
fields: fieldTypes,
locale: fakerLocale.value
});
if (status === 'success') {
closeModal();
emit('reload');
} }
}, else
watch: { addNotification({ status: 'error', message: response });
nInserts (val) { }
if (!val || val < 1) catch (err) {
this.nInserts = 1; addNotification({ status: 'error', message: err.stack });
else if (val > 1000) }
this.nInserts = 1000;
}
},
created () {
window.addEventListener('keydown', this.onKey);
},
mounted () {
const rowObj = {};
for (const field of this.fields) { isInserting.value = false;
};
const closeModal = () => {
emit('hide');
};
const fieldLength = (field: TableField) => {
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
else if (TEXT.includes(field.type)) return Number(field.charLength);
return Number(field.length);
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const toggleFields = (event: any, field: TableField) => {
if (event.target.checked)
fieldsToExclude.value = fieldsToExclude.value.filter(f => f !== field.name);
else
fieldsToExclude.value = [...fieldsToExclude.value, field.name];
};
const onKey = (e: KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape')
closeModal();
};
window.addEventListener('keydown', onKey);
onMounted(() => {
setTimeout(() => {
const inputs = Array.from(document.querySelectorAll<HTMLInputElement>('.modal-container .form-input'));
if (inputs?.length) {
const firstEnabledInput = inputs.find((el) => !el.disabled);
firstEnabledInput?.focus();
}
}, 50);
const rowObj: {[key: string]: unknown} = {};
if (!props.rowToDuplicate) {
// Set default values
for (const field of props.fields) {
let fieldDefault; let fieldDefault;
if (field.default === 'NULL') fieldDefault = null; if (field.default === 'NULL') fieldDefault = null;
@@ -253,95 +325,26 @@ export default {
rowObj[field.name] = { value: fieldDefault }; rowObj[field.name] = { value: fieldDefault };
if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields
this.fieldsToExclude = [...this.fieldsToExclude, field.name]; fieldsToExclude.value = [...fieldsToExclude.value, field.name];
}
this.localRow = { ...rowObj };
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
typeClass (type) {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return '';
},
async insertRows () {
this.isInserting = true;
const rowToInsert = this.localRow;
Object.keys(rowToInsert).forEach(key => {
if (this.fieldsToExclude.includes(key))
delete rowToInsert[key];
if (typeof rowToInsert[key] === 'undefined')
delete rowToInsert[key];
});
const fieldTypes = {};
this.fields.forEach(field => {
fieldTypes[field.name] = field.type;
});
try {
const { status, response } = await Tables.insertTableFakeRows({
uid: this.selectedWorkspace,
schema: this.workspace.breadcrumbs.schema,
table: this.workspace.breadcrumbs.table,
row: rowToInsert,
repeat: this.nInserts,
fields: fieldTypes,
locale: this.fakerLocale
});
if (status === 'success') {
this.closeModal();
this.$emit('reload');
}
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isInserting = false;
},
closeModal () {
this.$emit('hide');
},
fieldLength (field) {
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
else if (TEXT.includes(field.type)) return Number(field.charLength);
return Number(field.length);
},
toggleFields (event, field) {
if (event.target.checked)
this.fieldsToExclude = this.fieldsToExclude.filter(f => f !== field.name);
else
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
},
filesChange (event, field) {
const { files } = event.target;
if (!files.length) return;
this.localRow[field] = files[0].path;
},
getKeyUsage (keyName) {
return this.keyUsage.find(key => key.field === keyName);
},
onKey (e) {
e.stopPropagation();
if (e.key === 'Escape')
this.closeModal();
},
wrapNumber (num) {
if (!num) return '';
return `(${num})`;
} }
} }
}; 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] };
if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields
fieldsToExclude.value = [...fieldsToExclude.value, field.name];
}
}
localRow.value = { ...rowObj };
});
onBeforeMount(() => {
window.removeEventListener('keydown', onKey);
});
</script> </script>
<style scoped> <style scoped>

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