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

Compare commits

...

167 Commits

Author SHA1 Message Date
7a66c11868 chore(release): 0.7.31-beta.1 2025-01-22 10:56:01 +01:00
8544bb5378 refactor: reorder import statements in sqlUtils.ts for consistency 2025-01-22 10:53:58 +01:00
6709a75298 Merge pull request #921 from curiouslad/develop
Zoom in/out and fullscreen shortcuts
2025-01-20 09:07:08 +01:00
f25f6659d5 refactor: enhancement of new shortcuts implementation 2025-01-17 13:45:46 +01:00
8d0ff4953e Merge pull request #922 from jimcat8/cn_trans
Update localization
2025-01-16 09:45:33 +01:00
tianci
fbe28f0ff0 Update localization 2025-01-16 12:22:07 +08:00
0d8bcf5cd6 Merge pull request #920 from dyaskur/develop
fix: Cannot update column value with composite primary key and JSON column #916
2025-01-15 09:12:26 +01:00
mladen
47ac729d2f feat: zoom in/out and fullscreen shortcuts 2025-01-14 05:24:33 +01:00
️Yaskur Dyas⚔⚔️⚔
450c4c47f3 refactor: improve update cell condition and move whereJson formatter to sqlUtils 2025-01-14 05:44:12 +07:00
️Yaskur Dyas⚔⚔️⚔
110dcd335a fix: cannot update on JSON column in MariaDB and PostgreSQL 2025-01-14 05:15:46 +07:00
️Yaskur Dyas⚔⚔️⚔
0029967619 fix: cannot update column value with composite primary key and JSON column, fixes #916 2025-01-14 04:44:29 +07:00
34848e8dc3 Merge branch 'develop' of https://github.com/antares-sql/antares into develop 2025-01-13 10:23:23 +01:00
c32add76e8 Merge pull request #919 from dyaskur/develop
fix: fail to duplicate JSON row
2025-01-13 10:23:17 +01:00
️Yaskur Dyas⚔⚔️⚔
507dc7d55b fix: fail to duplicate JSON row 2025-01-12 10:30:31 +07:00
4a2b5926f4 fix: saved connections lost opening a second window after first app run 2025-01-10 18:20:36 +01:00
ed90b12a7b Merge pull request #918 from antares-sql/all-contributors/add-JoseGonzalez84
docs: add JoseGonzalez84 as a contributor for translation
2025-01-10 08:46:59 +01:00
allcontributors[bot]
00ce76a12e docs: update .all-contributorsrc [skip ci] 2025-01-10 07:46:47 +00:00
allcontributors[bot]
77b3a8a354 docs: update README.md [skip ci] 2025-01-10 07:46:46 +00:00
d3ae45ec94 perf(translation): update spanish translation 2025-01-10 08:45:58 +01:00
ad4478a822 Merge pull request #917 from antares-sql/all-contributors/add-salvymc
docs: add salvymc as a contributor for code
2025-01-09 16:50:59 +01:00
allcontributors[bot]
ba5dd9ff15 docs: update .all-contributorsrc [skip ci] 2025-01-09 15:50:45 +00:00
allcontributors[bot]
5aab824fe9 docs: update README.md [skip ci] 2025-01-09 15:50:44 +00:00
87ab58c50f Merge pull request #912 from salvymc/patch-1
Update WorkspaceExploreBarSchema.vue - Changed search to not be case sensitive
2025-01-09 16:48:37 +01:00
e986f287c6 chore(release): 0.7.31-beta.0 2025-01-06 11:11:23 +01:00
Salvatore Forino
39a30e48dd Update WorkspaceExploreBarSchema.vue
Changed search to not be case sensitive
2025-01-03 13:09:15 +01:00
46165d2f4f Merge pull request #910 from antares-sql/all-contributors/add-r4f4dev
docs: add r4f4dev as a contributor for translation
2024-12-28 23:46:00 +01:00
allcontributors[bot]
d0e56e4eb6 docs: update .all-contributorsrc [skip ci] 2024-12-28 22:45:32 +00:00
allcontributors[bot]
c803c072d1 docs: update README.md [skip ci] 2024-12-28 22:45:31 +00:00
232211811b Merge pull request #909 from r4f4dev/feat/locale-uzbek
feat(language): add uzbek language support
2024-12-28 15:22:23 +01:00
r4f4dev
fb9c258cc1 feat(language): add uzbek language support 2024-12-28 19:15:41 +05:00
8de99dae7b fix: prevent delete confirmation modal from triggering on non-delete key presses, fixes #906 2024-12-24 11:03:27 +01:00
2bd69c6263 refactor: removed software-side sorting logic, fixes #904 2024-12-24 09:54:47 +01:00
4d1a81033d chore(release): 0.7.30 2024-12-04 18:14:31 +01:00
5887eea2ed Merge branch 'master' of https://github.com/antares-sql/antares 2024-12-04 18:14:11 +01:00
6c69583c90 Merge pull request #899 from antares-sql/all-contributors/add-carvalhods
docs: add carvalhods as a contributor for platform
2024-11-22 09:16:26 +01:00
allcontributors[bot]
03461522b7 docs: update .all-contributorsrc [skip ci] 2024-11-22 08:13:36 +00:00
allcontributors[bot]
11807e3bb6 docs: update README.md [skip ci] 2024-11-22 08:13:35 +00:00
a54b8d719c fix: issue saving queries as file 2024-11-21 13:28:33 +01:00
d1f68da495 chore(release): 0.7.30-beta.1 2024-11-15 14:27:20 +01:00
d666281daa Update README.md 2024-11-11 15:50:55 +01:00
481ae842dd Merge pull request #894 from leaked/master
Update README.md
2024-11-11 15:49:43 +01:00
Mohsen Nasiri
acf5d459e2 Update README.md 2024-11-10 02:47:52 +03:30
2ee9cfcf0b fix: missing support check for table check features 2024-11-08 18:12:02 +01:00
f0d312fb59 perf(PostgreSQL): improved support of connection strings, closes #893 2024-11-08 18:09:37 +01:00
c97ade949c chore(release): 0.7.30-beta.0 2024-10-25 18:46:27 +02:00
38af648440 refactor: ts fix 2024-10-25 18:44:49 +02:00
ccbcffc7f0 Merge pull request #890 from antares-sql/feat/mysql-check-support
Feat: MySQL check support
2024-10-25 18:34:06 +02:00
dfa7cf9905 perf: added more notifications in debug console 2024-10-25 18:32:07 +02:00
6365e07534 feat(MySQL): check constraints management support 2024-10-25 18:30:34 +02:00
24605d01e1 Merge pull request #887 from antares-sql/all-contributors/add-SawGoD
docs: add SawGoD as a contributor for translation
2024-10-22 15:53:15 +02:00
f083a8a185 Merge pull request #886 from SawGoD/master
feat: update ru-RU.ts file and update translation
2024-10-22 15:52:58 +02:00
allcontributors[bot]
f639bc7983 docs: update .all-contributorsrc [skip ci] 2024-10-22 13:26:01 +00:00
allcontributors[bot]
5e51997e5b docs: update README.md [skip ci] 2024-10-22 13:26:00 +00:00
SawGoD
1e3c9edb50 Update ru-RU.ts file and update translation 2024-10-22 11:41:36 +03:00
60e1e59505 fix: incorrect behavior sorting tables with numeric values 2024-10-18 18:24:46 +02:00
dba490f226 fix(MySQL): routines do not return results, fixes #885 2024-10-17 18:14:41 +02:00
b6c5dff15c fix: incorrect behavior in sorting tables with null/empty values, fixes #883 2024-10-17 18:12:34 +02:00
d2da8c2446 chore(release): 0.7.29 2024-10-14 09:40:47 +02:00
b54d2c9f5e chore(release): 0.7.29-beta.3 2024-10-08 18:38:02 +02:00
9a0ad80bb5 perf(MySQL): made some common errors related to corrupted tables non-blocking, closes #877 2024-10-08 18:34:29 +02:00
2f3f5de8d6 feat(translation): add hebrew translation, closes #878 2024-10-08 18:26:30 +02:00
b2c046fd38 Merge pull request #879 from antares-sql/all-contributors/add-LeviEyal
docs: add LeviEyal as a contributor for translation
2024-10-08 12:47:01 +02:00
allcontributors[bot]
bbc29a6335 docs: update .all-contributorsrc [skip ci] 2024-10-08 10:46:28 +00:00
allcontributors[bot]
b6c337638c docs: update README.md [skip ci] 2024-10-08 10:46:27 +00:00
2120a59d41 chore(release): 0.7.29-beta.2 2024-10-02 10:18:14 +02:00
2cda4a1fa1 fix(MySQL): missing exported values for DEFAULT_GENERATED table fields, fixes #854 2024-10-01 18:08:58 +02:00
76c8cd1beb Merge pull request #875 from mirrorb/master
fix(PostgreSQL): unable to change table comment to empty, error changing the comment for a specific table name
2024-09-30 18:12:02 +02:00
14aeebed9c feat(UI): new context menu and some minor improvements to query tabs, closes #867 2024-09-30 18:10:38 +02:00
mirrorb
1a1118452a Merge remote-tracking branch 'upstream/develop' 2024-09-30 11:22:46 +08:00
mirrorb
4b0f596405 Merge branch 'master' of https://github.com/antares-sql/antares 2024-09-30 11:05:02 +08:00
mirrorb
eb749f0f66 fix(PostgreSQL): error changing the comment for a specific table name 2024-09-30 11:02:26 +08:00
mirrorb
d78e59dd09 fix(PostgreSQL): unable to change table comment to empty 2024-09-30 10:57:19 +08:00
7969294a93 fix(MySQL): incorrect representation of the DATE if the year is prior to 1900, fixes #860 2024-09-29 13:50:22 +02:00
2ae016f0b6 chore(release): 0.7.29-beta.1 2024-09-28 15:50:38 +02:00
b4f33bc474 Merge branch 'develop' of https://github.com/antares-sql/antares into beta 2024-09-28 15:50:09 +02:00
f185463866 Merge branch 'master' of https://github.com/antares-sql/antares into develop 2024-09-28 15:46:48 +02:00
3fa0bd3cd1 Merge pull request #873 from antares-sql/all-contributors/add-mirrorb
docs: add mirrorb as a contributor for code
2024-09-28 15:46:16 +02:00
allcontributors[bot]
0d3ef39822 docs: update .all-contributorsrc [skip ci] 2024-09-28 13:46:03 +00:00
allcontributors[bot]
a02913f4e5 docs: update README.md [skip ci] 2024-09-28 13:46:02 +00:00
f9f993cbcd Merge pull request #872 from mirrorb/master
feat(PostgreSQL): table and field comments
2024-09-28 15:43:05 +02:00
mirrorb
ebd1a75445 feat(PostgreSQL): table and field comments 2024-09-27 17:43:54 +08:00
4201532081 Merge pull request #870 from antares-sql/all-contributors/add-zwei-c
docs: add zwei-c as a contributor for translation
2024-09-25 09:19:44 +02:00
allcontributors[bot]
c5cb586358 docs: update .all-contributorsrc [skip ci] 2024-09-25 07:19:18 +00:00
allcontributors[bot]
c111b2c0f5 docs: update README.md [skip ci] 2024-09-25 07:19:17 +00:00
b70ed124eb feat(translation): traditional chinese translation, closes #869 2024-09-25 09:18:37 +02:00
010147b553 chore(release): 0.7.29-beta.0 2024-09-23 09:12:15 +02:00
da8cc39157 fix: mismatch between table field columns and results with duplicate fields, fixes #848 2024-09-20 18:08:11 +02:00
37d44c95ee perf(UI): hide edit/delete functions in readonly mode 2024-09-18 16:42:21 +02:00
4df4c6197d Merge pull request #858 from Lawondyss/lawondyss
feat: update czech translation
2024-08-30 16:25:29 +02:00
Ladislav Vondráček
0506b653d7 feat: update czech translation 2024-08-30 15:22:19 +02:00
5fd9fe48a2 Merge pull request #853 from hatch01/master
build(deps): update various dependencies
2024-08-30 09:07:42 +02:00
b6a7124f33 feat: cancel button when waiting to connect database, closes #830 2024-08-28 18:05:04 +02:00
eymeric
5855ab0921 build(deps): update various dependencies 2024-08-26 22:15:47 +02:00
c2b602785a chore(release): 0.7.28 2024-08-20 17:48:59 +02:00
97279742e9 chore(release): 0.7.28-beta.0 2024-08-06 18:45:16 +02:00
6cb21ff792 fix: html tags searching in history or saved queries, fixes #847 2024-08-05 18:03:10 +02:00
72bacdeabf fix: disabled column sort during loadings 2024-08-05 18:02:24 +02:00
c434855879 fix(PostgreSQL): issue exporting tables with primary keys 2024-07-30 16:52:20 +02:00
ba0ffcc6f5 fix: wrong password message importing app data 2024-07-19 18:00:52 +02:00
8e7965a0f9 fix(PostgreSQL): wrong export formato of JSON fields 2024-07-19 18:00:13 +02:00
4aab84fbd5 Merge pull request #839 from 64knl/feat-update-dutch-string
feat: add missing Dutch strings
2024-07-18 09:15:00 +02:00
Rene
74e97e660d feat(translation): Add more faker translations 2024-07-16 14:56:15 +02:00
Rene
f5d236b521 fix(translation): Spelling error 2024-07-16 14:49:21 +02:00
Rene
e794d207ad feat: add missing Dutch strings 2024-07-16 14:36:35 +02:00
f99a3cc054 chore(release): 0.7.27 2024-07-16 14:01:25 +02:00
59f7d3c670 fix: issue with new console and languages different than english, fixes #837 2024-07-16 14:00:02 +02:00
8f2daa0f1c chore(release): 0.7.26 2024-07-15 10:23:38 +02:00
141d5bb69c chore(release): 0.7.26-beta.1 2024-07-11 18:09:01 +02:00
171b6f924a feat: custom SVG icons for connections, closes #663 2024-07-11 18:06:41 +02:00
f7419d8e9c fix: table name in column list on export as SQL features, fixes #822 2024-07-04 18:01:29 +02:00
5b1cd70e25 chore(release): 0.7.26-beta.0 2024-07-01 19:44:25 +02:00
3e223b475e feat: in-app debug console, closes #824 2024-07-01 18:16:18 +02:00
4a38656b7e fix(PostgreSQL): issue with ssl enabled and connection strings, fixes #786 2024-06-21 08:55:29 +02:00
25b7ae57c6 perf(UI): improved all connections modal, closes #761 2024-06-20 18:12:47 +02:00
4b5718e9b7 ci: update gh actions dependencies 2024-06-20 08:17:09 +02:00
780d83deaa Merge branch 'master' of https://github.com/antares-sql/antares into develop 2024-06-19 14:16:34 +02:00
e952f9f5f8 ci: fix macos build actions 2024-06-19 09:08:25 +02:00
49f1a8ef2e chore(release): 0.7.25 2024-06-19 08:57:50 +02:00
121aa21a6d Merge branch 'beta' of https://github.com/antares-sql/antares 2024-06-19 08:57:07 +02:00
cb25963a67 Merge branch 'master' of https://github.com/antares-sql/antares into develop 2024-06-17 09:26:41 +02:00
3a47607a5f Merge branch 'master' of https://github.com/antares-sql/antares 2024-06-17 09:13:55 +02:00
7494ff6fcf ci: changes on create-artifact-macos.yml 2024-06-17 09:13:31 +02:00
838491bfd4 chore(release): 0.7.25-beta.2 2024-06-16 13:40:02 +02:00
0b9898f3e7 feat(PostgreSQL): support to materialized views, closes #804 2024-06-14 18:05:29 +02:00
a973ec3c60 perf(UI): views grouped in folders 2024-06-13 18:07:05 +02:00
d0c50f17ca ci: temporary disabled auto test e2e 2024-06-10 08:52:00 +02:00
b4cdd58973 chore(release): 0.7.25-beta.1 2024-06-08 17:29:47 +02:00
9947479fdc Merge branch 'beta' of https://github.com/antares-sql/antares into develop 2024-06-07 16:12:10 +02:00
4a1697d633 fix: issue switching table after using a filter, fixes#691 2024-06-05 18:34:43 +02:00
b7dfd5cb8c build(deps): update various dependencies 2024-05-26 17:28:42 +02:00
0ec9d3cfc1 Merge pull request #807 from antares-sql/all-contributors/add-mangas
docs: add mangas as a contributor for code
2024-05-26 17:20:52 +02:00
allcontributors[bot]
4f615b26cf docs: update .all-contributorsrc [skip ci] 2024-05-26 15:19:12 +00:00
allcontributors[bot]
86a1e05197 docs: update README.md [skip ci] 2024-05-26 15:19:11 +00:00
3fa9873d20 Merge pull request #805 from mangas/upgrade-electron
chore: upgrade electron
2024-05-26 17:18:21 +02:00
37a160a03f build(deps): update @electron/remote 2024-05-26 17:15:11 +02:00
2385a8207c chore(release): 0.7.25-beta.0 2024-05-26 15:57:02 +02:00
Filipe Azevedo
243984e697 chore: upgrade electron 2024-05-17 12:46:58 +01:00
d1bb50b2bb fix(PostgreSQL): unable to search for databases, fixes #798 2024-05-08 08:56:23 +02:00
8501fa2e81 Merge branch 'develop' of https://github.com/antares-sql/antares into develop 2024-05-07 18:16:30 +02:00
25123e34ef fix: missing resizebars on mouse over 2024-05-07 18:15:32 +02:00
bd4502ee47 Merge pull request #800 from antares-sql/all-contributors/add-penguinlab
docs: add penguinlab as a contributor for translation
2024-05-07 16:31:40 +02:00
b2f9d475a2 Merge pull request #799 from penguinlab/master
feat: update japanese translation
2024-05-07 16:30:37 +02:00
allcontributors[bot]
93de974b09 docs: update .all-contributorsrc [skip ci] 2024-05-07 14:29:50 +00:00
allcontributors[bot]
949bf4cbcb docs: update README.md [skip ci] 2024-05-07 14:29:49 +00:00
penguinlab
bb3c87b2cf feat: update japanese translation 2024-05-07 18:18:45 +09:00
40bf9a040a Merge pull request #797 from jimcat8/cn_trans
Update zh-CN.ts file and update translation
2024-05-05 20:56:29 +02:00
tianci
978b55fdb1 Update again 2024-05-05 18:11:22 +08:00
tianci
098d4e96d6 Update zh-CN.ts file and update translation 2024-05-05 18:07:02 +08:00
957cb9e1a5 chore(release): 0.7.24 2024-05-03 14:21:14 +02:00
09c274a724 fix: missing accent color change 2024-05-02 18:00:07 +02:00
9bcd874e80 chore(release): 0.7.24-beta.1 2024-04-30 18:09:52 +02:00
ece2ee05cc perf(UI): improvements on light theme 2024-04-30 18:08:07 +02:00
058fc2fc0b feat: accent color based on folder color, closes #762 2024-04-30 18:07:08 +02:00
33bbc0e7e6 fix(PostgreSQL): better handle connection errors, should fix #794 2024-04-30 18:06:11 +02:00
23c59b4d4e fix(PostgreSQL): issue with similar tabs on differend databases 2024-04-18 18:22:29 +02:00
6600197b82 perf(UI): hide "insert row" button in read-only mode, closes #695 2024-04-14 16:23:56 +02:00
33203aeb04 refactor(UI): change query tab buttons order 2024-04-12 18:03:17 +02:00
f4f385589f chore(release): 0.7.24-beta.0 2024-04-12 08:44:08 +02:00
0565ae1204 fix(translation): missing translation for "Open notes" shortcut 2024-04-08 18:33:37 +02:00
258fbc81f7 Merge branch 'master' of https://github.com/antares-sql/antares into develop 2024-04-08 18:30:23 +02:00
8d8650fbe7 feat: unsaved file reminder closing file tabs 2024-04-08 18:29:05 +02:00
af2812f2b0 Merge pull request #788 from antares-sql/all-contributors/add-bagusindrayana
docs: add bagusindrayana as a contributor for code
2024-04-08 12:49:46 +02:00
099a71a189 Merge pull request #785 from bagusindrayana/feat-open-edit-save-file
Feat open, edit, and save file in query tab
2024-04-08 12:49:01 +02:00
e7efb9c616 refactor(UI): change to query tab icons to avoid ambiguity with new features 2024-04-08 09:52:46 +02:00
bagusindrayana
c1e58eb695 feat: open,save, and save as file in query tab 2024-04-06 15:34:42 +08:00
bagusindrayana
f7204dc0ae feat: add translation for open,save, and save as file 2024-04-06 15:34:18 +08:00
bagusindrayana
6b56c60b68 feat: add shortcut open,save, and save as file 2024-04-06 15:33:01 +08:00
104 changed files with 11121 additions and 9464 deletions

View File

@@ -275,6 +275,96 @@
"contributions": [ "contributions": [
"code" "code"
] ]
},
{
"login": "penguinlab",
"name": "Naoki Ishikawa",
"avatar_url": "https://avatars.githubusercontent.com/u/10959317?v=4",
"profile": "https://github.com/penguinlab",
"contributions": [
"translation"
]
},
{
"login": "mangas",
"name": "Filipe Azevedo",
"avatar_url": "https://avatars.githubusercontent.com/u/1640325?v=4",
"profile": "https://fazevedo.dev",
"contributions": [
"code"
]
},
{
"login": "zwei-c",
"name": "CHANG, CHIH WEI",
"avatar_url": "https://avatars.githubusercontent.com/u/55912811?v=4",
"profile": "https://github.com/zwei-c",
"contributions": [
"translation"
]
},
{
"login": "mirrorb",
"name": "GaoChun",
"avatar_url": "https://avatars.githubusercontent.com/u/34116207?v=4",
"profile": "https://github.com/mirrorb",
"contributions": [
"code"
]
},
{
"login": "LeviEyal",
"name": "Eyal Levi",
"avatar_url": "https://avatars.githubusercontent.com/u/48846533?v=4",
"profile": "https://github.com/LeviEyal",
"contributions": [
"translation"
]
},
{
"login": "SawGoD",
"name": "Nikita Karelikov",
"avatar_url": "https://avatars.githubusercontent.com/u/67802757?v=4",
"profile": "http://telegram.dog/SawGoD",
"contributions": [
"translation"
]
},
{
"login": "carvalhods",
"name": "David Carvalho",
"avatar_url": "https://avatars.githubusercontent.com/u/6569255?v=4",
"profile": "https://github.com/carvalhods",
"contributions": [
"platform"
]
},
{
"login": "r4f4dev",
"name": "r4f4dev",
"avatar_url": "https://avatars.githubusercontent.com/u/65920592?v=4",
"profile": "https://github.com/r4f4dev",
"contributions": [
"translation"
]
},
{
"login": "salvymc",
"name": "Salvatore Forino",
"avatar_url": "https://avatars.githubusercontent.com/u/10051897?v=4",
"profile": "https://github.com/salvymc",
"contributions": [
"code"
]
},
{
"login": "JoseGonzalez84",
"name": "José González",
"avatar_url": "https://avatars.githubusercontent.com/u/16820141?v=4",
"profile": "https://gadev.com.es/",
"contributions": [
"translation"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

View File

@@ -2,4 +2,5 @@ node_modules
assets assets
out out
dist dist
build build
misc

View File

@@ -19,17 +19,19 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
ref: beta ref: beta
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
- name: Install dependencies - name: Install dependencies
run: npm i run: |
npm i
npm install "dmg-license" --save-optional
- name: "Build" - name: "Build"
run: npm run build run: npm run build

View File

@@ -25,17 +25,19 @@ jobs:
exit 0 exit 0
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
ref: master ref: master
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
- name: Install dependencies - name: Install dependencies
run: npm i run: |
npm i
npm install "dmg-license" --save-optional
- name: "Build" - name: "Build"
run: npm run build run: npm run build

View File

@@ -25,7 +25,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
# We must fetch at least the immediate parents so that if this is # We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head. # a pull request then we can checkout the head.

View File

@@ -8,12 +8,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
ref: master ref: master
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
@@ -24,7 +24,7 @@ jobs:
run: npm run build -- --arm64 --linux deb AppImage run: npm run build -- --arm64 --linux deb AppImage
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: linux-build name: linux-build
retention-days: 3 retention-days: 3

View File

@@ -8,10 +8,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
@@ -22,7 +22,7 @@ jobs:
run: npm run build run: npm run build
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: linux-build name: linux-build
retention-days: 3 retention-days: 3

View File

@@ -8,20 +8,21 @@ jobs:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
- name: npm install & build - name: npm install & build
run: | run: |
npm install npm install
npm install "dmg-license" --save-optional
npm run build npm run build
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: macos-build name: macos-build
retention-days: 3 retention-days: 3
@@ -38,17 +39,18 @@ jobs:
ref: beta ref: beta
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
- name: npm install & build - name: npm install & build
run: | run: |
npm install npm install
npm install "dmg-license" --save-optional
npm run build npm run build
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: macos-build-beta name: macos-build-beta
retention-days: 3 retention-days: 3

View File

@@ -11,7 +11,7 @@ jobs:
- name: Install Python - name: Install Python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: '3.8' python-version: '3.11'
- name: Install pipx - name: Install pipx
uses: CfirTsabari/actions-pipx@v1 uses: CfirTsabari/actions-pipx@v1
@@ -30,7 +30,7 @@ jobs:
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 20
# - name: Delete old package-lock.json # - name: Delete old package-lock.json
# run: rm package-lock.json # run: rm package-lock.json

View File

@@ -17,12 +17,12 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
ref: develop ref: develop
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20

View File

@@ -1,9 +1,10 @@
name: Test end-to-end name: Test end-to-end
on: on:
push: workflow_dispatch: {}
branches: # push:
- develop # branches:
# - develop
jobs: jobs:
release: release:
@@ -15,10 +16,10 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20

View File

@@ -2,6 +2,263 @@
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.31-beta.1](https://github.com/antares-sql/antares/compare/v0.7.31-beta.0...v0.7.31-beta.1) (2025-01-22)
### Features
* zoom in/out and fullscreen shortcuts ([47ac729](https://github.com/antares-sql/antares/commit/47ac729d2f5cced2c503358f7d45a1795f232a20))
### Bug Fixes
* cannot update column value with composite primary key and JSON column, fixes [#916](https://github.com/antares-sql/antares/issues/916) ([0029967](https://github.com/antares-sql/antares/commit/002996761997444ff689bf2384dae64ccb9ef8f7))
* fail to duplicate JSON row ([507dc7d](https://github.com/antares-sql/antares/commit/507dc7d55b342240bf18fd58e6bc71709e8e33a0))
* saved connections lost opening a second window after first app run ([4a2b592](https://github.com/antares-sql/antares/commit/4a2b5926f4783d0b9b1e28485e9293a25ddd31f3))
### Improvements
* **translation:** update spanish translation ([d3ae45e](https://github.com/antares-sql/antares/commit/d3ae45ec94b3538e84ac3013b285034caea695cf))
### [0.7.31-beta.0](https://github.com/antares-sql/antares/compare/v0.7.30...v0.7.31-beta.0) (2025-01-06)
### Features
* **language:** add uzbek language support ([fb9c258](https://github.com/antares-sql/antares/commit/fb9c258cc10e4d85242ca533a66a95f4101d472c))
### Bug Fixes
* prevent delete confirmation modal from triggering on non-delete key presses, fixes [#906](https://github.com/antares-sql/antares/issues/906) ([8de99da](https://github.com/antares-sql/antares/commit/8de99dae7b6eb72bd6833c607d3c3a5db9508ebb))
### [0.7.30](https://github.com/antares-sql/antares/compare/v0.7.30-beta.1...v0.7.30) (2024-12-04)
### Bug Fixes
* issue saving queries as file ([a54b8d7](https://github.com/antares-sql/antares/commit/a54b8d719c6454500b885050c9ce6feaf7cfae1f))
### [0.7.30-beta.1](https://github.com/antares-sql/antares/compare/v0.7.30-beta.0...v0.7.30-beta.1) (2024-11-15)
### Bug Fixes
* missing support check for table check features ([2ee9cfc](https://github.com/antares-sql/antares/commit/2ee9cfcf0bbcf86e8a194d2eff78801300ce7cb3))
### Improvements
* **PostgreSQL:** improved support of connection strings, closes [#893](https://github.com/antares-sql/antares/issues/893) ([f0d312f](https://github.com/antares-sql/antares/commit/f0d312fb59fd98d6e4501bc407959b91eb0650f2))
### [0.7.30-beta.0](https://github.com/antares-sql/antares/compare/v0.7.29...v0.7.30-beta.0) (2024-10-25)
### Features
* **MySQL:** check constraints management support ([6365e07](https://github.com/antares-sql/antares/commit/6365e075349e00caa1454cce862e918f2069878f))
### Bug Fixes
* incorrect behavior in sorting tables with null/empty values, fixes [#883](https://github.com/antares-sql/antares/issues/883) ([b6c5dff](https://github.com/antares-sql/antares/commit/b6c5dff15c165261e9a11a389ed415e59c7b7628))
* incorrect behavior sorting tables with numeric values ([60e1e59](https://github.com/antares-sql/antares/commit/60e1e595057c3ba7f36e0f829dba11b470e1069b))
* **MySQL:** routines do not return results, fixes [#885](https://github.com/antares-sql/antares/issues/885) ([dba490f](https://github.com/antares-sql/antares/commit/dba490f22634f87d3af5a3a4c0866fc3095c9842))
### Improvements
* added more notifications in debug console ([dfa7cf9](https://github.com/antares-sql/antares/commit/dfa7cf9905a4d0a79eaed823a14477574b329dfa))
### [0.7.29](https://github.com/antares-sql/antares/compare/v0.7.29-beta.3...v0.7.29) (2024-10-14)
### [0.7.29-beta.3](https://github.com/antares-sql/antares/compare/v0.7.29-beta.2...v0.7.29-beta.3) (2024-10-08)
### Features
* **translation:** add hebrew translation, closes [#878](https://github.com/antares-sql/antares/issues/878) ([2f3f5de](https://github.com/antares-sql/antares/commit/2f3f5de8d6b02cfbf5217adfcb09a61e13d1e901))
### Improvements
* **MySQL:** made some common errors related to corrupted tables non-blocking, closes [#877](https://github.com/antares-sql/antares/issues/877) ([9a0ad80](https://github.com/antares-sql/antares/commit/9a0ad80bb55f84bd6c90cc1e9b63b33512d336a8))
### [0.7.29-beta.2](https://github.com/antares-sql/antares/compare/v0.7.29-beta.1...v0.7.29-beta.2) (2024-10-02)
### Features
* **UI:** new context menu and some minor improvements to query tabs, closes [#867](https://github.com/antares-sql/antares/issues/867) ([14aeebe](https://github.com/antares-sql/antares/commit/14aeebed9cd8e475548f5e0ade105f4b11954cb2))
### Bug Fixes
* **MySQL:** incorrect representation of the DATE if the year is prior to 1900, fixes [#860](https://github.com/antares-sql/antares/issues/860) ([7969294](https://github.com/antares-sql/antares/commit/7969294a93a51861c57d4396c7a0d89ecc7e8a84))
* **MySQL:** missing exported values for DEFAULT_GENERATED table fields, fixes [#854](https://github.com/antares-sql/antares/issues/854) ([2cda4a1](https://github.com/antares-sql/antares/commit/2cda4a1fa1c80f3567e160caf0b93bc19d76fbaa))
* **PostgreSQL:** error changing the comment for a specific table name ([eb749f0](https://github.com/antares-sql/antares/commit/eb749f0f66bf6547053e30b1503c8b2990ae5950))
* **PostgreSQL:** unable to change table comment to empty ([d78e59d](https://github.com/antares-sql/antares/commit/d78e59dd0910d3ea6ec5183a8748420b2db57050))
### [0.7.29-beta.1](https://github.com/antares-sql/antares/compare/v0.7.29-beta.0...v0.7.29-beta.1) (2024-09-28)
### Features
* **PostgreSQL:** table and field comments ([ebd1a75](https://github.com/antares-sql/antares/commit/ebd1a7544594eb4498560cc64de4b94146ee8439))
* **translation:** traditional chinese translation, closes [#869](https://github.com/antares-sql/antares/issues/869) ([b70ed12](https://github.com/antares-sql/antares/commit/b70ed124eb753091a6afe637d75e59ee9771c8eb))
### [0.7.29-beta.0](https://github.com/antares-sql/antares/compare/v0.7.28...v0.7.29-beta.0) (2024-09-23)
### Features
* cancel button when waiting to connect database, closes [#830](https://github.com/antares-sql/antares/issues/830) ([b6a7124](https://github.com/antares-sql/antares/commit/b6a7124f33397a2ae7da654b5867f6982ac5810e))
* update czech translation ([0506b65](https://github.com/antares-sql/antares/commit/0506b653d74d8cd5e848bc2ec4d29d4b0247c880))
### Bug Fixes
* mismatch between table field columns and results with duplicate fields, fixes [#848](https://github.com/antares-sql/antares/issues/848) ([da8cc39](https://github.com/antares-sql/antares/commit/da8cc39157a4b507d3d377ee1e888b8f8a52b7c5))
### Improvements
* **UI:** hide edit/delete functions in readonly mode ([37d44c9](https://github.com/antares-sql/antares/commit/37d44c95ee559f3ee1345e91fca5e2c1e86c5fbf))
### [0.7.28](https://github.com/antares-sql/antares/compare/v0.7.28-beta.0...v0.7.28) (2024-08-20)
### [0.7.28-beta.0](https://github.com/antares-sql/antares/compare/v0.7.27...v0.7.28-beta.0) (2024-08-06)
### Features
* add missing Dutch strings ([e794d20](https://github.com/antares-sql/antares/commit/e794d207ad75e0f5380f57df579912893e5edb6a))
* **translation:** Add more faker translations ([74e97e6](https://github.com/antares-sql/antares/commit/74e97e660df089ed8273565942118e112f6b3220))
### Bug Fixes
* disabled column sort during loadings ([72bacde](https://github.com/antares-sql/antares/commit/72bacdeabf833880482a839c4735505573d33bdc))
* html tags searching in history or saved queries, fixes [#847](https://github.com/antares-sql/antares/issues/847) ([6cb21ff](https://github.com/antares-sql/antares/commit/6cb21ff7926c74469b421c47b434612b3894b4c2))
* **PostgreSQL:** issue exporting tables with primary keys ([c434855](https://github.com/antares-sql/antares/commit/c434855879de16f83e17784e38e931decdd94873))
* **PostgreSQL:** wrong export formato of JSON fields ([8e7965a](https://github.com/antares-sql/antares/commit/8e7965a0f94a17ed73d5c8913f66e4e9cf0b11c7))
* **translation:** Spelling error ([f5d236b](https://github.com/antares-sql/antares/commit/f5d236b521a3534754de0b1031513f8eb83b3cc0))
* wrong password message importing app data ([ba0ffcc](https://github.com/antares-sql/antares/commit/ba0ffcc6f56c5506c1768c05d43bb07f7b090a68))
### [0.7.27](https://github.com/antares-sql/antares/compare/v0.7.26...v0.7.27) (2024-07-16)
### Bug Fixes
* issue with new console and languages different than english, fixes [#837](https://github.com/antares-sql/antares/issues/837) ([59f7d3c](https://github.com/antares-sql/antares/commit/59f7d3c67083ac7e32bd29c9b7e6e044f2060c2f))
### [0.7.26](https://github.com/antares-sql/antares/compare/v0.7.26-beta.1...v0.7.26) (2024-07-15)
### [0.7.26-beta.1](https://github.com/antares-sql/antares/compare/v0.7.26-beta.0...v0.7.26-beta.1) (2024-07-11)
### Features
* custom SVG icons for connections, closes [#663](https://github.com/antares-sql/antares/issues/663) ([171b6f9](https://github.com/antares-sql/antares/commit/171b6f924acc7d7696f4f850a704af0baf616b87))
### Bug Fixes
* table name in column list on export as SQL features, fixes [#822](https://github.com/antares-sql/antares/issues/822) ([f7419d8](https://github.com/antares-sql/antares/commit/f7419d8e9c4fe8ea80dbf9b2612ff44a66f50365))
### [0.7.26-beta.0](https://github.com/antares-sql/antares/compare/v0.7.25...v0.7.26-beta.0) (2024-07-01)
### Features
* in-app debug console, closes [#824](https://github.com/antares-sql/antares/issues/824) ([3e223b4](https://github.com/antares-sql/antares/commit/3e223b475ea57b24a6782feeabecad9c5596e271))
### Bug Fixes
* **PostgreSQL:** issue with ssl enabled and connection strings, fixes [#786](https://github.com/antares-sql/antares/issues/786) ([4a38656](https://github.com/antares-sql/antares/commit/4a38656b7e1094d3a9df28ce263c272f2014adb7))
### Improvements
* **UI:** improved all connections modal, closes [#761](https://github.com/antares-sql/antares/issues/761) ([25b7ae5](https://github.com/antares-sql/antares/commit/25b7ae57c6a4be9e825dddc7a52a49b67e03771b))
### [0.7.25](https://github.com/antares-sql/antares/compare/v0.7.25-beta.2...v0.7.25) (2024-06-19)
### [0.7.25-beta.2](https://github.com/antares-sql/antares/compare/v0.7.25-beta.1...v0.7.25-beta.2) (2024-06-16)
### Features
* **PostgreSQL:** support to materialized views, closes [#804](https://github.com/antares-sql/antares/issues/804) ([0b9898f](https://github.com/antares-sql/antares/commit/0b9898f3e714d2cb24d100f55dd3858a644de162))
### Improvements
* **UI:** views grouped in folders ([a973ec3](https://github.com/antares-sql/antares/commit/a973ec3c60398cb16685a4f991c43ec4ee74c986))
### [0.7.25-beta.1](https://github.com/antares-sql/antares/compare/v0.7.25-beta.0...v0.7.25-beta.1) (2024-06-08)
### Bug Fixes
* issue switching table after using a filter, fixes[#691](https://github.com/antares-sql/antares/issues/691) ([4a1697d](https://github.com/antares-sql/antares/commit/4a1697d63351b9990efff5804b95d92ac2fc9783))
### [0.7.25-beta.0](https://github.com/antares-sql/antares/compare/v0.7.24...v0.7.25-beta.0) (2024-05-26)
### Features
* update japanese translation ([bb3c87b](https://github.com/antares-sql/antares/commit/bb3c87b2cf6fa38e3cfb68317c02aa350aae7887))
### Bug Fixes
* missing resizebars on mouse over ([25123e3](https://github.com/antares-sql/antares/commit/25123e34ef860d8bf019c496097e68e0101c9ab9))
* **PostgreSQL:** unable to search for databases, fixes [#798](https://github.com/antares-sql/antares/issues/798) ([d1bb50b](https://github.com/antares-sql/antares/commit/d1bb50b2bb48d3445080990c28fdc656cf27a6d3))
### [0.7.24](https://github.com/antares-sql/antares/compare/v0.7.24-beta.1...v0.7.24) (2024-05-03)
### Bug Fixes
* missing accent color change ([09c274a](https://github.com/antares-sql/antares/commit/09c274a724b5020efc650aaf7eecb2404343a6fc))
### [0.7.24-beta.1](https://github.com/antares-sql/antares/compare/v0.7.24-beta.0...v0.7.24-beta.1) (2024-04-30)
### Features
* accent color based on folder color, closes [#762](https://github.com/antares-sql/antares/issues/762) ([058fc2f](https://github.com/antares-sql/antares/commit/058fc2fc0b34cde5aa19233a4a999ef3624dae71))
### Bug Fixes
* **PostgreSQL:** better handle connection errors, should fix [#794](https://github.com/antares-sql/antares/issues/794) ([33bbc0e](https://github.com/antares-sql/antares/commit/33bbc0e7e6be370c944e979a34ab2cb19562d1e3))
* **PostgreSQL:** issue with similar tabs on differend databases ([23c59b4](https://github.com/antares-sql/antares/commit/23c59b4d4e8f250acad75f54d157c7c162e1c4f8))
### Improvements
* **UI:** hide "insert row" button in read-only mode, closes [#695](https://github.com/antares-sql/antares/issues/695) ([6600197](https://github.com/antares-sql/antares/commit/6600197b8286ced4c79378883594d21e69a83d8c))
* **UI:** improvements on light theme ([ece2ee0](https://github.com/antares-sql/antares/commit/ece2ee05cc90a58c1926e882e3ccf4f057f02d68))
### [0.7.24-beta.0](https://github.com/antares-sql/antares/compare/v0.7.23...v0.7.24-beta.0) (2024-04-12)
### Features
* add shortcut open,save, and save as file ([6b56c60](https://github.com/antares-sql/antares/commit/6b56c60b68647bc7182548a137cccc3413e3fbd5))
* add translation for open,save, and save as file ([f7204dc](https://github.com/antares-sql/antares/commit/f7204dc0ae721534eaefbde097d1c26c1d72ad41))
* open,save, and save as file in query tab ([c1e58eb](https://github.com/antares-sql/antares/commit/c1e58eb695de78fbf1d2b26c608692f0962373df))
* unsaved file reminder closing file tabs ([8d8650f](https://github.com/antares-sql/antares/commit/8d8650fbe76c79fd66be857d049b3baaa9ab1f9f))
### Bug Fixes
* **translation:** missing translation for "Open notes" shortcut ([0565ae1](https://github.com/antares-sql/antares/commit/0565ae12042901b9d67fe3e0ea269562ec444994))
### [0.7.23](https://github.com/antares-sql/antares/compare/v0.7.23-beta.1...v0.7.23) (2024-04-07) ### [0.7.23](https://github.com/antares-sql/antares/compare/v0.7.23-beta.1...v0.7.23) (2024-04-07)
### [0.7.23-beta.1](https://github.com/antares-sql/antares/compare/v0.7.23-beta.0...v0.7.23-beta.1) (2024-04-02) ### [0.7.23-beta.1](https://github.com/antares-sql/antares/compare/v0.7.23-beta.0...v0.7.23-beta.1) (2024-04-02)

View File

@@ -1,13 +1,13 @@
<!-- markdownlint-disable --> <!-- markdownlint-disable -->
<p align="center"> <p align="center">
<img width="800" src="https://raw.githubusercontent.com/Fabio286/antares/master/docs/gh-logo.png"> <img width="800" src="https://raw.githubusercontent.com/antares-sql/antares/master/docs/gh-logo.png">
</p> </p>
<!-- markdownlint-restore --> <!-- markdownlint-restore -->
# Antares SQL Client # Antares SQL Client
![GitHub package.json version](https://img.shields.io/github/package-json/v/fabio286/antares) ![GitHub](https://img.shields.io/github/license/fabio286/antares) ![Test e2e](https://github.com/antares-sql/antares/actions/workflows/test-e2e-win.yml/badge.svg?branch=develop) ![Mastodon Follow](https://img.shields.io/mastodon/follow/%20110860460902482117?domain=https%3A%2F%2Ffosstodon.org&style=social) [![Plant a Tree](https://raw.githubusercontent.com/Fabio286/treedom-badge/master/svg/plant-a-tree.svg)](https://www.treedom.net/en/user/fabio-di-stasio/event/antares-for-the-planet) ![GitHub package.json version](https://img.shields.io/github/package-json/v/antares-sql/antares) ![GitHub](https://img.shields.io/github/license/antares-sql/antares) ![Test e2e](https://github.com/antares-sql/antares/actions/workflows/test-e2e-win.yml/badge.svg?branch=develop) ![Mastodon Follow](https://img.shields.io/mastodon/follow/%20110860460902482117?domain=https%3A%2F%2Ffosstodon.org&style=social) [![Plant a Tree](https://raw.githubusercontent.com/Fabio286/treedom-badge/master/svg/plant-a-tree.svg)](https://www.treedom.net/en/user/fabio-di-stasio/event/antares-for-the-planet)
Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers. Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers.
Our target is to support as many databases as possible, and all major operating systems, including the ARM versions. Our target is to support as many databases as possible, and all major operating systems, including the ARM versions.
@@ -16,7 +16,7 @@ Our target is to support as many databases as possible, and all major operating
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/antares-sql/antares/releases/latest).
👁 To stay tuned for new releases follow Antares SQL on [Mastodon](https://fosstodon.org/@AntaresSQL). 👁 To stay tuned for new releases follow Antares SQL on [Mastodon](https://fosstodon.org/@AntaresSQL).
🌟 Don't forget to **leave a star** if you appreciate this project. 🌟 Don't forget to **leave a star** if you appreciate this project.
@@ -60,7 +60,7 @@ On Linux you can simply download and run the `.AppImage` distribution, install f
### Windows ### Windows
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. 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/antares-sql/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
@@ -99,8 +99,8 @@ On macOS you can run `.dmg` distribution following [this guide](https://support.
## How to contribute ## How to contribute
- 🌍 [Translate Antares](https://github.com/Fabio286/antares/wiki/Translate-Antares) - 🌍 [Translate Antares](https://github.com/antares-sql/antares/wiki/Translate-Antares)
- 📖 [Contributors Guide](https://github.com/Fabio286/antares/wiki/Contributors-Guide) - 📖 [Contributors Guide](https://github.com/antares-sql/antares/wiki/Contributors-Guide)
- 🚧 [Project Board](https://github.com/orgs/antares-sql/projects/3/views/2) - 🚧 [Project Board](https://github.com/orgs/antares-sql/projects/3/views/2)
## Contributors ✨ ## Contributors ✨
@@ -150,6 +150,18 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
</tr> </tr>
<tr> <tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bagusindrayana"><img src="https://avatars.githubusercontent.com/u/36830534?v=4?s=100" width="100px;" alt="Bagus Indrayana"/><br /><sub><b>Bagus Indrayana</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=bagusindrayana" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/bagusindrayana"><img src="https://avatars.githubusercontent.com/u/36830534?v=4?s=100" width="100px;" alt="Bagus Indrayana"/><br /><sub><b>Bagus Indrayana</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=bagusindrayana" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/penguinlab"><img src="https://avatars.githubusercontent.com/u/10959317?v=4?s=100" width="100px;" alt="Naoki Ishikawa"/><br /><sub><b>Naoki Ishikawa</b></sub></a><br /><a href="#translation-penguinlab" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://fazevedo.dev"><img src="https://avatars.githubusercontent.com/u/1640325?v=4?s=100" width="100px;" alt="Filipe Azevedo"/><br /><sub><b>Filipe Azevedo</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=mangas" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zwei-c"><img src="https://avatars.githubusercontent.com/u/55912811?v=4?s=100" width="100px;" alt="CHANG, CHIH WEI"/><br /><sub><b>CHANG, CHIH WEI</b></sub></a><br /><a href="#translation-zwei-c" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mirrorb"><img src="https://avatars.githubusercontent.com/u/34116207?v=4?s=100" width="100px;" alt="GaoChun"/><br /><sub><b>GaoChun</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=mirrorb" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LeviEyal"><img src="https://avatars.githubusercontent.com/u/48846533?v=4?s=100" width="100px;" alt="Eyal Levi"/><br /><sub><b>Eyal Levi</b></sub></a><br /><a href="#translation-LeviEyal" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://telegram.dog/SawGoD"><img src="https://avatars.githubusercontent.com/u/67802757?v=4?s=100" width="100px;" alt="Nikita Karelikov"/><br /><sub><b>Nikita Karelikov</b></sub></a><br /><a href="#translation-SawGoD" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/carvalhods"><img src="https://avatars.githubusercontent.com/u/6569255?v=4?s=100" width="100px;" alt="David Carvalho"/><br /><sub><b>David Carvalho</b></sub></a><br /><a href="#platform-carvalhods" title="Packaging/porting to new platform">📦</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/r4f4dev"><img src="https://avatars.githubusercontent.com/u/65920592?v=4?s=100" width="100px;" alt="r4f4dev"/><br /><sub><b>r4f4dev</b></sub></a><br /><a href="#translation-r4f4dev" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/salvymc"><img src="https://avatars.githubusercontent.com/u/10051897?v=4?s=100" width="100px;" alt="Salvatore Forino"/><br /><sub><b>Salvatore Forino</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=salvymc" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://gadev.com.es/"><img src="https://avatars.githubusercontent.com/u/16820141?v=4?s=100" width="100px;" alt="José González"/><br /><sub><b>José González</b></sub></a><br /><a href="#translation-JoseGonzalez84" title="Translation">🌍</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

10754
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "antares", "name": "antares",
"productName": "Antares", "productName": "Antares",
"version": "0.7.23", "version": "0.7.31-beta.1",
"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",
@@ -119,7 +119,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@electron/remote": "~2.0.1", "@electron/remote": "~2.1.2",
"@fabio286/ssh2-promise": "~1.0.4-b", "@fabio286/ssh2-promise": "~1.0.4-b",
"@faker-js/faker": "~6.1.2", "@faker-js/faker": "~6.1.2",
"@jamescoyle/vue-icon": "~0.1.2", "@jamescoyle/vue-icon": "~0.1.2",
@@ -127,10 +127,11 @@
"@turf/helpers": "~6.5.0", "@turf/helpers": "~6.5.0",
"@vue/compiler-sfc": "~3.2.33", "@vue/compiler-sfc": "~3.2.33",
"@vueuse/core": "~10.4.1", "@vueuse/core": "~10.4.1",
"ace-builds": "~1.24.1", "ace-builds": "~1.34.1",
"babel-loader": "~8.2.3", "babel-loader": "~8.2.3",
"better-sqlite3": "~9.4.1", "better-sqlite3": "~10.0.0",
"chalk": "~4.1.2", "chalk": "~4.1.2",
"cpu-features": "^0.0.10",
"cross-env": "~7.0.2", "cross-env": "~7.0.2",
"css-loader": "~6.5.0", "css-loader": "~6.5.0",
"electron-log": "~5.0.1", "electron-log": "~5.0.1",
@@ -146,11 +147,10 @@
"marked": "~12.0.0", "marked": "~12.0.0",
"mini-css-extract-plugin": "~2.4.5", "mini-css-extract-plugin": "~2.4.5",
"moment": "~2.30.1", "moment": "~2.30.1",
"mysql2": "~3.9.1", "mysql2": "~3.9.7",
"node-firebird": "~1.1.4", "node-firebird": "~1.1.8",
"node-loader": "~2.0.0", "node-loader": "~2.0.0",
"pg": "~8.11.3", "pg": "~8.11.5",
"pg-connection-string": "~2.5.0",
"pg-query-stream": "~4.2.3", "pg-query-stream": "~4.2.3",
"pgsql-ast-parser": "~7.2.1", "pgsql-ast-parser": "~7.2.1",
"pinia": "~2.1.7", "pinia": "~2.1.7",
@@ -169,11 +169,11 @@
"typescript": "~4.6.3", "typescript": "~4.6.3",
"unzip-crx-3": "~0.2.0", "unzip-crx-3": "~0.2.0",
"v-mask": "~2.3.0", "v-mask": "~2.3.0",
"vue": "~3.4.19", "vue": "~3.4.27",
"vue-i18n": "~9.2.2", "vue-i18n": "~9.13.1",
"vue-loader": "~16.8.3", "vue-loader": "~16.8.3",
"vuedraggable": "~4.1.0", "vuedraggable": "~4.1.0",
"webpack": "~5.72.0", "webpack": "^5.91.0",
"webpack-cli": "~4.9.1" "webpack-cli": "~4.9.1"
}, },
"devDependencies": { "devDependencies": {
@@ -192,8 +192,8 @@
"@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",
"all-contributors-cli": "~6.20.0", "all-contributors-cli": "~6.20.0",
"electron": "~26.6.9", "electron": "~30.0.8",
"electron-builder": "~24.6.4", "electron-builder": "~24.13.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",

View File

@@ -55,6 +55,7 @@ export const defaults: Customizations = {
tableArray: false, tableArray: false,
tableRealCount: false, tableRealCount: false,
tableDuplicate: false, tableDuplicate: false,
tableCheck: false,
viewSettings: false, viewSettings: false,
triggerSettings: false, triggerSettings: false,
triggerFunctionSettings: false, triggerFunctionSettings: false,

View File

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

View File

@@ -31,6 +31,7 @@ export const customizations: Customizations = {
schemas: true, schemas: true,
tables: true, tables: true,
views: true, views: true,
materializedViews: true,
triggers: true, triggers: true,
triggerFunctions: true, triggerFunctions: true,
routines: true, routines: true,
@@ -42,6 +43,7 @@ export const customizations: Customizations = {
tableDuplicate: true, tableDuplicate: true,
tableDdl: true, tableDdl: true,
viewAdd: true, viewAdd: true,
materializedViewAdd: true,
triggerAdd: true, triggerAdd: true,
triggerFunctionAdd: true, triggerFunctionAdd: true,
routineAdd: true, routineAdd: true,
@@ -52,6 +54,7 @@ export const customizations: Customizations = {
databaseEdit: false, databaseEdit: false,
tableSettings: true, tableSettings: true,
viewSettings: true, viewSettings: true,
materializedViewSettings: true,
triggerSettings: true, triggerSettings: true,
triggerFunctionSettings: true, triggerFunctionSettings: true,
routineSettings: true, routineSettings: true,
@@ -59,6 +62,7 @@ export const customizations: Customizations = {
indexes: true, indexes: true,
foreigns: true, foreigns: true,
nullable: true, nullable: true,
comment: true,
tableArray: true, tableArray: true,
procedureSql: '$procedure$\r\n\r\n$procedure$', procedureSql: '$procedure$\r\n\r\n$procedure$',
procedureContext: true, procedureContext: true,

View File

@@ -18,7 +18,7 @@ export type Importer = MySQLImporter | PostgreSQLImporter
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export interface IpcResponse<T = any> { export interface IpcResponse<T = any> {
status: 'success' | 'error'; status: 'success' | 'error' | 'abort';
response?: T; response?: T;
} }
@@ -57,6 +57,7 @@ export interface ConnectionParams {
cert?: string; cert?: string;
key?: string; key?: string;
ca?: string; ca?: string;
connString?: string;
untrustedConnection: boolean; untrustedConnection: boolean;
ciphers?: string; ciphers?: string;
ssh: boolean; ssh: boolean;
@@ -159,6 +160,13 @@ export interface TableForeign {
oldName?: string; oldName?: string;
} }
export interface TableCheck {
// eslint-disable-next-line camelcase
_antares_id?: string;
name: string;
clause: string;
}
export interface CreateTableParams { export interface CreateTableParams {
/** Connection UID */ /** Connection UID */
uid?: string; uid?: string;
@@ -166,6 +174,7 @@ export interface CreateTableParams {
fields: TableField[]; fields: TableField[];
foreigns: TableForeign[]; foreigns: TableForeign[];
indexes: TableIndex[]; indexes: TableIndex[];
checks?: TableCheck[];
options: TableOptions; options: TableOptions;
} }
@@ -193,6 +202,11 @@ export interface AlterTableParams {
changes: TableForeign[]; changes: TableForeign[];
deletions: TableForeign[]; deletions: TableForeign[];
}; };
checkChanges?: {
additions: TableCheck[];
changes: TableCheck[];
deletions: TableCheck[];
};
options: TableOptions; options: TableOptions;
} }

View File

@@ -28,6 +28,7 @@ export interface Customizations {
schemas?: boolean; schemas?: boolean;
tables?: boolean; tables?: boolean;
views?: boolean; views?: boolean;
materializedViews?: boolean;
triggers?: boolean; triggers?: boolean;
triggerFunctions?: boolean; triggerFunctions?: boolean;
routines?: boolean; routines?: boolean;
@@ -42,9 +43,12 @@ export interface Customizations {
tableArray?: boolean; tableArray?: boolean;
tableRealCount?: boolean; tableRealCount?: boolean;
tableTruncateDisableFKCheck?: boolean; tableTruncateDisableFKCheck?: boolean;
tableCheck?: boolean;
tableDdl?: boolean; tableDdl?: boolean;
viewAdd?: boolean; viewAdd?: boolean;
viewSettings?: boolean; viewSettings?: boolean;
materializedViewAdd?: boolean;
materializedViewSettings?: boolean;
triggerAdd?: boolean; triggerAdd?: boolean;
triggerFunctionAdd?: boolean; triggerFunctionAdd?: boolean;
routineAdd?: boolean; routineAdd?: boolean;

View File

@@ -2,6 +2,7 @@
/* eslint-disable no-useless-escape */ /* eslint-disable no-useless-escape */
import { lineString, point, polygon } from '@turf/helpers'; import { lineString, point, polygon } from '@turf/helpers';
import { BIT, BLOB, DATE, DATETIME, FLOAT, IS_MULTI_SPATIAL, NUMBER, SPATIAL, TEXT_SEARCH } from 'common/fieldTypes'; import { BIT, BLOB, DATE, DATETIME, FLOAT, IS_MULTI_SPATIAL, NUMBER, SPATIAL, TEXT_SEARCH } from 'common/fieldTypes';
import * as antares from 'common/interfaces/antares';
import * as moment from 'moment'; import * as moment from 'moment';
import customizations from '../customizations'; import customizations from '../customizations';
@@ -40,7 +41,7 @@ export const objectToGeoJSON = (val: any) => {
export const escapeAndQuote = (val: string, client: ClientCode) => { export const escapeAndQuote = (val: string, client: ClientCode) => {
const { stringsWrapper: sw } = customizations[client]; const { stringsWrapper: sw } = customizations[client];
// eslint-disable-next-line no-control-regex // eslint-disable-next-line no-control-regex
const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g; const CHARS_TO_ESCAPE = sw === '"' ? /[\0\b\t\n\r\x1a"'\\]/g : /[\0\b\t\n\r\x1a'\\]/g;
const CHARS_ESCAPE_MAP: Record<string, string> = { const CHARS_ESCAPE_MAP: Record<string, string> = {
'\0': '\\0', '\0': '\\0',
'\b': '\\b', '\b': '\\b',
@@ -48,10 +49,13 @@ export const escapeAndQuote = (val: string, client: ClientCode) => {
'\n': '\\n', '\n': '\\n',
'\r': '\\r', '\r': '\\r',
'\x1a': '\\Z', '\x1a': '\\Z',
'"': '\\"',
'\'': '\\\'', '\'': '\\\'',
'\\': '\\\\' '\\': '\\\\'
}; };
if (sw === '"')
CHARS_ESCAPE_MAP['"'] = '\\"';
let chunkIndex = CHARS_TO_ESCAPE.lastIndex = 0; let chunkIndex = CHARS_TO_ESCAPE.lastIndex = 0;
let escapedVal = ''; let escapedVal = '';
let match; let match;
@@ -97,10 +101,19 @@ export const valueToSqlString = (args: {
} }
else if ('isArray' in field && field.isArray) { else if ('isArray' in field && field.isArray) {
let localVal; let localVal;
if (Array.isArray(val)) if (Array.isArray(val)) {
localVal = JSON.stringify(val).replaceAll('[', '{').replaceAll(']', '}'); localVal = JSON
else .stringify(val)
localVal = typeof val === 'string' ? val.replaceAll('[', '{').replaceAll(']', '}') : ''; .replaceAll('[', '{')
.replaceAll(']', '}');
}
else {
localVal = typeof val === 'string'
? val
.replaceAll('[', '{')
.replaceAll(']', '}')
: '';
}
parsedValue = `'${localVal}'`; parsedValue = `'${localVal}'`;
} }
else if (TEXT_SEARCH.includes(field.type)) else if (TEXT_SEARCH.includes(field.type))
@@ -163,7 +176,7 @@ export const jsonToSqlInsert = (args: {
const sqlInsertAfter = options && options.sqlInsertAfter ? options.sqlInsertAfter : 1; const sqlInsertAfter = options && options.sqlInsertAfter ? options.sqlInsertAfter : 1;
const sqlInsertDivider = options && options.sqlInsertDivider ? options.sqlInsertDivider : 'rows'; const sqlInsertDivider = options && options.sqlInsertDivider ? options.sqlInsertDivider : 'rows';
const { elementsWrapper: ew } = customizations[client]; const { elementsWrapper: ew } = customizations[client];
const fieldNames = Object.keys(json[0]).map(key => `${ew}${key}${ew}`); const fieldNames = Object.keys(json[0]).map(key => `${ew}${key.split('.').pop()}${ew}`);
let insertStmt = `INSERT INTO ${ew}${table}${ew} (${fieldNames.join(', ')}) VALUES `; let insertStmt = `INSERT INTO ${ew}${table}${ew} (${fieldNames.join(', ')}) VALUES `;
let insertsString = ''; let insertsString = '';
let queryLength = 0; let queryLength = 0;
@@ -197,3 +210,20 @@ export const jsonToSqlInsert = (args: {
return insertsString; return insertsString;
}; };
export const formatJsonForSqlWhere = (jsonValue: object, clientType: antares.ClientCode) => {
const formattedValue = JSON.stringify(jsonValue);
switch (clientType) {
case 'mysql':
return ` = CAST('${formattedValue}' AS JSON)`;
case 'maria':
return ` = '${formattedValue}'`;
case 'pg':
return `::text = '${formattedValue}'`;
case 'firebird':
case 'sqlite':
default:
return ` = '${formattedValue}'`;
}
};

View File

@@ -1,26 +1,34 @@
export const shortcutEvents: Record<string, { l18n: string; l18nParam?: string | number; context?: 'tab' }> = { export const shortcutEvents: Record<string, { i18n: string; i18nParam?: string | number; context?: 'tab' | 'main' }> = {
'run-or-reload': { l18n: 'application.runOrReload', context: 'tab' }, 'run-or-reload': { i18n: 'application.runOrReload', context: 'tab' },
'open-new-tab': { l18n: 'application.openNewTab', context: 'tab' }, 'open-new-tab': { i18n: 'application.openNewTab', context: 'tab' },
'close-tab': { l18n: 'application.closeTab', context: 'tab' }, 'close-tab': { i18n: 'application.closeTab', context: 'tab' },
'format-query': { l18n: 'database.formatQuery', context: 'tab' }, 'format-query': { i18n: 'database.formatQuery', context: 'tab' },
'kill-query': { l18n: 'database.killQuery', context: 'tab' }, 'kill-query': { i18n: 'database.killQuery', context: 'tab' },
'query-history': { l18n: 'database.queryHistory', context: 'tab' }, 'query-history': { i18n: 'database.queryHistory', context: 'tab' },
'clear-query': { l18n: 'database.clearQuery', context: 'tab' }, 'clear-query': { i18n: 'database.clearQuery', context: 'tab' },
'next-tab': { l18n: 'application.nextTab' }, // 'save-file': { i18n: 'application.saveFile', context: 'tab' },
'prev-tab': { l18n: 'application.previousTab' }, 'open-file': { i18n: 'application.openFile', context: 'tab' },
'open-all-connections': { l18n: 'application.openAllConnections' }, 'save-file-as': { i18n: 'application.saveFileAs', context: 'tab' },
'open-filter': { l18n: 'application.openFilter' }, 'next-tab': { i18n: 'application.nextTab' },
'next-page': { l18n: 'application.nextResultsPage' }, 'prev-tab': { i18n: 'application.previousTab' },
'prev-page': { l18n: 'application.previousResultsPage' }, 'open-all-connections': { i18n: 'application.openAllConnections' },
'toggle-console': { l18n: 'application.toggleConsole' }, 'open-filter': { i18n: 'application.openFilter' },
'save-content': { l18n: 'application.saveContent' }, 'next-page': { i18n: 'application.nextResultsPage' },
'create-connection': { l18n: 'connection.createNewConnection' }, 'prev-page': { i18n: 'application.previousResultsPage' },
'open-settings': { l18n: 'application.openSettings' }, 'toggle-console': { i18n: 'application.toggleConsole' },
'open-scratchpad': { l18n: 'application.openScratchpad' } 'save-content': { i18n: 'application.saveContent' },
'create-connection': { i18n: 'connection.createNewConnection' },
'open-settings': { i18n: 'application.openSettings' },
'open-scratchpad': { i18n: 'application.openNotes' },
setFullScreen: { i18n: 'application.fullScreen', context: 'main' },
setZoomIn: { i18n: 'application.zoomIn', context: 'main' },
setZoomOut: { i18n: 'application.zoomOut', context: 'main' },
setZoomReset: { i18n: 'application.zoomReset', context: 'main' }
}; };
interface ShortcutRecord { interface ShortcutRecord {
event: string; event: string;
isFunction?: boolean;
keys: Electron.Accelerator[] | string[]; keys: Electron.Accelerator[] | string[];
/** Needed for default shortcuts */ /** Needed for default shortcuts */
os: NodeJS.Platform[]; os: NodeJS.Platform[];
@@ -35,6 +43,30 @@ const shortcuts: ShortcutRecord[] = [
keys: ['F5'], keys: ['F5'],
os: ['darwin', 'linux', 'win32'] os: ['darwin', 'linux', 'win32']
}, },
{
event: 'setFullScreen',
isFunction: true,
keys: ['F11'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'setZoomIn',
isFunction: true,
keys: ['CommandOrControl+='],
os: ['darwin', 'linux', 'win32']
},
{
event: 'setZoomOut',
isFunction: true,
keys: ['CommandOrControl+-'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'setZoomReset',
isFunction: true,
keys: ['CommandOrControl+0'],
os: ['darwin', 'linux', 'win32']
},
{ {
event: 'save-content', event: 'save-content',
keys: ['CommandOrControl+S'], keys: ['CommandOrControl+S'],
@@ -119,13 +151,28 @@ const shortcuts: ShortcutRecord[] = [
event: 'toggle-console', event: 'toggle-console',
keys: ['CommandOrControl+`'], keys: ['CommandOrControl+`'],
os: ['darwin', 'linux', 'win32'] os: ['darwin', 'linux', 'win32']
},
// {
// event: 'save-file',
// keys: ['CommandOrControl+S'],
// os: ['darwin', 'linux', 'win32']
// },
{
event: 'open-file',
keys: ['CommandOrControl+O'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'save-file-as',
keys: ['Shift+CommandOrControl+S'],
os: ['darwin', 'linux', 'win32']
} }
]; ];
for (let i = 1; i <= 9; i++) { for (let i = 1; i <= 9; i++) {
shortcutEvents[`select-tab-${i}`] = { shortcutEvents[`select-tab-${i}`] = {
l18n: 'application.selectTabNumber', i18n: 'application.selectTabNumber',
l18nParam: i i18nParam: i
}; };
shortcuts.push({ shortcuts.push({

View File

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

View File

@@ -5,17 +5,28 @@ import { SslOptions } from 'mysql2';
import { ClientsFactory } from '../libs/ClientsFactory'; import { ClientsFactory } from '../libs/ClientsFactory';
import { validateSender } from '../libs/misc/validateSender'; import { validateSender } from '../libs/misc/validateSender';
const isAborting: Record<string, boolean> = {};
export default (connections: Record<string, antares.Client>) => { export default (connections: Record<string, antares.Client>) => {
ipcMain.handle('test-connection', async (event, conn: antares.ConnectionParams) => { ipcMain.handle('test-connection', async (event, conn: antares.ConnectionParams) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
let isLocalAborted = false;
const abortChecker = setInterval(() => { // Intercepts abort request
if (isAborting[conn.uid]) {
isAborting[conn.uid] = false;
isLocalAborted = true;
clearInterval(abortChecker);
}
}, 50);
const params = { const params = {
host: conn.host, host: conn.host,
port: +conn.port, port: +conn.port,
user: conn.user, user: conn.user,
password: conn.password, password: conn.password,
readonly: conn.readonly, readonly: conn.readonly,
connectionString: conn.connString,
database: '', database: '',
schema: '', schema: '',
databasePath: '', databasePath: '',
@@ -65,19 +76,27 @@ export default (connections: Record<string, antares.Client>) => {
client: conn.client, client: conn.client,
params params
}); });
await connection.connect();
if (conn.client === 'firebird') await connection.connect();
connection.raw('SELECT rdb$get_context(\'SYSTEM\', \'DB_NAME\') FROM rdb$database'); if (isLocalAborted) {
else connection.destroy();
await connection.select('1+1').run(); return;
}
await connection.ping();
connection.destroy(); connection.destroy();
clearInterval(abortChecker);
return { status: 'success' }; return { status: 'success' };
} }
catch (err) { catch (err) {
return { status: 'error', response: err.toString() }; clearInterval(abortChecker);
if (!isLocalAborted)
return { status: 'error', response: err.toString() };
else
return { status: 'abort', response: 'Connection aborted' };
} }
}); });
@@ -88,6 +107,15 @@ export default (connections: Record<string, antares.Client>) => {
ipcMain.handle('connect', async (event, conn: antares.ConnectionParams) => { ipcMain.handle('connect', async (event, conn: antares.ConnectionParams) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
let isLocalAborted = false;
const abortChecker = setInterval(() => { // Intercepts abort request
if (isAborting[conn.uid]) {
isAborting[conn.uid] = false;
isLocalAborted = true;
clearInterval(abortChecker);
}
}, 50);
const params = { const params = {
host: conn.host, host: conn.host,
port: +conn.port, port: +conn.port,
@@ -95,6 +123,7 @@ export default (connections: Record<string, antares.Client>) => {
password: conn.password, password: conn.password,
application_name: 'Antares SQL', application_name: 'Antares SQL',
readonly: conn.readonly, readonly: conn.readonly,
connectionString: conn.connString,
database: '', database: '',
schema: '', schema: '',
databasePath: '', databasePath: '',
@@ -150,18 +179,36 @@ export default (connections: Record<string, antares.Client>) => {
}); });
await connection.connect(); await connection.connect();
if (isLocalAborted) {
connection.destroy();
return { status: 'abort', response: 'Connection aborted' };
}
const structure = await connection.getStructure(new Set()); const structure = await connection.getStructure(new Set());
if (isLocalAborted) {
connection.destroy();
return { status: 'abort', response: 'Connection aborted' };
}
connections[conn.uid] = connection; connections[conn.uid] = connection;
clearInterval(abortChecker);
return { status: 'success', response: structure }; return { status: 'success', response: structure };
} }
catch (err) { catch (err) {
return { status: 'error', response: err.toString() }; clearInterval(abortChecker);
if (!isLocalAborted)
return { status: 'error', response: err.toString() };
else
return { status: 'abort', response: 'Connection aborted' };
} }
}); });
ipcMain.on('abort-connection', (event, uid) => {
isAborting[uid] = true;
});
ipcMain.handle('disconnect', (event, uid) => { ipcMain.handle('disconnect', (event, uid) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };

View File

@@ -251,7 +251,7 @@ export default (connections: Record<string, antares.Client>) => {
setTimeout(() => { // Ensures that writing thread has finished setTimeout(() => { // Ensures that writing thread has finished
exporter?.terminate(); exporter?.terminate();
exporter = null; exporter = null;
}, 2000); }, 500);
resolve({ status: 'success', response: payload }); resolve({ status: 'success', response: payload });
break; break;
case 'cancel': case 'cancel':

View File

@@ -3,7 +3,7 @@ import { ARRAY, BIT, BLOB, BOOLEAN, DATE, DATETIME, FLOAT, LONG_TEXT, NUMBER, TE
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 { fakerCustom } from 'common/libs/fakerCustom'; import { fakerCustom } from 'common/libs/fakerCustom';
import { sqlEscaper } from 'common/libs/sqlUtils'; import { formatJsonForSqlWhere, sqlEscaper } from 'common/libs/sqlUtils';
import { ipcMain } from 'electron'; import { ipcMain } from 'electron';
import * as fs from 'fs'; import * as fs from 'fs';
import * as moment from 'moment'; import * as moment from 'moment';
@@ -87,6 +87,19 @@ export default (connections: Record<string, antares.Client>) => {
} }
}); });
ipcMain.handle('get-table-checks', async (event, params) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
try {
const result = await connections[params.uid].getTableChecks(params);
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-table-ddl', async (event, params) => { ipcMain.handle('get-table-ddl', async (event, params) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' }; if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
@@ -220,9 +233,10 @@ export default (connections: Record<string, antares.Client>) => {
for (const key in orgRow) { for (const key in orgRow) {
if (typeof orgRow[key] === 'string') if (typeof orgRow[key] === 'string')
orgRow[key] = `'${orgRow[key]}'`; orgRow[key] = ` = '${orgRow[key]}'`;
else if (typeof orgRow[key] === 'object' && orgRow[key] !== null)
if (orgRow[key] === null) orgRow[key] = formatJsonForSqlWhere(orgRow[key], connections[params.uid]._client);
else if (orgRow[key] === null)
orgRow[key] = `IS ${orgRow[key]}`; orgRow[key] = `IS ${orgRow[key]}`;
else else
orgRow[key] = `= ${orgRow[key]}`; orgRow[key] = `= ${orgRow[key]}`;

View File

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

View File

@@ -81,7 +81,15 @@ export class ShortcutRegister {
accelerator: key, accelerator: key,
visible: isMenuVisible, visible: isMenuVisible,
click: () => { click: () => {
this._mainWindow.webContents.send(shortcut.event); if (shortcut.isFunction) {
if (shortcut.event in this) {
type exporterMethods = 'setFullScreen' | 'setZoomIn' | 'setZoomOut' | 'setZoomReset';
this[shortcut.event as exporterMethods]();
}
}
else
this._mainWindow.webContents.send(shortcut.event);
if (isDevelopment) console.log('LOCAL EVENT:', shortcut); if (isDevelopment) console.log('LOCAL EVENT:', shortcut);
} }
}); });
@@ -121,6 +129,24 @@ export class ShortcutRegister {
} }
} }
setFullScreen () {
this._mainWindow.setFullScreen(!this._mainWindow.isFullScreen());
}
setZoomIn () {
const currentZoom = this._mainWindow.webContents.getZoomLevel();
this._mainWindow.webContents.setZoomLevel(currentZoom + 1);
}
setZoomOut () {
const currentZoom = this._mainWindow.webContents.getZoomLevel();
this._mainWindow.webContents.setZoomLevel(currentZoom - 1);
}
setZoomReset () {
this._mainWindow.webContents.setZoomLevel(0);
}
reload () { reload () {
this.unregister(); this.unregister();
this.init(); this.init();

View File

@@ -3,14 +3,25 @@ import mysql from 'mysql2/promise';
import * as pg from 'pg'; import * as pg from 'pg';
import SSH2Promise = require('@fabio286/ssh2-promise'); import SSH2Promise = require('@fabio286/ssh2-promise');
const queryLogger = ({ sql, cUid }: {sql: string; cUid: string}) => { export type LoggerLevel = 'query' | 'error'
// Remove comments, newlines and multiple spaces
const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' '); const ipcLogger = ({ content, cUid, level }: {content: string; cUid: string; level: LoggerLevel}) => {
if (process.type !== undefined) { if (level === 'error') {
const mainWindow = require('electron').webContents.fromId(1); if (process.type !== undefined) {
mainWindow.send('query-log', { cUid, sql: escapedSql, date: new Date() }); const mainWindow = require('electron').webContents.fromId(1);
mainWindow.send('non-blocking-exception', { cUid, message: content, date: new Date() });
}
if (process.env.NODE_ENV === 'development' && process.type === 'browser') console.log(content);
}
else if (level === 'query') {
// Remove comments, newlines and multiple spaces
const escapedSql = content.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' ');
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' && process.type === 'browser') console.log(escapedSql);
} }
if (process.env.NODE_ENV === 'development' && process.type === 'browser') console.log(escapedSql);
}; };
/** /**
@@ -22,7 +33,7 @@ export abstract class BaseClient {
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: (args: {sql: string; cUid: string}) => void; protected _logger: (args: {content: string; cUid: string; level: LoggerLevel}) => void;
protected _queryDefaults: antares.QueryBuilderObject; protected _queryDefaults: antares.QueryBuilderObject;
protected _query: antares.QueryBuilderObject; protected _query: antares.QueryBuilderObject;
@@ -31,7 +42,7 @@ export abstract class BaseClient {
this._cUid = args.uid; 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 || ipcLogger;
this._queryDefaults = { this._queryDefaults = {
schema: '', schema: '',
@@ -178,6 +189,10 @@ export abstract class BaseClient {
throw new Error('Method "dropSchema" not implemented'); throw new Error('Method "dropSchema" not implemented');
} }
getTableChecks (...args: any) {
throw new Error('Method "getTableDll" not implemented');
}
getTableDll (...args: any) { getTableDll (...args: any) {
throw new Error('Method "getTableDll" not implemented'); throw new Error('Method "getTableDll" not implemented');
} }
@@ -234,6 +249,18 @@ export abstract class BaseClient {
throw new Error('Method "getVariables" not implemented'); throw new Error('Method "getVariables" not implemented');
} }
getMaterializedViewInformations (...args: any) {
throw new Error('Method "getMaterializedViewInformations" not implemented');
}
dropMaterializedView (...args: any) {
throw new Error('Method "dropMaterializedView" not implemented');
}
createMaterializedView (...args: any) {
throw new Error('Method "createMaterializedView" not implemented');
}
getEventInformations (...args: any) { getEventInformations (...args: any) {
throw new Error('Method "getEventInformations" not implemented'); throw new Error('Method "getEventInformations" not implemented');
} }

View File

@@ -109,6 +109,10 @@ export class FirebirdSQLClient extends BaseClient {
return firebird.pool(this._poolSize, { ...this._params, blobAsText: true }); return firebird.pool(this._poolSize, { ...this._params, blobAsText: true });
} }
ping () {
return this.raw('SELECT rdb$get_context(\'SYSTEM\', \'DB_NAME\') FROM rdb$database');
}
destroy () { destroy () {
if (this._poolSize) if (this._poolSize)
return (this._connection as firebird.ConnectionPool).destroy(); return (this._connection as firebird.ConnectionPool).destroy();
@@ -1020,7 +1024,7 @@ export class FirebirdSQLClient extends BaseClient {
alias: string; alias: string;
} }
this._logger({ cUid: this._cUid, sql }); this._logger({ cUid: this._cUid, content: sql, level: 'query' });
args = { args = {
nest: false, nest: false,

View File

@@ -161,6 +161,8 @@ export class MySQLClient extends BaseClient {
this._ssh = new SSH2Promise({ this._ssh = new SSH2Promise({
...this._params.ssh, ...this._params.ssh,
reconnect: true,
reconnectTries: 3,
debug: process.env.NODE_ENV !== 'production' ? (s) => console.log(s) : null debug: process.env.NODE_ENV !== 'production' ? (s) => console.log(s) : null
}); });
@@ -214,6 +216,10 @@ export class MySQLClient extends BaseClient {
} }
} }
ping () {
return this.select('1+1').run();
}
destroy () { destroy () {
this._connection.end(); this._connection.end();
clearInterval(this._keepaliveTimer); clearInterval(this._keepaliveTimer);
@@ -228,12 +234,13 @@ export class MySQLClient extends BaseClient {
const dbConfig = await this.getDbConfig(); const dbConfig = await this.getDbConfig();
const connection = await mysql.createConnection({ const connection = await mysql.createConnection({
...dbConfig, ...dbConfig,
typeCast: (field, next) => { dateStrings: true
if (field.type === 'DATETIME') // typeCast: (field, next) => {
return field.string(); // if (field.type === 'DATETIME')
else // return field.string();
return next(); // else
} // return next();
// }
}); });
return connection; return connection;
@@ -245,12 +252,13 @@ export class MySQLClient extends BaseClient {
...dbConfig, ...dbConfig,
connectionLimit: this._poolSize, connectionLimit: this._poolSize,
enableKeepAlive: true, enableKeepAlive: true,
typeCast: (field, next) => { dateStrings: true
if (field.type === 'DATETIME') // typeCast: (field, next) => {
return field.string(); // if (field.type === 'DATETIME')
else // return field.string();
return next(); // else
} // return next();
// }
}); });
this._keepaliveTimer = setInterval(async () => { this._keepaliveTimer = setInterval(async () => {
@@ -348,10 +356,21 @@ export class MySQLClient extends BaseClient {
if (this._params.schema) if (this._params.schema)
filteredDatabases = filteredDatabases.filter(db => db.Database === this._params.schema); filteredDatabases = filteredDatabases.filter(db => db.Database === this._params.schema);
const { rows: functions } = await this.raw('SHOW FUNCTION STATUS'); /* eslint-disable @typescript-eslint/no-explicit-any */
const { rows: procedures } = await this.raw('SHOW PROCEDURE STATUS'); let functions: any[] = [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any let procedures: any[] = [];
let schedulers: any[] = []; let schedulers: any[] = [];
/* eslint-enable @typescript-eslint/no-explicit-any */
try {
const { rows: functionRows } = await this.raw('SHOW FUNCTION STATUS');
const { rows: procedureRows } = await this.raw('SHOW PROCEDURE STATUS');
functions = functionRows;
procedures = procedureRows;
}
catch (err) {
this._logger({ content: err.sqlMessage, cUid: this._cUid, level: 'error' });
}
try { // Avoid exception with event_scheduler DISABLED with MariaDB 10 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`'); const { rows } = await this.raw('SELECT *, EVENT_SCHEMA AS `Db`, EVENT_NAME AS `Name` FROM information_schema.`EVENTS`');
@@ -657,7 +676,7 @@ export class MySQLClient extends BaseClient {
charset: field.CHARACTER_SET_NAME, charset: field.CHARACTER_SET_NAME,
collation: field.COLLATION_NAME, collation: field.COLLATION_NAME,
autoIncrement: field.EXTRA.includes('auto_increment'), autoIncrement: field.EXTRA.includes('auto_increment'),
generated: field.EXTRA.toLowerCase().includes('generated'), generated: ['VIRTUAL GENERATED', 'VIRTUAL STORED'].includes(field.EXTRA),
onUpdate: field.EXTRA.toLowerCase().includes('on update') onUpdate: field.EXTRA.toLowerCase().includes('on update')
? field.EXTRA.substr(field.EXTRA.indexOf('on update') + 9, field.EXTRA.length).trim() ? field.EXTRA.substr(field.EXTRA.indexOf('on update') + 9, field.EXTRA.length).trim()
: '', : '',
@@ -672,6 +691,34 @@ export class MySQLClient extends BaseClient {
return rows.length ? rows[0].count : 0; return rows.length ? rows[0].count : 0;
} }
async getTableChecks ({ schema, table }: { schema: string; table: string }): Promise<antares.TableCheck[]> {
const { rows } = await this.raw(`
SELECT
CONSTRAINT_NAME as name,
CHECK_CLAUSE as clausole
FROM information_schema.CHECK_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = "${schema}"
AND CONSTRAINT_NAME IN (
SELECT
CONSTRAINT_NAME
FROM
information_schema.TABLE_CONSTRAINTS
WHERE
TABLE_SCHEMA = "${schema}"
AND TABLE_NAME = "${table}"
AND CONSTRAINT_TYPE = 'CHECK'
)
`);
if (rows.length) {
return rows.map(row => ({
name: row.name,
clause: row.clausole
}));
}
return [];
}
async getTableOptions ({ schema, table }: { schema: string; table: string }) { async getTableOptions ({ schema, table }: { schema: string; table: string }) {
/* eslint-disable camelcase */ /* eslint-disable camelcase */
interface TableOptionsResult { interface TableOptionsResult {
@@ -848,11 +895,13 @@ export class MySQLClient extends BaseClient {
fields, fields,
foreigns, foreigns,
indexes, indexes,
checks,
options options
} = params; } = params;
const newColumns: string[] = []; const newColumns: string[] = [];
const newIndexes: string[] = []; const newIndexes: string[] = [];
const newForeigns: string[] = []; const newForeigns: string[] = [];
const newChecks: string[] = [];
let sql = `CREATE TABLE \`${schema}\`.\`${options.name}\``; let sql = `CREATE TABLE \`${schema}\`.\`${options.name}\``;
@@ -893,7 +942,13 @@ export class MySQLClient extends BaseClient {
newForeigns.push(`CONSTRAINT \`${foreign.constraintName}\` FOREIGN KEY (\`${foreign.field}\`) REFERENCES \`${foreign.refTable}\` (\`${foreign.refField}\`) ON UPDATE ${foreign.onUpdate} ON DELETE ${foreign.onDelete}`); newForeigns.push(`CONSTRAINT \`${foreign.constraintName}\` FOREIGN KEY (\`${foreign.field}\`) REFERENCES \`${foreign.refTable}\` (\`${foreign.refField}\`) ON UPDATE ${foreign.onUpdate} ON DELETE ${foreign.onDelete}`);
}); });
sql = `${sql} (${[...newColumns, ...newIndexes, ...newForeigns].join(', ')}) COMMENT='${options.comment}', COLLATE='${options.collation}', ENGINE=${options.engine}`; // ADD TABLE CHECKS
checks.forEach(check => {
if (!check.clause.trim().length) return;
newChecks.push(`${check.name ? `CONSTRAINT \`${check.name}\` ` : ''}CHECK (${check.clause})`);
});
sql = `${sql} (${[...newColumns, ...newIndexes, ...newForeigns, ...newChecks].join(', ')}) COMMENT='${options.comment}', COLLATE='${options.collation}', ENGINE=${options.engine}`;
return await this.raw(sql); return await this.raw(sql);
} }
@@ -907,6 +962,7 @@ export class MySQLClient extends BaseClient {
changes, changes,
indexChanges, indexChanges,
foreignChanges, foreignChanges,
checkChanges,
options options
} = params; } = params;
@@ -914,6 +970,7 @@ export class MySQLClient extends BaseClient {
const alterColumnsAdd: string[] = []; const alterColumnsAdd: string[] = [];
const alterColumnsChange: string[] = []; const alterColumnsChange: string[] = [];
const alterColumnsDrop: string[] = []; const alterColumnsDrop: string[] = [];
const alterQueryes: string[] = [];
// OPTIONS // OPTIONS
if ('comment' in options) alterColumnsChange.push(`COMMENT='${options.comment}'`); if ('comment' in options) alterColumnsChange.push(`COMMENT='${options.comment}'`);
@@ -959,6 +1016,12 @@ export class MySQLClient extends BaseClient {
alterColumnsAdd.push(`ADD CONSTRAINT \`${addition.constraintName}\` FOREIGN KEY (\`${addition.field}\`) REFERENCES \`${addition.refTable}\` (\`${addition.refField}\`) ON UPDATE ${addition.onUpdate} ON DELETE ${addition.onDelete}`); alterColumnsAdd.push(`ADD CONSTRAINT \`${addition.constraintName}\` FOREIGN KEY (\`${addition.field}\`) REFERENCES \`${addition.refTable}\` (\`${addition.refField}\`) ON UPDATE ${addition.onUpdate} ON DELETE ${addition.onDelete}`);
}); });
// ADD TABLE CHECKS
checkChanges.additions.forEach(addition => {
if (!addition.clause.trim().length) return;
alterColumnsAdd.push(`ADD ${addition.name ? `CONSTRAINT \`${addition.name}\` ` : ''}CHECK (${addition.clause})`);
});
// CHANGE FIELDS // CHANGE FIELDS
changes.forEach(change => { changes.forEach(change => {
const typeInfo = this.getTypeInfo(change.type); const typeInfo = this.getTypeInfo(change.type);
@@ -970,9 +1033,9 @@ export class MySQLClient extends BaseClient {
${change.zerofill ? 'ZEROFILL' : ''} ${change.zerofill ? 'ZEROFILL' : ''}
${change.nullable ? 'NULL' : 'NOT NULL'} ${change.nullable ? 'NULL' : 'NOT NULL'}
${change.autoIncrement ? 'AUTO_INCREMENT' : ''} ${change.autoIncrement ? 'AUTO_INCREMENT' : ''}
${change.collation ? `COLLATE ${change.collation}` : ''}
${change.default !== null ? `DEFAULT ${change.default || '\'\''}` : ''} ${change.default !== null ? `DEFAULT ${change.default || '\'\''}` : ''}
${change.comment ? `COMMENT '${change.comment}'` : ''} ${change.comment ? `COMMENT '${change.comment}'` : ''}
${change.collation ? `COLLATE ${change.collation}` : ''}
${change.onUpdate ? `ON UPDATE ${change.onUpdate}` : ''} ${change.onUpdate ? `ON UPDATE ${change.onUpdate}` : ''}
${change.after ? `AFTER \`${change.after}\`` : 'FIRST'}`); ${change.after ? `AFTER \`${change.after}\`` : 'FIRST'}`);
}); });
@@ -1003,6 +1066,13 @@ export class MySQLClient extends BaseClient {
alterColumnsChange.push(`ADD CONSTRAINT \`${change.constraintName}\` FOREIGN KEY (\`${change.field}\`) REFERENCES \`${change.refTable}\` (\`${change.refField}\`) ON UPDATE ${change.onUpdate} ON DELETE ${change.onDelete}`); alterColumnsChange.push(`ADD CONSTRAINT \`${change.constraintName}\` FOREIGN KEY (\`${change.field}\`) REFERENCES \`${change.refTable}\` (\`${change.refField}\`) ON UPDATE ${change.onUpdate} ON DELETE ${change.onDelete}`);
}); });
// CHANGE CHECK TABLE
checkChanges.changes.forEach(change => {
if (!change.clause.trim().length) return;
alterQueryes.push(`${sql} DROP CONSTRAINT \`${change.name}\``);
alterQueryes.push(`${sql} ADD ${change.name ? `CONSTRAINT \`${change.name}\` ` : ''}CHECK (${change.clause})`);
});
// DROP FIELDS // DROP FIELDS
deletions.forEach(deletion => { deletions.forEach(deletion => {
alterColumnsDrop.push(`DROP COLUMN \`${deletion.name}\``); alterColumnsDrop.push(`DROP COLUMN \`${deletion.name}\``);
@@ -1021,7 +1091,11 @@ export class MySQLClient extends BaseClient {
alterColumnsDrop.push(`DROP FOREIGN KEY \`${deletion.constraintName}\``); alterColumnsDrop.push(`DROP FOREIGN KEY \`${deletion.constraintName}\``);
}); });
const alterQueryes = []; // DROP CHECK TABLE
checkChanges.deletions.forEach(deletion => {
alterQueryes.push(`${sql} DROP CONSTRAINT \`${deletion.name}\``);
});
if (alterColumnsAdd.length) alterQueryes.push(sql+alterColumnsAdd.join(', ')); if (alterColumnsAdd.length) alterQueryes.push(sql+alterColumnsAdd.join(', '));
if (alterColumnsChange.length) alterQueryes.push(sql+alterColumnsChange.join(', ')); if (alterColumnsChange.length) alterQueryes.push(sql+alterColumnsChange.join(', '));
if (alterColumnsDrop.length) alterQueryes.push(sql+alterColumnsDrop.join(', ')); if (alterColumnsDrop.length) alterQueryes.push(sql+alterColumnsDrop.join(', '));
@@ -1661,7 +1735,7 @@ export class MySQLClient extends BaseClient {
} }
async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) { async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) {
this._logger({ cUid: this._cUid, sql }); this._logger({ cUid: this._cUid, content: sql, level: 'query' });
args = { args = {
nest: false, nest: false,
@@ -1697,9 +1771,10 @@ export class MySQLClient extends BaseClient {
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;
const fieldsArr = fields ? Array.isArray(fields[0]) ? fields[0] : fields : false;// Some times fields are nested in an array
let remappedFields = fields let remappedFields = fieldsArr
? fields.map(field => { ? fieldsArr.map(field => {
if (!field || Array.isArray(field)) if (!field || Array.isArray(field))
return undefined; return undefined;
@@ -1768,7 +1843,7 @@ export class MySQLClient extends BaseClient {
resolve({ resolve({
duration: timeStop.getTime() - timeStart.getTime(), duration: timeStop.getTime() - timeStart.getTime(),
rows: Array.isArray(queryResult) ? queryResult.some(el => Array.isArray(el)) ? [] : queryResult : false, rows: Array.isArray(queryResult) ? queryResult.some(el => Array.isArray(el)) ? queryResult[0] : queryResult : false,
report: !Array.isArray(queryResult) ? queryResult : false, report: !Array.isArray(queryResult) ? queryResult : false,
fields: remappedFields, fields: remappedFields,
keys: keysArr keys: keysArr

View File

@@ -155,6 +155,7 @@ export class PostgreSQLClient extends BaseClient {
host: this._params.host, host: this._params.host,
port: this._params.port, port: this._params.port,
user: this._params.user, user: this._params.user,
connectionString: this._params.connectionString,
database: 'postgres' as string, database: 'postgres' as string,
password: this._params.password, password: this._params.password,
ssl: null as ConnectionOptions ssl: null as ConnectionOptions
@@ -168,6 +169,8 @@ export class PostgreSQLClient extends BaseClient {
try { try {
this._ssh = new SSH2Promise({ this._ssh = new SSH2Promise({
...this._params.ssh, ...this._params.ssh,
reconnect: true,
reconnectTries: 3,
debug: process.env.NODE_ENV !== 'production' ? (s) => console.log(s) : null debug: process.env.NODE_ENV !== 'production' ? (s) => console.log(s) : null
}); });
@@ -210,6 +213,10 @@ export class PostgreSQLClient extends BaseClient {
if (this._params.readonly) if (this._params.readonly)
await connection.query('SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY'); await connection.query('SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY');
connection.on('error', err => { // Intercepts errors and converts to rejections
Promise.reject(err);
});
return connection; return connection;
} }
@@ -239,6 +246,10 @@ export class PostgreSQLClient extends BaseClient {
return connection; return connection;
} }
ping () {
return this.select('1+1').run();
}
destroy () { destroy () {
this._connection.end(); this._connection.end();
clearInterval(this._keepaliveTimer); clearInterval(this._keepaliveTimer);
@@ -331,6 +342,19 @@ export class PostgreSQLClient extends BaseClient {
ORDER BY table_name ORDER BY table_name
`); `);
let { rows: matViews } = await this.raw<antares.QueryResult<ShowTableResult>>(`
SELECT schemaname AS schema_name,
matviewname AS table_name,
matviewowner AS owner,
ispopulated AS is_populated,
definition,
'materializedview' AS table_type
FROM pg_matviews
WHERE schemaname = '${db.database}'
ORDER BY schema_name,
table_name;
`);
if (tables.length) { if (tables.length) {
tables = tables.map(table => { tables = tables.map(table => {
table.Db = db.database; table.Db = db.database;
@@ -339,6 +363,14 @@ export class PostgreSQLClient extends BaseClient {
tablesArr.push(...tables); tablesArr.push(...tables);
} }
if (matViews.length) {
matViews = matViews.map(view => {
view.Db = db.database;
return view;
});
tablesArr.push(...matViews);
}
let { rows: triggers } = await this.raw<antares.QueryResult<ShowTriggersResult>>(` let { rows: triggers } = await this.raw<antares.QueryResult<ShowTriggersResult>>(`
SELECT SELECT
pg_class.relname AS table_name, pg_class.relname AS table_name,
@@ -374,7 +406,11 @@ export class PostgreSQLClient extends BaseClient {
return { return {
name: table.table_name, name: table.table_name,
type: table.table_type === 'VIEW' ? 'view' : 'table', type: table.table_type === 'VIEW'
? 'view'
: table.table_type === 'materializedview'
? 'materializedview'
: 'table',
rows: table.reltuples, rows: table.reltuples,
size: tableSize, size: tableSize,
collation: table.Collation, collation: table.Collation,
@@ -465,16 +501,27 @@ export class PostgreSQLClient extends BaseClient {
column_default: string; column_default: string;
character_set_name: string; character_set_name: string;
collation_name: string; collation_name: string;
column_comment: string;
} }
/* eslint-enable camelcase */ /* eslint-enable camelcase */
const { rows } = await this // Table columns
.select('*') const { rows } = await this.raw<antares.QueryResult<TableColumnsResult>>(`
.schema('information_schema') WITH comments AS (
.from('columns') SELECT attr.attname AS column, des.description AS comment, pgc.relname
.where({ table_schema: `= '${schema}'`, table_name: `= '${table}'` }) FROM pg_attribute AS attr, pg_description AS des, pg_class AS pgc
.orderBy({ ordinal_position: 'ASC' }) WHERE pgc.oid = attr.attrelid
.run<TableColumnsResult>(); AND des.objoid = pgc.oid
AND pg_table_is_visible(pgc.oid)
AND attr.attnum = des.objsubid
)
SELECT cols.*, comments.comment AS column_comment
FROM "information_schema"."columns" AS cols
LEFT JOIN comments ON comments.column = cols.column_name AND comments.relname = cols.table_name
WHERE cols.table_schema = '${schema}'
AND cols.table_name = '${table}'
ORDER BY "ordinal_position" ASC
`);
return rows.map(field => { return rows.map(field => {
let type = field.data_type; let type = field.data_type;
@@ -503,7 +550,7 @@ export class PostgreSQLClient extends BaseClient {
collation: field.collation_name, collation: field.collation_name,
autoIncrement: false, autoIncrement: false,
onUpdate: null, onUpdate: null,
comment: '' comment: field.column_comment
}; };
}); });
} }
@@ -562,8 +609,8 @@ export class PostgreSQLClient extends BaseClient {
} }
/* eslint-enable camelcase */ /* eslint-enable camelcase */
if (schema !== 'public') // if (schema !== 'public')
await this.use(schema); await this.use(schema);
const { rows } = await this.raw<antares.QueryResult<ShowIntexesResult>>(`WITH ndx_list AS ( const { rows } = await this.raw<antares.QueryResult<ShowIntexesResult>>(`WITH ndx_list AS (
SELECT pg_index.indexrelid, pg_class.oid SELECT pg_index.indexrelid, pg_class.oid
@@ -607,35 +654,7 @@ export class PostgreSQLClient extends BaseClient {
}, {} as {table: string; schema: string}[]); }, {} as {table: string; schema: string}[]);
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async getTableDll ({ schema, table }: { schema: string; table: string }) { async getTableDll ({ schema, table }: { schema: string; table: string }) {
// const { rows } = await this.raw<antares.QueryResult<{'ddl'?: string}>>(`
// SELECT
// 'CREATE TABLE ' || relname || E'\n(\n' ||
// array_to_string(
// array_agg(' ' || column_name || ' ' || type || ' '|| not_null)
// , E',\n'
// ) || E'\n);\n' AS ddl
// FROM (
// SELECT
// a.attname AS column_name
// , pg_catalog.format_type(a.atttypid, a.atttypmod) AS type
// , CASE WHEN a.attnotnull THEN 'NOT NULL' ELSE 'NULL' END AS not_null
// , c.relname
// FROM pg_attribute a, pg_class c, pg_type t
// WHERE a.attnum > 0
// AND a.attrelid = c.oid
// AND a.atttypid = t.oid
// AND c.relname = '${table}'
// ORDER BY a.attnum
// ) AS tabledefinition
// GROUP BY relname
// `);
// if (rows.length)
// return rows[0].ddl;
// else return '';
/* eslint-disable camelcase */ /* eslint-disable camelcase */
interface SequenceRecord { interface SequenceRecord {
sequence_catalog: string; sequence_catalog: string;
@@ -677,6 +696,34 @@ export class PostgreSQLClient extends BaseClient {
if (!rows.length) return ''; if (!rows.length) return '';
const indexes = await this.getTableIndexes({ schema, table });
const primaryKey = indexes
.filter(i => i.type === 'PRIMARY')
.reduce((acc, cur) => {
if (!Object.keys(acc).length) {
cur.column = `"${cur.column}"`;
acc = cur;
}
else
acc.column += `, "${cur.column}"`;
return acc;
}, {} as { name: string; column: string; type: string});
const remappedIndexes = indexes
.filter(i => i.type !== 'PRIMARY')
.reduce((acc, cur) => {
const existingIndex = acc.findIndex(i => i.name === cur.name);
if (existingIndex >= 0)
acc[existingIndex].column += `, "${cur.column}"`;
else {
cur.column = `"${cur.column}"`;
acc.push(cur);
}
return acc;
}, [] as { name: string; column: string; type: string}[]);
for (const column of rows) { for (const column of rows) {
let fieldType = column.data_type; let fieldType = column.data_type;
if (fieldType === 'USER-DEFINED') fieldType = `"${schema}".${column.udt_name}`; if (fieldType === 'USER-DEFINED') fieldType = `"${schema}".${column.udt_name}`;
@@ -704,6 +751,9 @@ export class PostgreSQLClient extends BaseClient {
columnsSql.push(columnArr.join(' ')); columnsSql.push(columnArr.join(' '));
} }
if (primaryKey)
columnsSql.push(`CONSTRAINT "${primaryKey.name}" PRIMARY KEY (${primaryKey.column})`);
// Table sequences // Table sequences
for (let sequence of sequences) { for (let sequence of sequences) {
if (sequence.includes('.')) sequence = sequence.split('.')[1]; if (sequence.includes('.')) sequence = sequence.split('.')[1];
@@ -720,25 +770,22 @@ export class PostgreSQLClient extends BaseClient {
INCREMENT BY ${rows[0].increment} INCREMENT BY ${rows[0].increment}
MINVALUE ${rows[0].minimum_value} MINVALUE ${rows[0].minimum_value}
MAXVALUE ${rows[0].maximum_value} MAXVALUE ${rows[0].maximum_value}
CACHE 1;\n`; CACHE 1;\n\n`;
} }
} }
// Table create // Table create
createSql += `\nCREATE TABLE "${schema}"."${table}"( createSql += `CREATE TABLE "${schema}"."${table}"(
${columnsSql.join(',\n ')} ${columnsSql.join(',\n ')}
);\n`; );\n`;
// Table indexes // Table indexes
createSql += '\n'; createSql += '\n';
const { rows: indexes } = await this.select('*')
.schema('pg_catalog')
.from('pg_indexes')
.where({ schemaname: `= '${schema}'`, tablename: `= '${table}'` })
.run<{indexdef: string}>();
for (const index of indexes) for (const index of remappedIndexes) {
createSql += `${index.indexdef};\n`; if (index.type !== 'PRIMARY')
createSql += `CREATE ${index.type}${index.type === 'UNIQUE' ? ' INDEX' : ''} "${index.name}" ON "${schema}"."${table}" (${index.column});\n`;
}
return createSql; return createSql;
} }
@@ -840,6 +887,7 @@ export class PostgreSQLClient extends BaseClient {
const newIndexes: string[] = []; const newIndexes: string[] = [];
const manageIndexes: string[] = []; const manageIndexes: string[] = [];
const newForeigns: string[] = []; const newForeigns: string[] = [];
const modifyComment: string[] = [];
let sql = `CREATE TABLE "${schema}"."${options.name}"`; let sql = `CREATE TABLE "${schema}"."${options.name}"`;
@@ -855,6 +903,8 @@ export class PostgreSQLClient extends BaseClient {
${field.nullable ? 'NULL' : 'NOT NULL'} ${field.nullable ? 'NULL' : 'NOT NULL'}
${field.default !== null ? `DEFAULT ${field.default || '\'\''}` : ''} ${field.default !== null ? `DEFAULT ${field.default || '\'\''}` : ''}
${field.onUpdate ? `ON UPDATE ${field.onUpdate}` : ''}`); ${field.onUpdate ? `ON UPDATE ${field.onUpdate}` : ''}`);
if (field.comment != null)
modifyComment.push(`COMMENT ON COLUMN "${schema}"."${options.name}"."${field.name}" IS '${field.comment}'`);
}); });
// ADD INDEX // ADD INDEX
@@ -875,8 +925,12 @@ export class PostgreSQLClient extends BaseClient {
newForeigns.push(`CONSTRAINT "${foreign.constraintName}" FOREIGN KEY ("${foreign.field}") REFERENCES "${schema}"."${foreign.refTable}" ("${foreign.refField}") ON UPDATE ${foreign.onUpdate} ON DELETE ${foreign.onDelete}`); newForeigns.push(`CONSTRAINT "${foreign.constraintName}" FOREIGN KEY ("${foreign.field}") REFERENCES "${schema}"."${foreign.refTable}" ("${foreign.refField}") ON UPDATE ${foreign.onUpdate} ON DELETE ${foreign.onDelete}`);
}); });
sql = `${sql} (${[...newColumns, ...newIndexes, ...newForeigns].join(', ')})`; sql = `${sql} (${[...newColumns, ...newIndexes, ...newForeigns].join(', ')}); `;
if (manageIndexes.length) sql = `${sql}; ${manageIndexes.join(';')}`; if (manageIndexes.length) sql = `${sql} ${manageIndexes.join(';')}; `;
// TABLE COMMENT
if (options.comment != null) sql = `${sql} COMMENT ON TABLE "${schema}"."${options.name}" IS '${options.comment}'; `;
// FIELDS COMMENT
if (modifyComment.length) sql = `${sql} ${modifyComment.join(';')}; `;
return await this.raw(sql); return await this.raw(sql);
} }
@@ -901,6 +955,7 @@ export class PostgreSQLClient extends BaseClient {
const renameColumns: string[] = []; const renameColumns: string[] = [];
const createSequences: string[] = []; const createSequences: string[] = [];
const manageIndexes: string[] = []; const manageIndexes: string[] = [];
const modifyComment: string[] = [];
// ADD FIELDS // ADD FIELDS
additions.forEach(addition => { additions.forEach(addition => {
@@ -914,6 +969,8 @@ export class PostgreSQLClient extends BaseClient {
${addition.nullable ? 'NULL' : 'NOT NULL'} ${addition.nullable ? 'NULL' : 'NOT NULL'}
${addition.default !== null ? `DEFAULT ${addition.default || '\'\''}` : ''} ${addition.default !== null ? `DEFAULT ${addition.default || '\'\''}` : ''}
${addition.onUpdate ? `ON UPDATE ${addition.onUpdate}` : ''}`); ${addition.onUpdate ? `ON UPDATE ${addition.onUpdate}` : ''}`);
if (addition.comment != null)
modifyComment.push(`COMMENT ON COLUMN "${schema}"."${table}"."${addition.name}" IS '${addition.comment}'`);
}); });
// ADD INDEX // ADD INDEX
@@ -966,6 +1023,8 @@ export class PostgreSQLClient extends BaseClient {
if (change.orgName !== change.name) if (change.orgName !== change.name)
renameColumns.push(`ALTER TABLE "${schema}"."${table}" RENAME COLUMN "${change.orgName}" TO "${change.name}"`); renameColumns.push(`ALTER TABLE "${schema}"."${table}" RENAME COLUMN "${change.orgName}" TO "${change.name}"`);
if (change.comment != null)
modifyComment.push(`COMMENT ON COLUMN "${schema}"."${table}"."${change.name}" IS '${change.comment}'`);
}); });
// CHANGE INDEX // CHANGE INDEX
@@ -1013,8 +1072,11 @@ export class PostgreSQLClient extends BaseClient {
if (alterColumns.length) sql += `ALTER TABLE "${schema}"."${table}" ${alterColumns.join(', ')}; `; if (alterColumns.length) sql += `ALTER TABLE "${schema}"."${table}" ${alterColumns.join(', ')}; `;
if (createSequences.length) sql = `${createSequences.join(';')}; ${sql}`; if (createSequences.length) sql = `${createSequences.join(';')}; ${sql}`;
if (manageIndexes.length) sql = `${manageIndexes.join(';')}; ${sql}`; if (manageIndexes.length) sql = `${manageIndexes.join(';')}; ${sql}`;
// TABLE COMMENT
if (options.comment != null) sql = `${sql} COMMENT ON TABLE "${schema}"."${table}" IS '${options.comment}'; `;
// FIELDS COMMENT
if (modifyComment.length) sql = `${sql} ${modifyComment.join(';')}; `;
if (options.name) sql += `ALTER TABLE "${schema}"."${table}" RENAME TO "${options.name}"; `; if (options.name) sql += `ALTER TABLE "${schema}"."${table}" RENAME TO "${options.name}"; `;
// RENAME // RENAME
if (renameColumns.length) sql = `${renameColumns.join(';')}; ${sql}`; if (renameColumns.length) sql = `${renameColumns.join(';')}; ${sql}`;
@@ -1052,11 +1114,32 @@ export class PostgreSQLClient extends BaseClient {
})[0]; })[0];
} }
async getMaterializedViewInformations ({ schema, view }: { schema: string; view: string }) {
const sql = `SELECT "definition" FROM "pg_matviews" WHERE "matviewname"='${view}' AND "schemaname"='${schema}'`;
const results = await this.raw(sql);
return results.rows.map(row => {
return {
algorithm: '',
definer: '',
security: '',
updateOption: '',
sql: row.definition,
name: view
};
})[0];
}
async dropView (params: { schema: string; view: string }) { async dropView (params: { schema: string; view: string }) {
const sql = `DROP VIEW "${params.schema}"."${params.view}"`; const sql = `DROP VIEW "${params.schema}"."${params.view}"`;
return await this.raw(sql); return await this.raw(sql);
} }
async dropMaterializedView (params: { schema: string; view: string }) {
const sql = `DROP MATERIALIZED VIEW "${params.schema}"."${params.view}"`;
return await this.raw(sql);
}
async alterView ({ view }: { view: antares.AlterViewParams }) { async alterView ({ view }: { view: antares.AlterViewParams }) {
let sql = `CREATE OR REPLACE VIEW "${view.schema}"."${view.oldName}" AS ${view.sql}`; let sql = `CREATE OR REPLACE VIEW "${view.schema}"."${view.oldName}" AS ${view.sql}`;
@@ -1066,11 +1149,25 @@ export class PostgreSQLClient extends BaseClient {
return await this.raw(sql); return await this.raw(sql);
} }
async alterMaterializedView ({ view }: { view: antares.AlterViewParams }) {
let sql = `CREATE OR REPLACE MATERIALIZED VIEW "${view.schema}"."${view.oldName}" AS ${view.sql}`;
if (view.name !== view.oldName)
sql += `; ALTER VIEW "${view.schema}"."${view.oldName}" RENAME TO "${view.name}"`;
return await this.raw(sql);
}
async createView (params: antares.CreateViewParams) { async createView (params: antares.CreateViewParams) {
const sql = `CREATE VIEW "${params.schema}"."${params.name}" AS ${params.sql}`; const sql = `CREATE VIEW "${params.schema}"."${params.name}" AS ${params.sql}`;
return await this.raw(sql); return await this.raw(sql);
} }
async createMaterializedView (params: antares.CreateViewParams) {
const sql = `CREATE MATERIALIZED VIEW "${params.schema}"."${params.name}" AS ${params.sql}`;
return await this.raw(sql);
}
async getTriggerInformations ({ schema, trigger }: { schema: string; trigger: string }) { async getTriggerInformations ({ schema, trigger }: { schema: string; trigger: string }) {
const [table, triggerName] = trigger.split('.'); const [table, triggerName] = trigger.split('.');
@@ -1551,7 +1648,7 @@ export class PostgreSQLClient extends BaseClient {
} }
async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) { async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) {
this._logger({ cUid: this._cUid, sql }); this._logger({ cUid: this._cUid, content: sql, level: 'query' });
args = { args = {
nest: false, nest: false,

View File

@@ -35,6 +35,10 @@ export class SQLiteClient extends BaseClient {
}); });
} }
ping () {
return this.select('1+1').run();
}
destroy () { destroy () {
this._connection.close(); this._connection.close();
} }
@@ -608,7 +612,7 @@ export class SQLiteClient extends BaseClient {
} }
async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) { async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) {
this._logger({ cUid: this._cUid, sql });// TODO: replace BLOB content with a placeholder this._logger({ cUid: this._cUid, content: sql, level: 'query' });// TODO: replace BLOB content with a placeholder
args = { args = {
nest: false, nest: false,

View File

@@ -336,7 +336,8 @@ CREATE TABLE \`${view.Name}\`(
const connection = await this._client.getConnection(); const connection = await this._client.getConnection();
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const stream = (connection as any).connection.query(sql).stream(); const stream = (connection as any).connection.query(sql).stream();
const dispose = () => connection.end(); // eslint-disable-next-line @typescript-eslint/no-explicit-any
const dispose = () => (connection as any).release();
stream.on('end', dispose); stream.on('end', dispose);
stream.on('error', dispose); stream.on('error', dispose);

View File

@@ -39,115 +39,7 @@ SET row_security = off;\n\n\n`;
} }
async getCreateTable (tableName: string) { async getCreateTable (tableName: string) {
/* eslint-disable camelcase */ const createSql = await this._client.getTableDll({ schema: this.schemaName, table: tableName });
interface SequenceRecord {
sequence_catalog: string;
sequence_schema: string;
sequence_name: string;
data_type: string;
numeric_precision: number;
numeric_precision_radix: number;
numeric_scale: number;
start_value: string;
minimum_value: string;
maximum_value: string;
increment: string;
cycle_option: string;
}
/* eslint-enable camelcase */
let createSql = '';
const sequences = [];
const columnsSql = [];
const arrayTypes: Record<string, string> = {
_int2: 'smallint',
_int4: 'integer',
_int8: 'bigint',
_float4: 'real',
_float8: 'double precision',
_char: '"char"',
_varchar: 'character varying'
};
// Table columns
const { rows } = await this._client.raw(`
SELECT *
FROM "information_schema"."columns"
WHERE "table_schema" = '${this.schemaName}'
AND "table_name" = '${tableName}'
ORDER BY "ordinal_position" ASC
`, { schema: 'information_schema' });
if (!rows.length) return '';
for (const column of rows) {
let fieldType = column.data_type;
if (fieldType === 'USER-DEFINED') fieldType = `"${this.schemaName}".${column.udt_name}`;
else if (fieldType === 'ARRAY') {
if (Object.keys(arrayTypes).includes(fieldType))
fieldType = arrayTypes[column.udt_name] + '[]';
else
fieldType = column.udt_name.replaceAll('_', '') + '[]';
}
const columnArr = [
`"${column.column_name}"`,
`${fieldType}${column.character_maximum_length ? `(${column.character_maximum_length})` : ''}`
];
if (column.column_default) {
columnArr.push(`DEFAULT ${column.column_default}`);
if (column.column_default.includes('nextval')) {
const sequenceName = column.column_default.split('\'')[1];
sequences.push(sequenceName);
}
}
if (column.is_nullable === 'NO') columnArr.push('NOT NULL');
columnsSql.push(columnArr.join(' '));
}
// Table sequences
for (let sequence of sequences) {
if (sequence.includes('.')) sequence = sequence.split('.')[1];
const { rows } = await this._client
.select('*')
.schema('information_schema')
.from('sequences')
.where({ sequence_schema: `= '${this.schemaName}'`, sequence_name: `= '${sequence}'` })
.run<SequenceRecord>();
if (rows.length) {
createSql += `CREATE SEQUENCE "${this.schemaName}"."${sequence}"
START WITH ${rows[0].start_value}
INCREMENT BY ${rows[0].increment}
MINVALUE ${rows[0].minimum_value}
MAXVALUE ${rows[0].maximum_value}
CACHE 1;\n`;
// createSql += `\nALTER TABLE "${sequence}" OWNER TO ${this._client._params.user};\n\n`;
}
}
// Table create
createSql += `\nCREATE TABLE "${this.schemaName}"."${tableName}"(
${columnsSql.join(',\n ')}
);\n`;
// createSql += `\nALTER TABLE "${tableName}" OWNER TO ${this._client._params.user};\n\n`;
// Table indexes
createSql += '\n';
const { rows: indexes } = await this._client
.select('*')
.schema('pg_catalog')
.from('pg_indexes')
.where({ schemaname: `= '${this.schemaName}'`, tablename: `= '${tableName}'` })
.run<{indexdef: string}>();
for (const index of indexes)
createSql += `${index.indexdef};\n`;
// Table foreigns // Table foreigns
const { rows: foreigns } = await this._client.raw(` const { rows: foreigns } = await this._client.raw(`

View File

@@ -10,9 +10,7 @@
:key="connection.uid" :key="connection.uid"
:connection="connection" :connection="connection"
/> />
<div class="connection-panel-wrapper p-relative"> <WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
</div>
</div> </div>
<TheFooter /> <TheFooter />
<TheNotificationsBoard /> <TheNotificationsBoard />
@@ -48,6 +46,8 @@ import { useSchemaExportStore } from '@/stores/schemaExport';
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import { useConsoleStore } from './stores/console';
const { t } = useI18n(); const { t } = useI18n();
const TheTitleBar = defineAsyncComponent(() => import(/* webpackChunkName: "TheTitleBar" */'@/components/TheTitleBar.vue')); const TheTitleBar = defineAsyncComponent(() => import(/* webpackChunkName: "TheTitleBar" */'@/components/TheTitleBar.vue'));
@@ -80,6 +80,8 @@ const schemaExportStore = useSchemaExportStore();
const { hideExportModal } = schemaExportStore; const { hideExportModal } = schemaExportStore;
const { isExportModal: isExportSchemaModal } = storeToRefs(schemaExportStore); const { isExportModal: isExportSchemaModal } = storeToRefs(schemaExportStore);
const consoleStore = useConsoleStore();
const isAllConnectionsModal: Ref<boolean> = ref(false); const isAllConnectionsModal: Ref<boolean> = ref(false);
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
@@ -139,8 +141,11 @@ onMounted(() => {
while (node) { while (node) {
if (node.nodeName.match(/^(input|textarea)$/i) || node.isContentEditable) { if (node.nodeName.match(/^(input|textarea)$/i) || node.isContentEditable) {
InputMenu.popup({ window: getCurrentWindow() }); if (!node.parentNode.className.split(' ').includes('editor-query')) {
break; InputMenu.popup({ window: getCurrentWindow() });
console.log(node.parentNode.className);
break;
}
} }
node = node.parentNode; node = node.parentNode;
} }
@@ -152,6 +157,60 @@ onMounted(() => {
} }
}); });
}); });
// Console messages
const oldLog = console.log;
const oldWarn = console.warn;
const oldInfo = console.info;
const oldError = console.error;
console.log = function (...args) {
consoleStore.putLog('debug', {
level: 'log',
process: 'renderer',
message: args.join(' '),
date: new Date()
});
oldLog.apply(this, args);
};
console.info = function (...args) {
consoleStore.putLog('debug', {
level: 'info',
process: 'renderer',
message: args.join(' '),
date: new Date()
});
oldInfo.apply(this, args);
};
console.warn = function (...args) {
consoleStore.putLog('debug', {
level: 'warn',
process: 'renderer',
message: args.join(' '),
date: new Date()
});
oldWarn.apply(this, args);
};
console.error = function (...args) {
consoleStore.putLog('debug', {
level: 'error',
process: 'renderer',
message: args.join(' '),
date: new Date()
});
oldError.apply(this, args);
};
window.addEventListener('unhandledrejection', (event) => {
console.error(event.reason);
});
window.addEventListener('error', (event) => {
console.error(event.error, '| File name:', event.filename.split('/').pop().split('?')[0]);
});
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@@ -1,11 +1,19 @@
<template> <template>
<SvgIcon <SvgIcon
v-if="type === 'mdi'"
:type="type" :type="type"
:path="iconPath" :path="iconPath"
:size="size" :size="size"
:rotate="rotate" :rotate="rotate"
:class="iconFlip" :class="iconFlip"
/> />
<svg
v-else
:width="size"
:height="size"
:viewBox="`0 0 ${size} ${size}`"
v-html="iconPath"
/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -13,6 +21,10 @@ import SvgIcon from '@jamescoyle/vue-icon';
import * as Icons from '@mdi/js'; import * as Icons from '@mdi/js';
import { computed, PropType } from 'vue'; import { computed, PropType } from 'vue';
import { useConnectionsStore } from '@/stores/connections';
const { getIconByUid } = useConnectionsStore();
const props = defineProps({ const props = defineProps({
iconName: { iconName: {
type: String, type: String,
@@ -23,7 +35,7 @@ const props = defineProps({
default: 48 default: 48
}, },
type: { type: {
type: String, type: String as PropType<'mdi' | 'custom'>,
default: () => 'mdi' default: () => 'mdi'
}, },
flip: { flip: {
@@ -37,7 +49,18 @@ const props = defineProps({
}); });
const iconPath = computed(() => { const iconPath = computed(() => {
return (Icons as {[k:string]: string})[props.iconName]; if (props.type === 'mdi')
return (Icons as {[k:string]: string})[props.iconName];
else if (props.type === 'custom') {
const base64 = getIconByUid(props.iconName)?.base64;
const svgString = Buffer
.from(base64, 'base64')
.toString('utf-8')
.replaceAll(/width="[^"]*"|height="[^"]*"/g, '');
return svgString;
}
return null;
}); });
const iconFlip = computed(() => { const iconFlip = computed(() => {

View File

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

View File

@@ -365,7 +365,11 @@ export default defineComponent({
}; };
const handleWheelEvent = (e) => { const handleWheelEvent = (e) => {
if (!e.target.className.includes('select__')) deactivate(); try {
if (!e.target.className.includes('select__')) deactivate();
}
catch (_) {
}
}; };
onMounted(() => { onMounted(() => {

View File

@@ -0,0 +1,285 @@
<template>
<div
ref="wrapper"
class="console-wrapper"
@mouseenter="isHover = true"
@mouseleave="isHover = false"
>
<div ref="resizer" class="console-resizer" />
<div
id="console"
ref="queryConsole"
class="console column col-12"
:style="{height: localHeight ? localHeight+'px' : ''}"
>
<div class="console-header">
<ul class="tab tab-block">
<li class="tab-item" :class="{'active': selectedTab === 'query'}">
<a class="tab-link" @click="selectedTab = 'query'">{{ t('application.executedQueries') }}</a>
</li>
<li class="tab-item" :class="{'active': selectedTab === 'debug'}">
<a class="tab-link" @click="selectedTab = 'debug'">{{ t('application.debugConsole') }}</a>
</li>
</ul>
<button class="btn btn-clear mr-1" @click="resizeConsole(0)" />
</div>
<div
v-show="selectedTab === 'query'"
ref="queryConsoleBody"
class="console-body"
>
<div
v-for="(wLog, i) in workspaceQueryLogs"
:key="i"
class="console-log"
tabindex="0"
@contextmenu.prevent="contextMenu($event, wLog)"
>
<span class="console-log-datetime">{{ moment(wLog.date).format('HH:mm:ss') }}</span>: <code class="console-log-sql" v-html="highlight(wLog.sql, {html: true})" />
</div>
</div>
<div
v-show="selectedTab === 'debug'"
ref="logConsoleBody"
class="console-body"
>
<div
v-for="(log, i) in debugLogs"
:key="i"
class="console-log"
tabindex="0"
@contextmenu.prevent="contextMenu($event, log)"
>
<span class="console-log-datetime">{{ moment(log.date).format('HH:mm:ss') }}</span> <small>[{{ log.process.substring(0, 1).toUpperCase() }}]</small>: <span class="console-log-message" :class="`console-log-level-${log.level}`">{{ log.message }}</span>
</div>
</div>
</div>
</div>
<BaseContextMenu
v-if="isContext"
:context-event="contextEvent"
@close-context="isContext = false"
>
<div class="context-element" @click="copyLog">
<span class="d-flex">
<BaseIcon
class="text-light mt-1 mr-1"
icon-name="mdiContentCopy"
:size="18"
/> {{ t('general.copy') }}</span>
</div>
</BaseContextMenu>
</template>
<script setup lang="ts">
import * as moment from 'moment';
import { storeToRefs } from 'pinia';
import { highlight } from 'sql-highlight';
import { computed, nextTick, onMounted, Ref, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import BaseContextMenu from '@/components/BaseContextMenu.vue';
import BaseIcon from '@/components/BaseIcon.vue';
import { copyText } from '@/libs/copyText';
import { useConsoleStore } from '@/stores/console';
const { t } = useI18n();
const consoleStore = useConsoleStore();
const { resizeConsole, getLogsByWorkspace } = consoleStore;
const {
isConsoleOpen,
consoleHeight,
selectedTab,
debugLogs
} = storeToRefs(consoleStore);
const props = defineProps({
uid: {
type: String,
default: null,
required: false
}
});
const wrapper: Ref<HTMLInputElement> = ref(null);
const queryConsole: Ref<HTMLInputElement> = ref(null);
const queryConsoleBody: Ref<HTMLInputElement> = ref(null);
const logConsoleBody: Ref<HTMLInputElement> = ref(null);
const resizer: Ref<HTMLInputElement> = ref(null);
const localHeight = ref(consoleHeight.value);
const isHover = ref(false);
const isContext = ref(false);
const contextContent: Ref<string> = ref(null);
const contextEvent: Ref<MouseEvent> = ref(null);
const resize = (e: MouseEvent) => {
const el = queryConsole.value;
let elementHeight = el.getBoundingClientRect().bottom - e.pageY;
if (elementHeight > 400) elementHeight = 400;
localHeight.value = elementHeight;
};
const workspaceQueryLogs = computed(() => {
return getLogsByWorkspace(props.uid);
});
const stopResize = () => {
if (localHeight.value < 0) localHeight.value = 0;
resizeConsole(localHeight.value);
window.removeEventListener('mousemove', resize);
window.removeEventListener('mouseup', stopResize);
};
const contextMenu = (event: MouseEvent, wLog: {date: Date; sql?: string; message?: string}) => {
contextEvent.value = event;
contextContent.value = wLog.sql || wLog.message;
isContext.value = true;
};
const copyLog = () => {
copyText(contextContent.value);
isContext.value = false;
};
watch(workspaceQueryLogs, async () => {
if (!isHover.value) {
await nextTick();
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
}
});
watch(() => debugLogs.value.length, async () => {
if (!isHover.value) {
await nextTick();
logConsoleBody.value.scrollTop = logConsoleBody.value.scrollHeight;
}
});
watch(isConsoleOpen, async () => {
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
logConsoleBody.value.scrollTop = logConsoleBody.value.scrollHeight;
});
watch(selectedTab, async () => {
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
logConsoleBody.value.scrollTop = logConsoleBody.value.scrollHeight;
});
watch(consoleHeight, async (val) => {
await nextTick();
localHeight.value = val;
});
onMounted(() => {
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
logConsoleBody.value.scrollTop = logConsoleBody.value.scrollHeight;
resizer.value.addEventListener('mousedown', (e: MouseEvent) => {
e.preventDefault();
window.addEventListener('mousemove', resize);
window.addEventListener('mouseup', stopResize);
});
});
</script>
<style lang="scss" scoped>
.console-wrapper {
width: -webkit-fill-available;
z-index: 9;
margin-top: auto;
position: absolute;
bottom: 0;
.console-resizer {
height: 4px;
top: -1px;
width: 100%;
cursor: ns-resize;
position: absolute;
z-index: 99;
transition: background 0.2s;
&:hover {
background: var(--primary-color-dark);
}
}
.console {
padding: 0;
padding-bottom: $footer-height;
.console-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 4px;
.tab-block {
margin-top: 0;
margin-bottom: 0;
}
.tab-block,
.tab-item {
background-color: transparent;
}
.tab-link {
padding: 0.2rem 0.6rem;
cursor: pointer;
white-space: nowrap;
}
}
.console-body {
overflow: auto;
display: flex;
flex-direction: column;
max-height: 100%;
padding: 0 6px 3px;
.console-log {
padding: 1px 3px;
margin: 1px 0;
border-radius: $border-radius;
user-select: text;
&-datetime {
opacity: .6;
font-size: 90%;
}
&-sql {
font-size: 95%;
opacity: 0.8;
font-weight: 700;
&:hover {
user-select: text;
}
}
&-message {
font-size: 95%;
}
&-level {
// &-log,
// &-info {}
&-warn {
color: orange;
}
&-error {
color: red;
}
}
small {
opacity: .6;
}
}
}
}
}
</style>

View File

@@ -57,8 +57,22 @@
> >
<div class="panel"> <div class="panel">
<div class="panel-header p-2 text-center p-relative"> <div class="panel-header p-2 text-center p-relative">
<figure class="avatar avatar-lg pt-1 mb-1"> <figure class="avatar avatar-lg pt-1 mb-1 bg-dark">
<i class="settingbar-element-icon dbi" :class="[`dbi-${connection.client}`]" /> <div
v-if="connection.icon"
class="settingbar-connection-icon"
>
<BaseIcon
:icon-name="camelize(connection.icon)"
:type="connection.hasCustomIcon ? 'custom' : 'mdi'"
:size="42"
/>
</div>
<div
v-else
class="settingbar-element-icon dbi ml-1"
:class="[`dbi-${connection.client}`]"
/>
</figure> </figure>
<div class="panel-title h6 text-ellipsis"> <div class="panel-title h6 text-ellipsis">
{{ getConnectionName(connection.uid) }} {{ getConnectionName(connection.uid) }}
@@ -136,7 +150,19 @@
</div> </div>
</div> </div>
<div class="panel-footer text-center py-0"> <div class="panel-footer text-center py-0">
<div v-if="connection.ssl" class="chip bg-success mt-2"> <div
v-if="connection.folderName"
class="chip mt-2 bg-dark"
>
<BaseIcon
icon-name="mdiFolder"
class="mr-1"
:style="[connection.color ? `color: ${connection.color};`: '']"
:size="18"
/>
{{ connection.folderName }}
</div>
<div v-if="connection.ssl" class="chip bg-dark mt-2">
<BaseIcon <BaseIcon
icon-name="mdiShieldKey" icon-name="mdiShieldKey"
class="mr-1" class="mr-1"
@@ -144,7 +170,7 @@
/> />
SSL SSL
</div> </div>
<div v-if="connection.ssh" class="chip bg-success mt-2"> <div v-if="connection.ssh" class="chip bg-dark mt-2">
<BaseIcon <BaseIcon
icon-name="mdiConsoleNetwork" icon-name="mdiConsoleNetwork"
class="mr-1" class="mr-1"
@@ -152,7 +178,7 @@
/> />
SSH SSH
</div> </div>
<div v-if="connection.readonly" class="chip bg-success mt-2"> <div v-if="connection.readonly" class="chip bg-dark mt-2">
<BaseIcon <BaseIcon
icon-name="mdiLock" icon-name="mdiLock"
class="mr-1" class="mr-1"
@@ -209,6 +235,7 @@ import { useI18n } from 'vue-i18n';
import ConfirmModal from '@/components/BaseConfirmModal.vue'; import ConfirmModal from '@/components/BaseConfirmModal.vue';
import BaseIcon from '@/components/BaseIcon.vue'; import BaseIcon from '@/components/BaseIcon.vue';
import { useFocusTrap } from '@/composables/useFocusTrap'; import { useFocusTrap } from '@/composables/useFocusTrap';
import { camelize } from '@/libs/camelize';
import { useConnectionsStore } from '@/stores/connections'; import { useConnectionsStore } from '@/stores/connections';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
@@ -218,7 +245,9 @@ const connectionsStore = useConnectionsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { connections, const { connections,
lastConnections connectionsOrder,
lastConnections,
getFolders: folders
} = storeToRefs(connectionsStore); } = storeToRefs(connectionsStore);
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
@@ -236,7 +265,8 @@ const clients = new Map([
['mysql', 'MySQL'], ['mysql', 'MySQL'],
['maria', 'MariaDB'], ['maria', 'MariaDB'],
['pg', 'PostgreSQL'], ['pg', 'PostgreSQL'],
['sqlite', 'SQLite'] ['sqlite', 'SQLite'],
['firebird', 'Firebird SQL']
]); ]);
const searchTerm = ref(''); const searchTerm = ref('');
@@ -244,12 +274,20 @@ const isConfirmModal = ref(false);
const connectionHover: Ref<string> = ref(null); const connectionHover: Ref<string> = ref(null);
const selectedConnection: Ref<ConnectionParams> = ref(null); const selectedConnection: Ref<ConnectionParams> = ref(null);
const sortedConnections = computed(() => { const remappedConnections = computed(() => {
return connections.value return connections.value
.map(c => { .map(c => {
const connTime = lastConnections.value.find((lc) => lc.uid === c.uid)?.time || 0; const connTime = lastConnections.value.find((lc) => lc.uid === c.uid)?.time || 0;
const connIcon = connectionsOrder.value.find((co) => co.uid === c.uid).icon;
const connHasCustomIcon = connectionsOrder.value.find((co) => co.uid === c.uid).hasCustomIcon;
const folder = folders.value.find(f => f.connections.includes(c.uid));
return { return {
...c, ...c,
icon: connIcon,
color: folder?.color,
folderName: folder?.name,
hasCustomIcon: connHasCustomIcon,
time: connTime time: connTime
}; };
}) })
@@ -261,7 +299,7 @@ const sortedConnections = computed(() => {
}); });
const filteredConnections = computed(() => { const filteredConnections = computed(() => {
return sortedConnections.value.filter(connection => { return remappedConnections.value.filter(connection => {
return connection.name?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) || return connection.name?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
connection.host?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) || connection.host?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
connection.database?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) || connection.database?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
@@ -360,7 +398,7 @@ onBeforeUnmount(() => {
outline: none; outline: none;
&:focus { &:focus {
box-shadow: 0 0 3px 0.1rem rgba($primary-color, 80%); box-shadow: 0 0 3px 0.1rem rgba(var(--primary-color), 80%);
} }
&:hover { &:hover {

View File

@@ -49,18 +49,46 @@
class="icon-box" class="icon-box"
:title="icon.name" :title="icon.name"
:class="[{'selected': localConnection.icon === icon.code}]" :class="[{'selected': localConnection.icon === icon.code}]"
@click="localConnection.icon = icon.code" @click="setIcon(icon.code)"
/> />
<div <div
v-else v-else
class="icon-box" class="icon-box"
:title="icon.name" :title="icon.name"
:class="[`dbi dbi-${connection.client}`, {'selected': localConnection.icon === icon.code}]" :class="[`dbi dbi-${connection.client}`, {'selected': localConnection.icon === null}]"
@click="localConnection.icon = icon.code" @click="setIcon(null)"
/> />
</div> </div>
</div> </div>
</div> </div>
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ t('application.customIcon') }}</label>
</div>
<div class="col-9 icons-wrapper">
<div
v-for="icon in customIcons"
:key="icon.uid"
>
<BaseIcon
v-if="icon.uid"
:icon-name="icon.uid"
type="custom"
:size="36"
class="icon-box"
:class="[{'selected': localConnection.icon === icon.uid}]"
@click="setIcon(icon.uid, 'custom')"
@contextmenu.prevent="contextMenu($event, icon.uid)"
/>
</div>
<BaseIcon
:icon-name="'mdiPlus'"
:size="36"
class="icon-box"
@click="openFile"
/>
</div>
</div>
</form> </form>
</div> </div>
</div> </div>
@@ -74,20 +102,45 @@
</div> </div>
</div> </div>
</div> </div>
<BaseContextMenu
v-if="isContext"
:context-event="contextEvent"
@close-context="isContext = false"
>
<div class="context-element" @click="removeIconHandler">
<span class="d-flex">
<BaseIcon
class="text-light mt-1 mr-1"
icon-name="mdiDelete"
:size="18"
/> {{ t('general.delete') }}</span>
</div>
</BaseContextMenu>
</Teleport> </Teleport>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { storeToRefs } from 'pinia';
import { onBeforeUnmount, PropType, Ref, ref } from 'vue'; import { onBeforeUnmount, PropType, Ref, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import BaseContextMenu from '@/components/BaseContextMenu.vue';
import BaseIcon from '@/components/BaseIcon.vue'; import BaseIcon from '@/components/BaseIcon.vue';
import { useFocusTrap } from '@/composables/useFocusTrap'; import { useFocusTrap } from '@/composables/useFocusTrap';
import Application from '@/ipc-api/Application';
import { camelize } from '@/libs/camelize';
import { unproxify } from '@/libs/unproxify'; import { unproxify } from '@/libs/unproxify';
import { SidebarElement, useConnectionsStore } from '@/stores/connections'; import { SidebarElement, useConnectionsStore } from '@/stores/connections';
const connectionsStore = useConnectionsStore(); const connectionsStore = useConnectionsStore();
const { addIcon, removeIcon, updateConnectionOrder, getConnectionName } = connectionsStore;
const { customIcons } = storeToRefs(connectionsStore);
const isContext = ref(false);
const contextContent: Ref<string> = ref(null);
const contextEvent: Ref<MouseEvent> = ref(null);
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@@ -99,8 +152,6 @@ const props = defineProps({
const emit = defineEmits(['close']); const emit = defineEmits(['close']);
const { updateConnectionOrder, getConnectionName } = connectionsStore;
const icons = [ const icons = [
{ name: 'default', code: null }, { name: 'default', code: null },
@@ -160,14 +211,33 @@ const editFolderAppearance = () => {
closeModal(); closeModal();
}; };
const camelize = (text: string) => { const setIcon = (code: string, type?: 'mdi' | 'custom') => {
const textArr = text.split('-'); localConnection.value.icon = code;
for (let i = 0; i < textArr.length; i++) { localConnection.value.hasCustomIcon = type === 'custom';
if (i === 0) continue; };
textArr[i] = textArr[i].charAt(0).toUpperCase() + textArr[i].slice(1);
}
return textArr.join(''); const removeIconHandler = () => {
if (localConnection.value.icon === contextContent.value) {
setIcon(null);
updateConnectionOrder(localConnection.value);
}
removeIcon(contextContent.value);
isContext.value = false;
};
const openFile = async () => {
const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: '"SVG"', extensions: ['svg'] }] });
if (result && !result.canceled) {
const file = result.filePaths[0];
const content = await Application.readFile({ filePath: file, encoding: 'base64url' });
addIcon(content);
}
};
const contextMenu = (event: MouseEvent, iconUid: string) => {
contextEvent.value = event;
contextContent.value = iconUid;
isContext.value = true;
}; };
const closeModal = () => emit('close'); const closeModal = () => emit('close');
@@ -204,7 +274,7 @@ onBeforeUnmount(() => {
cursor: pointer; cursor: pointer;
&.selected { &.selected {
outline: 2px solid $primary-color; outline: 2px solid var(--primary-color);
border-radius: 8px; border-radius: 8px;
} }
} }

View File

@@ -282,7 +282,7 @@
import { ClientCode, SchemaInfos } from 'common/interfaces/antares'; import { ClientCode, SchemaInfos } from 'common/interfaces/antares';
import { Customizations } from 'common/interfaces/customizations'; import { Customizations } from 'common/interfaces/customizations';
import { ExportOptions, ExportState } from 'common/interfaces/exporter'; import { ExportOptions, ExportState } from 'common/interfaces/exporter';
import { ipcRenderer } from 'electron'; import { ipcRenderer, IpcRendererEvent } from 'electron';
import * as moment from 'moment'; import * as moment from 'moment';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { computed, onBeforeUnmount, Ref, ref } from 'vue'; import { computed, onBeforeUnmount, Ref, ref } from 'vue';
@@ -293,6 +293,7 @@ import BaseSelect from '@/components/BaseSelect.vue';
import { useFocusTrap } from '@/composables/useFocusTrap'; 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 { useConsoleStore } from '@/stores/console';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useSchemaExportStore } from '@/stores/schemaExport'; import { useSchemaExportStore } from '@/stores/schemaExport';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
@@ -379,21 +380,34 @@ const startExport = async () => {
try { try {
const { status, response } = await Schema.export(params); const { status, response } = await Schema.export(params);
if (status === 'success') if (status === 'success')
progressStatus.value = response.cancelled ? t('general.aborted') : t('general.completed'); progressStatus.value = response.cancelled ? t('general.aborted') : t('general.completed');
else { else {
progressStatus.value = response; progressStatus.value = response;
addNotification({ status: 'error', message: response }); addNotification({ status: 'error', message: response });
useConsoleStore().putLog('debug', {
level: 'error',
process: 'worker',
message: response,
date: new Date()
});
} }
} }
catch (err) { catch (err) {
addNotification({ status: 'error', message: err.stack }); addNotification({ status: 'error', message: err.stack });
useConsoleStore().putLog('debug', {
level: 'error',
process: 'worker',
message: err.stack,
date: new Date()
});
} }
isExporting.value = false; isExporting.value = false;
}; };
const updateProgress = (event: Event, state: ExportState) => { const updateProgress = (event: IpcRendererEvent, state: ExportState) => {
progressPercentage.value = Number((state.currentItemIndex / state.totalItems * 100).toFixed(1)); progressPercentage.value = Number((state.currentItemIndex / state.totalItems * 100).toFixed(1));
switch (state.op) { switch (state.op) {
case 'PROCESSING': case 'PROCESSING':

View File

@@ -339,6 +339,8 @@ onMounted(() => {
for (const field of props.fields) { for (const field of props.fields) {
if (typeof props.rowToDuplicate[field.name] !== 'object') if (typeof props.rowToDuplicate[field.name] !== 'object')
rowObj[field.name] = { value: props.rowToDuplicate[field.name] }; rowObj[field.name] = { value: props.rowToDuplicate[field.name] };
else if (field.type === 'JSON')
rowObj[field.name] = { value: JSON.stringify(props.rowToDuplicate[field.name]) };
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
fieldsToExclude.value = [...fieldsToExclude.value, field.name]; fieldsToExclude.value = [...fieldsToExclude.value, field.name];

View File

@@ -75,7 +75,7 @@
<code <code
class="cut-text" class="cut-text"
:title="query.sql" :title="query.sql"
v-html="highlight(highlightWord(query.sql), {html: true})" v-html="highlight(query.sql, {html: true})"
/> />
</div> </div>
<div class="tile-bottom-content"> <div class="tile-bottom-content">
@@ -210,17 +210,6 @@ const resizeResults = () => {
const refreshScroller = () => resizeResults(); const refreshScroller = () => resizeResults();
const closeModal = () => emit('close'); const closeModal = () => emit('close');
const highlightWord = (string: string) => {
string = string.replaceAll('<', '&lt;').replaceAll('>', '&gt;');
if (searchTerm.value) {
const regexp = new RegExp(`(${searchTerm.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
return string.replace(regexp, '<span class="text-primary text-bold">$1</span>');
}
else
return string;
};
const onKey = (e: KeyboardEvent) => { const onKey = (e: KeyboardEvent) => {
e.stopPropagation(); e.stopPropagation();
if (e.key === 'Escape') if (e.key === 'Escape')
@@ -287,7 +276,7 @@ onBeforeUnmount(() => {
max-width: 100%; max-width: 100%;
display: inline-block; display: inline-block;
font-size: 100%; font-size: 100%;
// color: $primary-color; // color: var(--primary-color);
opacity: 0.8; opacity: 0.8;
font-weight: 600; font-weight: 600;
} }

View File

@@ -55,7 +55,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ImportState } from 'common/interfaces/importer'; import { ImportState } from 'common/interfaces/importer';
import { ipcRenderer } from 'electron'; import { ipcRenderer, IpcRendererEvent } from 'electron';
import * as moment from 'moment'; import * as moment from 'moment';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { computed, onBeforeUnmount, Ref, ref } from 'vue'; import { computed, onBeforeUnmount, Ref, ref } from 'vue';
@@ -63,6 +63,7 @@ import { useI18n } from 'vue-i18n';
import BaseIcon from '@/components/BaseIcon.vue'; import BaseIcon from '@/components/BaseIcon.vue';
import Schema from '@/ipc-api/Schema'; import Schema from '@/ipc-api/Schema';
import { useConsoleStore } from '@/stores/console';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
@@ -118,23 +119,35 @@ const startImport = async (file: string) => {
else { else {
progressStatus.value = response; progressStatus.value = response;
addNotification({ status: 'error', message: response }); addNotification({ status: 'error', message: response });
useConsoleStore().putLog('debug', {
level: 'error',
process: 'worker',
message: response,
date: new Date()
});
} }
refreshSchema({ uid, schema: props.selectedSchema }); refreshSchema({ uid, schema: props.selectedSchema });
completed.value = true; completed.value = true;
} }
catch (err) { catch (err) {
addNotification({ status: 'error', message: err.stack }); addNotification({ status: 'error', message: err.stack });
useConsoleStore().putLog('debug', {
level: 'error',
process: 'worker',
message: err.stack,
date: new Date()
});
} }
isImporting.value = false; isImporting.value = false;
}; };
const updateProgress = (event: Event, state: ImportState) => { const updateProgress = (event: IpcRendererEvent, state: ImportState) => {
progressPercentage.value = parseFloat(Number(state.percentage).toFixed(1)); progressPercentage.value = parseFloat(Number(state.percentage).toFixed(1));
queryCount.value = Number(state.queryCount); queryCount.value = Number(state.queryCount);
}; };
const handleQueryError = (event: Event, err: { time: string; message: string }) => { const handleQueryError = (event: IpcRendererEvent, err: { time: string; message: string }) => {
queryErrors.value.push(err); queryErrors.value.push(err);
}; };

View File

@@ -612,7 +612,7 @@ const otherContributors = computed(() => {
return contributors return contributors
.split(',') .split(',')
.filter(c => !c.includes(appAuthor)) .filter(c => !c.includes(appAuthor))
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())); .sort((a, b) => a.toLowerCase().trim().localeCompare(b.toLowerCase()));
}); });
const selectTab = (tab: string) => { const selectTab = (tab: string) => {
@@ -703,7 +703,7 @@ onBeforeUnmount(() => {
&.selected { &.selected {
img { img {
box-shadow: 0 0 0 3px $primary-color; box-shadow: 0 0 0 3px var(--primary-color);
} }
} }
@@ -731,7 +731,7 @@ onBeforeUnmount(() => {
.badge-update::after { .badge-update::after {
bottom: initial; bottom: initial;
background: $primary-color; background: var(--primary-color);
} }
.form-label { .form-label {

View File

@@ -169,7 +169,7 @@ const emit = defineEmits(['close']);
const { trapRef } = useFocusTrap(); const { trapRef } = useFocusTrap();
const { getConnectionName } = useConnectionsStore(); const { getConnectionName } = useConnectionsStore();
const { connectionsOrder, connections } = storeToRefs(useConnectionsStore()); const { connectionsOrder, connections, customIcons } = storeToRefs(useConnectionsStore());
const localConnections = unproxify<ConnectionParams[]>(connections.value); const localConnections = unproxify<ConnectionParams[]>(connections.value);
const localConnectionsOrder = unproxify<SidebarElement[]>(connectionsOrder.value); const localConnectionsOrder = unproxify<SidebarElement[]>(connectionsOrder.value);
@@ -246,7 +246,8 @@ const exportData = () => {
const exportObj = encrypt(JSON.stringify({ const exportObj = encrypt(JSON.stringify({
connections: filteredConnections, connections: filteredConnections,
connectionsOrder: filteredOrders connectionsOrder: filteredOrders,
customIcons: customIcons.value
}), options.value.passkey); }), options.value.passkey);
// console.log(exportObj, JSON.parse(decrypt(exportObj, options.value.passkey))); // console.log(exportObj, JSON.parse(decrypt(exportObj, options.value.passkey)));

View File

@@ -103,7 +103,7 @@ import { useI18n } from 'vue-i18n';
import BaseIcon from '@/components/BaseIcon.vue'; import BaseIcon from '@/components/BaseIcon.vue';
import BaseUploadInput from '@/components/BaseUploadInput.vue'; import BaseUploadInput from '@/components/BaseUploadInput.vue';
import { unproxify } from '@/libs/unproxify'; import { unproxify } from '@/libs/unproxify';
import { SidebarElement, useConnectionsStore } from '@/stores/connections'; import { CustomIcon, SidebarElement, useConnectionsStore } from '@/stores/connections';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
const { t } = useI18n(); const { t } = useI18n();
@@ -156,6 +156,7 @@ const importData = () => {
const importObj: { const importObj: {
connections: ConnectionParams[]; connections: ConnectionParams[];
connectionsOrder: SidebarElement[]; connectionsOrder: SidebarElement[];
customIcons: CustomIcon[];
} = JSON.parse(decrypt(hash, options.value.passkey)); } = JSON.parse(decrypt(hash, options.value.passkey));
if (options.value.ignoreDuplicates) { if (options.value.ignoreDuplicates) {
@@ -205,7 +206,6 @@ const importData = () => {
.includes(c.uid) || .includes(c.uid) ||
(c.isFolder && c.connections.every(c => newConnectionsUid.includes(c)))); (c.isFolder && c.connections.every(c => newConnectionsUid.includes(c))));
} }
importConnections(importObj); importConnections(importObj);
addNotification({ addNotification({
@@ -215,6 +215,7 @@ const importData = () => {
closeModal(); closeModal();
} }
catch (error) { catch (error) {
console.error(error);
addNotification({ addNotification({
status: 'error', status: 'error',
message: t('application.wrongImportPassword') message: t('application.wrongImportPassword')
@@ -222,6 +223,7 @@ const importData = () => {
} }
} }
catch (error) { catch (error) {
console.error(error);
addNotification({ addNotification({
status: 'error', status: 'error',
message: t('application.wrongFileFormat') message: t('application.wrongFileFormat')

View File

@@ -42,7 +42,7 @@
tabindex="0" tabindex="0"
> >
<div class="td py-1"> <div class="td py-1">
{{ t(shortcutEvents[shortcut.event].l18n, {param: shortcutEvents[shortcut.event].l18nParam}) }} {{ t(shortcutEvents[shortcut.event].i18n, {param: shortcutEvents[shortcut.event].i18nParam}) }}
</div> </div>
<div <div
class="td py-1" class="td py-1"
@@ -167,7 +167,7 @@
</template> </template>
<template #body> <template #body>
<div class="mb-2"> <div class="mb-2">
{{ t('general.deleteConfirm') }} <b>{{ t(shortcutEvents[shortcutToDelete.event].l18n, {param: shortcutEvents[shortcutToDelete.event].l18nParam}) }} (<span v-html="parseKeys(shortcutToDelete.keys)" />)</b>? {{ t('general.deleteConfirm') }} <b>{{ t(shortcutEvents[shortcutToDelete.event].i18n, {param: shortcutEvents[shortcutToDelete.event].i18nParam}) }} (<span v-html="parseKeys(shortcutToDelete.keys)" />)</b>?
</div> </div>
</template> </template>
</ConfirmModal> </ConfirmModal>
@@ -233,7 +233,7 @@ const { shortcuts } = storeToRefs(settingsStore);
const eventOptions = computed(() => { const eventOptions = computed(() => {
return Object.keys(shortcutEvents) return Object.keys(shortcutEvents)
.map(key => { .map(key => {
return { value: key, label: t(shortcutEvents[key].l18n, { param: shortcutEvents[key].l18nParam }) }; return { value: key, label: t(shortcutEvents[key].i18n, { param: shortcutEvents[key].i18nParam }) };
}) })
.sort((a, b) => { .sort((a, b) => {
if (a.label < b.label) return -1; if (a.label < b.label) return -1;

View File

@@ -3,6 +3,7 @@
<div <div
:id="`editor-${id}`" :id="`editor-${id}`"
class="editor" class="editor"
:class="editorClasses"
:style="{height: `${height}px`}" :style="{height: `${height}px`}"
/> />
</div> </div>
@@ -54,7 +55,8 @@ const props = defineProps({
schema: { type: String, default: '' }, schema: { type: String, default: '' },
autoFocus: { type: Boolean, default: false }, autoFocus: { type: Boolean, default: false },
readOnly: { type: Boolean, default: false }, readOnly: { type: Boolean, default: false },
height: { type: Number, default: 200 } height: { type: Number, default: 200 },
editorClasses: { type: String, default: '' }
}); });
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
@@ -341,7 +343,7 @@ onMounted(() => {
lastTableFields.value = res.response.map((field: { name: string }) => field.name); lastTableFields.value = res.response.map((field: { name: string }) => field.name);
editor.value.completers = [tableFieldsCompleter.value]; editor.value.completers = [tableFieldsCompleter.value];
editor.value.execCommand('startAutocomplete'); editor.value.execCommand('startAutocomplete');
}).catch(console.log); }).catch(console.error);
} }
else else
editor.value.completers = customCompleter.value; editor.value.completers = customCompleter.value;
@@ -405,18 +407,17 @@ defineExpose({ editor });
.ace_gutter-cell.ace_breakpoint { .ace_gutter-cell.ace_breakpoint {
&::before { &::before {
content: '\F0403'; content: '';
position: absolute; position: absolute;
left: 3px; left: 0px;
top: 2px; top: 8px;
color: $primary-color;
display: inline-block; display: inline-block;
font: normal normal normal 24px/1 "Material Design Icons", sans-serif; width: 0;
font-size: inherit; height: 0;
text-rendering: auto; border-left: 8px solid transparent;
line-height: inherit; border-top: 8px solid transparent;
-webkit-font-smoothing: antialiased; border-right: 8px solid var(--primary-color);
-moz-osx-font-smoothing: grayscale; transform: rotate(-45deg);
} }
} }
</style> </style>

View File

@@ -14,7 +14,7 @@
<div class="tile-icon"> <div class="tile-icon">
<BaseIcon <BaseIcon
:icon-name="note.type === 'query' :icon-name="note.type === 'query'
? 'mdiStarOutline' ? 'mdiHeartOutline'
: note.type === 'todo' : note.type === 'todo'
? note.isArchived ? note.isArchived
? 'mdiCheckboxMarkedOutline' ? 'mdiCheckboxMarkedOutline'
@@ -32,7 +32,7 @@
v-if="note.type === 'query'" v-if="note.type === 'query'"
ref="noteParagraph" ref="noteParagraph"
class="tile-paragraph sql" class="tile-paragraph sql"
v-html="highlight(highlightWord(note.note), {html: true})" v-html="highlight(note.note, {html: true})"
/> />
<div <div
v-else v-else

View File

@@ -56,6 +56,7 @@
> >
<BaseIcon <BaseIcon
:icon-name="camelize(element.icon)" :icon-name="camelize(element.icon)"
:type="element.hasCustomIcon ? 'custom' : 'mdi'"
:size="36" :size="36"
/> />
</div> </div>
@@ -93,6 +94,7 @@ import * as Draggable from 'vuedraggable';
import BaseIcon from '@/components/BaseIcon.vue'; import BaseIcon from '@/components/BaseIcon.vue';
import SettingBarConnectionsFolder from '@/components/SettingBarConnectionsFolder.vue'; import SettingBarConnectionsFolder from '@/components/SettingBarConnectionsFolder.vue';
import { camelize } from '@/libs/camelize';
import { SidebarElement, useConnectionsStore } from '@/stores/connections'; import { SidebarElement, useConnectionsStore } from '@/stores/connections';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
@@ -165,16 +167,6 @@ const getStatusBadge = (uid: string) => {
} }
}; };
const camelize = (text: string) => {
const textArr = text.split('-');
for (let i = 0; i < textArr.length; i++) {
if (i === 0) continue;
textArr[i] = textArr[i].charAt(0).toUpperCase() + textArr[i].slice(1);
}
return textArr.join('');
};
watch(() => dummyNested.value.length, () => { watch(() => dummyNested.value.length, () => {
dummyNested.value = []; dummyNested.value = [];
}); });

View File

@@ -70,6 +70,7 @@
> >
<BaseIcon <BaseIcon
:icon-name="camelize(getConnectionOrderByUid(element).icon)" :icon-name="camelize(getConnectionOrderByUid(element).icon)"
:type="getConnectionOrderByUid(element).hasCustomIcon ? 'custom' : 'mdi'"
:size="36" :size="36"
/> />
</div> </div>

View File

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

View File

@@ -233,6 +233,7 @@ if (!connectionsArr.value.length)
border-radius: 0; border-radius: 0;
padding: 0; padding: 0;
position: relative; position: relative;
border: none;
&:hover { &:hover {
opacity: 1; opacity: 1;

File diff suppressed because it is too large Load Diff

View File

@@ -1,440 +1,461 @@
<template> <template>
<div class="connection-panel"> <div class="connection-panel-wrapper p-relative">
<div class="panel"> <div class="connection-panel">
<div class="panel-nav"> <div class="panel">
<ul class="tab tab-block"> <div class="panel-nav">
<li <ul class="tab tab-block">
class="tab-item c-hand" <li
:class="{'active': selectedTab === 'general'}" class="tab-item c-hand"
@click="selectTab('general')" :class="{'active': selectedTab === 'general'}"
> @click="selectTab('general')"
<a class="tab-link">{{ t('application.general') }}</a> >
</li> <a class="tab-link">{{ t('application.general') }}</a>
<li </li>
v-if="clientCustomizations.sslConnection" <li
class="tab-item c-hand" v-if="clientCustomizations.sslConnection"
:class="{'active': selectedTab === 'ssl'}" class="tab-item c-hand"
@click="selectTab('ssl')" :class="{'active': selectedTab === 'ssl'}"
> @click="selectTab('ssl')"
<a class="tab-link">{{ t('connection.ssl') }}</a> >
</li> <a class="tab-link">{{ t('connection.ssl') }}</a>
<li </li>
v-if="clientCustomizations.sshConnection" <li
class="tab-item c-hand" v-if="clientCustomizations.sshConnection"
:class="{'active': selectedTab === 'ssh'}" class="tab-item c-hand"
@click="selectTab('ssh')" :class="{'active': selectedTab === 'ssh'}"
> @click="selectTab('ssh')"
<a class="tab-link">{{ t('connection.sshTunnel') }}</a> >
</li> <a class="tab-link">{{ t('connection.sshTunnel') }}</a>
</ul> </li>
</div> </ul>
<div v-if="selectedTab === 'general'" class="panel-body py-0">
<div>
<form class="form-horizontal">
<fieldset class="m-0" :disabled="isBusy">
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.connectionName') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
ref="firstInput"
v-model="connection.name"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.client') }}</label>
</div>
<div class="column col-7 col-sm-12">
<BaseSelect
v-model="connection.client"
:options="clients"
option-track-by="slug"
option-label="name"
class="form-select"
/>
</div>
</div>
<div v-if="connection.client === 'pg'" class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.connectionString') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
ref="pgString"
v-model="connection.pgConnString"
class="form-input"
type="text"
>
</div>
</div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.hostName') }}/IP</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.host"
class="form-input"
type="text"
>
</div>
</div>
<div v-if="clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('database.database') }}</label>
</div>
<div class="column col-7 col-sm-12">
<BaseUploadInput
:model-value="connection.databasePath"
:message="t('general.browse')"
@clear="pathClear('databasePath')"
@change="pathSelection($event, 'databasePath')"
/>
</div>
</div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.port') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.port"
class="form-input"
type="number"
min="1"
max="65535"
>
</div>
</div>
<div v-if="clientCustomizations.database" class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('database.database') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.database"
class="form-input"
type="text"
:placeholder="clientCustomizations.defaultDatabase"
>
</div>
</div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.user') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.user"
class="form-input"
type="text"
:disabled="connection.ask"
>
</div>
</div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.password') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.password"
class="form-input"
type="password"
:disabled="connection.ask"
>
</div>
</div>
<div v-if="clientCustomizations.connectionSchema" class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('database.schema') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.schema"
class="form-input"
type="text"
:placeholder="t('general.all')"
>
</div>
</div>
<div v-if="clientCustomizations.readOnlyMode" class="form-group columns mb-0">
<div class="column col-5 col-sm-12" />
<div class="column col-7 col-sm-12">
<label class="form-checkbox form-inline my-0">
<input v-model="connection.readonly" type="checkbox"><i class="form-icon" /> {{ t('connection.readOnlyMode') }}
</label>
</div>
</div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns mb-0">
<div class="column col-5 col-sm-12" />
<div class="column col-7 col-sm-12">
<label class="form-checkbox form-inline my-0">
<input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ t('connection.askCredentials') }}
</label>
</div>
</div>
<div v-if="clientCustomizations.singleConnectionMode" class="form-group columns mb-0">
<div class="column col-5 col-sm-12" />
<div class="column col-7 col-sm-12">
<label class="form-checkbox form-inline my-0">
<input v-model="connection.singleConnectionMode" type="checkbox"><i class="form-icon" /> {{ t('connection.singleConnection') }}
</label>
</div>
</div>
</fieldset>
</form>
</div> </div>
</div> <div v-if="selectedTab === 'general'" class="panel-body py-0">
<div v-if="selectedTab === 'ssl'" class="panel-body py-0"> <div>
<div> <form class="form-horizontal">
<form class="form-horizontal"> <fieldset class="m-0" :disabled="isBusy">
<div class="form-group columns"> <div class="form-group columns">
<div class="column col-5 col-sm-12"> <div class="column col-5 col-sm-12">
<label class="form-label cut-text"> <label class="form-label cut-text">{{ t('connection.connectionName') }}</label>
{{ t('connection.enableSsl') }} </div>
</label> <div class="column col-7 col-sm-12">
</div>
<div class="column col-7 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleSsl">
<input type="checkbox" :checked="connection.ssl">
<i class="form-icon" />
</label>
</div>
</div>
<fieldset class="m-0" :disabled="isBusy || !connection.ssl">
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.privateKey') }}</label>
</div>
<div class="column col-7 col-sm-12">
<BaseUploadInput
:model-value="connection.key"
:message="t('general.browse')"
@clear="pathClear('key')"
@change="pathSelection($event, 'key')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.certificate') }}</label>
</div>
<div class="column col-7 col-sm-12">
<BaseUploadInput
:model-value="connection.cert"
:message="t('general.browse')"
@clear="pathClear('cert')"
@change="pathSelection($event, 'cert')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.caCertificate') }}</label>
</div>
<div class="column col-7 col-sm-12">
<BaseUploadInput
:model-value="connection.ca"
:message="t('general.browse')"
@clear="pathClear('ca')"
@change="pathSelection($event, 'ca')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.ciphers') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
ref="firstInput"
v-model="connection.ciphers"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12" />
<div class="column col-7 col-sm-12">
<label class="form-checkbox form-inline">
<input v-model="connection.untrustedConnection" type="checkbox"><i class="form-icon" /> {{ t('connection.untrustedConnection') }}
</label>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div v-if="selectedTab === 'ssh'" class="panel-body py-0">
<div>
<form class="form-horizontal">
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">
{{ t('connection.enableSsh') }}
</label>
</div>
<div class="column col-7 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleSsh">
<input type="checkbox" :checked="connection.ssh">
<i class="form-icon" />
</label>
</div>
</div>
<fieldset class="m-0" :disabled="isBusy || !connection.ssh">
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.hostName') }}/IP</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.sshHost"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.user') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.sshUser"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.password') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.sshPass"
class="form-input"
type="password"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.port') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.sshPort"
class="form-input"
type="number"
min="1"
max="65535"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.privateKey') }}</label>
</div>
<div class="column col-7 col-sm-12">
<BaseUploadInput
:model-value="connection.sshKey"
:message="t('general.browse')"
@clear="pathClear('sshKey')"
@change="pathSelection($event, 'sshKey')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.passphrase') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.sshPassphrase"
class="form-input"
type="password"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.keepAliveInterval') }}</label>
</div>
<div class="column col-7 col-sm-12">
<div class="input-group">
<input <input
v-model="connection.sshKeepAliveInterval" ref="firstInput"
v-model="connection.name"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.client') }}</label>
</div>
<div class="column col-7 col-sm-12">
<BaseSelect
v-model="connection.client"
:options="clients"
option-track-by="slug"
option-label="name"
class="form-select"
/>
</div>
</div>
<div v-if="connection.client === 'pg'" class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.connectionString') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
ref="pgString"
v-model="connection.connString"
class="form-input"
type="text"
>
</div>
</div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.hostName') }}/IP</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.host"
class="form-input"
type="text"
>
</div>
</div>
<div v-if="clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('database.database') }}</label>
</div>
<div class="column col-7 col-sm-12">
<BaseUploadInput
:model-value="connection.databasePath"
:message="t('general.browse')"
@clear="pathClear('databasePath')"
@change="pathSelection($event, 'databasePath')"
/>
</div>
</div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.port') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.port"
class="form-input" class="form-input"
type="number" type="number"
min="1" min="1"
max="65535"
> >
<span class="input-group-addon">{{ t('general.seconds') }}</span>
</div> </div>
</div> </div>
<div v-if="clientCustomizations.database" class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('database.database') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.database"
class="form-input"
type="text"
:placeholder="clientCustomizations.defaultDatabase"
>
</div>
</div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.user') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.user"
class="form-input"
type="text"
:disabled="connection.ask"
>
</div>
</div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.password') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.password"
class="form-input"
type="password"
:disabled="connection.ask"
>
</div>
</div>
<div v-if="clientCustomizations.connectionSchema" class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('database.schema') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.schema"
class="form-input"
type="text"
:placeholder="t('general.all')"
>
</div>
</div>
<div v-if="clientCustomizations.readOnlyMode" class="form-group columns mb-0">
<div class="column col-5 col-sm-12" />
<div class="column col-7 col-sm-12">
<label class="form-checkbox form-inline my-0">
<input v-model="connection.readonly" type="checkbox"><i class="form-icon" /> {{ t('connection.readOnlyMode') }}
</label>
</div>
</div>
<div v-if="!clientCustomizations.fileConnection" class="form-group columns mb-0">
<div class="column col-5 col-sm-12" />
<div class="column col-7 col-sm-12">
<label class="form-checkbox form-inline my-0">
<input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ t('connection.askCredentials') }}
</label>
</div>
</div>
<div v-if="clientCustomizations.singleConnectionMode" class="form-group columns mb-0">
<div class="column col-5 col-sm-12" />
<div class="column col-7 col-sm-12">
<label class="form-checkbox form-inline my-0">
<input v-model="connection.singleConnectionMode" type="checkbox"><i class="form-icon" /> {{ t('connection.singleConnection') }}
</label>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div v-if="selectedTab === 'ssl'" class="panel-body py-0">
<div>
<form class="form-horizontal">
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">
{{ t('connection.enableSsl') }}
</label>
</div>
<div class="column col-7 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleSsl">
<input type="checkbox" :checked="connection.ssl">
<i class="form-icon" />
</label>
</div>
</div> </div>
</fieldset> <fieldset class="m-0" :disabled="isBusy || !connection.ssl">
</form> <div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.privateKey') }}</label>
</div>
<div class="column col-7 col-sm-12">
<BaseUploadInput
:model-value="connection.key"
:message="t('general.browse')"
@clear="pathClear('key')"
@change="pathSelection($event, 'key')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.certificate') }}</label>
</div>
<div class="column col-7 col-sm-12">
<BaseUploadInput
:model-value="connection.cert"
:message="t('general.browse')"
@clear="pathClear('cert')"
@change="pathSelection($event, 'cert')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.caCertificate') }}</label>
</div>
<div class="column col-7 col-sm-12">
<BaseUploadInput
:model-value="connection.ca"
:message="t('general.browse')"
@clear="pathClear('ca')"
@change="pathSelection($event, 'ca')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.ciphers') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
ref="firstInput"
v-model="connection.ciphers"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12" />
<div class="column col-7 col-sm-12">
<label class="form-checkbox form-inline">
<input v-model="connection.untrustedConnection" type="checkbox"><i class="form-icon" /> {{ t('connection.untrustedConnection') }}
</label>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div v-if="selectedTab === 'ssh'" class="panel-body py-0">
<div>
<form class="form-horizontal">
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">
{{ t('connection.enableSsh') }}
</label>
</div>
<div class="column col-7 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleSsh">
<input type="checkbox" :checked="connection.ssh">
<i class="form-icon" />
</label>
</div>
</div>
<fieldset class="m-0" :disabled="isBusy || !connection.ssh">
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.hostName') }}/IP</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.sshHost"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.user') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.sshUser"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.password') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.sshPass"
class="form-input"
type="password"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.port') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.sshPort"
class="form-input"
type="number"
min="1"
max="65535"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.privateKey') }}</label>
</div>
<div class="column col-7 col-sm-12">
<BaseUploadInput
:model-value="connection.sshKey"
:message="t('general.browse')"
@clear="pathClear('sshKey')"
@change="pathSelection($event, 'sshKey')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.passphrase') }}</label>
</div>
<div class="column col-7 col-sm-12">
<input
v-model="connection.sshPassphrase"
class="form-input"
type="password"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-5 col-sm-12">
<label class="form-label cut-text">{{ t('connection.keepAliveInterval') }}</label>
</div>
<div class="column col-7 col-sm-12">
<div class="input-group">
<input
v-model="connection.sshKeepAliveInterval"
class="form-input"
type="number"
min="1"
>
<span class="input-group-addon">{{ t('general.seconds') }}</span>
</div>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div class="panel-footer">
<div
@mouseenter="setCancelTestButtonVisibility(true)"
@mouseleave="setCancelTestButtonVisibility(false)"
>
<button
v-if="showTestCancel && isTesting"
class="btn btn-gray mr-2 cancellable"
:title="t('general.cancel')"
@click="abortConnection()"
>
<BaseIcon icon-name="mdiWindowClose" :size="24" />
<span class="d-invisible pr-1">{{ t('connection.testConnection') }}</span>
</button>
<button
v-else
id="connection-test"
class="btn btn-gray mr-2 d-flex"
:class="{'loading': isTesting}"
:disabled="isBusy"
@click="startTest"
>
<BaseIcon
icon-name="mdiLightningBolt"
:size="24"
class="mr-1"
/>
{{ t('connection.testConnection') }}
</button>
</div>
<button
id="connection-save"
class="btn btn-primary mr-2 d-flex"
:disabled="isBusy"
@click="saveConnection"
>
<BaseIcon
icon-name="mdiContentSave"
:size="24"
class="mr-1"
/>
{{ t('general.save') }}
</button>
</div> </div>
</div> </div>
<div class="panel-footer"> <ModalAskCredentials
<button v-if="isAsking"
id="connection-test" @close-asking="closeAsking"
class="btn btn-gray mr-2 d-flex" @credentials="continueTest"
:class="{'loading': isTesting}" />
:disabled="isBusy"
@click="startTest"
>
<BaseIcon
icon-name="mdiLightningBolt"
:size="24"
class="mr-1"
/>
{{ t('connection.testConnection') }}
</button>
<button
id="connection-save"
class="btn btn-primary mr-2 d-flex"
:disabled="isBusy"
@click="saveConnection"
>
<BaseIcon
icon-name="mdiContentSave"
:size="24"
class="mr-1"
/>
{{ t('general.save') }}
</button>
</div>
</div> </div>
<ModalAskCredentials
v-if="isAsking"
@close-asking="closeAsking"
@credentials="continueTest"
/>
</div> </div>
<DebugConsole v-if="isConsoleOpen" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import customizations from 'common/customizations'; import customizations from 'common/customizations';
import { ConnectionParams } from 'common/interfaces/antares'; import { ConnectionParams } from 'common/interfaces/antares';
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
import { storeToRefs } from 'pinia';
import { computed, Ref, ref, watch } from 'vue'; import { computed, Ref, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import BaseIcon from '@/components/BaseIcon.vue'; import BaseIcon from '@/components/BaseIcon.vue';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import BaseUploadInput from '@/components/BaseUploadInput.vue'; import BaseUploadInput from '@/components/BaseUploadInput.vue';
import DebugConsole from '@/components/DebugConsole.vue';
import ModalAskCredentials from '@/components/ModalAskCredentials.vue'; import ModalAskCredentials from '@/components/ModalAskCredentials.vue';
import Connection from '@/ipc-api/Connection'; import Connection from '@/ipc-api/Connection';
import { useConnectionsStore } from '@/stores/connections'; import { useConnectionsStore } from '@/stores/connections';
import { useConsoleStore } from '@/stores/console';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
@@ -443,6 +464,7 @@ const { t } = useI18n();
const { addConnection } = useConnectionsStore(); const { addConnection } = useConnectionsStore();
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { isConsoleOpen } = storeToRefs(useConsoleStore());
const { connectWorkspace, selectWorkspace } = workspacesStore; const { connectWorkspace, selectWorkspace } = workspacesStore;
@@ -480,13 +502,15 @@ const connection = ref({
sshKey: '', sshKey: '',
sshPort: 22, sshPort: 22,
sshKeepAliveInterval: 1800, sshKeepAliveInterval: 1800,
pgConnString: '' connString: ''
}) as Ref<ConnectionParams & { pgConnString: string }>; }) as Ref<ConnectionParams & { connString: string }>;
const firstInput: Ref<HTMLInputElement> = ref(null); const firstInput: Ref<HTMLInputElement> = ref(null);
const isConnecting = ref(false); const isConnecting = ref(false);
const isTesting = ref(false); const isTesting = ref(false);
const isAsking = ref(false); const isAsking = ref(false);
const showTestCancel = ref(false);
const abortController: Ref<AbortController> = ref(new AbortController());
const selectedTab = ref('general'); const selectedTab = ref('general');
const clientCustomizations = computed(() => { const clientCustomizations = computed(() => {
@@ -509,6 +533,10 @@ const setDefaults = () => {
connection.value.database = clientCustomizations.value.defaultDatabase; connection.value.database = clientCustomizations.value.defaultDatabase;
}; };
const setCancelTestButtonVisibility = (val: boolean) => {
showTestCancel.value = val;
};
const startTest = async () => { const startTest = async () => {
isTesting.value = true; isTesting.value = true;
@@ -519,7 +547,7 @@ const startTest = async () => {
const res = await Connection.makeTest(connection.value); const res = await Connection.makeTest(connection.value);
if (res.status === 'error') if (res.status === 'error')
addNotification({ status: 'error', message: res.response.message || res.response.toString() }); addNotification({ status: 'error', message: res.response.message || res.response.toString() });
else else if (res.status === 'success')
addNotification({ status: 'success', message: t('connection.connectionSuccessfullyMade') }); addNotification({ status: 'success', message: t('connection.connectionSuccessfullyMade') });
} }
catch (err) { catch (err) {
@@ -530,13 +558,21 @@ const startTest = async () => {
} }
}; };
const abortConnection = (): void => {
abortController.value.abort();
Connection.abortConnection(connection.value.uid);
isTesting.value = false;
isConnecting.value = false;
abortController.value = new AbortController();
};
const continueTest = async (credentials: { user: string; password: string }) => { // if "Ask for credentials" is true const continueTest = async (credentials: { user: string; password: string }) => { // if "Ask for credentials" is true
isAsking.value = false; isAsking.value = false;
const params = Object.assign({}, connection.value, credentials); const params = Object.assign({}, connection.value, credentials);
try { try {
if (isConnecting.value) { if (isConnecting.value) {
await connectWorkspace(params); await connectWorkspace(params, { signal: abortController.value.signal }).catch(() => undefined);
isConnecting.value = false; isConnecting.value = false;
} }
else { else {

View File

@@ -68,7 +68,7 @@
<div class="column col-7 col-sm-12"> <div class="column col-7 col-sm-12">
<input <input
ref="pgString" ref="pgString"
v-model="localConnection.pgConnString" v-model="localConnection.connString"
class="form-input" class="form-input"
type="text" type="text"
> >
@@ -387,20 +387,35 @@
</div> </div>
</div> </div>
<div class="panel-footer"> <div class="panel-footer">
<button <div
id="connection-test" @mouseenter="setCancelTestButtonVisibility(true)"
class="btn btn-gray mr-2 d-flex" @mouseleave="setCancelTestButtonVisibility(false)"
:class="{'loading': isTesting}"
:disabled="isBusy"
@click="startTest"
> >
<BaseIcon <button
icon-name="mdiLightningBolt" v-if="showTestCancel && isTesting"
:size="24" class="btn btn-gray mr-2 cancellable"
class="mr-1" :title="t('general.cancel')"
/> @click="abortConnection()"
{{ t('connection.testConnection') }} >
</button> <BaseIcon icon-name="mdiWindowClose" :size="24" />
<span class="d-invisible pr-1">{{ t('connection.testConnection') }}</span>
</button>
<button
v-else
id="connection-test"
class="btn btn-gray mr-2 d-flex"
:class="{'loading': isTesting}"
:disabled="isBusy"
@click="startTest"
>
<BaseIcon
icon-name="mdiLightningBolt"
:size="24"
class="mr-1"
/>
{{ t('connection.testConnection') }}
</button>
</div>
<button <button
id="connection-save" id="connection-save"
class="btn btn-primary mr-2 d-flex" class="btn btn-primary mr-2 d-flex"
@@ -414,20 +429,35 @@
/> />
{{ t('general.save') }} {{ t('general.save') }}
</button> </button>
<button <div
id="connection-connect" @mouseenter="setCancelConnectButtonVisibility(true)"
class="btn btn-success d-flex" @mouseleave="setCancelConnectButtonVisibility(false)"
:class="{'loading': isConnecting}"
:disabled="isBusy"
@click="startConnection"
> >
<BaseIcon <button
icon-name="mdiConnection" v-if="showConnectCancel && isConnecting"
:size="24" class="btn btn-success cancellable"
class="mr-1" :title="t('general.cancel')"
/> @click="abortConnection()"
{{ t('connection.connect') }} >
</button> <BaseIcon icon-name="mdiWindowClose" :size="24" />
<span class="d-invisible pr-1">{{ t('connection.connect') }}</span>
</button>
<button
v-else
id="connection-connect"
class="btn btn-success d-flex"
:class="{'loading': isConnecting}"
:disabled="isBusy"
@click="startConnection"
>
<BaseIcon
icon-name="mdiConnection"
:size="24"
class="mr-1"
/>
{{ t('connection.connect') }}
</button>
</div>
</div> </div>
</div> </div>
<ModalAskCredentials <ModalAskCredentials
@@ -472,10 +502,13 @@ const clients = [
]; ];
const firstInput: Ref<HTMLInputElement> = ref(null); const firstInput: Ref<HTMLInputElement> = ref(null);
const localConnection: Ref<ConnectionParams & { pgConnString: string }> = ref(null); const localConnection: Ref<ConnectionParams & { connString: string }> = ref(null);
const isConnecting = ref(false); const isConnecting = ref(false);
const isTesting = ref(false); const isTesting = ref(false);
const isAsking = ref(false); const isAsking = ref(false);
const showTestCancel = ref(false);
const showConnectCancel = ref(false);
const abortController: Ref<AbortController> = ref(new AbortController());
const selectedTab = ref('general'); const selectedTab = ref('general');
const clientCustomizations = computed(() => { const clientCustomizations = computed(() => {
@@ -501,7 +534,7 @@ const startConnection = async () => {
if (localConnection.value.ask) if (localConnection.value.ask)
isAsking.value = true; isAsking.value = true;
else { else {
await connectWorkspace(localConnection.value); await connectWorkspace(localConnection.value, { signal: abortController.value.signal }).catch(() => undefined);
isConnecting.value = false; isConnecting.value = false;
} }
}; };
@@ -516,7 +549,7 @@ const startTest = async () => {
const res = await Connection.makeTest(localConnection.value); const res = await Connection.makeTest(localConnection.value);
if (res.status === 'error') if (res.status === 'error')
addNotification({ status: 'error', message: res.response.message || res.response.toString() }); addNotification({ status: 'error', message: res.response.message || res.response.toString() });
else else if (res.status === 'success')
addNotification({ status: 'success', message: t('connection.connectionSuccessfullyMade') }); addNotification({ status: 'success', message: t('connection.connectionSuccessfullyMade') });
} }
catch (err) { catch (err) {
@@ -527,20 +560,36 @@ const startTest = async () => {
} }
}; };
const setCancelTestButtonVisibility = (val: boolean) => {
showTestCancel.value = val;
};
const setCancelConnectButtonVisibility = (val: boolean) => {
showConnectCancel.value = val;
};
const abortConnection = (): void => {
abortController.value.abort();
Connection.abortConnection(localConnection.value.uid);
isTesting.value = false;
isConnecting.value = false;
abortController.value = new AbortController();
};
const continueTest = async (credentials: {user: string; password: string }) => { // if "Ask for credentials" is true const continueTest = async (credentials: {user: string; password: string }) => { // if "Ask for credentials" is true
isAsking.value = false; isAsking.value = false;
const params = Object.assign({}, localConnection.value, credentials); const params = Object.assign({}, localConnection.value, credentials);
try { try {
if (isConnecting.value) { if (isConnecting.value) {
const params = Object.assign({}, props.connection, credentials); const params = Object.assign({}, props.connection, credentials);
await connectWorkspace(params); await connectWorkspace(params, { signal: abortController.value.signal }).catch(() => undefined);
isConnecting.value = false; isConnecting.value = false;
} }
else { else {
const res = await Connection.makeTest(params); const res = await Connection.makeTest(params);
if (res.status === 'error') if (res.status === 'error')
addNotification({ status: 'error', message: res.response.message || res.response.toString() }); addNotification({ status: 'error', message: res.response.message || res.response.toString() });
else else if (res.status === 'success')
addNotification({ status: 'success', message: t('connection.connectionSuccessfullyMade') }); addNotification({ status: 'success', message: t('connection.connectionSuccessfullyMade') });
} }
} }
@@ -598,6 +647,22 @@ localConnection.value = JSON.parse(JSON.stringify(props.connection));
min-width: 450px; min-width: 450px;
border-radius: $border-radius; border-radius: $border-radius;
.panel-nav {
.tab-block {
background: transparent;
margin: 0.2rem 0 0.15rem 0;
.tab-item {
background: transparent;
flex: 1 0 0;
> a {
padding: 8px 4px 6px 4px
}
}
}
}
.panel-body { .panel-body {
flex: initial; flex: initial;
} }

View File

@@ -19,6 +19,8 @@
v-model="selectedDatabase" v-model="selectedDatabase"
:options="databases" :options="databases"
class="form-select select-sm text-bold my-0" class="form-select select-sm text-bold my-0"
@keypress.stop=""
@keydown.stop=""
/> />
</div> </div>
<span v-else class="workspace-explorebar-title">{{ connectionName }}</span> <span v-else class="workspace-explorebar-title">{{ connectionName }}</span>
@@ -109,6 +111,7 @@
@close-context="closeDatabaseContext" @close-context="closeDatabaseContext"
@open-create-table-tab="openCreateElementTab('table')" @open-create-table-tab="openCreateElementTab('table')"
@open-create-view-tab="openCreateElementTab('view')" @open-create-view-tab="openCreateElementTab('view')"
@open-create-materialized-view-tab="openCreateElementTab('materialized-view')"
@open-create-trigger-tab="openCreateElementTab('trigger')" @open-create-trigger-tab="openCreateElementTab('trigger')"
@open-create-routine-tab="openCreateElementTab('routine')" @open-create-routine-tab="openCreateElementTab('routine')"
@open-create-function-tab="openCreateElementTab('function')" @open-create-function-tab="openCreateElementTab('function')"
@@ -139,10 +142,12 @@
:selected-misc="selectedMisc" :selected-misc="selectedMisc"
:selected-schema="selectedSchema" :selected-schema="selectedSchema"
:context-event="miscContextEvent" :context-event="miscContextEvent"
@open-create-view-tab="openCreateElementTab('view')"
@open-create-materializedview-tab="openCreateElementTab('materialized-view')"
@open-create-trigger-tab="openCreateElementTab('trigger')" @open-create-trigger-tab="openCreateElementTab('trigger')"
@open-create-trigger-function-tab="openCreateElementTab('trigger-function')"
@open-create-routine-tab="openCreateElementTab('routine')" @open-create-routine-tab="openCreateElementTab('routine')"
@open-create-function-tab="openCreateElementTab('function')" @open-create-function-tab="openCreateElementTab('function')"
@open-create-trigger-function-tab="openCreateElementTab('trigger-function')"
@open-create-scheduler-tab="openCreateElementTab('scheduler')" @open-create-scheduler-tab="openCreateElementTab('scheduler')"
@close-context="closeMiscFolderContext" @close-context="closeMiscFolderContext"
@reload="refresh" @reload="refresh"
@@ -501,7 +506,7 @@ const toggleSearchMethod = () => {
transition: background 0.2s; transition: background 0.2s;
&:hover { &:hover {
background: rgba($primary-color, 50%); background: var(--primary-color-dark);
} }
} }

View File

@@ -16,7 +16,7 @@
/> {{ t('general.run') }}</span> /> {{ t('general.run') }}</span>
</div> </div>
<div <div
v-if="selectedMisc.type === 'trigger' && customizations.triggerEnableDisable" v-if="selectedMisc.type === 'trigger' && customizations.triggerEnableDisable && !connection.readonly"
class="context-element" class="context-element"
@click="toggleTrigger" @click="toggleTrigger"
> >
@@ -36,7 +36,7 @@
</span> </span>
</div> </div>
<div <div
v-if="selectedMisc.type === 'scheduler'" v-if="selectedMisc.type === 'scheduler' && !connection.readonly"
class="context-element" class="context-element"
@click="toggleScheduler" @click="toggleScheduler"
> >
@@ -63,7 +63,11 @@
:size="18" :size="18"
/> {{ t('general.copyName') }}</span> /> {{ t('general.copyName') }}</span>
</div> </div>
<div class="context-element" @click="showDeleteModal"> <div
v-if="!connection.readonly"
class="context-element"
@click="showDeleteModal"
>
<span class="d-flex"> <span class="d-flex">
<BaseIcon <BaseIcon
class="text-light mt-1 mr-1" class="text-light mt-1 mr-1"
@@ -117,6 +121,7 @@ import Routines from '@/ipc-api/Routines';
import Schedulers from '@/ipc-api/Schedulers'; import Schedulers from '@/ipc-api/Schedulers';
import Triggers from '@/ipc-api/Triggers'; import Triggers from '@/ipc-api/Triggers';
import { copyText } from '@/libs/copyText'; import { copyText } from '@/libs/copyText';
import { useConnectionsStore } from '@/stores/connections';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
@@ -132,6 +137,7 @@ const emit = defineEmits(['close-context', 'reload']);
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { getConnectionByUid } = useConnectionsStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
@@ -154,6 +160,7 @@ const workspace = computed(() => {
const customizations = computed(() => { const customizations = computed(() => {
return getWorkspace(selectedWorkspace.value).customizations; return getWorkspace(selectedWorkspace.value).customizations;
}); });
const connection = computed(() => getConnectionByUid(selectedWorkspace.value));
const deleteMessage = computed(() => { const deleteMessage = computed(() => {
switch (props.selectedMisc.type) { switch (props.selectedMisc.type) {

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
:context-event="contextEvent" :context-event="contextEvent"
@close-context="closeContext" @close-context="closeContext"
> >
<div class="context-element"> <div v-if="!connection.readonly" class="context-element">
<span class="d-flex"> <span class="d-flex">
<BaseIcon <BaseIcon
class="text-light mt-1 mr-1" class="text-light mt-1 mr-1"
@@ -40,6 +40,18 @@
:size="18" :size="18"
/> {{ t('database.view') }}</span> /> {{ t('database.view') }}</span>
</div> </div>
<div
v-if="workspace.customizations.materializedViewAdd"
class="context-element"
@click="openCreateMaterializedViewTab"
>
<span class="d-flex">
<BaseIcon
class="text-light mt-1 mr-1"
icon-name="mdiTableEye"
:size="18"
/> {{ t('database.materializedview') }}</span>
</div>
<div <div
v-if="workspace.customizations.triggerAdd" v-if="workspace.customizations.triggerAdd"
class="context-element" class="context-element"
@@ -123,7 +135,7 @@
/> {{ t('database.export') }}</span> /> {{ t('database.export') }}</span>
</div> </div>
<div <div
v-if="workspace.customizations.schemaImport" v-if="workspace.customizations.schemaImport && !connection.readonly"
class="context-element" class="context-element"
@click="initImport" @click="initImport"
> >
@@ -135,7 +147,7 @@
/> {{ t('database.import') }}</span> /> {{ t('database.import') }}</span>
</div> </div>
<div <div
v-if="workspace.customizations.schemaEdit" v-if="workspace.customizations.schemaEdit && !connection.readonly"
class="context-element" class="context-element"
@click="showEditModal" @click="showEditModal"
> >
@@ -147,7 +159,7 @@
/> {{ t('database.editSchema') }}</span> /> {{ t('database.editSchema') }}</span>
</div> </div>
<div <div
v-if="workspace.customizations.schemaDrop" v-if="workspace.customizations.schemaDrop && !connection.readonly"
class="context-element" class="context-element"
@click="showDeleteModal" @click="showDeleteModal"
> >
@@ -207,6 +219,7 @@ import ModalImportSchema from '@/components/ModalImportSchema.vue';
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 { copyText } from '@/libs/copyText'; import { copyText } from '@/libs/copyText';
import { useConnectionsStore } from '@/stores/connections';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useSchemaExportStore } from '@/stores/schemaExport'; import { useSchemaExportStore } from '@/stores/schemaExport';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
@@ -221,6 +234,7 @@ const props = defineProps({
const emit = defineEmits([ const emit = defineEmits([
'open-create-table-tab', 'open-create-table-tab',
'open-create-view-tab', 'open-create-view-tab',
'open-create-materialized-view-tab',
'open-create-trigger-tab', 'open-create-trigger-tab',
'open-create-routine-tab', 'open-create-routine-tab',
'open-create-function-tab', 'open-create-function-tab',
@@ -235,7 +249,9 @@ const workspacesStore = useWorkspacesStore();
const schemaExportStore = useSchemaExportStore(); const schemaExportStore = useSchemaExportStore();
const { showExportModal } = schemaExportStore; const { showExportModal } = schemaExportStore;
const connectionsStore = useConnectionsStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { getConnectionByUid } = connectionsStore;
const { const {
getWorkspace, getWorkspace,
@@ -248,6 +264,7 @@ const isEditModal = ref(false);
const isImportSchemaModal = ref(false); const isImportSchemaModal = ref(false);
const workspace = computed(() => getWorkspace(selectedWorkspace.value)); const workspace = computed(() => getWorkspace(selectedWorkspace.value));
const connection = computed(() => getConnectionByUid(selectedWorkspace.value));
const openCreateTableTab = () => { const openCreateTableTab = () => {
emit('open-create-table-tab'); emit('open-create-table-tab');
@@ -257,6 +274,10 @@ const openCreateViewTab = () => {
emit('open-create-view-tab'); emit('open-create-view-tab');
}; };
const openCreateMaterializedViewTab = () => {
emit('open-create-materialized-view-tab');
};
const openCreateTriggerTab = () => { const openCreateTriggerTab = () => {
emit('open-create-trigger-tab'); emit('open-create-trigger-tab');
}; };

View File

@@ -48,7 +48,19 @@
/> {{ t('application.settings') }}</span> /> {{ t('application.settings') }}</span>
</div> </div>
<div <div
v-if="selectedTable && selectedTable.type === 'table' && customizations.tableDuplicate" v-if="selectedTable && selectedTable.type === 'materializedview' && customizations.materializedViewSettings"
class="context-element"
@click="openMaterializedViewSettingTab"
>
<span class="d-flex">
<BaseIcon
class="text-light mt-1 mr-1"
icon-name="mdiWrenchCog"
:size="18"
/> {{ t('application.settings') }}</span>
</div>
<div
v-if="selectedTable && selectedTable.type === 'table' && customizations.tableDuplicate && !connection.readonly"
class="context-element" class="context-element"
@click="duplicateTable" @click="duplicateTable"
> >
@@ -60,7 +72,7 @@
/> {{ t('database.duplicateTable') }}</span> /> {{ t('database.duplicateTable') }}</span>
</div> </div>
<div <div
v-if="selectedTable && selectedTable.type === 'table'" v-if="selectedTable && selectedTable.type === 'table' && !connection.readonly"
class="context-element" class="context-element"
@click="showEmptyModal" @click="showEmptyModal"
> >
@@ -71,7 +83,11 @@
:size="18" :size="18"
/> {{ t('database.emptyTable') }}</span> /> {{ t('database.emptyTable') }}</span>
</div> </div>
<div class="context-element" @click="showDeleteModal"> <div
v-if="!connection.readonly"
class="context-element"
@click="showDeleteModal"
>
<span class="d-flex"> <span class="d-flex">
<BaseIcon <BaseIcon
class="text-light mt-1 mr-1" class="text-light mt-1 mr-1"
@@ -139,6 +155,7 @@ import BaseContextMenu from '@/components/BaseContextMenu.vue';
import BaseIcon from '@/components/BaseIcon.vue'; import BaseIcon from '@/components/BaseIcon.vue';
import Tables from '@/ipc-api/Tables'; import Tables from '@/ipc-api/Tables';
import { copyText } from '@/libs/copyText'; import { copyText } from '@/libs/copyText';
import { useConnectionsStore } from '@/stores/connections';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useSchemaExportStore } from '@/stores/schemaExport'; import { useSchemaExportStore } from '@/stores/schemaExport';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
@@ -156,6 +173,7 @@ const emit = defineEmits(['close-context', 'duplicate-table', 'reload', 'delete-
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { showExportModal } = useSchemaExportStore(); const { showExportModal } = useSchemaExportStore();
const { getConnectionByUid } = useConnectionsStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
@@ -173,6 +191,7 @@ const forceTruncate = ref(false);
const workspace = computed(() => getWorkspace(selectedWorkspace.value)); const workspace = computed(() => getWorkspace(selectedWorkspace.value));
const customizations = computed(() => workspace.value && workspace.value.customizations ? workspace.value.customizations : null); const customizations = computed(() => workspace.value && workspace.value.customizations ? workspace.value.customizations : null);
const connection = computed(() => getConnectionByUid(selectedWorkspace.value));
const showTableExportModal = () => { const showTableExportModal = () => {
showExportModal(props.selectedSchema, props.selectedTable.name); showExportModal(props.selectedSchema, props.selectedTable.name);
@@ -238,6 +257,23 @@ const openViewSettingTab = () => {
closeContext(); closeContext();
}; };
const openMaterializedViewSettingTab = () => {
newTab({
uid: selectedWorkspace.value,
elementType: 'table',
elementName: props.selectedTable.name,
schema: props.selectedSchema,
type: 'materialized-view-props'
});
changeBreadcrumbs({
schema: props.selectedSchema,
view: props.selectedTable.name
});
closeContext();
};
const duplicateTable = () => { const duplicateTable = () => {
emit('duplicate-table', { schema: props.selectedSchema, table: props.selectedTable }); emit('duplicate-table', { schema: props.selectedSchema, table: props.selectedTable });
}; };

View File

@@ -1,189 +0,0 @@
<template>
<div
ref="wrapper"
class="query-console-wrapper"
@mouseenter="isHover = true"
@mouseleave="isHover = false"
>
<div ref="resizer" class="query-console-resizer" />
<div
id="query-console"
ref="queryConsole"
class="query-console column col-12"
:style="{height: localHeight ? localHeight+'px' : ''}"
>
<div class="query-console-header">
<div>{{ t('application.console') }}</div>
<button class="btn btn-clear mr-1" @click="resizeConsole(0)" />
</div>
<div ref="queryConsoleBody" class="query-console-body">
<div
v-for="(wLog, i) in workspaceLogs"
:key="i"
class="query-console-log"
tabindex="0"
@contextmenu.prevent="contextMenu($event, wLog)"
>
<span class="type-datetime">{{ moment(wLog.date).format('HH:mm:ss') }}</span>: <code class="query-console-log-sql" v-html="highlight(wLog.sql, {html: true})" />
</div>
</div>
</div>
</div>
<BaseContextMenu
v-if="isContext"
:context-event="contextEvent"
@close-context="isContext = false"
>
<div class="context-element" @click="copyQuery">
<span class="d-flex">
<BaseIcon
class="text-light mt-1 mr-1"
icon-name="mdiContentCopy"
:size="18"
/> {{ t('general.copy') }}</span>
</div>
</BaseContextMenu>
</template>
<script setup lang="ts">
import * as moment from 'moment';
import { storeToRefs } from 'pinia';
import { highlight } from 'sql-highlight';
import { computed, nextTick, onMounted, Ref, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import BaseContextMenu from '@/components/BaseContextMenu.vue';
import BaseIcon from '@/components/BaseIcon.vue';
import { copyText } from '@/libs/copyText';
import { useConsoleStore } from '@/stores/console';
const { t } = useI18n();
const consoleStore = useConsoleStore();
const { resizeConsole, getLogsByWorkspace } = consoleStore;
const { consoleHeight } = storeToRefs(consoleStore);
const props = defineProps({
uid: String
});
const wrapper: Ref<HTMLInputElement> = ref(null);
const queryConsole: Ref<HTMLInputElement> = ref(null);
const queryConsoleBody: Ref<HTMLInputElement> = ref(null);
const resizer: Ref<HTMLInputElement> = ref(null);
const localHeight = ref(250);
const isHover = ref(false);
const isContext = ref(false);
const contextQuery: Ref<string> = ref(null);
const contextEvent: Ref<MouseEvent> = ref(null);
const resize = (e: MouseEvent) => {
const el = queryConsole.value;
let consoleHeight = el.getBoundingClientRect().bottom - e.pageY;
if (consoleHeight > 400) consoleHeight = 400;
localHeight.value = consoleHeight;
};
const workspaceLogs = computed(() => {
return getLogsByWorkspace(props.uid);
});
const stopResize = () => {
if (localHeight.value < 0) localHeight.value = 0;
resizeConsole(localHeight.value);
window.removeEventListener('mousemove', resize);
window.removeEventListener('mouseup', stopResize);
};
const contextMenu = (event: MouseEvent, wLog: {date: Date; sql: string}) => {
contextEvent.value = event;
contextQuery.value = wLog.sql;
isContext.value = true;
};
const copyQuery = () => {
copyText(contextQuery.value);
isContext.value = false;
};
watch(workspaceLogs, async () => {
if (!isHover.value) {
await nextTick();
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
}
});
onMounted(() => {
localHeight.value = consoleHeight.value;
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
});
onMounted(() => {
resizer.value.addEventListener('mousedown', (e: MouseEvent) => {
e.preventDefault();
window.addEventListener('mousemove', resize);
window.addEventListener('mouseup', stopResize);
});
});
</script>
<style lang="scss" scoped>
.query-console-wrapper {
width: 100%;
z-index: 9;
margin-top: auto;
position: absolute;
bottom: 0;
.query-console-resizer {
height: 4px;
top: -1px;
width: 100%;
cursor: ns-resize;
position: absolute;
z-index: 99;
transition: background 0.2s;
&:hover {
background: rgba($primary-color, 50%);
}
}
.query-console {
padding: 0;
padding-bottom: $footer-height;
.query-console-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px;
font-weight: 700;
}
.query-console-body {
overflow: auto;
display: flex;
flex-direction: column;
max-height: 100%;
padding: 0 6px 3px;
.query-console-log {
padding: 1px 3px;
margin: 1px 0;
border-radius: $border-radius;
.query-console-log-sql {
font-size: 95%;
opacity: 0.8;
font-weight: 700;
&:hover {
user-select: text;
}
}
}
}
}
}
</style>

View File

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

View File

@@ -72,6 +72,20 @@
/> />
<span>{{ t('database.foreignKeys') }}</span> <span>{{ t('database.foreignKeys') }}</span>
</button> </button>
<button
v-if="workspace.customizations.tableCheck"
class="btn btn-dark btn-sm ml-2 mr-0"
:disabled="isSaving || !localFields.length"
:title="t('database.manageTableChecks')"
@click="showTableChecksModal"
>
<BaseIcon
class="mr-1"
icon-name="mdiTableCheck"
:size="24"
/>
<span>{{ t('database.tableChecks') }}</span>
</button>
</div> </div>
<div class="workspace-query-info"> <div class="workspace-query-info">
<div class="d-flex" :title="t('database.schema')"> <div class="d-flex" :title="t('database.schema')">
@@ -183,11 +197,19 @@
@hide="hideForeignModal" @hide="hideForeignModal"
@foreigns-update="foreignsUpdate" @foreigns-update="foreignsUpdate"
/> />
<WorkspaceTabPropsTableChecksModal
v-if="isTableChecksModal"
:local-checks="localTableChecks"
table="new"
:workspace="workspace"
@hide="hideTableChecksModal"
@checks-update="checksUpdate"
/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ConnectionParams, TableField, TableForeign, TableIndex, TableOptions } from 'common/interfaces/antares'; import { ConnectionParams, TableCheck, TableField, TableForeign, TableIndex, TableOptions } from 'common/interfaces/antares';
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
@@ -198,6 +220,7 @@ import BaseIcon from '@/components/BaseIcon.vue';
import BaseLoader from '@/components/BaseLoader.vue'; import BaseLoader from '@/components/BaseLoader.vue';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import WorkspaceTabNewTableEmptyState from '@/components/WorkspaceTabNewTableEmptyState.vue'; import WorkspaceTabNewTableEmptyState from '@/components/WorkspaceTabNewTableEmptyState.vue';
import WorkspaceTabPropsTableChecksModal from '@/components/WorkspaceTabPropsTableChecksModal.vue';
import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields.vue'; import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields.vue';
import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal.vue'; import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal.vue';
import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal.vue'; import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal.vue';
@@ -236,12 +259,16 @@ const isLoading = ref(false);
const isSaving = ref(false); const isSaving = ref(false);
const isIndexesModal = ref(false); const isIndexesModal = ref(false);
const isForeignModal = ref(false); const isForeignModal = ref(false);
const isTableChecksModal = ref(false);
const originalFields: Ref<TableField[]> = ref([]); const originalFields: Ref<TableField[]> = ref([]);
const localFields: Ref<TableField[]> = ref([]); const localFields: Ref<TableField[]> = ref([]);
const originalKeyUsage: Ref<TableForeign[]> = ref([]); const originalKeyUsage: Ref<TableForeign[]> = ref([]);
const localKeyUsage: Ref<TableForeign[]> = ref([]); const localKeyUsage: Ref<TableForeign[]> = ref([]);
const originalIndexes: Ref<TableIndex[]> = ref([]); const originalIndexes: Ref<TableIndex[]> = ref([]);
const localIndexes: Ref<TableIndex[]> = ref([]); const localIndexes: Ref<TableIndex[]> = ref([]);
const originalTableChecks: Ref<TableCheck[]> = ref([]);
const localTableChecks: Ref<TableCheck[]> = ref([]);
const tableOptions: Ref<TableOptions> = ref(null); const tableOptions: Ref<TableOptions> = ref(null);
const localOptions: Ref<TableOptions> = ref(null); const localOptions: Ref<TableOptions> = ref(null);
const newFieldsCounter = ref(0); const newFieldsCounter = ref(0);
@@ -274,6 +301,7 @@ const isChanged = computed(() => {
return JSON.stringify(originalFields.value) !== JSON.stringify(localFields.value) || return JSON.stringify(originalFields.value) !== JSON.stringify(localFields.value) ||
JSON.stringify(originalKeyUsage.value) !== JSON.stringify(localKeyUsage.value) || JSON.stringify(originalKeyUsage.value) !== JSON.stringify(localKeyUsage.value) ||
JSON.stringify(originalIndexes.value) !== JSON.stringify(localIndexes.value) || JSON.stringify(originalIndexes.value) !== JSON.stringify(localIndexes.value) ||
JSON.stringify(originalTableChecks.value) !== JSON.stringify(localTableChecks.value) ||
JSON.stringify(tableOptions.value) !== JSON.stringify(localOptions.value); JSON.stringify(tableOptions.value) !== JSON.stringify(localOptions.value);
}); });
@@ -291,6 +319,7 @@ const saveChanges = async () => {
fields: localFields.value, fields: localFields.value,
foreigns: localKeyUsage.value, foreigns: localKeyUsage.value,
indexes: localIndexes.value, indexes: localIndexes.value,
checks: localTableChecks.value,
options: localOptions.value options: localOptions.value
}; };
@@ -326,6 +355,7 @@ const clearChanges = () => {
localFields.value = JSON.parse(JSON.stringify(originalFields.value)); localFields.value = JSON.parse(JSON.stringify(originalFields.value));
localIndexes.value = JSON.parse(JSON.stringify(originalIndexes.value)); localIndexes.value = JSON.parse(JSON.stringify(originalIndexes.value));
localKeyUsage.value = JSON.parse(JSON.stringify(originalKeyUsage.value)); localKeyUsage.value = JSON.parse(JSON.stringify(originalKeyUsage.value));
localTableChecks.value = JSON.parse(JSON.stringify(originalTableChecks.value));
tableOptions.value = { tableOptions.value = {
name: '', name: '',
@@ -446,10 +476,22 @@ const hideForeignModal = () => {
isForeignModal.value = false; isForeignModal.value = false;
}; };
const showTableChecksModal = () => {
isTableChecksModal.value = true;
};
const hideTableChecksModal = () => {
isTableChecksModal.value = false;
};
const foreignsUpdate = (foreigns: TableForeign[]) => { const foreignsUpdate = (foreigns: TableForeign[]) => {
localKeyUsage.value = foreigns; localKeyUsage.value = foreigns;
}; };
const checksUpdate = (checks: TableCheck[]) => {
localTableChecks.value = checks;
};
const saveContentListener = () => { const saveContentListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length; const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
if (props.isSelected && !hasModalOpen && isChanged.value) if (props.isSelected && !hasModalOpen && isChanged.value)

View File

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

View File

@@ -62,7 +62,7 @@
<button <button
class="btn btn-dark btn-sm mr-0" class="btn btn-dark btn-sm mr-0"
:disabled="isSaving" :disabled="isSaving"
:title="t('database.manageIndexes')" :title="t('database.manageForeignKeys')"
@click="showForeignModal" @click="showForeignModal"
> >
<BaseIcon <BaseIcon
@@ -72,6 +72,20 @@
/> />
<span>{{ t('database.foreignKeys') }}</span> <span>{{ t('database.foreignKeys') }}</span>
</button> </button>
<button
v-if="workspace.customizations.tableCheck"
class="btn btn-dark btn-sm ml-2 mr-0"
:disabled="isSaving"
:title="t('database.manageTableChecks')"
@click="showTableChecksModal"
>
<BaseIcon
class="mr-1"
icon-name="mdiTableCheck"
:size="24"
/>
<span>{{ t('database.tableChecks') }}</span>
</button>
<div class="divider-vert py-3" /> <div class="divider-vert py-3" />
@@ -218,11 +232,19 @@
:workspace="workspace" :workspace="workspace"
@hide="hideDdlModal" @hide="hideDdlModal"
/> />
<WorkspaceTabPropsTableChecksModal
v-if="isTableChecksModal"
:local-checks="localTableChecks"
:table="table"
:workspace="workspace"
@hide="hideTableChecksModal"
@checks-update="checksUpdate"
/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { AlterTableParams, TableField, TableForeign, TableIndex, TableInfos, TableOptions } from 'common/interfaces/antares'; import { AlterTableParams, TableCheck, TableField, TableForeign, TableIndex, TableInfos, TableOptions } from 'common/interfaces/antares';
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
@@ -232,6 +254,7 @@ import { useI18n } from 'vue-i18n';
import BaseIcon from '@/components/BaseIcon.vue'; import BaseIcon from '@/components/BaseIcon.vue';
import BaseLoader from '@/components/BaseLoader.vue'; import BaseLoader from '@/components/BaseLoader.vue';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import WorkspaceTabPropsTableChecksModal from '@/components/WorkspaceTabPropsTableChecksModal.vue';
import WorkspaceTabPropsTableDdlModal from '@/components/WorkspaceTabPropsTableDdlModal.vue'; import WorkspaceTabPropsTableDdlModal from '@/components/WorkspaceTabPropsTableDdlModal.vue';
import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields.vue'; import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields.vue';
import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal.vue'; import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal.vue';
@@ -273,13 +296,17 @@ const isLoading = ref(false);
const isSaving = ref(false); const isSaving = ref(false);
const isIndexesModal = ref(false); const isIndexesModal = ref(false);
const isForeignModal = ref(false); const isForeignModal = ref(false);
const isTableChecksModal = ref(false);
const isDdlModal = ref(false); const isDdlModal = ref(false);
const originalFields: Ref<TableField[]> = ref([]); const originalFields: Ref<TableField[]> = ref([]);
const localFields: Ref<TableField[]> = ref([]); const localFields: Ref<TableField[]> = ref([]);
const originalKeyUsage: Ref<TableForeign[]> = ref([]); const originalKeyUsage: Ref<TableForeign[]> = ref([]);
const localKeyUsage: Ref<TableForeign[]> = ref([]); const localKeyUsage: Ref<TableForeign[]> = ref([]);
const originalIndexes: Ref<TableIndex[]> = ref([]); const originalIndexes: Ref<TableIndex[]> = ref([]);
const localIndexes: Ref<TableIndex[]> = ref([]); const localIndexes: Ref<TableIndex[]> = ref([]);
const originalTableChecks: Ref<TableCheck[]> = ref([]);
const localTableChecks: Ref<TableCheck[]> = ref([]);
const tableOptions: Ref<TableOptions> = ref(null); const tableOptions: Ref<TableOptions> = ref(null);
const localOptions: Ref<TableOptions> = ref({} as TableOptions); const localOptions: Ref<TableOptions> = ref({} as TableOptions);
const lastTable = ref(null); const lastTable = ref(null);
@@ -307,6 +334,7 @@ const isChanged = computed(() => {
return JSON.stringify(originalFields.value) !== JSON.stringify(localFields.value) || return JSON.stringify(originalFields.value) !== JSON.stringify(localFields.value) ||
JSON.stringify(originalKeyUsage.value) !== JSON.stringify(localKeyUsage.value) || JSON.stringify(originalKeyUsage.value) !== JSON.stringify(localKeyUsage.value) ||
JSON.stringify(originalIndexes.value) !== JSON.stringify(localIndexes.value) || JSON.stringify(originalIndexes.value) !== JSON.stringify(localIndexes.value) ||
JSON.stringify(originalTableChecks.value) !== JSON.stringify(localTableChecks.value) ||
JSON.stringify(tableOptions.value) !== JSON.stringify(localOptions.value); JSON.stringify(tableOptions.value) !== JSON.stringify(localOptions.value);
}); });
@@ -430,6 +458,27 @@ const getFieldsData = async () => {
addNotification({ status: 'error', message: err.stack }); addNotification({ status: 'error', message: err.stack });
} }
if (workspace.value.customizations.tableCheck) {
try { // Table checks
const { status, response } = await Tables.getTableChecks(params);
if (status === 'success') {
originalTableChecks.value = response.map((check: TableCheck) => {
return {
_antares_id: uidGen(),
...check
};
});
localTableChecks.value = JSON.parse(JSON.stringify(originalTableChecks.value));
}
else
addNotification({ status: 'error', message: response });
}
catch (err) {
addNotification({ status: 'error', message: err.stack });
}
}
isLoading.value = false; isLoading.value = false;
}; };
@@ -527,6 +576,33 @@ const saveChanges = async () => {
// Foreigns Deletions // Foreigns Deletions
foreignChanges.deletions = originalKeyUsage.value.filter(foreign => !localForeignIDs.includes(foreign._antares_id)); foreignChanges.deletions = originalKeyUsage.value.filter(foreign => !localForeignIDs.includes(foreign._antares_id));
// CHECKS
const checkChanges = {
additions: [] as TableCheck[],
changes: [] as TableCheck[],
deletions: [] as TableCheck[]
};
const originalCheckIDs = originalTableChecks.value.reduce((acc, curr) => [...acc, curr._antares_id], []);
const localCheckIDs = localTableChecks.value.reduce((acc, curr) => [...acc, curr._antares_id], []);
// Check Additions
checkChanges.additions = localTableChecks.value.filter(check => !originalCheckIDs.includes(check._antares_id));
// Check Changes
originalTableChecks.value.forEach(originalCheck => {
const lI = localTableChecks.value.findIndex(localCheck => localCheck._antares_id === originalCheck._antares_id);
if (JSON.stringify(originalCheck) !== JSON.stringify(localTableChecks.value[lI])) {
if (localTableChecks.value[lI]) {
checkChanges.changes.push({
...localTableChecks.value[lI]
});
}
}
});
// Check Deletions
checkChanges.deletions = originalTableChecks.value.filter(check => !localCheckIDs.includes(check._antares_id));
// ALTER // ALTER
const params = { const params = {
uid: props.connection.uid, uid: props.connection.uid,
@@ -543,6 +619,7 @@ const saveChanges = async () => {
deletions, deletions,
indexChanges, indexChanges,
foreignChanges, foreignChanges,
checkChanges,
options options
} as unknown as AlterTableParams; } as unknown as AlterTableParams;
@@ -583,6 +660,7 @@ const clearChanges = () => {
localFields.value = JSON.parse(JSON.stringify(originalFields.value)); localFields.value = JSON.parse(JSON.stringify(originalFields.value));
localIndexes.value = JSON.parse(JSON.stringify(originalIndexes.value)); localIndexes.value = JSON.parse(JSON.stringify(originalIndexes.value));
localKeyUsage.value = JSON.parse(JSON.stringify(originalKeyUsage.value)); localKeyUsage.value = JSON.parse(JSON.stringify(originalKeyUsage.value));
localTableChecks.value = JSON.parse(JSON.stringify(originalTableChecks.value));
localOptions.value = JSON.parse(JSON.stringify(tableOptions.value)); localOptions.value = JSON.parse(JSON.stringify(tableOptions.value));
newFieldsCounter.value = 0; newFieldsCounter.value = 0;
}; };
@@ -702,6 +780,14 @@ const hideForeignModal = () => {
isForeignModal.value = false; isForeignModal.value = false;
}; };
const showTableChecksModal = () => {
isTableChecksModal.value = true;
};
const hideTableChecksModal = () => {
isTableChecksModal.value = false;
};
const showDdlModal = () => { const showDdlModal = () => {
isDdlModal.value = true; isDdlModal.value = true;
}; };
@@ -714,6 +800,10 @@ const foreignsUpdate = (foreigns: TableForeign[]) => {
localKeyUsage.value = foreigns; localKeyUsage.value = foreigns;
}; };
const checksUpdate = (checks: TableCheck[]) => {
localTableChecks.value = checks;
};
const saveContentListener = () => { const saveContentListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length; const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
if (props.isSelected && !hasModalOpen && isChanged.value) if (props.isSelected && !hasModalOpen && isChanged.value)

View File

@@ -0,0 +1,268 @@
<template>
<ConfirmModal
:confirm-text="t('general.confirm')"
size="medium"
class="options-modal"
@confirm="confirmChecksChange"
@hide="$emit('hide')"
>
<template #header>
<div class="d-flex">
<BaseIcon
class="mr-1"
icon-name="mdiTableCheck"
:size="24"
/>
<span class="cut-text">{{ t('database.tableChecks') }} "{{ table }}"</span>
</div>
</template>
<template #body>
<div class="columns col-gapless">
<div class="column col-5">
<div class="panel" :style="{ height: modalInnerHeight + 'px'}">
<div class="panel-header pt-0 pl-0">
<div class="d-flex">
<button class="btn btn-dark btn-sm d-flex" @click="addCheck">
<BaseIcon
class="mr-1"
icon-name="mdiCheckboxMarkedCirclePlusOutline"
:size="24"
/>
<span>{{ t('general.add') }}</span>
</button>
<button
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
:title="t('database.clearChanges')"
:disabled="!isChanged"
@click.prevent="clearChanges"
>
<BaseIcon
class="mr-1"
icon-name="mdiDeleteSweep"
:size="24"
/>
<span>{{ t('general.clear') }}</span>
</button>
</div>
</div>
<div ref="checksPanel" class="panel-body p-0 pr-1">
<div
v-for="check in checksProxy"
:key="check._antares_id"
class="tile tile-centered c-hand mb-1 p-1"
:class="{'selected-element': selectedCheckID === check._antares_id}"
@click="selectCheck($event, check._antares_id)"
>
<div class="tile-icon">
<div>
<BaseIcon
class="mt-2 column-key"
icon-name="mdiCheckboxMarkedCircleOutline"
:size="24"
/>
</div>
</div>
<div class="tile-content">
<div class="tile-title">
{{ check.name }}
</div>
<small class="tile-subtitle text-gray d-inline-block cut-text" style="width: 100%;">{{ check.clause }}</small>
</div>
<div class="tile-action">
<button
class="btn btn-link remove-field p-0 mr-2"
:title="t('general.delete')"
@click.prevent="removeCheck(check._antares_id)"
>
<BaseIcon
icon-name="mdiClose"
:size="18"
class="mt-2"
/>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="column col-7 pl-2 editor-col">
<form
v-if="selectedCheckObj"
:style="{ height: modalInnerHeight + 'px'}"
class="form-horizontal"
>
<div class="form-group">
<label class="form-label col-3">
{{ t('general.name') }}
</label>
<div class="column">
<input
v-model="selectedCheckObj.name"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group">
<label class="form-label col-3">
{{ t('database.checkClause') }}
</label>
<div class="column">
<textarea
v-model="selectedCheckObj.clause"
class="form-input"
style="resize: vertical;"
rows="5"
/>
</div>
</div>
</form>
<div v-if="!checksProxy.length" class="empty">
<div class="empty-icon">
<BaseIcon
class="mr-1"
icon-name="mdiCheckboxMarkedCircleOutline"
:size="48"
/>
</div>
<p class="empty-title h5">
{{ t('database.thereAreNoTableChecks') }}
</p>
<div class="empty-action">
<button class="btn btn-primary" @click="addCheck">
{{ t('database.createNewCheck') }}
</button>
</div>
</div>
</div>
</div>
</template>
</ConfirmModal>
</template>
<script setup lang="ts">
import { TableCheck } from 'common/interfaces/antares';
import { uidGen } from 'common/libs/uidGen';
import { computed, onMounted, onUnmounted, Ref, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import ConfirmModal from '@/components/BaseConfirmModal.vue';
import BaseIcon from '@/components/BaseIcon.vue';
const { t } = useI18n();
const props = defineProps({
localChecks: Array,
table: String,
workspace: Object
});
const emit = defineEmits(['hide', 'checks-update']);
const checksPanel: Ref<HTMLDivElement> = ref(null);
const checksProxy: Ref<TableCheck[]> = ref([]);
const selectedCheckID = ref('');
const modalInnerHeight = ref(400);
const selectedCheckObj = computed(() => checksProxy.value.find(index => index._antares_id === selectedCheckID.value));
const isChanged = computed(() => JSON.stringify(props.localChecks) !== JSON.stringify(checksProxy.value));
const confirmChecksChange = () => {
const filteredChecks = checksProxy.value.filter(check => check.clause.trim().length);
emit('checks-update', filteredChecks);
};
const selectCheck = (event: MouseEvent, id: string) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (selectedCheckID.value !== id && !(event.target as any).classList.contains('remove-field'))
selectedCheckID.value = id;
};
const getModalInnerHeight = () => {
const modalBody = document.querySelector('.modal-body');
if (modalBody)
modalInnerHeight.value = modalBody.clientHeight - (parseFloat(getComputedStyle(modalBody).paddingTop) + parseFloat(getComputedStyle(modalBody).paddingBottom));
};
const addCheck = () => {
const uid = uidGen();
checksProxy.value = [...checksProxy.value, {
_antares_id: uid,
name: `CHK_${uid.substring(0, 4)}`,
clause: ''
}];
if (checksProxy.value.length === 1)
resetSelectedID();
setTimeout(() => {
checksPanel.value.scrollTop = checksPanel.value.scrollHeight + 60;
selectedCheckID.value = uid;
}, 20);
};
const removeCheck = (id: string) => {
checksProxy.value = checksProxy.value.filter(index => index._antares_id !== id);
if (selectedCheckID.value === id && checksProxy.value.length)
resetSelectedID();
};
const clearChanges = () => {
checksProxy.value = JSON.parse(JSON.stringify(props.localChecks));
if (!checksProxy.value.some(index => index._antares_id === selectedCheckID.value))
resetSelectedID();
};
const resetSelectedID = () => {
selectedCheckID.value = checksProxy.value.length ? checksProxy.value[0]._antares_id : '';
};
onMounted(() => {
checksProxy.value = JSON.parse(JSON.stringify(props.localChecks));
if (checksProxy.value.length)
resetSelectedID();
getModalInnerHeight();
window.addEventListener('resize', getModalInnerHeight);
});
onUnmounted(() => {
window.removeEventListener('resize', getModalInnerHeight);
});
</script>
<style lang="scss" scoped>
.tile {
border-radius: $border-radius;
opacity: 0.5;
transition: background 0.2s;
transition: opacity 0.2s;
.tile-action {
opacity: 0;
transition: opacity 0.2s;
}
&:hover {
.tile-action {
opacity: 1;
}
}
&.selected-element {
opacity: 1;
}
}
.fields-list {
max-height: 300px;
overflow: auto;
}
.remove-field svg {
pointer-events: none;
}
</style>

View File

@@ -15,11 +15,15 @@
:schema="breadcrumbsSchema" :schema="breadcrumbsSchema"
:is-selected="isSelected" :is-selected="isSelected"
:height="editorHeight" :height="editorHeight"
editor-classes="editor-query"
/> />
<div ref="resizer" class="query-area-resizer" /> <div ref="resizer" class="query-area-resizer" />
<div ref="queryAreaFooter" class="workspace-query-runner-footer"> <div ref="queryAreaFooter" class="workspace-query-runner-footer">
<div class="workspace-query-buttons"> <div class="workspace-query-buttons">
<div @mouseenter="setCancelButtonVisibility(true)" @mouseleave="setCancelButtonVisibility(false)"> <div
@mouseenter="setCancelButtonVisibility(true)"
@mouseleave="setCancelButtonVisibility(false)"
>
<button <button
v-if="showCancel && isQuering" v-if="showCancel && isQuering"
class="btn btn-primary btn-sm cancellable" class="btn btn-primary btn-sm cancellable"
@@ -94,6 +98,48 @@
> >
<BaseIcon icon-name="mdiBrush" :size="24" /> <BaseIcon icon-name="mdiBrush" :size="24" />
</button> </button>
<div class="btn-group">
<button
class="btn btn-dark btn-sm mr-0"
:disabled="!filePath || lastSavedQuery === query"
:title="t('application.saveFile')"
@click="saveFile()"
>
<BaseIcon icon-name="mdiContentSaveCheckOutline" :size="24" />
</button>
<button
class="btn btn-dark btn-sm mr-0"
:title="t('application.saveFileAs')"
@click="saveFileAs()"
>
<BaseIcon icon-name="mdiContentSavePlusOutline" :size="24" />
</button>
<button
class="btn btn-dark btn-sm"
:title="t('application.openFile')"
@click="openFile()"
>
<BaseIcon icon-name="mdiFolderOpenOutline" :size="24" />
</button>
</div>
<div class="btn-group">
<button
class="btn btn-dark btn-sm mr-0"
:disabled="isQuering || (isQuerySaved || query.length < 5)"
:title="t('application.saveAsNote')"
@click="saveQuery()"
>
<BaseIcon icon-name="mdiHeartPlusOutline" :size="24" />
</button>
<button
class="btn btn-dark btn-sm"
:disabled="isQuering"
:title="t('database.savedQueries')"
@click="openSavedModal()"
>
<BaseIcon icon-name="mdiNotebookHeartOutline" :size="24" />
</button>
</div>
<button <button
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:disabled="isQuering" :disabled="isQuering"
@@ -102,24 +148,6 @@
> >
<BaseIcon icon-name="mdiHistory" :size="24" /> <BaseIcon icon-name="mdiHistory" :size="24" />
</button> </button>
<div class="btn-group">
<button
class="btn btn-dark btn-sm mr-0"
:disabled="isQuering || (isQuerySaved || query.length < 5)"
:title="t('general.save')"
@click="saveQuery()"
>
<BaseIcon icon-name="mdiContentSaveOutline" :size="24" />
</button>
<button
class="btn btn-dark btn-sm"
:disabled="isQuering"
:title="t('database.savedQueries')"
@click="openSavedModal()"
>
<BaseIcon icon-name="mdiStarOutline" :size="24" />
</button>
</div>
<div class="dropdown table-dropdown pr-2"> <div class="dropdown table-dropdown pr-2">
<button <button
:disabled="!hasResults || isQuering" :disabled="!hasResults || isQuering"
@@ -226,6 +254,7 @@
v-if="results" v-if="results"
v-show="!isQuering" v-show="!isQuering"
ref="queryTable" ref="queryTable"
:is-quering="isQuering"
:results="results" :results="results"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:conn-uid="connection.uid" :conn-uid="connection.uid"
@@ -245,13 +274,14 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { getCurrentWindow, Menu } from '@electron/remote';
import { Ace } from 'ace-builds'; import { Ace } from 'ace-builds';
import { ConnectionParams } from 'common/interfaces/antares'; import { ConnectionParams } from 'common/interfaces/antares';
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { format } from 'sql-formatter'; import { format } from 'sql-formatter';
import { Component, computed, onBeforeUnmount, onMounted, Prop, Ref, ref, watch } from 'vue'; import { Component, computed, onBeforeUnmount, onMounted, Prop, Ref, ref, toRaw, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import BaseIcon from '@/components/BaseIcon.vue'; import BaseIcon from '@/components/BaseIcon.vue';
@@ -262,6 +292,7 @@ import QueryEditor from '@/components/QueryEditor.vue';
import WorkspaceTabQueryEmptyState from '@/components/WorkspaceTabQueryEmptyState.vue'; import WorkspaceTabQueryEmptyState from '@/components/WorkspaceTabQueryEmptyState.vue';
import WorkspaceTabQueryTable from '@/components/WorkspaceTabQueryTable.vue'; import WorkspaceTabQueryTable from '@/components/WorkspaceTabQueryTable.vue';
import { useResultTables } from '@/composables/useResultTables'; import { useResultTables } from '@/composables/useResultTables';
import Application from '@/ipc-api/Application';
import Schema from '@/ipc-api/Schema'; import Schema from '@/ipc-api/Schema';
import { useApplicationStore } from '@/stores/application'; import { useApplicationStore } from '@/stores/application';
import { useConsoleStore } from '@/stores/console'; import { useConsoleStore } from '@/stores/console';
@@ -302,14 +333,18 @@ const {
getWorkspace, getWorkspace,
changeBreadcrumbs, changeBreadcrumbs,
updateTabContent, updateTabContent,
setUnsavedChanges setUnsavedChanges,
newTab
} = workspacesStore; } = workspacesStore;
const queryEditor: Ref<Component & { editor: Ace.Editor; $el: HTMLElement }> = ref(null); const queryEditor: Ref<Component & { editor: Ace.Editor; $el: HTMLElement }> = ref(null);
const queryAreaFooter: Ref<HTMLDivElement> = ref(null); const queryAreaFooter: Ref<HTMLDivElement> = ref(null);
const resizer: Ref<HTMLDivElement> = ref(null); const resizer: Ref<HTMLDivElement> = ref(null);
const queryName = ref('');
const query = ref(''); const query = ref('');
const filePath = ref('');
const lastQuery = ref(''); const lastQuery = ref('');
const lastSavedQuery = ref('');
const isCancelling = ref(false); const isCancelling = ref(false);
const showCancel = ref(false); const showCancel = ref(false);
const autocommit = ref(true); const autocommit = ref(true);
@@ -333,17 +368,41 @@ const databaseSchemas = computed(() => {
}); });
const hasResults = computed(() => results.value.length && results.value[0].rows); const hasResults = computed(() => results.value.length && results.value[0].rows);
const hasAffected = computed(() => affectedCount.value || (!resultsCount.value && affectedCount.value !== null)); const hasAffected = computed(() => affectedCount.value || (!resultsCount.value && affectedCount.value !== null));
const isChanged = computed(() => {
return filePath.value && lastSavedQuery.value !== query.value;
});
watch(query, (val) => { watch(query, (val) => {
clearTimeout(debounceTimeout.value); clearTimeout(debounceTimeout.value);
debounceTimeout.value = setTimeout(() => { debounceTimeout.value = setTimeout(() => {
updateTabContent({ updateTabContent({
elementName: queryName.value,
filePath: filePath.value,
uid: props.connection.uid, uid: props.connection.uid,
tab: props.tab.uid, tab: props.tab.uid,
type: 'query', type: 'query',
schema: selectedSchema.value, schema: selectedSchema.value,
content: val content: val
});
isQuerySaved.value = false;
}, 200);
});
watch(queryName, (val) => {
clearTimeout(debounceTimeout.value);
debounceTimeout.value = setTimeout(() => {
updateTabContent({
elementName: val,
filePath: filePath.value,
uid: props.connection.uid,
tab: props.tab.uid,
type: 'query',
schema: selectedSchema.value,
content: query.value
}); });
isQuerySaved.value = false; isQuerySaved.value = false;
@@ -377,6 +436,10 @@ watch(() => props.tab.content, () => {
queryEditor.value.editor.session.setValue(query.value); queryEditor.value.editor.session.setValue(query.value);
}); });
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
});
const runQuery = async (query: string) => { const runQuery = async (query: string) => {
if (!query || isQuering.value) return; if (!query || isQuering.value) return;
isQuering.value = true; isQuering.value = true;
@@ -414,6 +477,8 @@ const runQuery = async (query: string) => {
saveHistory(params); saveHistory(params);
if (!autocommit.value) if (!autocommit.value)
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: true }); setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: true });
queryEditor.value.editor.focus();
} }
else else
addNotification({ status: 'error', message: response }); addNotification({ status: 'error', message: response });
@@ -529,7 +594,8 @@ const saveQuery = () => {
type: 'query', type: 'query',
date: new Date(), date: new Date(),
note: query.value, note: query.value,
isArchived: false isArchived: false,
title: queryName.value
}); });
isQuerySaved.value = true; isQuerySaved.value = true;
}; };
@@ -596,6 +662,8 @@ const rollbackTab = async () => {
defineExpose({ resizeResults }); defineExpose({ resizeResults });
query.value = props.tab.content as string; query.value = props.tab.content as string;
queryName.value = props.tab.elementName as string;
filePath.value = props.tab.filePath as string;
selectedSchema.value = props.tab.schema || breadcrumbsSchema.value; selectedSchema.value = props.tab.schema || breadcrumbsSchema.value;
window.addEventListener('resize', onWindowResize); window.addEventListener('resize', onWindowResize);
@@ -630,6 +698,81 @@ const historyListener = () => {
openHistoryModal(); openHistoryModal();
}; };
const openFileListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
if (props.isSelected && !hasModalOpen)
openFile();
};
const saveFileAsListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
if (props.isSelected && !hasModalOpen)
saveFileAs();
};
const saveContentListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
if (props.isSelected && !hasModalOpen && filePath)
saveFile();
};
const openFile = async () => {
const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: 'SQL', extensions: ['sql', 'txt'] }] });
if (result && !result.canceled) {
const file = result.filePaths[0];
const content = await Application.readFile({ filePath: file, encoding: 'utf-8' });
const fileName = file.split('/').pop().split('\\').pop();
if (props.tab.filePath && props.tab.filePath !== file) {
newTab({
uid: props.connection.uid,
type: 'query',
filePath: file,
content: '',
schema: selectedSchema.value,
elementName: fileName
});
}
else {
filePath.value = file;
queryName.value = fileName;
query.value = content;
lastSavedQuery.value = content;
}
}
};
const saveFileAs = async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result: any = await Application.showSaveDialog({
filters: [{ name: 'SQL', extensions: ['sql'] }],
defaultPath: (queryName.value !== undefined && !queryName.value.includes('.sql') ? `${queryName.value}.sql` : queryName.value) || 'query.sql'
});
if (result && !result.canceled) {
await Application.writeFile(result.filePath, query.value);
addNotification({ status: 'success', message: t('general.actionSuccessful', { action: t('application.saveFile') }) });
queryName.value = result.filePath.split('/').pop().split('\\').pop();
filePath.value = result.filePath;
lastSavedQuery.value = toRaw(query.value);
}
};
const saveFile = async () => {
if (filePath.value) {
await Application.writeFile(filePath.value, query.value);
addNotification({ status: 'success', message: t('general.actionSuccessful', { action: t('application.saveFile') }) });
lastSavedQuery.value = toRaw(query.value);
}
else
saveFileAs();
};
const loadFileContent = async (file: string) => {
const content = await Application.readFile({ filePath: file, encoding: 'utf-8' });
query.value = content;
lastSavedQuery.value = content;
};
onMounted(() => { onMounted(() => {
const localResizer = resizer.value; const localResizer = resizer.value;
@@ -638,6 +781,9 @@ onMounted(() => {
ipcRenderer.on('kill-query', killQueryListener); ipcRenderer.on('kill-query', killQueryListener);
ipcRenderer.on('clear-query', clearQueryListener); ipcRenderer.on('clear-query', clearQueryListener);
ipcRenderer.on('query-history', historyListener); ipcRenderer.on('query-history', historyListener);
ipcRenderer.on('open-file', openFileListener);
ipcRenderer.on('save-file-as', saveFileAsListener);
ipcRenderer.on('save-content', saveContentListener);
localResizer.addEventListener('mousedown', (e: MouseEvent) => { localResizer.addEventListener('mousedown', (e: MouseEvent) => {
e.preventDefault(); e.preventDefault();
@@ -648,6 +794,70 @@ onMounted(() => {
if (props.tab.autorun) if (props.tab.autorun)
runQuery(query.value); runQuery(query.value);
if (props.tab.filePath)
loadFileContent(props.tab.filePath);
queryEditor.value.editor.container.addEventListener('contextmenu', (e) => {
const InputMenu = Menu.buildFromTemplate([
{
label: t('general.run'),
click: () => runQuery(query.value)
},
{
label: t('general.clear'),
click: () => clear()
},
{
type: 'separator'
},
{
label: t('application.saveFile'),
click: () => saveFile()
},
{
label: t('application.saveFileAs'),
click: () => saveFileAs()
},
{
label: t('application.openFile'),
click: () => openFile()
},
{
type: 'separator'
},
{
label: t('general.cut'),
role: 'cut'
},
{
label: t('general.copy'),
role: 'copy'
},
{
label: t('general.paste'),
role: 'paste'
},
{
type: 'separator'
},
{
label: t('general.selectAll'),
role: 'selectAll'
}
]);
e.preventDefault();
// 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;
}
});
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
@@ -663,6 +873,9 @@ onBeforeUnmount(() => {
ipcRenderer.removeListener('kill-query', killQueryListener); ipcRenderer.removeListener('kill-query', killQueryListener);
ipcRenderer.removeListener('clear-query', clearQueryListener); ipcRenderer.removeListener('clear-query', clearQueryListener);
ipcRenderer.removeListener('query-history', historyListener); ipcRenderer.removeListener('query-history', historyListener);
ipcRenderer.removeListener('open-file', openFileListener);
ipcRenderer.removeListener('save-file-as', saveFileAsListener);
ipcRenderer.removeListener('save-content', saveContentListener);
}); });
</script> </script>
@@ -682,7 +895,7 @@ onBeforeUnmount(() => {
transition: background 0.2s; transition: background 0.2s;
&:hover { &:hover {
background: rgba($primary-color, 50%); background: var(--primary-color-dark);
} }
} }
@@ -721,4 +934,4 @@ onBeforeUnmount(() => {
min-height: 200px; min-height: 200px;
} }
} }
</style> </style>filePathsfilePathsfilePaths

View File

@@ -7,7 +7,7 @@
:key="i" :key="i"
class="mb-4" class="mb-4"
> >
{{ t(shortcutEvents[shortcut.event].l18n, {param: shortcutEvents[shortcut.event].l18nParam}) }} {{ t(shortcutEvents[shortcut.event].i18n, {param: shortcutEvents[shortcut.event].i18nParam}) }}
</div> </div>
</div> </div>
<div class="column col-16"> <div class="column col-16">

View File

@@ -38,7 +38,7 @@
<div class="thead"> <div class="thead">
<div class="tr"> <div class="tr">
<div <div
v-for="(field, index) in fields" v-for="(field, index) in filteredFields"
:key="index" :key="index"
class="th c-hand" class="th c-hand"
:title="`${field.type} ${fieldLength(field) ? `(${fieldLength(field)})` : ''}`" :title="`${field.type} ${fieldLength(field) ? `(${fieldLength(field)})` : ''}`"
@@ -284,13 +284,14 @@ const settingsStore = useSettingsStore();
const consoleStore = useConsoleStore(); const consoleStore = useConsoleStore();
const { getWorkspace } = useWorkspacesStore(); const { getWorkspace } = useWorkspacesStore();
const { dataTabLimit: pageSize, defaultCopyType } = storeToRefs(settingsStore); const { /* dataTabLimit: pageSize, */ defaultCopyType } = storeToRefs(settingsStore);
const { consoleHeight } = storeToRefs(consoleStore); const { consoleHeight } = storeToRefs(consoleStore);
const props = defineProps({ const props = defineProps({
results: Array as Prop<QueryResult[]>, results: Array as Prop<QueryResult[]>,
connUid: String, connUid: String,
isQuering: Boolean,
mode: String as Prop<'table' | 'query'>, mode: String as Prop<'table' | 'query'>,
page: { page: {
type: Number, type: Number,
@@ -356,32 +357,46 @@ const isSortable = computed(() => {
return fields.value.every(field => field.name); return fields.value.every(field => field.name);
}); });
const isHardSort = computed(() => { // const isHardSort = computed(() => {
return props.mode === 'table' && localResults.value.length === pageSize.value; // return props.mode === 'table' && localResults.value.length === pageSize.value;
}); // });
const sortedResults = computed(() => { const sortedResults = computed(() => {
if (currentSort.value[resultsetIndex.value] && !isHardSort.value) { // if (currentSort.value[resultsetIndex.value] && !isHardSort.value) {
const sortObj = currentSort.value[resultsetIndex.value]; // const sortObj = currentSort.value[resultsetIndex.value];
return [...localResults.value].sort((a: any, b: any) => { // return [...localResults.value].sort((a: any, b: any) => {
let modifier = 1; // const modifier = sortObj.dir === 'desc' ? -1 : 1;
let valA = typeof a[sortObj.field] === 'string' ? a[sortObj.field].toLowerCase() : a[sortObj.field]; // let valA = a[sortObj.field];
if (!isNaN(valA)) valA = Number(valA); // let valB = b[sortObj.field];
let valB = typeof b[sortObj.field] === 'string' ? b[sortObj.field].toLowerCase() : b[sortObj.field];
if (!isNaN(valB)) valB = Number(valB); // // Handle null values
if (sortObj.dir === 'desc') modifier = -1; // if (valA === null && valB !== null) return sortObj.dir === 'asc' ? -1 : 1;
if (valA < valB) return -1 * modifier; // if (valA !== null && valB === null) return sortObj.dir === 'asc' ? 1 : -1;
if (valA > valB) return 1 * modifier; // if (valA === null && valB === null) return 0;
return 0;
}); // valA = typeof valA === 'string' ? valA.toLowerCase() : valA;
} // valB = typeof valB === 'string' ? valB.toLowerCase() : valB;
else
return localResults.value; // if (typeof valA !== 'number' && !isNaN(valA)) valA = String(Number(valA));
// if (typeof valB !== 'number' && !isNaN(valB)) valB = String(Number(valB));
// if (valA < valB) return -1 * modifier;
// if (valA > valB) return 1 * modifier;
// return 0;
// });
// }
// else
return localResults.value;
}); });
const resultsWithRows = computed(() => props.results.filter(result => result.rows.length)); const resultsWithRows = computed(() => props.results.filter(result => result.rows.length));
const fields = computed(() => resultsWithRows.value.length ? resultsWithRows.value[resultsetIndex.value].fields : []); const fields = computed(() => resultsWithRows.value.length ? resultsWithRows.value[resultsetIndex.value].fields : []);
const filteredFields = computed(() => fields.value.reduce((acc, cur) => {
if (acc.findIndex(f => JSON.stringify(f) === JSON.stringify(cur)))
acc.push(cur);
return acc;
}, [] as TableField[]));
const keyUsage = computed(() => resultsWithRows.value.length ? resultsWithRows.value[resultsetIndex.value].keys : []); const keyUsage = computed(() => resultsWithRows.value.length ? resultsWithRows.value[resultsetIndex.value].keys : []);
const fieldsObj = computed(() => { const fieldsObj = computed(() => {
@@ -523,6 +538,7 @@ const closeContext = () => {
}; };
const showDeleteConfirmModal = (e: any) => { const showDeleteConfirmModal = (e: any) => {
if (e.code !== 'Delete') return;
if (e && e.path && ['INPUT', 'TEXTAREA', 'SELECT'].includes(e.path[0].tagName)) if (e && e.path && ['INPUT', 'TEXTAREA', 'SELECT'].includes(e.path[0].tagName))
return; return;
if (selectedRows.value.length === 0) return; if (selectedRows.value.length === 0) return;
@@ -790,7 +806,7 @@ const contextMenu = (event: MouseEvent, cell: any) => {
}; };
const sort = (field: TableField) => { const sort = (field: TableField) => {
if (!isSortable.value) return; if (!isSortable.value || props.isQuering) return;
selectedRows.value = []; selectedRows.value = [];
let fieldName = field.name; let fieldName = field.name;
@@ -812,12 +828,12 @@ const sort = (field: TableField) => {
}; };
} }
if (isHardSort.value) { // if (isHardSort.value) {
emit('hard-sort', { emit('hard-sort', {
field: currentSort.value[resultsetIndex.value].field, field: currentSort.value[resultsetIndex.value].field,
dir: currentSort.value[resultsetIndex.value].dir dir: currentSort.value[resultsetIndex.value].dir
}); });
} // }
}; };
const resetSort = () => { const resetSort = () => {

View File

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

View File

@@ -1,3 +1,13 @@
/**
* [TRANSLATION UPDATE HELPER]
* - Open a terminal in antares folder and run `npm run translation:check short-code` replacing short-code with the one you are updating.
* - The command will output which terms are missing or not translated from english.
* - Open antares folder with your editor of choice.
* - Go to antares/src/renderer/i18n/ and open the locale file you want to translate.
* - Add and translate missing terms and consider whether to translate untranslated terms.
*/
export const csCZ = { export const csCZ = {
general: { // General purpose terms general: { // General purpose terms
edit: 'Upravit', edit: 'Upravit',
@@ -18,7 +28,7 @@ export const csCZ = {
download: 'Stáhnout', download: 'Stáhnout',
add: 'Přidat', add: 'Přidat',
data: 'Data', data: 'Data',
properties: 'Vlastnosti', // Lawondyss: Not used properties: 'Vlastnosti',
name: 'Název', name: 'Název',
clear: 'Vyčistit', clear: 'Vyčistit',
options: 'Možnosti', options: 'Možnosti',
@@ -39,6 +49,7 @@ export const csCZ = {
new: 'Nové', new: 'Nové',
select: 'Vybrat', select: 'Vybrat',
change: 'Změnit', change: 'Změnit',
include: 'Včetně',
includes: 'Zahrnout', includes: 'Zahrnout',
completed: 'Dokončeno', completed: 'Dokončeno',
aborted: 'Zrušeno', aborted: 'Zrušeno',
@@ -46,15 +57,15 @@ export const csCZ = {
enable: 'Zapnuto', enable: 'Zapnuto',
disable: 'Vypnout', disable: 'Vypnout',
contributors: 'Přispěvatelé', contributors: 'Přispěvatelé',
pin: 'Připnout', // Lawondyss: Not used pin: 'Připnout',
unpin: 'Odepnout', // Lawondyss: Not used unpin: 'Odepnout',
folder: 'Složka | Složky', // Lawondyss: Used only 1 folder: 'Složka | Složky',
none: 'Nic', none: 'Nic',
singleQuote: 'Apostrofy', singleQuote: 'Apostrofy',
doubleQuote: 'Uvozovky', doubleQuote: 'Uvozovky',
deleteConfirm: 'Skutečně chcete smazat', deleteConfirm: 'Skutečně chcete smazat',
uploadFile: 'Nahrát soubor', uploadFile: 'Nahrát soubor',
format: 'Formátovat', // Format code format: 'Formátovat',
history: 'Historie', history: 'Historie',
filter: 'Filtrovat', filter: 'Filtrovat',
manualValue: 'Vlastní hodnota', manualValue: 'Vlastní hodnota',
@@ -64,9 +75,16 @@ export const csCZ = {
actionSuccessful: '{action} úspěšný', actionSuccessful: '{action} úspěšný',
outputFormat: 'Formát výstupu', outputFormat: 'Formát výstupu',
singleFile: 'Jediný {ext} soubor', singleFile: 'Jediný {ext} soubor',
zipCompressedFile: 'ZIP komprimovaný {ext} soubor' zipCompressedFile: 'ZIP komprimovaný {ext} soubor',
copyName: 'Kopírovat název',
search: 'Hledat',
title: 'Titulek',
archive: 'Archivovat', // verb
undo: 'Zpět',
moveTo: 'Přesunout do'
}, },
connection: { // Database connection connection: { // Database connection
connection: 'Připojení',
connectionName: 'Název', connectionName: 'Název',
hostName: 'Host', hostName: 'Host',
client: 'Klient', client: 'Klient',
@@ -75,9 +93,9 @@ export const csCZ = {
password: 'Heslo', password: 'Heslo',
credentials: 'Pověření', credentials: 'Pověření',
connect: 'Připojit', connect: 'Připojit',
connected: 'Připojeno', // Lawondyss: Not used connected: 'Připojeno',
disconnect: 'Odpojit', disconnect: 'Odpojit',
disconnected: 'Odpojeno', // Lawondyss: Not used disconnected: 'Odpojeno',
ssl: 'SSL', ssl: 'SSL',
enableSsl: 'Použít SSL', enableSsl: 'Použít SSL',
privateKey: 'Soukromý klíč', privateKey: 'Soukromý klíč',
@@ -90,28 +108,30 @@ export const csCZ = {
enableSsh: 'Použít SSH', enableSsh: 'Použít SSH',
connectionString: 'String připojení', connectionString: 'String připojení',
addConnection: 'Add connection', addConnection: 'Add connection',
createConnection: 'Vytvořit připojení', // Lawondyss: Not used createConnection: 'Vytvořit připojení',
createNewConnection: 'Vytvořit nové připojení', createNewConnection: 'Vytvořit nové připojení',
askCredentials: 'Vyžadovat přihlášení', askCredentials: 'Vyžadovat přihlášení',
testConnection: 'Vyzkoušet', testConnection: 'Vyzkoušet',
editConnection: 'Upravit připojení', // Lawondyss: Not used editConnection: 'Upravit připojení',
deleteConnection: 'Smazat připojení', deleteConnection: 'Smazat připojení',
connectionSuccessfullyMade: 'Spojení úspěšně navázáno!', connectionSuccessfullyMade: 'Spojení úspěšně navázáno!',
readOnlyMode: 'Pouze pro čtení', readOnlyMode: 'Pouze pro čtení',
allConnections: 'Všechna připojení', allConnections: 'Všechna připojení',
searchForConnections: 'Hledat připojení' searchForConnections: 'Hledat připojení',
keepAliveInterval: 'Keep alive interval',
singleConnection: 'Jediné spojení'
}, },
database: { // Database related terms database: { // Database related terms
schema: 'Schéma', schema: 'Schéma',
type: 'Typ', type: 'Typ',
insert: 'Vložit', // Lawondyss: Not used insert: 'Vložit',
indexes: 'Indexy', indexes: 'Indexy',
foreignKeys: 'Cizí klíče', foreignKeys: 'Cizí klíče',
length: 'Délka', length: 'Délka',
unsigned: 'Unsigned', unsigned: 'Unsigned',
default: 'Výchozí', default: 'Výchozí',
comment: 'Komentář', comment: 'Komentář',
key: 'Klíč | Klíče', // Lawondyss: Used only 2 key: 'Klíč | Klíče',
order: 'Pořadí', order: 'Pořadí',
expression: 'Výraz', expression: 'Výraz',
autoIncrement: 'Auto Increment', autoIncrement: 'Auto Increment',
@@ -119,8 +139,9 @@ export const csCZ = {
field: 'Sloupec | Sloupce', field: 'Sloupec | Sloupce',
approximately: 'Přibližně', approximately: 'Přibližně',
total: 'Celkem', total: 'Celkem',
table: 'Tabulka | Tabulky', // Lawondyss: Used only without argument table: 'Tabulka | Tabulky',
view: 'Pohled | Pohledy', // Lawondyss: Used only without argument view: 'Pohled | Pohledy',
materializedview: 'Materializovaný pohled',
definer: 'Definér', definer: 'Definér',
algorithm: 'Algoritmus', algorithm: 'Algoritmus',
trigger: 'Trigger | Triggery', trigger: 'Trigger | Triggery',
@@ -147,25 +168,25 @@ export const csCZ = {
row: 'Řádek | Řádky', row: 'Řádek | Řádky',
cell: 'Buňka | Buňky', cell: 'Buňka | Buňky',
triggerFunction: 'Trigger funkce | Trigger funkce', triggerFunction: 'Trigger funkce | Trigger funkce',
routine: 'Routina | Routiny', // Lawondyss: Not used routine: 'Routina | Routiny',
drop: 'Drop', drop: 'Drop',
commit: 'Commit', commit: 'Commit',
rollback: 'Rollback', rollback: 'Rollback',
ddl: 'DDL', ddl: 'DDL',
collation: 'Porovnání', collation: 'Porovnání',
resultsTable: 'Tabulka výsledků', resultsTable: 'Tabulka výsledků',
unableEditFieldWithoutPrimary: 'Nelze upravit buňku bez primárního klíče ve výsledku', // Lawondyss: Not used unableEditFieldWithoutPrimary: 'Nelze upravit buňku bez primárního klíče ve výsledku',
editCell: 'Upravit buňku', // Lawondyss: Not used editCell: 'Upravit buňku',
deleteRows: 'Smazat řádek | Smazat {count} řádků', deleteRows: 'Smazat řádek | Smazat {count} řádků',
confirmToDeleteRows: 'Skutečně chcete smazat řádek? | Skutečně chcete smazat {count} řádků?', confirmToDeleteRows: 'Skutečně chcete smazat řádek? | Skutečně chcete smazat {count} řádků?',
addNewRow: 'Přidat nový řádek', // Lawondyss: Not used addNewRow: 'Přidat nový řádek',
numberOfInserts: 'Počet opakování', numberOfInserts: 'Počet opakování',
affectedRows: 'Ovlivněno řádků', affectedRows: 'Ovlivněno řádků',
createNewDatabase: 'Vytvořit novou databázi', // Lawondyss: Not used createNewDatabase: 'Vytvořit novou databázi',
databaseName: 'Název databáze', // Lawondyss: Not use databaseName: 'Název databáze',
serverDefault: 'Porovnání serveru', serverDefault: 'Porovnání serveru',
deleteDatabase: 'Smazat databázi', // Lawondyss: Not used deleteDatabase: 'Smazat databázi',
editDatabase: 'Upravit databázi', // Lawondyss: Not used editDatabase: 'Upravit databázi',
clearChanges: 'Zrušit změny', clearChanges: 'Zrušit změny',
addNewField: 'Přidat nový sloupec', addNewField: 'Přidat nový sloupec',
manageIndexes: 'Správa indexů', manageIndexes: 'Správa indexů',
@@ -177,17 +198,18 @@ export const csCZ = {
deleteField: 'Smazat sloupec', deleteField: 'Smazat sloupec',
createNewIndex: 'Vytvořit nový index', createNewIndex: 'Vytvořit nový index',
addToIndex: 'Přidat do indexu', addToIndex: 'Přidat do indexu',
createNewTable: 'Vytvořit novou databázi', // Lawondyss: Not used createNewTable: 'Vytvořit novou databázi',
emptyTable: 'Smazat obsah tabulky', emptyTable: 'Smazat obsah tabulky',
duplicateTable: 'Duplikovat tabulku', duplicateTable: 'Duplikovat tabulku',
deleteTable: 'Smazat tabulku', deleteTable: 'Smazat tabulku',
exportTable: 'Exportovat tabulku',
emptyConfirm: 'Skutečně smazat obsah tabulky', emptyConfirm: 'Skutečně smazat obsah tabulky',
thereAreNoIndexes: 'Nemá žádné indexy', thereAreNoIndexes: 'Nemá žádné indexy',
thereAreNoForeign: 'Nemá žádné cizí klíče', thereAreNoForeign: 'Nemá žádné cizí klíče',
createNewForeign: 'Vytvořit nový cizí klíč', createNewForeign: 'Vytvořit nový cizí klíč',
referenceTable: 'Ref. tabulka', referenceTable: 'Ref. tabulka',
referenceField: 'Ref. sloupec', referenceField: 'Ref. sloupec',
foreignFields: 'Cizí sloupce', // Lawondyss: Not used foreignFields: 'Cizí sloupce',
invalidDefault: 'Neplatná výchozí hodnota', invalidDefault: 'Neplatná výchozí hodnota',
onDelete: 'Při smazání', onDelete: 'Při smazání',
selectStatement: 'Definice pohledu', selectStatement: 'Definice pohledu',
@@ -195,7 +217,8 @@ export const csCZ = {
sqlSecurity: 'SQL zabezpečení', sqlSecurity: 'SQL zabezpečení',
updateOption: 'Volba aktualizace', updateOption: 'Volba aktualizace',
deleteView: 'Smazat pohled', deleteView: 'Smazat pohled',
createNewView: 'Vytvořit nový pohled', // Lawondyss: Not used createNewView: 'Vytvořit nový pohled',
createNewMaterializedView: 'Vytvořit nový materializovaný pohled',
deleteTrigger: 'Smazat trigger', deleteTrigger: 'Smazat trigger',
createNewTrigger: 'Vytvořit nový trigger', createNewTrigger: 'Vytvořit nový trigger',
currentUser: 'Současný uživatel', currentUser: 'Současný uživatel',
@@ -212,7 +235,7 @@ export const csCZ = {
createNewScheduler: 'Vytvořit nový scheduler', createNewScheduler: 'Vytvořit nový scheduler',
deleteScheduler: 'Smazat scheduler', deleteScheduler: 'Smazat scheduler',
preserveOnCompletion: 'Uchovat po dokončení', preserveOnCompletion: 'Uchovat po dokončení',
tableFiller: 'Vyplňovač tabulky', // Lawondyss: Not used tableFiller: 'Vyplňovač tabulky',
fakeDataLanguage: 'Jazyk pro fake data', fakeDataLanguage: 'Jazyk pro fake data',
queryDuration: 'Doba trvání dotazu', queryDuration: 'Doba trvání dotazu',
setNull: 'Nastavit NULL', setNull: 'Nastavit NULL',
@@ -224,10 +247,11 @@ export const csCZ = {
editSchema: 'Upravit schéma', editSchema: 'Upravit schéma',
deleteSchema: 'Smazat schema', deleteSchema: 'Smazat schema',
noSchema: 'Bez schématu', noSchema: 'Bez schématu',
runQuery: 'Spustit dotaz', // Lawondyss: Not used runQuery: 'Spustit dotaz',
thereAreNoTableFields: 'Nemá žádné sloupce', thereAreNoTableFields: 'Nemá žádné sloupce',
newTable: 'Nová tabulka', newTable: 'Nová tabulka',
newView: 'Nový pohled', newView: 'Nový pohled',
newMaterializedView: 'Nový materializovaný pohled',
newTrigger: 'Nový trigger', newTrigger: 'Nový trigger',
newRoutine: 'Nová routina', newRoutine: 'Nová routina',
newFunction: 'Nová funkce', newFunction: 'Nová funkce',
@@ -244,17 +268,17 @@ export const csCZ = {
writingTableExport: 'Zapisuji data tabulky {table}', writingTableExport: 'Zapisuji data tabulky {table}',
checkAllTables: 'Vybrat všechny tabulky', checkAllTables: 'Vybrat všechny tabulky',
uncheckAllTables: 'Vybrat žádnou tabulku', uncheckAllTables: 'Vybrat žádnou tabulku',
killQuery: 'Zabít dotaz', // Lawondyss: Not used killQuery: 'Zabít dotaz',
insertRow: 'Vložit řádek | Vložit řádky', // Lawondyss: Used only 2 insertRow: 'Vložit řádek | Vložit řádky',
commitMode: 'Způsob commitování', commitMode: 'Způsob commitování',
autoCommit: 'Auto commit', autoCommit: 'Auto commit',
manualCommit: 'Ruční commit', manualCommit: 'Ruční commit',
importQueryErrors: 'Varování: došlo k chybě {n} | Varování: došlo k chybám {n}', // Lawondyss: Used without n argument importQueryErrors: 'Varování: došlo k chybě {n} | Varování: došlo k chybám {n}',
executedQueries: '{n} dotaz spuštěn | {n} dotazy spuštěny', // Lawondyss: Used withoum n argument executedQueries: '{n} dotaz spuštěn | {n} dotazy spuštěny',
disableFKChecks: 'Vypnout kontrolu cizích klíčů', disableFKChecks: 'Vypnout kontrolu cizích klíčů',
formatQuery: 'Formátovat dotaz', // Lawondyss: Not used, probably duplicate to general.format formatQuery: 'Formátovat dotaz',
queryHistory: 'Historie dotazů', // Lawondyss: Not used, probably duplicate to general.history queryHistory: 'Historie dotazů',
clearQuery: 'Clear query', // Lawondyss: Not used, probably duplicate to general.clear clearQuery: 'Clear query',
fillCell: 'Vyplnit buňku', fillCell: 'Vyplnit buňku',
executeSelectedQuery: 'Spustit vybraný dotaz', executeSelectedQuery: 'Spustit vybraný dotaz',
noResultsPresent: 'Nejsou k dispozici žádné výsledky', noResultsPresent: 'Nejsou k dispozici žádné výsledky',
@@ -262,12 +286,11 @@ export const csCZ = {
targetTable: 'Cílová tabulka', targetTable: 'Cílová tabulka',
switchDatabase: 'Přepnout databázi', switchDatabase: 'Přepnout databázi',
searchForElements: 'Vyhledávání prvků', searchForElements: 'Vyhledávání prvků',
searchForSchemas: 'Vyhledávání schémat' searchForSchemas: 'Vyhledávání schémat',
savedQueries: 'Uložit dotazy'
}, },
application: { // Application related terms application: { // Application related terms
settings: 'Nastavení', settings: 'Nastavení',
scratchpad: 'Zápisník',
disableScratchpad: 'Vypnout zápisník',
console: 'Konzole', console: 'Konzole',
general: 'Obecné', general: 'Obecné',
themes: 'Motivy', themes: 'Motivy',
@@ -275,7 +298,7 @@ export const csCZ = {
about: 'Informace', about: 'Informace',
language: 'Jazyk', language: 'Jazyk',
shortcuts: 'Zkratky', shortcuts: 'Zkratky',
key: 'Klávesa | Klávesy', // Keyboard key // Lawondyss: Usedn only 2 key: 'Klávesa | Klávesy', // Keyboard key
event: 'Akce', event: 'Akce',
light: 'Světlý', light: 'Světlý',
dark: 'Tmavý', dark: 'Tmavý',
@@ -283,13 +306,19 @@ export const csCZ = {
application: 'Aplikace', application: 'Aplikace',
editor: 'Editor', editor: 'Editor',
changelog: 'Changelog', changelog: 'Changelog',
small: 'Malé', // Lawondyss: Not used, probably obsolete font size settings small: 'Malé',
medium: 'Střední', // Lawondyss: Not used, probably obsolete font size settings medium: 'Střední',
large: 'Velké', // Lawondyss: Not used, probably obsolete font size settings large: 'Velké',
appearance: 'Vzhled', appearance: 'Vzhled',
color: 'Barva', color: 'Barva',
label: 'Název', label: 'Název',
icon: 'Ikona', icon: 'Ikona',
customIcon: 'Vlastní ikona',
fileName: 'Soubor',
choseFile: 'Vybrat soubor',
data: 'Data',
password: 'Heslo',
required: 'Povinné',
madeWithJS: 'Vytvořeno s 💛 a JavaScriptem!', madeWithJS: 'Vytvořeno s 💛 a JavaScriptem!',
checkForUpdates: 'Zkontrolovat aktualizace', checkForUpdates: 'Zkontrolovat aktualizace',
noUpdatesAvailable: 'Žádné dostupné aktualizace', noUpdatesAvailable: 'Žádné dostupné aktualizace',
@@ -308,7 +337,7 @@ export const csCZ = {
editorTheme: 'Motiv editoru', editorTheme: 'Motiv editoru',
wrapLongLines: 'Zalamovat dlouhé řádky', wrapLongLines: 'Zalamovat dlouhé řádky',
markdownSupported: 'Podporován Markdown', markdownSupported: 'Podporován Markdown',
plantATree: 'Zasaďte strom', // Lawondyss: Not used plantATree: 'Zasaďte strom',
dataTabPageSize: 'Počet řádků na stránku', dataTabPageSize: 'Počet řádků na stránku',
noOpenTabs: 'Žádné otevřené karty, vyberte z elementů vlevo nebo:', noOpenTabs: 'Žádné otevřené karty, vyberte z elementů vlevo nebo:',
restorePreviousSession: 'Pamatovat si otevřené karty', restorePreviousSession: 'Pamatovat si otevřené karty',
@@ -333,15 +362,16 @@ export const csCZ = {
saveContent: 'Uložit obsah', saveContent: 'Uložit obsah',
openAllConnections: 'Otevřít všechna připojení', openAllConnections: 'Otevřít všechna připojení',
openSettings: 'Otevřít nastavení', openSettings: 'Otevřít nastavení',
openScratchpad: 'Otevřít zápisník',
runOrReload: 'Spustit dotaz', runOrReload: 'Spustit dotaz',
openFilter: 'Otevřít filtr', openFilter: 'Otevřít filtr',
nextResultsPage: 'Další stránka výsledků', nextResultsPage: 'Další stránka výsledků',
previousResultsPage: 'Předešlá stránka výsledků', previousResultsPage: 'Předešlá stránka výsledků',
editFolder: 'Upravit složku', editFolder: 'Upravit složku',
folderName: 'Název složky', folderName: 'Název složky',
deleteFolder: 'Smazat složku', // Lawondyss: Not used deleteFolder: 'Smazat složku',
editConnectionAppearance: 'Upravit vzhled připojení', // Lawondyss: Not used newFolder: 'Nová složka',
outOfFolder: 'Mimo složku',
editConnectionAppearance: 'Upravit vzhled připojení',
defaultCopyType: 'Výchozí typ kopírování', defaultCopyType: 'Výchozí typ kopírování',
showTableSize: 'Velikost tabulky v panelu', showTableSize: 'Velikost tabulky v panelu',
showTableSizeDescription: 'Pouze MySQL/MariaDB. Povolení této možnosti může ovlivnit výkon u schémat s mnoha tabulkami.', showTableSizeDescription: 'Pouze MySQL/MariaDB. Povolení této možnosti může ovlivnit výkon u schémat s mnoha tabulkami.',
@@ -356,7 +386,33 @@ export const csCZ = {
csvStringDelimiter: 'Obalit text', csvStringDelimiter: 'Obalit text',
csvIncludeHeader: 'Včetně hlavičky', csvIncludeHeader: 'Včetně hlavičky',
csvExportOptions: 'Možnosti CSV exportu', csvExportOptions: 'Možnosti CSV exportu',
scratchPadDefaultValue: '# JAK PODPOŘIT ANTARES\n\n- [ ] Dát Antares hvězdičku [GitHub repo](https://github.com/antares-sql/antares)\n- [ ] Poslat názor či radu\n- [ ] Nahlásit chybu\n- [ ] Pokud se líbí, sdílet Antares s přáteli\n\n# O ZÁPISNÍKU\n\nToto je zápisník, který uchovává vaše **osobní poznámky**. Podporuje `markdown` formát, ale můžete použít obyčejný text.\nTento obsah je pouze ukázky, neváhejte ho smazat, abyste si udělali místo na poznámky.\n' exportData: 'Exportovat data',
exportDataExplanation: 'Export uložených připojení v Antaresu. Budete požádáni o zadání hesla pro zašifrování exportovaného souboru.',
importData: 'Importovat data',
importDataExplanation: 'Importuje soubor .antares obsahující připojení. Je třeba zadat heslo definované při exportu.',
includeConnectionPasswords: 'Včetně hesel připojení',
includeFolders: 'Včetně složek',
encryptionPassword: 'Heslo pro zašifrování souboru',
encryptionPasswordError: 'Heslo musí mít alespoň 8 znaků.',
ignoreDuplicates: 'Ignorovat duplicity',
wrongImportPassword: 'Chybné heslo pro import',
wrongFileFormat: 'Chybný formát souboru',
dataImportSuccess: 'Data úspěšně importována',
note: 'Poznámka',
thereAreNoNotesYet: 'Zatím tu nejsou žádné poznámky',
addNote: 'Přidat poznámku',
editNote: 'Upravit poznámku',
saveAsNote: 'Uložit jako poznámku',
showArchivedNotes: 'Zobrazit archivované poznámky',
hideArchivedNotes: 'Skrýt archivované poznámky',
tag: 'Tag', // Note tag
saveFile: 'Uložit soubor',
saveFileAs: 'Uložit do nového souboru',
openFile: 'Otevřít soubor',
openNotes: 'Otevřít poznámky',
debugConsole: 'Debug konzole', // <- console tab name
executedQueries: 'Log dotazů', // <- console tab name
sizeLimitError: 'Maximální velikost {size} překročena'
}, },
faker: { // Faker.js methods, used in random generated content faker: { // Faker.js methods, used in random generated content
address: 'Address', address: 'Address',

View File

@@ -126,6 +126,7 @@ export const enUS = {
insert: 'Insert', insert: 'Insert',
indexes: 'Indexes', indexes: 'Indexes',
foreignKeys: 'Foreign keys', foreignKeys: 'Foreign keys',
tableChecks: 'Table checks',
length: 'Length', length: 'Length',
unsigned: 'Unsigned', unsigned: 'Unsigned',
default: 'Default', default: 'Default',
@@ -140,6 +141,7 @@ export const enUS = {
total: 'Total', total: 'Total',
table: 'Table | Tables', table: 'Table | Tables',
view: 'View | Views', view: 'View | Views',
materializedview: 'Materialized view | Materialized views',
definer: 'Definer', definer: 'Definer',
algorithm: 'Algorithm', algorithm: 'Algorithm',
trigger: 'Trigger | Triggers', trigger: 'Trigger | Triggers',
@@ -189,12 +191,15 @@ export const enUS = {
addNewField: 'Add new field', addNewField: 'Add new field',
manageIndexes: 'Manage indexes', manageIndexes: 'Manage indexes',
manageForeignKeys: 'Manage foreign keys', manageForeignKeys: 'Manage foreign keys',
manageTableChecks: 'Manage table checks',
allowNull: 'Allow NULL', allowNull: 'Allow NULL',
zeroFill: 'Zero fill', zeroFill: 'Zero fill',
customValue: 'Custom value', customValue: 'Custom value',
onUpdate: 'On update', onUpdate: 'On update',
deleteField: 'Delete field', deleteField: 'Delete field',
createNewIndex: 'Create new index', createNewIndex: 'Create new index',
createNewCheck: 'Create new check',
checkClause: 'Check clause',
addToIndex: 'Add to index', addToIndex: 'Add to index',
createNewTable: 'Create new table', createNewTable: 'Create new table',
emptyTable: 'Empty table', emptyTable: 'Empty table',
@@ -204,6 +209,7 @@ export const enUS = {
emptyConfirm: 'Do you confirm to empty', emptyConfirm: 'Do you confirm to empty',
thereAreNoIndexes: 'There are no indexes', thereAreNoIndexes: 'There are no indexes',
thereAreNoForeign: 'There are no foreign keys', thereAreNoForeign: 'There are no foreign keys',
thereAreNoTableChecks: 'There are no table checks',
createNewForeign: 'Create new foreign key', createNewForeign: 'Create new foreign key',
referenceTable: 'Ref. table', referenceTable: 'Ref. table',
referenceField: 'Ref. field', referenceField: 'Ref. field',
@@ -216,6 +222,7 @@ export const enUS = {
updateOption: 'Update option', updateOption: 'Update option',
deleteView: 'Delete view', deleteView: 'Delete view',
createNewView: 'Create new view', createNewView: 'Create new view',
createNewMaterializedView: 'Create new materialized view',
deleteTrigger: 'Delete trigger', deleteTrigger: 'Delete trigger',
createNewTrigger: 'Create new trigger', createNewTrigger: 'Create new trigger',
currentUser: 'Current user', currentUser: 'Current user',
@@ -248,6 +255,7 @@ export const enUS = {
thereAreNoTableFields: 'There are no table fields', thereAreNoTableFields: 'There are no table fields',
newTable: 'New table', newTable: 'New table',
newView: 'New view', newView: 'New view',
newMaterializedView: 'New materialized view',
newTrigger: 'New trigger', newTrigger: 'New trigger',
newRoutine: 'New routine', newRoutine: 'New routine',
newFunction: 'New function', newFunction: 'New function',
@@ -309,6 +317,7 @@ export const enUS = {
color: 'Color', color: 'Color',
label: 'Label', label: 'Label',
icon: 'Icon', icon: 'Icon',
customIcon: 'Custom icon',
fileName: 'File name', fileName: 'File name',
choseFile: 'Choose file', choseFile: 'Choose file',
data: 'Data', data: 'Data',
@@ -397,9 +406,21 @@ export const enUS = {
thereAreNoNotesYet: 'There are no notes yet', thereAreNoNotesYet: 'There are no notes yet',
addNote: 'Add note', addNote: 'Add note',
editNote: 'Edit note', editNote: 'Edit note',
saveAsNote: 'Save as note',
showArchivedNotes: 'Show archived notes', showArchivedNotes: 'Show archived notes',
hideArchivedNotes: 'Hide archived notes', hideArchivedNotes: 'Hide archived notes',
tag: 'Tag' // Note tag tag: 'Tag', // Note tag,
saveFile: 'Save file',
saveFileAs: 'Save file as',
openFile: 'Open file',
openNotes: 'Open notes',
debugConsole: 'Debug console', // <- console tab name
executedQueries: 'Executed queries', // <- console tab name
sizeLimitError: 'Maximum size of {size} exceeded',
fullScreen: 'Full screen',
zoomIn: 'Zoom in',
zoomOut: 'Zoom out',
zoomReset: 'Reset zoom'
}, },
faker: { // Faker.js methods, used in random generated content faker: { // Faker.js methods, used in random generated content
address: 'Address', address: 'Address',

View File

@@ -1,5 +1,14 @@
/**
* [TRANSLATION UPDATE HELPER]
* - Open a terminal in antares folder and run `npm run translation:check short-code` replacing short-code with the one you are updating.
* - The command will output which terms are missing or not translated from english.
* - Open antares folder with your editor of choice.
* - Go to antares/src/renderer/i18n/ and open the locale file you want to translate.
* - Add and translate missing terms and consider whether to translate untranslated terms.
*/
export const esES = { export const esES = {
general: { general: { // General purpose terms
edit: 'Editar', edit: 'Editar',
save: 'Guardar', save: 'Guardar',
close: 'Cerrar', close: 'Cerrar',
@@ -8,6 +17,7 @@ export const esES = {
cancel: 'Cancelar', cancel: 'Cancelar',
send: 'Enviar', send: 'Enviar',
refresh: 'Refrescar', refresh: 'Refrescar',
autoRefresh: 'Auto refresco',
version: 'Versión', version: 'Versión',
donate: 'Donar', donate: 'Donar',
run: 'Ejecutar', run: 'Ejecutar',
@@ -18,12 +28,62 @@ export const esES = {
add: 'Añadir', add: 'Añadir',
data: 'Datos', data: 'Datos',
properties: 'Propiedades', properties: 'Propiedades',
name: 'Nombre',
clear: 'Limpiar',
options: 'Opciones',
insert: 'Insertar', insert: 'Insertar',
discard: 'Descartar',
stay: 'Mantener',
author: 'Autor',
upload: 'Subir',
browse: 'Navegar',
content: 'Contenido',
cut: 'Cortar',
copy: 'Copiar',
paste: 'Pegar',
duplicate: 'Duplicar',
tools: 'Herramientas',
seconds: 'Segundos', seconds: 'Segundos',
deleteConfirm: 'Confirmas la cancelación de', all: 'Todos',
uploadFile: 'Cargar fichero' new: 'Nuevo',
select: 'Seleccionar',
change: 'Cambiar',
include: 'Agregar',
includes: 'Agregados',
completed: 'Completado',
aborted: 'Cancelado',
disabled: 'Deshabilitado',
enable: 'Habilitar',
disable: 'Desabilitar',
contributors: 'Colaboradores',
pin: 'Fijar',
unpin: 'Desfijar',
folder: 'Carpeta | Carpetas',
none: 'Ninguno',
singleQuote: 'Comillas simples',
doubleQuote: 'Comillas dobles',
deleteConfirm: 'Confirmar la cancelación de',
uploadFile: 'Cargar fichero',
format: 'Formato', // Format code
history: 'Histórico',
filter: 'Filtro',
manualValue: 'Valor manual',
selectAll: 'Seleccionar todo',
pageNumber: 'Número de página',
directoryPath: 'Ruta de directorio',
actionSuccessful: '{action} exitoso',
outputFormat: 'Formato de salida',
singleFile: 'Fichero {ext} único',
zipCompressedFile: 'Fichero ZIP {ext} comprimido',
copyName: 'Copiar nombre',
search: 'Búsqueda',
title: 'Título',
archive: 'Archivo', // verb
undo: 'Deshacer',
moveTo: 'Mover a'
}, },
connection: { connection: { // Database connection
connection: 'Conexión',
connectionName: 'Nombre de la conexión', connectionName: 'Nombre de la conexión',
client: 'Cliente', client: 'Cliente',
hostName: 'Servidor', hostName: 'Servidor',
@@ -35,6 +95,17 @@ export const esES = {
connected: 'Conectado', connected: 'Conectado',
disconnect: 'Desconectar', disconnect: 'Desconectar',
disconnected: 'Desconectado', disconnected: 'Desconectado',
ssl: 'SSL',
enableSsl: 'Habilitar SSL',
privateKey: 'Clave privada',
certificate: 'Certificado',
caCertificate: 'Certificado CA',
ciphers: 'Cifrado',
untrustedConnection: 'Conexión no confiable',
passphrase: 'Frase de paso',
sshTunnel: 'Túnel SSH',
enableSsh: 'Habilitar SSH',
connectionString: 'Cadena de conexión',
addConnection: 'Añadir conexión', addConnection: 'Añadir conexión',
createConnection: 'Crear conexión', createConnection: 'Crear conexión',
createNewConnection: 'Crear nueva conexión', createNewConnection: 'Crear nueva conexión',
@@ -42,26 +113,216 @@ export const esES = {
testConnection: 'Comprobar conexión', testConnection: 'Comprobar conexión',
editConnection: 'Editar conexión', editConnection: 'Editar conexión',
deleteConnection: 'Eliminar conexión', deleteConnection: 'Eliminar conexión',
connectionSuccessfullyMade: 'Conexión realizada correctamente!' connectionSuccessfullyMade: 'Conexión realizada correctamente!',
readOnlyMode: 'Solo lectura',
allConnections: 'Todas las conexiones',
searchForConnections: 'Buscar por conexiones',
keepAliveInterval: 'Tiempo de mantenimiento de conexión',
singleConnection: 'Conexión única'
}, },
database: { database: { // Database related terms
schema: 'Esquema', schema: 'Esquema',
type: 'Tipo', type: 'Tipo',
unableEditFieldWithoutPrimary: 'No se puede editar una campo sin Llave Primaria en el registro', insert: 'Insertar',
editCell: 'Editar celda', indexes: 'Índices',
foreignKeys: 'Claves Foráneas',
tablaChecks: 'Validación de tabla',
length: 'Longitud',
unsigned: 'Sin signo',
default: 'Por defecto',
comment: 'Comentario',
key: 'Clave | Claves',
order: 'Orden',
expression: 'Expresión',
autoIncrement: 'Autoincremental',
engine: 'Motor',
field: 'Campo | Campos',
approximately: 'Aproximadamente',
total: 'Total',
table: 'Tabla | Tablas',
view: 'Vista | Vistas',
materializedview: 'Vista Materializada | Vistas Materializadas',
definer: 'Definidor',
algorithm: 'Algoritmo',
trigger: 'Disparador | Disparadores',
storedRoutine: 'Procedimiento almacenado | Procedimientos almacenados',
scheduler: 'Planificador | Planificadores',
event: 'Evento',
parameters: 'Parámetros',
function: 'Función | Funciones',
deterministic: 'Determinístico',
context: 'Contexto',
export: 'Exportar',
import: 'Importar',
returns: 'Retorno',
timing: 'Timing',
state: 'Estado',
execution: 'Ejecución',
starts: 'Inicio',
ends: 'Final',
variables: 'Variables',
processes: 'Procesos',
database: 'Base de Datos',
array: 'Tupla',
structure: 'Estructura',
row: 'Fila | Filas',
cell: 'Celda | Celdas',
triggerFunction: 'Función disparadora | Funciones disparadoras',
routine: 'Rutina | Rutinas',
drop: 'Abandonar',
commit: 'Commit',
rollback: 'Marcha atrás',
ddl: 'DDL',
collation: 'Colación',
resultsTable: 'Tabla de resultados',
unableEditFieldWithoutPrimary: 'No es posible modificar un campo sin una clave primaria en el set de resultados',
editCell: 'Modificar celda',
deleteRows: 'Eliminar fila | Eliminar {count} filas', deleteRows: 'Eliminar fila | Eliminar {count} filas',
confirmToDeleteRows: '¿Quiere realmente eliminar una fila? | ¿Quiere realmente eliminar {count} filas?', confirmToDeleteRows: '¿Quiere realmente eliminar una fila? | ¿Quiere realmente eliminar {count} filas?',
addNewRow: 'Añadir nueva fila', addNewRow: 'Añadir nueva fila',
numberOfInserts: 'Numero de inserciones', numberOfInserts: 'Número de inserciones',
affectedRows: 'Filas afectadas' affectedRows: 'Líneas afectadas',
createNewDatabase: 'Crear nueva Base de Datos',
databaseName: 'Nombre de Base de Datos',
serverDefault: 'Servidor por defecto',
deleteDatabase: 'Eliminar Base de Datos',
editDatabase: 'Modificar Base de Datos',
clearChanges: 'Deshacer cambios',
addNewField: 'Añadir nuevo campo',
manageIndexes: 'Administrar índices',
manageForeignKeys: 'Administrar claves foráneas',
manageTableChecks: 'Administrar validaciones de tabla',
allowNull: 'Permitir NULL',
zeroFill: 'Rellenar con ceros',
customValue: 'Valor predeterminado',
onUpdate: 'On UPDATE',
deleteField: 'Eliminar campo',
createNewIndex: 'Crear nuevo índice',
createNewCheck: 'Crear nueva verificación',
checkClause: 'Comprobar cláusula',
addToIndex: 'Añadir al índice',
createNewTable: 'Crear nueva tabla',
emptyTable: 'Tabla vacía',
duplicateTable: 'Duplicar tabla',
deleteTable: 'Eliminar tabla',
exportTable: 'Exportar tabla',
emptyConfirm: 'Confirmar vaciado',
thereAreNoIndexes: 'No hay índices',
thereAreNoForeign: 'No hay claves foráneas',
thereAreNoTableChecks: 'No hay validaciones de tabla',
createNewForeign: 'Crear nueva clave foránea',
referenceTable: 'Ref. tabla',
referenceField: 'Ref. campo',
foreignFields: 'Campos foráneos',
invalidDefault: 'Valor por defecto no válido',
onDelete: 'On DELETE',
selectStatement: 'Declaración SELECT',
triggerStatement: 'Declaración TRIGGER',
sqlSecurity: 'Seguridad SQL',
updateOption: 'Opción UPDATE',
deleteView: 'Eliminar vista',
createNewView: 'Crear nueva vista',
createNewMaterializedView: 'Crear nueva vista materializada',
deleteTrigger: 'Eliminar disparador',
createNewTrigger: 'Crear nuevo disparador',
currentUser: 'Usuario actual',
routineBody: 'Cuerpo de rutina',
dataAccess: 'Acceso a datos',
thereAreNoParameters: 'No hay parámetros',
createNewParameter: 'Crear nuevo parámetro',
createNewRoutine: 'Crear nuevo procedimiento almacenado',
deleteRoutine: 'Eliminar procedimiento almacenado',
functionBody: 'Cuerpo de función',
createNewFunction: 'Crear nueva función',
deleteFunction: 'Eliminar función',
schedulerBody: 'Cuerpo de planificador',
createNewScheduler: 'Crear nuevo planificador',
deleteScheduler: 'Eliminar planificador',
preserveOnCompletion: 'Mantener al finalizar',
tableFiller: 'Rellenador de tabla',
fakeDataLanguage: 'Lenguaje de datos dummy',
queryDuration: 'Duración de la consulta',
setNull: 'Establecer a NULL',
processesList: 'Lista de procesos',
processInfo: 'Información de proceso',
manageUsers: 'Administrar usuarios',
createNewSchema: 'Crear nuevo esquema',
schemaName: 'Nombre de esquema',
editSchema: 'Modificar esquema',
deleteSchema: 'Eliminar esquema',
noSchema: 'No hay esquemas',
runQuery: 'Ejecutar consulta',
thereAreNoTableFields: 'No hay campos en la tabla',
newTable: 'Nueva tabla',
newView: 'Nueva vista',
newMaterializedView: 'Nueva vista materializada',
newTrigger: 'Nuevo disparador',
newRoutine: 'Nueva rutina',
newFunction: 'Nueva función',
newScheduler: 'Nuevo planificador',
newTriggerFunction: 'Nueva función de disparador',
thereAreNoQueriesYet: 'No quedan mas consultas',
searchForQueries: 'Buscar consultas',
killProcess: 'Matar proceso',
exportSchema: 'Exportar esquema',
importSchema: 'Importar esquema',
newInsertStmtEvery: 'Nueva declaración INSERT',
processingTableExport: 'Procesando {table}',
fetchingTableExport: 'Obteniendo datos de {table}',
writingTableExport: 'Escribiendo datos de {table}',
checkAllTables: 'Verificar todas las tablas',
uncheckAllTables: 'Desmarcar todas las tablas',
killQuery: 'Matar consulta',
insertRow: 'Añadir fila | Añadir filas',
commitMode: 'Modo de Commit',
autoCommit: 'Automático',
manualCommit: 'Manual',
importQueryErrors: 'Atención: {n} de error encontrado | Atención: {n} errores encontrados',
executedQueries: '{n} consulta ejecutada | {n} consultas ejecutadas',
disableFKChecks: 'Deshabilitar comprobación de claves foráneas',
formatQuery: 'Formato de consulta',
queryHistory: 'Histórico de consultas',
clearQuery: 'Limpiar consulta',
fillCell: 'Rellenar celda',
executeSelectedQuery: 'Ejecutar consulta seleccionada',
noResultsPresent: 'No se obtuvieron resultados',
sqlExportOptions: 'Opciones de exportación de SQL',
targetTable: 'Tabla objetivo',
switchDatabase: 'Cambiar de Base de datos',
searchForElements: 'Buscar por elementos',
searchForSchemas: 'Buscar por esquemas',
savedQueries: 'Consultas almacenadas'
}, },
application: { application: {
settings: 'Configuración', settings: 'Configuración',
console: 'Consola',
general: 'General', general: 'General',
themes: 'Temas', themes: 'Temas',
update: 'Actualizar', update: 'Actualizar',
about: 'Sobre', about: 'Sobre',
language: 'Idioma', language: 'Idioma',
shortcuts: 'Atajos',
key: 'Tecla | Teclas', // Keyboard key
event: 'Evento',
light: 'Claro',
dark: 'Oscuro',
autoCompletion: 'Autocompletado',
application: 'Aplicación',
editor: 'Editor',
changelog: 'Histórico de cambios',
small: 'Pequeño',
medium: 'Mediano',
large: 'Grande',
appearance: 'Apariencia',
color: 'Color',
label: 'Etiqueta',
icon: 'Icono',
customIcon: 'Icono personalizado',
fileName: 'Nombre de fichero',
choseFile: 'Elegir fichero',
data: 'Datos',
password: 'Contraseña',
required: 'Requerido',
madeWithJS: 'Hecho con 💛 y JavaScript!', madeWithJS: 'Hecho con 💛 y JavaScript!',
checkForUpdates: 'Comprobar actualizaciones', checkForUpdates: 'Comprobar actualizaciones',
noUpdatesAvailable: 'No hay actualizaciones', noUpdatesAvailable: 'No hay actualizaciones',
@@ -71,7 +332,255 @@ export const esES = {
downloadingUpdate: 'Descargando actualización', downloadingUpdate: 'Descargando actualización',
updateDownloaded: 'Descargada actualización', updateDownloaded: 'Descargada actualización',
restartToInstall: 'Reiniciar Antares para instalar', restartToInstall: 'Reiniciar Antares para instalar',
includeBetaUpdates: 'Incluir actualizaciones en fase beta',
notificationsTimeout: 'Tiempo de espera', notificationsTimeout: 'Tiempo de espera',
openNewTab: 'Abrir nueva pestaña' openNewTab: 'Abrir nueva pestaña',
unsavedChanges: 'Cambios sin guardar',
discardUnsavedChanges: 'Tiene algunos cambios sin guardar. Al cerrar esta pestaña, serán descartados.',
applicationTheme: 'Tema de la Aplicación',
editorTheme: 'Editor de Tema',
wrapLongLines: 'Mantener lineas largas',
markdownSupported: 'Markdown soportado',
plantATree: 'Planta un arbol',
dataTabPageSize: 'Resultados por página',
noOpenTabs: 'No hay pestañas abiertas. Navega por la barra de la izquierda o:',
restorePreviousSession: 'Restablecer la sesión anterior',
closeTab: 'Cerrar pestaña',
goToDownloadPage: 'Ir a la página de descargas',
disableBlur: 'Deshabilitar opacidad',
missingOrIncompleteTranslation: '¿No hay traducción o hay algo incorrecto?',
findOutHowToContribute: 'Mira como puedes contribuir',
reportABug: 'Informar de un problema',
nextTab: 'Siguiente pestaña',
previousTab: 'Anterior pestaña',
selectTabNumber: 'Selecciona numero de pestaña {param}',
toggleConsole: 'Alternar vista de consola',
addShortcut: 'Añadir atajo',
editShortcut: 'Modificar atajo',
deleteShortcut: 'Eliminar atajo',
restoreDefaults: 'Restablecer valores por defecto',
restoreDefaultsQuestion: '¿Está seguro que quiere establecer los valores por defecto?',
registerAShortcut: 'Registrar un atajo',
invalidShortcutMessage: 'Combinación no válida, por favor intente otra',
shortcutAlreadyExists: 'El atajo ya existe',
saveContent: 'Guardar contenido',
openAllConnections: 'Abrir todas las conexiones',
openSettings: 'Abrir configuración',
runOrReload: 'Ejecutar o recargar',
openFilter: 'Abrir filtro',
nextResultsPage: 'Siguiente pagina de resultados',
previousResultsPage: 'Anterior página de resultados',
editFolder: 'Modificar carpeta',
folderName: 'Nombre de carpeta',
deleteFolder: 'Eliminar carpeta',
newFolder: 'Crear nueva carpeta',
outOfFolder: 'Fuera de la carpeta',
editConnectionAppearance: 'Modificar apariencia de conexión',
defaultCopyType: 'Default copy type',
showTableSize: 'Mostrar tamaño de tabla en la barra lateral',
showTableSizeDescription: 'Solo para MySQL/MariaDB. Habilitar esta opción puede afectar al rendimiento en esquemas con muchas tablas.',
switchSearchMethod: 'Switch search method',
phpArray: 'Array de PHP',
closeAllTabs: 'Cerrar todas las pestañas',
closeOtherTabs: 'Cerrar las otras pestañas',
closeTabsToLeft: 'Cerrar las pestañas a la izquierda',
closeTabsToRight: 'Cerrar las pestañas a la derecha',
csvFieldDelimiter: 'Delimitador de campos',
csvLinesTerminator: 'Terminador de líneas',
csvStringDelimiter: 'Delimitador de cadenas',
csvIncludeHeader: 'Incluir cabecera',
csvExportOptions: 'Opciones de exportación de CSV',
exportData: 'Exportar datos',
exportDataExplanation: 'Exportar conexiones guardadas en Antares. Se le preguntará por una contraseña para encriptar el fichero exportado.',
importData: 'Importar datos',
importDataExplanation: 'Importará un fichero con extensión .antares que contiene conexiones. Necesitará la contraseña con la que se encriptó el mismo.',
includeConnectionPasswords: 'Incluir contraseñas de conexión',
includeFolders: 'Incluir carpetas',
encryptionPassword: 'Encryption password',
encryptionPasswordError: 'The encryption password must be at least 8 characters long.',
ignoreDuplicates: 'Ignore duplicates',
wrongImportPassword: 'Wrong import password',
wrongFileFormat: 'Wrong file format',
dataImportSuccess: 'Data successfully imported',
note: 'Note | Notes',
thereAreNoNotesYet: 'There are no notes yet',
addNote: 'Add note',
editNote: 'Edit note',
saveAsNote: 'Save as note',
showArchivedNotes: 'Show archived notes',
hideArchivedNotes: 'Hide archived notes',
tag: 'Tag', // Note tag,
saveFile: 'Save file',
saveFileAs: 'Save file as',
openFile: 'Open file',
openNotes: 'Open notes',
debugConsole: 'Debug console', // <- console tab name
executedQueries: 'Executed queries', // <- console tab name
sizeLimitError: 'Maximum size of {size} exceeded'
},
faker: { // Faker.js methods, used in random generated content
address: 'Dirección',
commerce: 'Comercio',
company: 'Compañía',
database: 'Base de datos',
date: 'Fecha',
finance: 'Finanzas',
git: 'Git',
hacker: 'Hacker',
internet: 'Internet',
lorem: 'Lorem',
name: 'Nombre',
music: 'Música',
phone: 'Teléfono',
random: 'Aleatorio',
system: 'Sistema',
time: 'Hora',
vehicle: 'Vehículo',
zipCode: 'Código Postal',
zipCodeByState: 'Código Postal por Estado',
city: 'Ciudad',
cityPrefix: 'Prefijo de ciudad',
citySuffix: 'Sufijo de ciudad',
streetName: 'Nombre de calle',
streetAddress: 'Dirección',
streetSuffix: 'Sufijo de calle',
streetPrefix: 'Prefijo de calle',
secondaryAddress: 'Dirección secundaria',
county: 'Condado',
country: 'País',
countryCode: 'Código de país',
state: 'Estado',
stateAbbr: 'Abreviatura de Estado',
latitude: 'Latitud',
longitude: 'Longitud',
direction: 'Dirección',
cardinalDirection: 'Dirección cardinal',
ordinalDirection: 'Dirección ordinal',
nearbyGPSCoordinate: 'Coordenadas GPS',
timeZone: 'Zona horaria',
color: 'Color',
department: 'Departmento',
productName: 'Nombre de producto',
price: 'Precio',
productAdjective: 'Adjetivo de producto',
productMaterial: 'Material de producto',
product: 'Producto',
productDescription: 'Descripción de producto',
suffixes: 'Sufijos',
companyName: 'Nombre de compañía',
companySuffix: 'Sufijo de compañía',
catchPhrase: 'Catch phrase',
bs: 'BS',
catchPhraseAdjective: 'Catch phrase adjective',
catchPhraseDescriptor: 'Catch phrase descriptor',
catchPhraseNoun: 'Catch phrase noun',
bsAdjective: 'BS adjective',
bsBuzz: 'BS buzz',
bsNoun: 'BS noun',
column: 'Columna',
type: 'Tipo',
collation: 'Colación',
engine: 'Motor',
past: 'Pasado',
now: 'Ahora',
future: 'Futuro',
between: 'Entre',
recent: 'Reciente',
soon: 'Pronto',
month: 'Mes',
weekday: 'Día de la semana',
account: 'Cuenta',
accountName: 'Nombre de cuenta',
routingNumber: 'Número de enrutamiento',
mask: 'Máscara',
amount: 'Cantidad',
transactionType: 'Tipo de transacción',
currencyCode: 'Código de Moneda',
currencyName: 'Nombre de Moneda',
currencySymbol: 'Símbolo de Moneda',
bitcoinAddress: 'Dirección Bitcoin',
litecoinAddress: 'Dirección Litecoin',
creditCardNumber: 'Número de tarjeta',
creditCardCVV: 'CVV',
ethereumAddress: 'Dirección Ethereum',
iban: 'IBAN',
bic: 'BIC',
transactionDescription: 'Descripción de transacción',
branch: 'Rama',
commitEntry: 'Entrada de Commit',
commitMessage: 'Mensaje de Commit',
commitSha: 'SHA de Commit',
shortSha: 'SHA corto',
abbreviation: 'Abreviatura',
adjective: 'Adjetivo',
noun: 'Sustantivo',
verb: 'Verbo',
ingverb: 'Adverbio',
phrase: 'Frase',
avatar: 'Avatar',
email: 'Email',
exampleEmail: 'Email de ejemplo',
userName: 'Nombre de usuario',
protocol: 'Protocolo',
url: 'URL',
domainName: 'Dominio',
domainSuffix: 'Prefijo de dominio',
domainWord: 'Palabra de dominio',
ip: 'IP',
ipv6: 'IPv6',
userAgent: 'Agente de Usuario',
mac: 'MAC',
password: 'Contraseña',
word: 'Palabra',
words: 'Palabras',
sentence: 'Sentencia',
slug: 'Slug',
sentences: 'Sentencias',
paragraph: 'Frase',
paragraphs: 'Frases',
text: 'Texto',
lines: 'Lineas',
genre: 'Género',
firstName: 'Nombre',
lastName: 'Apellido',
middleName: 'Apellido',
findName: 'Nombre completo',
jobTitle: 'Ocupación',
gender: 'Género',
prefix: 'Prefijo',
suffix: 'Sufijo',
title: 'Título',
jobDescriptor: 'Descripción de trabajo',
jobArea: 'Area de trabajo',
jobType: 'Tipo de trabajo',
phoneNumber: 'Número de teléfono',
phoneNumberFormat: 'Formato de número de teléfono',
phoneFormats: 'Formatos de teléfono',
number: 'Número',
float: 'Decimal',
arrayElement: 'Elemento Array',
arrayElements: 'Elementos de Array',
objectElement: 'Elemento Objeto',
uuid: 'UUID',
boolean: 'Booleano',
image: 'Imagen',
locale: 'Conf. regional',
alpha: 'Alpha',
alphaNumeric: 'Alfanumérico',
hexaDecimal: 'Hexadecimal',
fileName: 'Nombre de fichero',
commonFileName: 'Common file name',
mimeType: 'Mime-Type',
commonFileType: 'Common file type',
commonFileExt: 'Common file extension',
fileType: 'Tipo de fichero',
fileExt: 'Extension de fichero',
directoryPath: 'Ruta de directorio',
filePath: 'Ruta de fichero',
semver: 'SemVer',
manufacturer: 'Fabricante',
model: 'Modelo',
fuel: 'Combustible',
vin: 'VIN'
} }
}; };

582
src/renderer/i18n/he-IL.ts Normal file
View File

@@ -0,0 +1,582 @@
/**
* [TRANSLATION UPDATE HELPER]
* - Open a terminal in antares folder and run `npm run translation:check short-code` replacing short-code with the one you are updating.
* - The command will output which terms are missing or not translated from english.
* - Open antares folder with your editor of choice.
* - Go to antares/src/renderer/i18n/ and open the locale file you want to translate.
* - Add and translate missing terms and consider whether to translate untranslated terms.
*/
export const heIL = {
general: { // General purpose terms
edit: 'עריכה',
save: 'שמירה',
close: 'סגירה',
delete: 'מחיקה',
confirm: 'אישור',
cancel: 'ביטול',
send: 'שליחה',
refresh: 'רענון',
autoRefresh: 'רענון אוטומטי',
version: 'גרסה',
donate: 'תרומה',
run: 'הרצה',
results: 'תוצאות',
size: 'גודל',
mimeType: 'סוג MIME',
download: 'הורדה',
add: 'הוספה',
data: 'נתונים',
properties: 'מאפיינים',
name: 'שם',
clear: 'ניקוי',
options: 'אפשרויות',
insert: 'הכנסה',
discard: 'ביטול',
stay: 'הישאר',
author: 'מחבר',
upload: 'העלאה',
browse: 'עיון',
content: 'תוכן',
cut: 'גזירה',
copy: 'העתקה',
paste: 'הדבקה',
duplicate: 'שכפול',
tools: 'כלים',
seconds: 'שניות',
all: 'הכל',
new: 'חדש',
select: 'בחירה',
change: 'שינוי',
include: 'כלול',
includes: 'כולל',
completed: 'הושלם',
aborted: 'בוטל',
disabled: 'מושבת',
enable: 'הפעל',
disable: 'השבת',
contributors: 'תורמים',
pin: 'נעץ',
unpin: 'בטל נעיצה',
folder: 'תיקייה | תיקיות',
none: 'אין',
singleQuote: 'מרכאה בודדת',
doubleQuote: 'מרכאות כפולות',
deleteConfirm: 'האם אתה מאשר את הביטול של',
uploadFile: 'העלאת קובץ',
format: 'פורמט', // Format code
history: 'היסטוריה',
filter: 'סינון',
manualValue: 'ערך ידני',
selectAll: 'בחר הכל',
pageNumber: 'מספר עמוד',
directoryPath: 'נתיב תיקייה',
actionSuccessful: '{action} בוצעה בהצלחה',
outputFormat: 'פורמט פלט',
singleFile: 'קובץ {ext} בודד',
zipCompressedFile: 'קובץ {ext} דחוס ב-ZIP',
copyName: 'העתק שם',
search: 'חיפוש',
title: 'כותרת',
archive: 'ארכיון', // verb
undo: 'ביטול פעולה',
moveTo: 'העבר אל'
},
connection: { // Database connection
connection: 'חיבור',
connectionName: 'שם החיבור',
hostName: 'שם המארח',
client: 'לקוח',
port: 'פורט',
user: 'משתמש',
password: 'סיסמה',
credentials: 'אישורים',
connect: 'התחבר',
connected: 'מחובר',
disconnect: 'התנתק',
disconnected: 'מנותק',
ssl: 'SSL',
enableSsl: 'הפעל SSL',
privateKey: 'מפתח פרטי',
certificate: 'תעודה',
caCertificate: 'תעודת CA',
ciphers: 'צפנים',
untrustedConnection: 'חיבור לא מהימן',
passphrase: 'ביטוי סיסמה',
sshTunnel: 'מנהרת SSH',
enableSsh: 'הפעל SSH',
connectionString: 'מחרוזת חיבור',
addConnection: 'הוסף חיבור',
createConnection: 'צור חיבור',
createNewConnection: 'צור חיבור חדש',
askCredentials: 'בקש אישורים',
testConnection: 'בדוק חיבור',
editConnection: 'ערוך חיבור',
deleteConnection: 'מחק חיבור',
connectionSuccessfullyMade: 'החיבור בוצע בהצלחה!',
readOnlyMode: 'מצב קריאה בלבד',
allConnections: 'כל החיבורים',
searchForConnections: 'חפש חיבורים',
keepAliveInterval: 'מרווח שמירת חיבור',
singleConnection: 'חיבור בודד'
},
database: { // Database related terms
schema: 'סכימה',
type: 'סוג',
insert: 'הכנס',
indexes: 'אינדקסים',
foreignKeys: 'מפתחות זרים',
length: 'אורך',
unsigned: 'ללא סימן',
default: 'ברירת מחדל',
comment: 'הערה',
key: 'מפתח | מפתחות',
order: 'סדר',
expression: 'ביטוי',
autoIncrement: 'מספור אוטומטי',
engine: 'מנוע',
field: 'שדה | שדות',
approximately: 'בקירוב',
total: 'סך הכל',
table: 'טבלה | טבלאות',
view: 'תצוגה | תצוגות',
materializedview: 'תצוגה ממומשת | תצוגות ממומשות',
definer: 'מגדיר',
algorithm: 'אלגוריתם',
trigger: 'טריגר | טריגרים',
storedRoutine: 'שגרה שמורה | שגרות שמורות',
scheduler: 'מתזמן | מתזמנים',
event: 'אירוע',
parameters: 'פרמטרים',
function: 'פונקציה | פונקציות',
deterministic: 'דטרמיניסטי',
context: 'הקשר',
export: 'ייצוא',
import: 'ייבוא',
returns: 'מחזיר',
timing: 'תזמון',
state: 'מצב',
execution: 'ביצוע',
starts: 'מתחיל',
ends: 'מסתיים',
variables: 'משתנים',
processes: 'תהליכים',
database: 'מסד נתונים',
array: 'מערך',
structure: 'מבנה',
row: 'שורה | שורות',
cell: 'תא | תאים',
triggerFunction: 'פונקציית טריגר | פונקציות טריגר',
routine: 'שגרה | שגרות',
drop: 'הסר',
commit: 'בצע',
rollback: 'שחזר',
ddl: 'DDL',
collation: 'אוסף',
resultsTable: 'טבלת תוצאות',
unableEditFieldWithoutPrimary: 'לא ניתן לערוך שדה ללא מפתח ראשי בתוצאות',
editCell: 'ערוך תא',
deleteRows: 'מחק שורה | מחק {count} שורות',
confirmToDeleteRows: 'האם אתה מאשר למחוק שורה אחת? | האם אתה מאשר למחוק {count} שורות?',
addNewRow: 'הוסף שורה חדשה',
numberOfInserts: 'מספר הכנסות',
affectedRows: 'שורות מושפעות',
createNewDatabase: 'צור מסד נתונים חדש',
databaseName: 'שם מסד הנתונים',
serverDefault: 'ברירת מחדל של השרת',
deleteDatabase: 'מחק מסד נתונים',
editDatabase: 'ערוך מסד נתונים',
clearChanges: 'נקה שינויים',
addNewField: 'הוסף שדה חדש',
manageIndexes: 'נהל אינדקסים',
manageForeignKeys: 'נהל מפתחות זרים',
allowNull: 'אפשר NULL',
zeroFill: 'מילוי אפסים',
customValue: 'ערך מותאם אישית',
onUpdate: 'בעת עדכון',
deleteField: 'מחק שדה',
createNewIndex: 'צור אינדקס חדש',
addToIndex: 'הוסף לאינדקס',
createNewTable: 'צור טבלה חדשה',
emptyTable: 'רוקן טבלה',
duplicateTable: 'שכפל טבלה',
deleteTable: 'מחק טבלה',
exportTable: 'ייצא טבלה',
emptyConfirm: 'האם אתה מאשר לרוקן',
thereAreNoIndexes: 'אין אינדקסים',
thereAreNoForeign: 'אין מפתחות זרים',
createNewForeign: 'צור מפתח זר חדש',
referenceTable: 'טבלת התייחסות',
referenceField: 'שדה התייחסות',
foreignFields: 'שדות זרים',
invalidDefault: 'ברירת מחדל לא חוקית',
onDelete: 'בעת מחיקה',
selectStatement: 'הצהרת SELECT',
triggerStatement: 'הצהרת טריגר',
sqlSecurity: 'אבטחת SQL',
updateOption: 'אפשרות עדכון',
deleteView: 'מחק תצוגה',
createNewView: 'צור תצוגה חדשה',
createNewMaterializedView: 'צור תצוגה ממומשת חדשה',
deleteTrigger: 'מחק טריגר',
createNewTrigger: 'צור טריגר חדש',
currentUser: 'משתמש נוכחי',
routineBody: 'גוף השגרה',
dataAccess: 'גישה לנתונים',
thereAreNoParameters: 'אין פרמטרים',
createNewParameter: 'צור פרמטר חדש',
createNewRoutine: 'צור שגרה שמורה חדשה',
deleteRoutine: 'מחק שגרה שמורה',
functionBody: 'גוף הפונקציה',
createNewFunction: 'צור פונקציה חדשה',
deleteFunction: 'מחק פונקציה',
schedulerBody: 'גוף המתזמן',
createNewScheduler: 'צור מתזמן חדש',
deleteScheduler: 'מחק מתזמן',
preserveOnCompletion: 'שמור בסיום',
tableFiller: 'ממלא טבלאות',
fakeDataLanguage: 'שפת נתונים מזויפים',
queryDuration: 'משך השאילתה',
setNull: 'הגדר NULL',
processesList: 'רשימת תהליכים',
processInfo: 'מידע על תהליך',
manageUsers: 'נהל משתמשים',
createNewSchema: 'צור סכימה חדשה',
schemaName: 'שם הסכימה',
editSchema: 'ערוך סכימה',
deleteSchema: 'מחק סכימה',
noSchema: 'אין סכימה',
runQuery: 'הרץ שאילתה',
thereAreNoTableFields: 'אין שדות בטבלה',
newTable: 'טבלה חדשה',
newView: 'תצוגה חדשה',
newMaterializedView: 'תצוגה ממומשת חדשה',
newTrigger: 'טריגר חדש',
newRoutine: 'שגרה חדשה',
newFunction: 'פונקציה חדשה',
newScheduler: 'מתזמן חדש',
newTriggerFunction: 'פונקציית טריגר חדשה',
thereAreNoQueriesYet: 'אין עדיין שאילתות',
searchForQueries: 'חפש שאילתות',
killProcess: 'סיים תהליך',
exportSchema: 'ייצא סכ',
importSchema: 'ייבא סכימה',
newInsertStmtEvery: 'הצהרת INSERT חדשה כל',
processingTableExport: 'מעבד {table}',
fetchingTableExport: 'מביא נתוני {table}',
writingTableExport: 'כותב נתוני {table}',
checkAllTables: 'סמן את כל הטבלאות',
uncheckAllTables: 'בטל סימון כל הטבלאות',
killQuery: 'הרוג שאילתה',
insertRow: 'הכנס שורה | הכנס שורות',
commitMode: 'מצב ביצוע',
autoCommit: 'ביצוע אוטומטי',
manualCommit: 'ביצוע ידני',
importQueryErrors: 'אזהרה: אירעה {n} שגיאה | אזהרה: אירעו {n} שגיאות',
executedQueries: 'בוצעה {n} שאילתה | בוצעו {n} שאילתות',
disableFKChecks: 'בטל בדיקות מפתח זר',
formatQuery: 'עצב שאילתה',
queryHistory: 'היסטוריית שאילתות',
clearQuery: 'נקה שאילתה',
fillCell: 'מלא תא',
executeSelectedQuery: 'בצע שאילתה נבחרת',
noResultsPresent: 'אין תוצאות',
sqlExportOptions: 'אפשרויות ייצוא SQL',
targetTable: 'טבלת יעד',
switchDatabase: 'החלף מסד נתונים',
searchForElements: 'חפש אלמנטים',
searchForSchemas: 'חפש סכימות',
savedQueries: 'שאילתות שמורות'
},
application: { // Application related terms
settings: 'הגדרות',
console: 'קונסולה',
general: 'כללי',
themes: 'ערכות נושא',
update: 'עדכון',
about: 'אודות',
language: 'שפה',
shortcuts: 'קיצורי דרך',
key: 'מקש | מקשים', // Keyboard key
event: 'אירוע',
light: 'בהיר',
dark: 'כהה',
autoCompletion: 'השלמה אוטומטית',
application: 'יישום',
editor: 'עורך',
changelog: 'יומן שינויים',
small: 'קטן',
medium: 'בינוני',
large: 'גדול',
appearance: 'מראה',
color: 'צבע',
label: 'תווית',
icon: 'סמל',
customIcon: 'סמל מותאם אישית',
fileName: 'שם קובץ',
choseFile: 'בחר קובץ',
data: 'נתונים',
password: 'סיסמה',
required: 'נדרש',
madeWithJS: 'נוצר עם 💛 ו-JavaScript!',
checkForUpdates: 'בדוק עדכונים',
noUpdatesAvailable: 'אין עדכונים זמינים',
checkingForUpdate: 'בודק עדכונים',
checkFailure: 'הבדיקה נכשלה, נסה שוב מאוחר יותר',
updateAvailable: 'עדכון זמין',
downloadingUpdate: 'מוריד עדכון',
updateDownloaded: 'העדכון הורד',
restartToInstall: 'הפעל מחדש את Antares כדי להתקין',
includeBetaUpdates: 'כלול עדכוני בטא',
notificationsTimeout: 'זמן התראות',
openNewTab: 'פתח כרטיסייה חדשה',
unsavedChanges: 'שינויים שלא נשמרו',
discardUnsavedChanges: 'יש לך שינויים שלא נשמרו. סגירת כרטיסייה זו תגרום לאובדן השינויים.',
applicationTheme: 'ערכת נושא ליישום',
editorTheme: 'ערכת נושא לעורך',
wrapLongLines: 'גלישת שורות ארוכות',
markdownSupported: 'תמיכה ב-Markdown',
plantATree: 'נטע עץ',
dataTabPageSize: 'תוצאות לעמוד',
noOpenTabs: 'אין כרטיסיות פתוחות, נווט בסרגל השמאלי או:',
restorePreviousSession: 'שחזר הפעלה קודמת',
closeTab: 'סגור כרטיסייה',
goToDownloadPage: 'עבור לדף ההורדה',
disableBlur: 'בטל טשטוש',
missingOrIncompleteTranslation: 'תרגום חסר או לא שלם?',
findOutHowToContribute: 'גלה כיצד לתרום',
reportABug: 'דווח על באג',
nextTab: 'כרטיסייה הבאה',
previousTab: 'כרטיסייה קודמת',
selectTabNumber: 'בחר כרטיסייה מספר {param}',
toggleConsole: 'הצג/הסתר קונסולה',
addShortcut: 'הוסף קיצור דרך',
editShortcut: 'ערוך קיצור דרך',
deleteShortcut: 'מחק קיצור דרך',
restoreDefaults: 'שחזר ברירות מחדל',
restoreDefaultsQuestion: 'האם אתה מאשר לשחזר את ערכי ברירת המחדל?',
registerAShortcut: 'רשום קיצור דרך',
invalidShortcutMessage: 'שילוב לא חוקי, המשך להקליד',
shortcutAlreadyExists: 'קיצור הדרך כבר קיים',
saveContent: 'שמור תוכן',
openAllConnections: 'פתח את כל החיבורים',
openSettings: 'פתח הגדרות',
runOrReload: 'הרץ או טען מחדש',
openFilter: 'פתח מסנן',
nextResultsPage: 'עמוד תוצאות הבא',
previousResultsPage: 'עמוד תוצאות קודם',
editFolder: 'ערוך תיקייה',
folderName: 'שם תיקייה',
deleteFolder: 'מחק תיקייה',
newFolder: 'תיקייה חדשה',
outOfFolder: 'מחוץ לתיקייה',
editConnectionAppearance: 'ערוך מראה חיבור',
defaultCopyType: 'סוג העתקה ברירת מחדל',
showTableSize: 'הצג גודל טבלה בסרגל הצד',
showTableSizeDescription: 'MySQL/MariaDB בלבד. הפעלת אפשרות זו עלולה להשפיע על הביצועים בסכימה עם טבלאות רבות.',
switchSearchMethod: 'החלף שיטת חיפוש',
phpArray: 'מערך PHP',
closeAllTabs: 'סגור את כל הכרטיסיות',
closeOtherTabs: 'סגור כרטיסיות אחרות',
closeTabsToLeft: 'סגור כרטיסיות משמאל',
closeTabsToRight: 'סגור כרטיסיות מימין',
csvFieldDelimiter: 'מפריד שדות',
csvLinesTerminator: 'מסיים שורות',
csvStringDelimiter: 'מפריד מחרוזות',
csvIncludeHeader: 'כלול כותרת',
csvExportOptions: 'אפשרויות ייצוא CSV',
exportData: 'ייצא נתונים',
exportDataExplanation: 'ייצא חיבורים שמורים ל-Antares. תתבקש להזין סיסמה להצפנת הקובץ המיוצא.',
importData: 'ייבא נתונים',
importDataExplanation: 'מייבא קובץ .antares המכיל חיבורים. תצטרך להזין את הסיסמה שהוגדרה בזמן הייצוא.',
includeConnectionPasswords: 'כלול סיסמאות חיבור',
includeFolders: 'כלול תיקיות',
encryptionPassword: 'סיסמת הצפנה',
encryptionPasswordError: 'סיסמת ההצפנה חייבת להיות באורך של 8 תווים לפחות.',
ignoreDuplicates: 'התעלם מכפילויות',
wrongImportPassword: 'סיסמת ייבוא שגויה',
wrongFileFormat: 'פורמט קובץ שגוי',
dataImportSuccess: 'הנתונים יובאו בהצלחה',
note: 'הערה | הערות',
thereAreNoNotesYet: 'אין עדיין הערות',
addNote: 'הוסף הערה',
editNote: 'ערוך הערה',
saveAsNote: 'שמור כהערה',
showArchivedNotes: 'הצג הערות בארכיון',
hideArchivedNotes: 'הסתר הערות בארכיון',
tag: 'תג', // Note tag
saveFile: 'שמור קובץ',
saveFileAs: 'שמור קובץ בשם',
openFile: 'פתח קובץ',
openNotes: 'פתח הערות',
debugConsole: 'קונסולת ניפוי', // <- console tab name
executedQueries: 'שאילתות שבוצעו', // <- console tab name
sizeLimitError: 'חריגה מהגודל המקסימלי של {size}'
},
faker: { // Faker.js methods, used in random generated content
address: 'כתובת',
commerce: 'מסחר',
company: 'חברה',
database: 'מסד נתונים',
date: 'תאריך',
finance: 'פיננסים',
git: 'Git',
hacker: 'האקר',
internet: 'אינטרנט',
lorem: 'לורם',
name: 'שם',
music: 'מוזיקה',
phone: 'טלפון',
random: 'אקראי',
system: 'מערכת',
time: 'זמן',
vehicle: 'רכב',
zipCode: 'מיקוד',
zipCodeByState: 'מיקוד לפי מדינה',
city: 'עיר',
cityPrefix: 'קידומת עיר',
citySuffix: 'סיומת עיר',
streetName: 'שם רחוב',
streetAddress: 'כתובת רחוב',
streetSuffix: 'סיומת רחוב',
streetPrefix: 'קידומת רחוב',
secondaryAddress: 'כתובת משנית',
county: 'מחוז',
country: 'מדינה',
countryCode: 'קוד מדינה',
state: 'מדינה',
stateAbbr: 'קיצור מדינה',
latitude: 'קו רוחב',
longitude: 'קו אורך',
direction: 'כיוון',
cardinalDirection: 'כיוון קרדינלי',
ordinalDirection: 'כיוון אורדינלי',
nearbyGPSCoordinate: 'קואורדינטת GPS קרובה',
timeZone: 'אזור זמן',
color: 'צבע',
department: 'מחלקה',
productName: 'שם מוצר',
price: 'מחיר',
productAdjective: 'תואר מוצר',
productMaterial: 'חומר מוצר',
product: 'מוצר',
productDescription: 'תיאור מוצר',
suffixes: 'סיומות',
companyName: 'שם חברה',
companySuffix: 'סיומת חברה',
catchPhrase: 'סיסמה',
bs: 'BS',
catchPhraseAdjective: 'תואר סיסמה',
catchPhraseDescriptor: 'מתאר סיסמה',
catchPhraseNoun: 'שם עצם סיסמה',
bsAdjective: 'תואר BS',
bsBuzz: 'באז BS',
bsNoun: 'שם עצם BS',
column: 'עמודה',
type: 'סוג',
collation: 'קולציה',
engine: 'מנוע',
past: 'עבר',
now: 'עכשיו',
future: 'עתיד',
between: 'בין',
recent: 'לאחרונה',
soon: 'בקרוב',
month: 'חודש',
weekday: 'יום בשבוע',
account: 'חשבון',
accountName: 'שם החשבון',
routingNumber: 'מספר ניתוב',
mask: 'מסכה',
amount: 'סכום',
transactionType: 'סוג העסקה',
currencyCode: 'קוד מטבע',
currencyName: 'שם המטבע',
currencySymbol: 'סמל המטבע',
bitcoinAddress: 'כתובת ביטקוין',
litecoinAddress: 'כתובת לייטקוין',
creditCardNumber: 'מספר כרטיס אשראי',
creditCardCVV: 'CVV של כרטיס אשראי',
ethereumAddress: 'כתובת אתריום',
iban: 'איבן',
bic: 'BIC',
transactionDescription: 'תיאור העסקה',
branch: 'סניף',
commitEntry: 'ערך קומיט',
commitMessage: 'הודעת קומיט',
commitSha: 'SHA של קומיט',
shortSha: 'SHA קצר',
abbreviation: 'קיצור',
adjective: 'שם תואר',
noun: 'שם עצם',
verb: 'פועל',
ingverb: 'פועל בצורת -ing',
phrase: 'ביטוי',
avatar: 'אווטאר',
email: 'אימייל',
exampleEmail: 'דוגמת אימייל',
userName: 'שם משתמש',
protocol: 'פרוטוקול',
url: 'כתובת URL',
domainName: 'שם דומיין',
domainSuffix: 'סיומת דומיין',
domainWord: 'מילת דומיין',
ip: 'IP',
ipv6: 'IPv6',
userAgent: 'User Agent',
mac: 'כתובת MAC',
password: 'סיסמה',
word: 'מילה',
words: 'מילים',
sentence: 'משפט',
slug: 'סלאג',
sentences: 'משפטים',
paragraph: 'פסקה',
paragraphs: 'פסקאות',
text: 'טקסט',
lines: 'שורות',
genre: 'ז\'אנר',
firstName: 'שם פרטי',
lastName: 'שם משפחה',
middleName: 'שם אמצעי',
findName: 'שם מלא',
jobTitle: 'תפקיד',
gender: 'מין',
prefix: 'תחילית',
suffix: 'סיומת',
title: 'כותרת',
jobDescriptor: 'תיאור תפקיד',
jobArea: 'תחום תפקיד',
jobType: 'סוג תפקיד',
phoneNumber: 'מספר טלפון',
phoneNumberFormat: 'פורמט מספר טלפון',
phoneFormats: 'פורמטים של מספר טלפון',
number: 'מספר',
float: 'מספר עשרוני',
arrayElement: 'אלמנט במערך',
arrayElements: 'אלמנטים במערך',
objectElement: 'אלמנט באובייקט',
uuid: 'UUID',
boolean: 'בוליאני',
image: 'תמונה',
locale: 'לוקאל',
alpha: 'אלפא',
alphaNumeric: 'אלפאנומרי',
hexaDecimal: 'הקסדצימלי',
fileName: 'שם קובץ',
commonFileName: 'שם קובץ נפוץ',
mimeType: 'סוג MIME',
commonFileType: 'סוג קובץ נפוץ',
commonFileExt: 'סיומת קובץ נפוצה',
fileType: 'סוג קובץ',
fileExt: 'סיומת קובץ',
directoryPath: 'נתיב תיקייה',
filePath: 'נתיב קובץ',
semver: 'גרסת Semver',
manufacturer: 'יצרן',
model: 'דגם',
fuel: 'דלק',
vin: 'מספר רכב (VIN)'
}
};

View File

@@ -7,6 +7,7 @@ import { deDE } from './de-DE';
import { enUS } from './en-US'; import { enUS } from './en-US';
import { esES } from './es-ES'; import { esES } from './es-ES';
import { frFR } from './fr-FR'; import { frFR } from './fr-FR';
import { heIL } from './he-IL';
import { idID } from './id-ID'; import { idID } from './id-ID';
import { itIT } from './it-IT'; import { itIT } from './it-IT';
import { jaJP } from './ja-JP'; import { jaJP } from './ja-JP';
@@ -15,8 +16,11 @@ import { nlNL } from './nl-NL';
import { ptBR } from './pt-BR'; import { ptBR } from './pt-BR';
import { ruRU } from './ru-RU'; import { ruRU } from './ru-RU';
import { ukUA } from './uk-UA'; import { ukUA } from './uk-UA';
import { uzUZ } from './uz-UZ';
import { viVN } from './vi-VN'; import { viVN } from './vi-VN';
import { zhCN } from './zh-CN'; import { zhCN } from './zh-CN';
import { zhTW } from './zh-TW';
const messages = { const messages = {
'en-US': enUS, 'en-US': enUS,
'it-IT': itIT, 'it-IT': itIT,
@@ -34,7 +38,10 @@ const messages = {
'nl-NL': nlNL, 'nl-NL': nlNL,
'ca-ES': caES, 'ca-ES': caES,
'cs-CZ': csCZ, 'cs-CZ': csCZ,
'uk-UA': ukUA 'uk-UA': ukUA,
'zh-TW': zhTW,
'he-IL': heIL,
'uz-UZ': uzUZ
}; };
type NestedPartial<T> = { type NestedPartial<T> = {
@@ -46,6 +53,8 @@ export type AvailableLocale = keyof typeof messages
const i18n = createI18n<[NestedPartial<MessageSchema>], AvailableLocale>({ const i18n = createI18n<[NestedPartial<MessageSchema>], AvailableLocale>({
fallbackLocale: 'en-US', fallbackLocale: 'en-US',
silentTranslationWarn: true,
silentFallbackWarn: true,
allowComposition: true, allowComposition: true,
messages messages
}); });

View File

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

View File

@@ -15,7 +15,7 @@ export const nlNL = {
results: 'Resultaten', results: 'Resultaten',
size: 'Grootte', size: 'Grootte',
mimeType: 'Mime-Type', mimeType: 'Mime-Type',
download: 'Download', download: 'Download', // Same as English
add: 'Toevoegen', add: 'Toevoegen',
data: 'Data', data: 'Data',
properties: 'Eigenschappen', properties: 'Eigenschappen',
@@ -65,7 +65,13 @@ export const nlNL = {
outputFormat: 'Uitvoerformaat', outputFormat: 'Uitvoerformaat',
singleFile: 'Enkel {ext}-bestand', singleFile: 'Enkel {ext}-bestand',
zipCompressedFile: 'ZIP gecomprimeerd {ext}-bestand', zipCompressedFile: 'ZIP gecomprimeerd {ext}-bestand',
include: 'Inclusief' include: 'Inclusief',
search: 'Zoek',
copyName: 'Kopieer naam',
title: 'Titel',
archive: 'Archief',
undo: 'Ongedaan maken',
moveTo: 'Verplaats naar'
}, },
connection: { connection: {
connectionName: 'Naam verbinding', connectionName: 'Naam verbinding',
@@ -100,7 +106,10 @@ export const nlNL = {
readOnlyMode: 'Alleen lezen modus', readOnlyMode: 'Alleen lezen modus',
untrustedConnection: 'Niet vertrouwde verbinding', untrustedConnection: 'Niet vertrouwde verbinding',
allConnections: 'Alle verbindingen', allConnections: 'Alle verbindingen',
searchForConnections: 'Zoek naar verbindingen' searchForConnections: 'Zoek naar verbindingen',
singleConnection: 'Enkele verbinding',
connection: 'Verbinding',
keepAliveInterval: 'Keep alive interval'
}, },
database: { database: {
schema: 'Schema', schema: 'Schema',
@@ -260,7 +269,15 @@ export const nlNL = {
targetTable: 'Doeltabel', targetTable: 'Doeltabel',
switchDatabase: 'Wissel van database', switchDatabase: 'Wissel van database',
importQueryErrors: 'Waarschuwing: {n} fout is opgetreden | Waarschuwing: {n} fouten opgetreden', importQueryErrors: 'Waarschuwing: {n} fout is opgetreden | Waarschuwing: {n} fouten opgetreden',
executedQueries: '{n} query uitgevoerd | {n} queries uitgevoerd' executedQueries: '{n} query uitgevoerd | {n} queries uitgevoerd',
insert: 'Invoegen',
exportTable: 'Exporteer tabel',
savedQueries: 'Opgeslagen queries',
searchForElements: 'Zoek naar elementen',
searchForSchemas: 'Zoek naar schema\'s',
materializedview: 'Materialized view | Materialized views',
createNewMaterializedView: 'Materialized view maken',
newMaterializedView: 'Nieuwe materialized view'
}, },
application: { application: {
settings: 'Instellingen', settings: 'Instellingen',
@@ -367,7 +384,30 @@ export const nlNL = {
wrongFileFormat: 'Bestand is geen geldig .antares bestand', wrongFileFormat: 'Bestand is geen geldig .antares bestand',
required: 'Verplicht', required: 'Verplicht',
choseFile: 'Selecteer bestand', choseFile: 'Selecteer bestand',
password: 'Wachtwoord' password: 'Wachtwoord',
note: 'Notitie',
data: 'Data',
event: 'Event',
key: 'Key',
customIcon: 'Aangepast pictogram',
fileName: 'bestandsnaam',
newFolder: 'Nieuwe map',
outOfFolder: 'Out of folder',
dataImportSuccess: 'Data succesvol geïmporteerd',
thereAreNoNotesYet: 'Er zijn nog geen notities',
addNote: 'Voeg notitie toe',
editNote: 'Bewerk notitie',
saveAsNote: 'Sla op als notitie',
showArchivedNotes: 'Toon gearchiveerde notities',
hideArchivedNotes: 'Verberg gearchiveerde notities',
tag: 'Tag',
saveFile: 'Bestand opslaan',
saveFileAs: 'Bestand opslaan als',
openFile: 'Open bestand',
openNotes: 'Open notities',
debugConsole: 'Debug Console',
executedQueries: 'Voer queries uit',
sizeLimitError: 'Maximum grootte {size} overschreden'
}, },
faker: { faker: {
address: 'Adres', address: 'Adres',
@@ -434,7 +474,7 @@ export const nlNL = {
engine: 'Engine', engine: 'Engine',
past: 'Verleden', past: 'Verleden',
now: 'Nu', now: 'Nu',
future: 'Future', future: 'Toekomstig',
between: 'Between', between: 'Between',
recent: 'Recent', recent: 'Recent',
soon: 'Soon', soon: 'Soon',
@@ -447,11 +487,11 @@ export const nlNL = {
amount: 'Amount', amount: 'Amount',
transactionType: 'Transaction type', transactionType: 'Transaction type',
currencyCode: 'Currency code', currencyCode: 'Currency code',
currencyName: 'Currency name', currencyName: 'Valutanaam',
currencySymbol: 'Currency symbol', currencySymbol: 'Valutateken',
bitcoinAddress: 'Bitcoin address', bitcoinAddress: 'Bitcoin adres',
litecoinAddress: 'Litecoin address', litecoinAddress: 'Litecoin adres',
creditCardNumber: 'Credit card number', creditCardNumber: 'Credit card nummer',
creditCardCVV: 'Credit card CVV', creditCardCVV: 'Credit card CVV',
ethereumAddress: 'Ethereum adres', ethereumAddress: 'Ethereum adres',
iban: 'IBAN', iban: 'IBAN',
@@ -487,10 +527,10 @@ export const nlNL = {
sentence: 'Zin', sentence: 'Zin',
slug: 'Slug', slug: 'Slug',
sentences: 'Zinnen', sentences: 'Zinnen',
paragraph: 'Paragraph', paragraph: 'Paragraaf',
paragraphs: 'Paragraphs', paragraphs: 'Paragrafen',
text: 'Text', text: 'Tekst',
lines: 'Lines', lines: 'Regels',
genre: 'Genre', genre: 'Genre',
firstName: 'Voornaam', firstName: 'Voornaam',
lastName: 'Achternaam', lastName: 'Achternaam',
@@ -500,7 +540,7 @@ export const nlNL = {
gender: 'Gender', gender: 'Gender',
prefix: 'Prefix', prefix: 'Prefix',
suffix: 'Suffix', suffix: 'Suffix',
title: 'Title', title: 'Titel',
jobDescriptor: 'Job descriptor', jobDescriptor: 'Job descriptor',
jobArea: 'Job area', jobArea: 'Job area',
jobType: 'Job type', jobType: 'Job type',
@@ -514,24 +554,24 @@ export const nlNL = {
objectElement: 'Object element', objectElement: 'Object element',
uuid: 'Uuid', uuid: 'Uuid',
boolean: 'Boolean', boolean: 'Boolean',
image: 'Image', image: 'Afbeelding',
locale: 'Locale', locale: 'Locale',
alpha: 'Alpha', alpha: 'Alpha',
alphaNumeric: 'Alphanumeric', alphaNumeric: 'Alfanumeriek',
hexaDecimal: 'Hexadecimal', hexaDecimal: 'Hexadecimaal',
fileName: 'File name', fileName: 'Bestandsnaam',
commonFileName: 'Common file name', commonFileName: 'Common file name',
mimeType: 'Mime type', mimeType: 'Mime type',
commonFileType: 'Common file type', commonFileType: 'Common file type',
commonFileExt: 'Common file extension', commonFileExt: 'Common file extension',
fileType: 'File type', fileType: 'Filetype',
fileExt: 'File extension', fileExt: 'File extension',
directoryPath: 'Directory path', directoryPath: 'Directory path',
filePath: 'File path', filePath: 'File path',
semver: 'Semver', semver: 'Semver',
manufacturer: 'Manufacturer', manufacturer: 'Fabrikant',
model: 'Model', model: 'Model',
fuel: 'Fuel', fuel: 'Brandstof',
vin: 'Vin' vin: 'Vin'
} }
}; };

View File

@@ -61,7 +61,17 @@ export const ruRU = {
actionSuccessful: '{action} успешно', actionSuccessful: '{action} успешно',
outputFormat: 'Формат вывода', outputFormat: 'Формат вывода',
singleFile: 'Один {ext} файл', singleFile: 'Один {ext} файл',
zipCompressedFile: 'ZIP сжатие {ext} файла' zipCompressedFile: 'ZIP сжатие {ext} файла',
include: 'Включая',
none: 'Нет',
singleQuote: 'Одинарная кавычка',
doubleQuote: 'Двойная кавычка',
copyName: 'Скопировать имя',
search: 'Поиск',
title: 'Название',
archive: 'Архив',
undo: 'Отменить',
moveTo: 'Переместить в'
}, },
connection: { connection: {
connectionName: 'Название соединения', connectionName: 'Название соединения',
@@ -96,7 +106,10 @@ export const ruRU = {
readOnlyMode: 'Режим только чтение', readOnlyMode: 'Режим только чтение',
untrustedConnection: 'Ненадежное соединение', untrustedConnection: 'Ненадежное соединение',
allConnections: 'Все соединения', allConnections: 'Все соединения',
searchForConnections: 'Поиск соединений' searchForConnections: 'Поиск соединений',
keepAliveInterval: 'Интервал поддержания соединения',
singleConnection: 'Одно соединение',
connection: 'Соединение'
}, },
database: { database: {
schema: 'Схема', schema: 'Схема',
@@ -255,7 +268,16 @@ export const ruRU = {
sqlExportOptions: 'Опции SQL экспорта', sqlExportOptions: 'Опции SQL экспорта',
targetTable: 'Целевая таблица', targetTable: 'Целевая таблица',
importQueryErrors: 'Внимание: {n} ошибка возникла | Внимание: {n} ошибок произошло', importQueryErrors: 'Внимание: {n} ошибка возникла | Внимание: {n} ошибок произошло',
executedQueries: '{n} запрос выполнен | {n} запросов выполнено' executedQueries: '{n} запрос выполнен | {n} запросов выполнено',
insert: 'Вставить',
materializedview: 'Материализованное представление | Материализованные представления',
exportTable: 'Экспорт таблицы',
createNewMaterializedView: 'Создать новое материализованное представление',
newMaterializedView: 'Новое материализованное представление',
switchDatabase: 'Переключить базу данных',
searchForElements: 'Поиск элементов',
searchForSchemas: 'Поиск схем',
savedQueries: 'Сохранённые запросы'
}, },
application: { application: {
settings: 'Настройки', settings: 'Настройки',
@@ -313,9 +335,9 @@ export const ruRU = {
previousTab: 'Предыдущая вкладка', previousTab: 'Предыдущая вкладка',
selectTabNumber: 'Выбрать вкладку под номером {param}', selectTabNumber: 'Выбрать вкладку под номером {param}',
toggleConsole: 'Переключиться на консоль', toggleConsole: 'Переключиться на консоль',
addShortcut: 'Добавить горячие клавиши', addShortcut: 'Добавить горячую клавишу',
editShortcut: 'Изменить горячие клавиши', editShortcut: 'Изменить горячую клавишу',
deleteShortcut: 'Удалить горячие клавиши', deleteShortcut: 'Удалить горячую клавишу',
restoreDefaults: 'Восстановить по-умолчанию', restoreDefaults: 'Восстановить по-умолчанию',
restoreDefaultsQuestion: 'Вы подтверждаете восстановление значений по-умолчанию?', restoreDefaultsQuestion: 'Вы подтверждаете восстановление значений по-умолчанию?',
registerAShortcut: 'Зарегистрировать горячие клавиши', registerAShortcut: 'Зарегистрировать горячие клавиши',
@@ -329,13 +351,14 @@ export const ruRU = {
openFilter: 'Открыть фильтр', openFilter: 'Открыть фильтр',
nextResultsPage: 'Следующая страница', nextResultsPage: 'Следующая страница',
previousResultsPage: 'Предыдущая страница', previousResultsPage: 'Предыдущая страница',
editFolder: 'Изменить директорию', editFolder: 'Изменить папку',
folderName: 'Название директории', folderName: 'Название папки',
deleteFolder: 'Удалить директорию', deleteFolder: 'Удалить папки',
editConnectionAppearance: 'Изменить внешний вид соединения', editConnectionAppearance: 'Изменить внешний вид соединения',
defaultCopyType: 'Тип копирования по-умолчанию', defaultCopyType: 'Тип копирования по-умолчанию',
showTableSize: 'Показывать размер таблицы в сайдбаре', showTableSize: 'Показывать размер таблицы в сайдбаре',
showTableSizeDescription: 'Только MySQL/MariaDB. Включение этого параметра может повлиять на производительность схемы с большим количеством таблиц.', showTableSizeDescription:
'Только MySQL/MariaDB. Включение этого параметра может повлиять на производительность схемы с большим количеством таблиц.',
searchForSchemas: 'Поиск схем', searchForSchemas: 'Поиск схем',
searchForElements: 'Поиск элементов', searchForElements: 'Поиск элементов',
switchSearchMethod: 'Переключить способ поиска', switchSearchMethod: 'Переключить способ поиска',
@@ -343,7 +366,49 @@ export const ruRU = {
closeOtherTabs: 'Закрыть остальные вкладки', closeOtherTabs: 'Закрыть остальные вкладки',
closeTabsToLeft: 'Закрыть вкладки слева', closeTabsToLeft: 'Закрыть вкладки слева',
closeTabsToRight: 'Закрыть вкладки справа', closeTabsToRight: 'Закрыть вкладки справа',
phpArray: 'PHP массив' phpArray: 'PHP массив',
event: 'Событие',
customIcon: 'Пользовательская иконка',
fileName: 'Имя файла',
choseFile: 'Выбрать файл',
data: 'Данные',
password: 'Пароль',
required: 'Обязательный',
newFolder: 'Новая папка',
outOfFolder: 'Вне папки',
csvFieldDelimiter: 'Разделитель полей CSV',
csvLinesTerminator: 'Терминатор строк CSV',
csvStringDelimiter: 'Разделитель строк CSV',
csvIncludeHeader: 'Включить заголовок',
csvExportOptions: 'Опции экспорта CSV',
exportData: 'Экспорт данных',
exportDataExplanation:
'Экспорт сохранённых соединений в Antares. Вам будет предложено ввести пароль для шифрования экспортируемого файла.',
importData: 'Импорт данных',
importDataExplanation: 'Импортирует файл .antares, содержащий соединения. Вам нужно будет ввести пароль, заданный во время экспорта.',
includeConnectionPasswords: 'Включить пароли соединений',
includeFolders: 'Включить папки',
encryptionPassword: 'Пароль шифрования',
encryptionPasswordError: 'Пароль шифрования должен содержать не менее 8 символов.',
ignoreDuplicates: 'Игнорировать дубликаты',
wrongImportPassword: 'Неверный пароль импорта',
wrongFileFormat: 'Неверный формат файла',
dataImportSuccess: 'Данные успешно импортированы',
note: 'Заметка | Заметки',
thereAreNoNotesYet: 'Заметок пока нет',
addNote: 'Добавить заметку',
editNote: 'Редактировать заметку',
saveAsNote: 'Сохранить как заметку',
showArchivedNotes: 'Показать архивированные заметки',
hideArchivedNotes: 'Скрыть архивированные заметки',
tag: 'Тег',
saveFile: 'Сохранить файл',
saveFileAs: 'Сохранить файл как',
openFile: 'Открыть файл',
openNotes: 'Открыть заметки',
debugConsole: 'Отладочная консоль',
executedQueries: 'Выполненные запросы',
sizeLimitError: 'Превышен максимальный размер {size}'
}, },
faker: { faker: {
address: 'Адрес', address: 'Адрес',

View File

@@ -9,11 +9,14 @@ export const localesNames: Record<string, string> = {
'vi-VN': 'Tiếng Việt', 'vi-VN': 'Tiếng Việt',
'ja-JP': '日本語', 'ja-JP': '日本語',
'zh-CN': '简体中文', 'zh-CN': '简体中文',
'zh-TW': '正體中文',
'ru-RU': 'Русский', 'ru-RU': 'Русский',
'id-ID': 'Bahasa Indonesia', 'id-ID': 'Bahasa Indonesia',
'ko-KR': '한국어', 'ko-KR': '한국어',
'nl-NL': 'Nederlands', 'nl-NL': 'Nederlands',
'ca-ES': 'Català', 'ca-ES': 'Català',
'cs-CZ': 'Čeština', 'cs-CZ': 'Čeština',
'uk-UA': 'Українська' 'uk-UA': 'Українська',
'uz-UZ': 'O`zbek',
'he-IL': 'עברית'
}; };

576
src/renderer/i18n/uz-UZ.ts Normal file
View File

@@ -0,0 +1,576 @@
export const uzUZ = {
general: {
edit: 'Tahrirlash',
save: 'Saqlash',
close: 'Yopish',
delete: 'Oʻchirish',
confirm: 'Tasdiqlash',
cancel: 'Bekor qilish',
send: 'Yuborish',
refresh: 'Yangilash',
autoRefresh: 'Avto yangilash',
version: 'Versiya',
donate: 'Donat',
run: 'Bajarish',
results: 'Korsatildi',
size: 'Hajmi',
mimeType: 'Mime-Turi',
download: 'Yuklab olish',
add: 'Qoʻshish',
data: 'Maʼlumotlar',
properties: 'Xususiyatlar',
insert: 'Kiritish',
name: 'Nomi',
clear: 'Tozalash',
seconds: 'Soniyalar',
options: 'Parametrlar',
discard: 'Bekor qilish',
stay: 'Qolish',
author: 'Muallif',
upload: 'Yuklash',
browse: 'Koʻrish',
content: 'Mazmun',
cut: 'Kesish',
copy: 'Nusxalash',
paste: 'Qoʻyish',
tools: 'Asboblar',
format: 'Formatlash',
all: 'Hammasi',
duplicate: 'Nusxa kochirish',
new: 'Yangi',
history: 'Tarix',
select: 'Tanlash',
filter: 'Filtr',
change: 'Oʻzgartirish',
includes: 'Oʻz ichiga oladi',
completed: 'Tugallandi',
aborted: 'Bekor qilindi',
disabled: 'Oʻchirib qoʻyilgan',
enable: 'Yoqish',
disable: 'Oʻchirish',
contributors: 'Hissa qoʻshuvchilar',
pin: 'Biriktirish',
unpin: 'Ajratish',
folder: 'Papka | Papkalar',
deleteConfirm: 'Oʻchirib tashlashni tasdiqlaysizmi',
uploadFile: 'Fayl yuklash',
manualValue: 'Qiymatni qoʻlda kiritish',
selectAll: 'Hammasini tanlash',
pageNumber: 'Sahifa raqami',
directoryPath: 'Papka yoʻli',
actionSuccessful: '{action} muvaffaqiyatli bajarildi',
outputFormat: 'Chiqarish formati',
singleFile: 'Bitta {ext} fayl',
zipCompressedFile: 'ZIP siqilgan {ext} fayl',
include: 'Qoʻshish',
none: 'Yoʻq',
singleQuote: 'Yagona qoʻshtirnoq',
doubleQuote: 'Ikkilik qoʻshtirnoq',
copyName: 'Nomi nusxalash',
search: 'Qidirish',
title: 'Sarlavha',
archive: 'Arxiv',
undo: 'Bekor qilish',
moveTo: 'Koʻchirish'
},
connection: {
connectionName: 'Ulanish nomi',
client: 'Mijoz',
hostName: 'Xost nomi',
port: 'Port',
user: 'Foydalanuvchi',
password: 'Parol',
credentials: 'Vakolatlar',
connect: 'Ulanish',
connected: 'Ulangan',
disconnect: 'Uzish',
disconnected: 'Uzilgan',
ssl: 'SSL',
privateKey: 'Yopiq kalit',
certificate: 'Sertifikat',
caCertificate: 'CA sertifikat',
ciphers: 'Shifrlar',
sshTunnel: 'SSH tunnel',
passphrase: 'Parol frazasi',
connectionString: 'Ulanish qatori',
addConnection: 'Ulanish qoʻshish',
createConnection: 'Ulanish yaratish',
createNewConnection: 'Yangi ulanish yaratish',
askCredentials: 'Hisob maʼlumotlarini soʻrash',
testConnection: 'Ulanishni sinash',
editConnection: 'Ulanishni tahrirlash',
deleteConnection: 'Ulanishni oʻchirish',
connectionSuccessfullyMade: 'Ulanish muvaffaqiyatli amalga oshirildi!',
enableSsl: 'SSL ni yoqish',
enableSsh: 'SSH ni yoqish',
readOnlyMode: 'Faqat oʻqish rejimi',
untrustedConnection: 'Ishonchsiz ulanish',
allConnections: 'Barcha ulanishlar',
searchForConnections: 'Ulanishlarni qidirish',
keepAliveInterval: 'Ulanishni saqlash oraliq vaqti',
singleConnection: 'Bitta ulanish',
connection: 'Ulanish'
},
database: {
schema: 'Shema',
type: 'Tur',
foreignKeys: 'Tashqi kalitlar',
length: 'Uzunlik',
unsigned: 'Imzosiz',
default: 'Standart',
comment: 'Izoh',
collation: 'Moslash',
key: 'Kalit | Kalitlar',
order: 'Tartib',
expression: 'Ifoda',
autoIncrement: 'Avtomatik oshirish',
engine: 'Dvigatel',
field: 'Maydon | Maydonlar',
approximately: 'Taxminan',
total: 'Jami',
table: 'Jadval',
view: 'Korinish',
indexes: 'Indekslar',
definer: 'Belgilar',
algorithm: 'Algoritm',
trigger: 'Trigge | Triggeler',
storedRoutine: 'Saqlangan protsedura | Saqlangan protseduralar',
scheduler: 'Rejalashtiruvchi | Rejalashtiruvchilar',
event: 'Voqea',
parameters: 'Parametrlar',
function: 'Funktsiya | Funktsiyalar',
deterministic: 'Deterministik',
context: 'Kontekst',
export: 'Eksport',
import: 'Import',
returns: 'Qaytaradi',
timing: 'Vaqt',
state: 'Holat',
execution: 'Bajarish',
starts: 'Boshlanishi',
ends: 'Tugashi',
variables: 'Ozgaruvchilar',
processes: 'Jarayonlar',
database: 'Maʼlumotlar bazasi',
array: 'Massiv',
structure: 'Tuzilish',
row: 'Qator | Qatorlar',
cell: 'Yacheyka | Yacheykalar',
triggerFunction: 'Trigger funktsiyasi | Trigger funktsiyalari',
routine: 'Protsedura',
commit: 'Tasdiqlash',
rollback: 'Bekor qilish',
resultsTable: 'Natija jadvali',
ddl: 'DDL',
drop: 'Oʻchirish',
unableEditFieldWithoutPrimary: 'Birlamchi kalit bolmasa, maydonni tahrirlash mumkin emas',
editCell: 'Yacheykani tahrirlash',
deleteRows: 'Qatorni oʻchirish | {count} qatorni oʻchirish',
confirmToDeleteRows: 'Qatorni oʻchirishni tasdiqlaysizmi? | {count} qatorni oʻchirishni tasdiqlaysizmi?',
addNewRow: 'Yangi qator qoʻshish',
numberOfInserts: 'Qoʻshilganlar soni',
affectedRows: 'Taqsir qilingan qatorlar',
createNewDatabase: 'Yangi maʼlumotlar bazasi yaratish',
databaseName: 'Maʼlumotlar bazasi nomi',
serverDefault: 'Server standartlari boyicha',
deleteDatabase: 'Maʼlumotlar bazasini oʻchirish',
editDatabase: 'Maʼlumotlar bazasini tahrirlash',
clearChanges: 'Oʻzgarishlarni oʻchirish',
addNewField: 'Yangi maydon qoshish',
manageIndexes: 'Indekslarni boshqarish',
manageForeignKeys: 'Tashqi kalitlarni boshqarish',
allowNull: 'NULL ga ruxsat berish',
zeroFill: 'Nol bilan toʻldirish',
customValue: 'Foydalanuvchi qiymati',
onUpdate: 'Yangilanishda',
deleteField: 'Maydonni oʻchirish',
createNewIndex: 'Yangi indeks yaratish',
addToIndex: 'Indeksga qoʻshish',
createNewTable: 'Yangi jadval yaratish',
emptyTable: 'Jadvalni tozalash',
deleteTable: 'Jadvalni oʻchirish',
emptyConfirm: 'Tozalashni tasdiqlaysizmi?',
thereAreNoIndexes: 'Indekslar mavjud emas',
thereAreNoForeign: 'Tashqi kalitlar mavjud emas',
createNewForeign: 'Yangi tashqi kalit yaratish',
referenceTable: 'Jadvalga murojaat',
referenceField: 'Maydonga murojaat',
foreignFields: 'Tashqi maydonlar',
invalidDefault: 'Notogri qiymat',
onDelete: 'Oʻchirishda',
selectStatement: 'Tanlash operatori',
triggerStatement: 'Trigger operatori',
sqlSecurity: 'SQL xavfsizligi',
updateOption: 'Yangilash parametrlari',
deleteView: 'Korinishni oʻchirish',
createNewView: 'Yangi korinish yaratish',
deleteTrigger: 'Trigerni oʻchirish',
createNewTrigger: 'Yangi trigger yaratish',
currentUser: 'Hozirgi foydalanuvchi',
routineBody: 'Protsedura matni',
dataAccess: 'Maʼlumotlarga kirish',
thereAreNoParameters: 'Parametrlar yoʻq',
createNewParameter: 'Yangi parametr yaratish',
createNewRoutine: 'Yangi protsedura yaratish',
deleteRoutine: 'Protsedurani oʻchirish',
functionBody: 'Funktsiya matni',
createNewFunction: 'Yangi funktsiya yaratish',
deleteFunction: 'Funktsiyani oʻchirish',
schedulerBody: 'Rejalashtiruvchi matni',
createNewScheduler: 'Yangi rejalashtiruvchi yaratish',
deleteScheduler: 'Rejalashtiruvchini oʻchirish',
preserveOnCompletion: 'Yakunlangandan soʻng saqlash',
tableFiller: 'Jadval toʻldiruvchisi',
fakeDataLanguage: 'Soxta maʼlumotlar tili',
queryDuration: 'Soʻrov davomiyligi',
setNull: 'NULL qiymatni oʻrnatish',
processesList: 'Jarayonlar roʻyxati',
processInfo: 'Jarayon maʼlumotlari',
manageUsers: 'Foydalanuvchilarni boshqarish',
createNewSchema: 'Yangi shema yaratish',
schemaName: 'Shema nomi',
editSchema: 'Shemaga ozgartirish kiritish',
deleteSchema: 'Shemadan oʻchirish',
duplicateTable: 'Jadvalni dublikat qilish',
noSchema: 'Shemalar mavjud emas',
runQuery: 'Soʻrovni bajarish',
thereAreNoTableFields: 'Jadvalda maydonlar yoʻq',
newTable: 'Yangi jadval',
newView: 'Yangi korinish',
newTrigger: 'Yangi trigger',
newRoutine: 'Yangi protsedura',
newFunction: 'Yangi funktsiya',
newScheduler: 'Yangi rejalashtiruvchi',
newTriggerFunction: 'Yangi trigger funktsiyasi',
thereAreNoQueriesYet: 'Soʻrovlar hali mavjud emas',
searchForQueries: 'Soʻrovlarni qidirish',
killProcess: 'Jarayonni toʻxtatish',
exportSchema: 'Shemadan eksport qilish',
importSchema: 'Shemaga import qilish',
newInsertStmtEvery: 'Har bir uchun yangi INSERT operator',
processingTableExport: '{table} ni qayta ishlash',
fetchingTableExport: '{table} maʼlumotlarini olish',
writingTableExport: '{table} ga yozish',
checkAllTables: 'Barcha jadvallarni belgilash',
uncheckAllTables: 'Barcha jadvallardan belgilashni olib tashlash',
killQuery: 'Soʻrovni toʻxtatish',
insertRow: 'Qator kiritish | Qatorlarni kiritish',
commitMode: 'Tranzaksiya tasdiqlash rejimi',
autoCommit: 'Avtomatik tasdiqlash',
manualCommit: 'Qoʻlda tasdiqlash',
disableFKChecks: 'Tashqi kalitlarni tekshirishni oʻchirish',
formatQuery: 'Soʻrovni formatlash',
queryHistory: 'Soʻrovlar tarixi',
clearQuery: 'Soʻrovni tozalash',
fillCell: 'Yacheykani toʻldirish',
executeSelectedQuery: 'Tanlangan soʻrovni bajarish',
noResultsPresent: 'Natijalar yoʻq',
sqlExportOptions: 'SQL eksport parametrlari',
targetTable: 'Maqsad jadvali',
importQueryErrors: 'Diqqat: {n} xato yuz berdi | Diqqat: {n} xatolar yuz berdi',
executedQueries: '{n} soʻrov bajarildi | {n} soʻrovlar bajarildi',
insert: 'Kiritish',
materializedview: 'Materializatsiya qilingan korinish | Materializatsiya qilingan korinishlar',
exportTable: 'Jadvalni eksport qilish',
createNewMaterializedView: 'Yangi materializatsiya qilingan korinish yaratish',
newMaterializedView: 'Yangi materializatsiya qilingan korinish',
switchDatabase: 'Maʼlumotlar bazasini almashtirish',
searchForElements: 'Elementlarni qidirish',
searchForSchemas: 'Shemalarni qidirish',
savedQueries: 'Saqlangan soʻrovlar'
},
application: {
settings: 'Sozlamalar',
general: 'Umumiy',
themes: 'Mavzular',
update: 'Yangilash',
about: 'Dastur haqida',
language: 'Til',
light: 'Yorugʻ',
dark: 'Qorongʻi',
autoCompletion: 'Avto-tugallash',
application: 'Ilova',
editor: 'Tahrirchi',
scratchpad: 'Eslatmalar',
changelog: 'Oʻzgarishlar jurnali',
small: 'Kichik',
medium: 'Oʻrta',
large: 'Katta',
console: 'Konsol',
shortcuts: 'Qisqa tugmalar',
appearance: 'Tashqi koʻrinish',
color: 'Rang',
label: 'Yorliq',
icon: 'Belgi',
madeWithJS: '💛 va JavaScript bilan yaratilgan!',
checkForUpdates: 'Yangilanishlarni tekshirish',
noUpdatesAvailable: 'Yangilanishlar topilmadi',
checkingForUpdate: 'Yangilanishlarni qidirish',
checkFailure: 'Yangilanishlarni tekshirib boʻlmadi, iltimos keyinroq urinib koʻring',
updateAvailable: 'Yangilanish mavjud',
downloadingUpdate: 'Yangilanishni yuklab olish',
updateDownloaded: 'Yangilanish yuklab olindi',
restartToInstall: 'Oʻrnatish uchun Antares-ni qayta ishga tushiring',
notificationsTimeout: 'Bildirishnoma vaqti',
openNewTab: 'Yangi tab ochish',
unsavedChanges: 'Saqlanmagan oʻzgarishlar',
discardUnsavedChanges: 'Saqlanmagan maʼlumotlaringiz mavjud. Ushbu oynani yopish ularni bekor qiladi.',
applicationTheme: 'Ilova mavzusi',
editorTheme: 'Tahrirchi mavzusi',
wrapLongLines: 'Uzun satrlarni oʻrash',
includeBetaUpdates: 'Beta yangilanishlarni olish',
markdownSupported: 'Markdown qoʻllab-quvvatlanadi',
plantATree: 'Daraxt ekish',
dataTabPageSize: 'MAʼLUMOTLAR yorligʻi sahifa oʻlchami',
noOpenTabs: 'Ochiq yorliqlar yoʻq, chap paneldan foydalaning yoki:',
restorePreviousSession: 'Avvalgi sessiyani tiklash',
closeTab: 'Yorliqni yopish',
goToDownloadPage: 'Yuklash sahifasiga oʻtish',
disableBlur: 'Bulutni oʻchirish',
missingOrIncompleteTranslation: 'Yetishmayotgan yoki toʻliq boʻlmagan tarjima?',
findOutHowToContribute: 'Qanday hissa qoʻshishni bilib oling',
disableScratchpad: 'Eslatmalarni oʻchirish',
reportABug: 'Xato haqida xabar berish',
nextTab: 'Keyingi yorliq',
previousTab: 'Oldingi yorliq',
selectTabNumber: '{param} raqamli yorliqni tanlash',
toggleConsole: 'Konsolni almashtirish',
addShortcut: 'Qisqa tugma qoʻshish',
editShortcut: 'Qisqa tugmani tahrirlash',
deleteShortcut: 'Qisqa tugmani oʻchirish',
restoreDefaults: 'Standartlarga qaytarish',
restoreDefaultsQuestion: 'Standart qiymatlarni tiklashga rozimisiz?',
registerAShortcut: 'Qisqa tugma roʻyxatdan oʻtkazish',
invalidShortcutMessage: 'Bu kombinatsiyani ishlatib boʻlmaydi, boshqa birini sinab koʻring',
shortcutAlreadyExists: 'Bunday kombinatsiya allaqachon mavjud',
saveContent: 'Mazmunni saqlash',
openAllConnections: 'Barcha ulanishlarni ochish',
openSettings: 'Sozlamalarni ochish',
openScratchpad: 'Eslatmalarni ochish',
runOrReload: 'Bajaring yoki qayta yuklang',
openFilter: 'Filtrni ochish',
nextResultsPage: 'Keyingi sahifa',
previousResultsPage: 'Oldingi sahifa',
editFolder: 'Papkani tahrirlash',
folderName: 'Papkalar nomi',
deleteFolder: 'Papkani oʻchirish',
editConnectionAppearance: 'Ulanish tashqi koʻrinishini oʻzgartirish',
defaultCopyType: 'Odatiy nusxa koʻchirish turi',
showTableSize: 'Yon panelda jadval oʻlchamini koʻrsatish',
showTableSizeDescription: 'Faqat MySQL/MariaDB. Ushbu parametrni yoqish koʻp jadvalli sxemalarda ishlash tezligiga taʼsir qilishi mumkin.',
searchForSchemas: 'Sxemalarni qidirish',
searchForElements: 'Elementlarni qidirish',
switchSearchMethod: 'Qidiruv usulini almashtirish',
closeAllTabs: 'Barcha yorliqlarni yopish',
closeOtherTabs: 'Boshqa yorliqlarni yopish',
closeTabsToLeft: 'Chapdagi yorliqlarni yopish',
closeTabsToRight: 'Oʻngdagi yorliqlarni yopish',
phpArray: 'PHP massiv',
event: 'Hodisa',
customIcon: 'Maxsus belgi',
fileName: 'Fayl nomi',
choseFile: 'Faylni tanlash',
data: 'Maʼlumotlar',
password: 'Parol',
required: 'Majburiy',
newFolder: 'Yangi papka',
outOfFolder: 'Papkadan tashqari',
csvFieldDelimiter: 'CSV maydon ajratgichi',
csvLinesTerminator: 'CSV satr terminatori',
csvStringDelimiter: 'CSV satr ajratgichi',
csvIncludeHeader: 'Sarlavhani kiritish',
csvExportOptions: 'CSV eksport imkoniyatlari',
exportData: 'Maʼlumotlarni eksport qilish',
exportDataExplanation: 'Antares-dagi saqlangan ulanishlarni eksport qilish. Shifrlangan fayl uchun parol kiritishingiz soʻraladi.',
importData: 'Maʼlumotlarni import qilish',
importDataExplanation: '.antares faylini import qiladi, unda ulanishlar mavjud. Eksport paytida kiritilgan parolni kiritishingiz kerak.',
includeConnectionPasswords: 'Ulanish parollarini kiritish',
includeFolders: 'Papkalarni kiritish',
encryptionPassword: 'Shifrlash paroli',
encryptionPasswordError: 'Shifrlash paroli kamida 8 ta belgi boʻlishi kerak.',
ignoreDuplicates: 'Nusxalarni inkor qilish',
wrongImportPassword: 'Import paroli notoʻgʻri',
wrongFileFormat: 'Fayl formati notoʻgʻri',
dataImportSuccess: 'Maʼlumotlar muvaffaqiyatli import qilindi',
note: 'Eslatma | Eslatmalar',
thereAreNoNotesYet: 'Hali eslatmalar yoʻq',
addNote: 'Eslatma qoʻshish',
editNote: 'Eslatmani tahrirlash',
saveAsNote: 'Eslatma sifatida saqlash',
showArchivedNotes: 'Arxivlangan eslatmalarni koʻrsatish',
hideArchivedNotes: 'Arxivlangan eslatmalarni yashirish',
tag: 'Teg',
saveFile: 'Faylni saqlash',
saveFileAs: 'Faylni sifatida saqlash',
openFile: 'Faylni ochish',
openNotes: 'Eslatmalarni ochish',
debugConsole: 'Nosozliklarni tuzatish konsoli',
executedQueries: 'Bajarilgan soʻrovlar',
sizeLimitError: 'Maksimal oʻlcham {size} dan oshib ketdi'
},
faker: {
address: 'Manzil',
commerce: 'Tijorat',
company: 'Kompaniya',
database: 'Ma`lumotlar bazasi',
date: 'Sana',
finance: 'Moliyaviy',
git: 'Git',
hacker: 'Xaker',
internet: 'Internet',
lorem: 'Lorem',
name: 'Ism',
music: 'Musiqa',
phone: 'Telefon',
random: 'Tasodifiy',
system: 'Tizim',
time: 'Vaqt',
vehicle: 'Transport vositasi',
zipCode: 'Pochta kodi',
zipCodeByState: 'Shahar pochta kodi',
city: 'Shahar',
cityPrefix: 'Shahar prefiksi',
citySuffix: 'Shahar sufixi',
streetName: 'Ko`cha nomi',
streetAddress: 'Ko`cha manzili',
streetSuffix: 'Ko`cha sufixi',
streetPrefix: 'Ko`cha prefiksi',
secondaryAddress: 'Ikkinchi manzil',
county: 'Okrug',
country: 'Davlat',
countryCode: 'Davlat kodi',
state: 'Viloyat',
stateAbbr: 'Viloyat qisqartmasi',
latitude: 'Kenglik',
longitude: 'Uzunlik',
direction: 'Yo`nalish',
cardinalDirection: 'Kardinal yo`nalish',
ordinalDirection: 'Tartibiy yo`nalish',
nearbyGPSCoordinate: 'Yaqin GPS koordinatasi',
timeZone: 'Vaqt zonasі',
color: 'Rang',
department: 'Bo`lim',
productName: 'Mahsulot nomi',
price: 'Narx',
productAdjective: 'Mahsulotga oid sifat',
productMaterial: 'Mahsulot materiali',
product: 'Mahsulot',
productDescription: 'Mahsulot ta`rifi',
suffixes: 'Sufikslari',
companyName: 'Kompaniya nomi',
companySuffix: 'Kompaniya sufixi',
catchPhrase: 'Slogan',
bs: 'BS',
catchPhraseAdjective: 'Slogan sifat',
catchPhraseDescriptor: 'Slogan tavsifi',
catchPhraseNoun: 'Slogan ot',
bsAdjective: 'BS sifat',
bsBuzz: 'BS shovqin',
bsNoun: 'BS ot',
column: 'Ustun',
type: 'Tur',
collation: 'Taqqoslash',
engine: 'Dvigatel',
past: 'O`tgan',
now: 'Hozir',
future: 'Kelajak',
between: 'Orasida',
recent: 'Yaqinda',
soon: 'Tez orada',
month: 'Oy',
weekday: 'Hafta kuni',
account: 'Hisob',
accountName: 'Hisob nomi',
routingNumber: 'Yo`nalish raqami',
mask: 'Maska',
amount: 'Miqdor',
transactionType: 'Transaksiya turi',
currencyCode: 'Valyuta kodi',
currencyName: 'Valyuta nomi',
currencySymbol: 'Valyuta belgilari',
bitcoinAddress: 'Bitcoin hamyoni',
litecoinAddress: 'Litecoin hamyoni',
creditCardNumber: 'Kredit karta raqami',
creditCardCVV: 'CVV kodi',
ethereumAddress: 'Ethereum hamyoni',
iban: 'Iban',
bic: 'Bic',
transactionDescription: 'Transaksiya ta`rifi',
branch: 'Filial',
commitEntry: 'Kommit',
commitMessage: 'Kommit xabari',
commitSha: 'SHA kommit',
shortSha: 'Qisqa SHA',
abbreviation: 'Qisqartma',
adjective: 'Sifat',
noun: 'Ot',
verb: 'Fe`l',
ingverb: 'Ildiz fe`l',
phrase: 'Ibora',
avatar: 'Avatar',
email: 'Elektron pochta',
exampleEmail: 'Elektron pochta misoli',
userName: 'Login',
protocol: 'Protokol',
url: 'Url',
domainName: 'Domen nomi',
domainSuffix: 'Domen sufixi',
domainWord: 'Domen so`zi',
ip: 'Ip',
ipv6: 'Ipv6',
userAgent: 'User agent',
mac: 'MAC-manzil',
password: 'Parol',
word: 'So`z',
words: 'So`zlar',
sentence: 'Gap',
slug: 'Slug',
sentences: 'Gaplar',
paragraph: 'Paragraf',
paragraphs: 'Paragraflar',
text: 'Matn',
lines: 'Chiziqlar',
genre: 'Janr',
firstName: 'Ism',
lastName: 'Familiya',
middleName: 'Otasining ismi',
findName: 'To`liq ism',
jobTitle: 'Lavozim',
gender: 'Jins',
prefix: 'Prefiks',
suffix: 'Sufiks',
title: 'Sarlavha',
jobDescriptor: 'Lavozim ta`rifi',
jobArea: 'Lavozim sohasi',
jobType: 'Lavozim turi',
phoneNumber: 'Telefon raqami',
phoneNumberFormat: 'Telefon raqami formati',
phoneFormats: 'Telefon raqami formatlari',
number: 'Raqam',
float: 'O`nlik son',
arrayElement: 'Massiv elementi',
arrayElements: 'Massiv elementlari',
objectElement: 'Obyekt elementi',
uuid: 'Uuid',
boolean: 'Mantiqiy',
image: 'Rasm',
locale: 'Mahalliy',
alpha: 'Harflar',
alphaNumeric: 'Alfavitli-sonli',
hexaDecimal: 'O`n oltilik',
fileName: 'Fayl nomi',
commonFileName: 'Ommaviy fayl nomi',
mimeType: 'Mime-turi',
commonFileType: 'Ommaviy fayl turi',
commonFileExt: 'Ommaviy fayl kengaytmasi',
fileType: 'Fayl turi',
fileExt: 'Fayl kengaytmasi',
directoryPath: 'Katalog yo`li',
filePath: 'Fayl yo`li',
semver: 'Semver',
manufacturer: 'Ishlab chiqaruvchi',
model: 'Model',
fuel: 'Yoqilg`i',
vin: 'Vin'
}
};

View File

@@ -65,9 +65,16 @@ export const zhCN = {
actionSuccessful: '{action} 成功', actionSuccessful: '{action} 成功',
outputFormat: '输出格式', outputFormat: '输出格式',
singleFile: '单个 {ext} 文件', singleFile: '单个 {ext} 文件',
zipCompressedFile: 'ZIP 压缩 {ext} 文件' zipCompressedFile: 'ZIP 压缩 {ext} 文件',
copyName: '复制名称',
search: '搜索',
title: '标题',
archive: '归档',
undo: '重做',
moveTo: '移动到'
}, },
connection: { // 数据库连接 connection: { // 数据库连接
connection: '连接',
connectionName: '连接名称', connectionName: '连接名称',
hostName: '主机名', hostName: '主机名',
client: '数据库类型', client: '数据库类型',
@@ -101,7 +108,8 @@ export const zhCN = {
readOnlyMode: '只读模式', readOnlyMode: '只读模式',
allConnections: '所有连接', allConnections: '所有连接',
searchForConnections: '搜索连接', searchForConnections: '搜索连接',
keepAliveInterval: '保持活跃间隔' keepAliveInterval: '保持活跃间隔',
singleConnection: '单一连接'
}, },
database: { // 数据库相关术语 database: { // 数据库相关术语
schema: '模式(schema)', schema: '模式(schema)',
@@ -109,6 +117,7 @@ export const zhCN = {
insert: '插入', insert: '插入',
indexes: '索引', indexes: '索引',
foreignKeys: '外键', foreignKeys: '外键',
tableChecks: '表检查',
length: '长度', length: '长度',
unsigned: '无符号', unsigned: '无符号',
default: '默认', default: '默认',
@@ -123,6 +132,7 @@ export const zhCN = {
total: '总计', total: '总计',
table: '表 | 表', table: '表 | 表',
view: '视图 | 视图', view: '视图 | 视图',
materializedview: '实体化视图 | 实体化视图',
definer: '定义者', definer: '定义者',
algorithm: '算法', algorithm: '算法',
trigger: '触发器 | 触发器', trigger: '触发器 | 触发器',
@@ -172,12 +182,15 @@ export const zhCN = {
addNewField: '添加新字段', addNewField: '添加新字段',
manageIndexes: '管理索引', manageIndexes: '管理索引',
manageForeignKeys: '管理外键', manageForeignKeys: '管理外键',
manageTableChecks: '管理表 check 约束',
allowNull: '允许 NULL', allowNull: '允许 NULL',
zeroFill: '零填充', zeroFill: '零填充',
customValue: '自定义值', customValue: '自定义值',
onUpdate: '在更新', onUpdate: '在更新',
deleteField: '删除字段', deleteField: '删除字段',
createNewIndex: '创建新索引', createNewIndex: '创建新索引',
createNewCheck: '创建新 check 约束',
checkClause: 'Check 约束',
addToIndex: '添加到索引', addToIndex: '添加到索引',
createNewTable: '创建新表', createNewTable: '创建新表',
emptyTable: '清空表', emptyTable: '清空表',
@@ -187,6 +200,7 @@ export const zhCN = {
emptyConfirm: '您是否确认清空', emptyConfirm: '您是否确认清空',
thereAreNoIndexes: '没有索引', thereAreNoIndexes: '没有索引',
thereAreNoForeign: '没有外键', thereAreNoForeign: '没有外键',
thereAreNoTableChecks: '没有表 check 约束',
createNewForeign: '创建新外键', createNewForeign: '创建新外键',
referenceTable: '参考表', referenceTable: '参考表',
referenceField: '参考字段', referenceField: '参考字段',
@@ -199,6 +213,7 @@ export const zhCN = {
updateOption: '更新选项', updateOption: '更新选项',
deleteView: '删除视图', deleteView: '删除视图',
createNewView: '创建新视图', createNewView: '创建新视图',
createNewMaterializedView: '创建新实体化视图',
deleteTrigger: '删除触发器', deleteTrigger: '删除触发器',
createNewTrigger: '创建新触发器', createNewTrigger: '创建新触发器',
currentUser: '当前用户', currentUser: '当前用户',
@@ -231,6 +246,7 @@ export const zhCN = {
thereAreNoTableFields: '没有表的字段', thereAreNoTableFields: '没有表的字段',
newTable: '新表', newTable: '新表',
newView: '新视图', newView: '新视图',
newMaterializedView: '新实体化视图',
newTrigger: '新触发器', newTrigger: '新触发器',
newRoutine: '新例程', newRoutine: '新例程',
newFunction: '新函数', newFunction: '新函数',
@@ -265,12 +281,11 @@ export const zhCN = {
targetTable: '目标表', targetTable: '目标表',
switchDatabase: '切换数据库', switchDatabase: '切换数据库',
searchForElements: '搜索元素', searchForElements: '搜索元素',
searchForSchemas: '搜索模式(schema)' searchForSchemas: '搜索模式(schema)',
savedQueries: '已保存的查询'
}, },
application: { // 应用程序相关术语 application: { // 应用程序相关术语
settings: '设置', settings: '设置',
scratchpad: '草稿栏',
disableScratchpad: '禁用草稿栏',
console: '控制台', console: '控制台',
general: '常规', general: '常规',
themes: '主题', themes: '主题',
@@ -293,6 +308,7 @@ export const zhCN = {
color: '颜色', color: '颜色',
label: '标签', label: '标签',
icon: '图标', icon: '图标',
customIcon: '定制图标',
fileName: '文件名称', fileName: '文件名称',
choseFile: '选择文件', choseFile: '选择文件',
data: '数据', data: '数据',
@@ -317,7 +333,7 @@ export const zhCN = {
wrapLongLines: '将长行换行显示', wrapLongLines: '将长行换行显示',
markdownSupported: '支持 Markdown', markdownSupported: '支持 Markdown',
plantATree: '种植一棵树', plantATree: '种植一棵树',
dataTabPageSize: '数据标签的页面大小', dataTabPageSize: '每页结果',
noOpenTabs: '没有打开的标签, 请在左侧栏上导航或:', noOpenTabs: '没有打开的标签, 请在左侧栏上导航或:',
restorePreviousSession: '恢复上一个会话', restorePreviousSession: '恢复上一个会话',
closeTab: '关闭标签', closeTab: '关闭标签',
@@ -341,7 +357,6 @@ export const zhCN = {
saveContent: '保存内容', saveContent: '保存内容',
openAllConnections: '打开所有连接', openAllConnections: '打开所有连接',
openSettings: '打开设置', openSettings: '打开设置',
openScratchpad: '打开草稿栏',
runOrReload: '运行或重新加载', runOrReload: '运行或重新加载',
openFilter: '打开过滤器', openFilter: '打开过滤器',
nextResultsPage: '下一个结果页', nextResultsPage: '下一个结果页',
@@ -349,6 +364,8 @@ export const zhCN = {
editFolder: '编辑文件夹', editFolder: '编辑文件夹',
folderName: '文件夹名称', folderName: '文件夹名称',
deleteFolder: '删除文件夹', deleteFolder: '删除文件夹',
newFolder: '新文件夹',
outOfFolder: '',
editConnectionAppearance: '编辑连接的外观', editConnectionAppearance: '编辑连接的外观',
defaultCopyType: '默认复制类型', defaultCopyType: '默认复制类型',
showTableSize: '在侧边栏显示表大小', showTableSize: '在侧边栏显示表大小',
@@ -375,7 +392,22 @@ export const zhCN = {
ignoreDuplicates: '忽略重复', ignoreDuplicates: '忽略重复',
wrongImportPassword: '错误的导入密码', wrongImportPassword: '错误的导入密码',
wrongFileFormat: '错误的文件格式', wrongFileFormat: '错误的文件格式',
dataImportSuccess: '数据已成功导入' dataImportSuccess: '数据已成功导入',
note: '笔记 | 笔记',
thereAreNoNotesYet: '目前还没有笔记',
addNote: '添加笔记',
editNote: '编辑笔记',
saveAsNote: '另存为笔记',
showArchivedNotes: '显示归档笔记',
hideArchivedNotes: '隐藏归档笔记',
tag: '笔记标签', // Note tag,
saveFile: '保存文件',
saveFileAs: '将文件另存为',
openFile: '打开文件',
openNotes: '打开笔记',
debugConsole: '调试控制台',
executedQueries: '执行的查询',
sizeLimitError: '超过 {size} 的最大大小'
}, },
faker: { // Faker.js 方法,用于随机生成的内容 faker: { // Faker.js 方法,用于随机生成的内容
address: '地址', address: '地址',

569
src/renderer/i18n/zh-TW.ts Normal file
View File

@@ -0,0 +1,569 @@
export const zhTW = {
general: {
// 通用術語
edit: '編輯',
save: '保存',
close: '關閉',
delete: '刪除',
confirm: '確定',
cancel: '取消',
send: '發送',
refresh: '重新整理',
autoRefresh: '自動重新整理',
version: '版本',
donate: '捐贈',
run: '執行',
results: '結果',
size: '大小',
mimeType: 'MIME類型',
download: '下載',
add: '新增',
data: '資料',
properties: '屬性',
name: '名稱',
clear: '清除',
options: '選項',
insert: '插入',
discard: '丟棄',
stay: '等待',
author: '作者',
upload: '上傳',
browse: '瀏覽',
content: '內容',
cut: '剪下',
copy: '複製',
paste: '貼上',
duplicate: '副本',
tools: '工具',
seconds: '秒',
all: '全部',
new: '新',
select: '選擇',
change: '變更',
include: '包含',
includes: '包含',
completed: '已完成',
aborted: '中止',
disabled: '已禁用',
enable: '啟用',
disable: '禁用',
contributors: '貢獻者',
pin: '固定',
unpin: '取消固定',
folder: '檔案夾 | 檔案夾',
none: '無',
singleQuote: '單引號',
doubleQuote: '雙引號',
deleteConfirm: '您是否確認取消',
uploadFile: '上傳檔案',
format: '格式碼', // 格式碼
history: '曆史',
filter: '過濾器',
manualValue: '手動值',
selectAll: '選擇全部',
pageNumber: '頁數',
directoryPath: '目錄路徑',
actionSuccessful: '{action} 成功',
outputFormat: '輸出格式',
singleFile: '單個 {ext} 檔案',
zipCompressedFile: 'ZIP 壓縮 {ext} 檔案',
copyName: '複製名稱',
search: '搜索',
title: '標題',
archive: '封存',
undo: '重做',
moveTo: '移動到'
},
connection: {
// 資料庫連接
connection: '連線',
connectionName: '連線名稱',
hostName: '主機名',
client: '資料庫類型',
port: '連線埠',
user: '使用者',
password: '密碼',
credentials: '憑證',
connect: '連線',
connected: '已連線',
disconnect: '斷開連線',
disconnected: '斷開連線',
ssl: 'SSL',
enableSsl: '啟用 SSL',
privateKey: '私鑰',
certificate: '證書',
caCertificate: 'CA 證書',
ciphers: '密碼',
untrustedConnection: '不受信任的連線',
passphrase: '密碼提示',
sshTunnel: 'SSH 通道',
enableSsh: '啟用 SSH',
connectionString: '連接字符串',
addConnection: '新增連線',
createConnection: '建立連線',
createNewConnection: '建立新連線',
askCredentials: '詢問憑據',
testConnection: '測試連線',
editConnection: '編輯連線',
deleteConnection: '刪除連線',
connectionSuccessfullyMade: '連線成功了!',
readOnlyMode: '唯讀模式',
allConnections: '所有連線',
searchForConnections: '搜索連線',
keepAliveInterval: '保持活躍間隔',
singleConnection: '單一連線'
},
database: {
// 資料庫庫相關術語
schema: '模式(schema)',
type: '類型',
insert: '插入',
indexes: '索引',
foreignKeys: '外鍵',
length: '長度',
unsigned: '無符號',
default: '預設',
comment: '註釋',
key: '鍵 | 鍵',
order: '排序',
expression: '表達式',
autoIncrement: '自動增量',
engine: '引擎',
field: '字段 | 字段',
approximately: '大約',
total: '總計',
table: '表 | 表',
view: '視圖 | 視圖',
definer: '定義者',
algorithm: '算法',
trigger: '觸發器 | 觸發器',
storedRoutine: '存儲例程 | 存儲例程',
scheduler: '調度器 | 調度器',
event: '事件',
parameters: '參數',
function: '函數 | 函數',
deterministic: '確定的',
context: '上下文',
export: '匯出',
import: '匯入',
returns: '回傳',
timing: '定時',
state: '狀態',
execution: '執行',
starts: '開始',
ends: '結束',
variables: '變數',
processes: '進程',
database: '資料庫',
array: '數據',
structure: '結構',
row: '行 | 行',
cell: '單元格 | 單元格',
triggerFunction: '觸發器函數 | 觸發器函數',
routine: '例程 | 例程',
drop: 'Drop',
commit: '提交',
rollback: '回滾',
ddl: '資料定義語言',
collation: '排序規則',
resultsTable: '結果表',
unableEditFieldWithoutPrimary: '無法編輯結果集中一個沒有主鍵的字段',
editCell: '編輯單元格',
deleteRows: '刪除行 | 刪除 {count} 行',
confirmToDeleteRows: '您是否確認要刪除一行? | 您是否確認要刪除 {count} 行?',
addNewRow: '新增行',
numberOfInserts: '插入的數量',
affectedRows: '受影響的行',
createNewDatabase: '建立新資料庫',
databaseName: '資料庫名稱',
serverDefault: '預設伺服器',
deleteDatabase: '刪除資料庫',
editDatabase: '編輯資料庫',
clearChanges: '清除變更',
addNewField: '新增新字段',
manageIndexes: '管理索引',
manageForeignKeys: '管理外鍵',
allowNull: '允許 NULL',
zeroFill: 'zeroFill',
customValue: '自定義值',
onUpdate: '在更新',
deleteField: '刪除字段',
createNewIndex: '建立新索引',
addToIndex: '新增到索引',
createNewTable: '建立新表',
emptyTable: '清空表',
duplicateTable: '重複表',
deleteTable: '刪除表',
exportTable: '導出表',
emptyConfirm: '您是否確認清空',
thereAreNoIndexes: '沒有索引',
thereAreNoForeign: '沒有外鍵',
createNewForeign: '建立新外鍵',
referenceTable: '參考表',
referenceField: '參考字段',
foreignFields: '外鍵字段',
invalidDefault: '無效預設值',
onDelete: '在刪除',
selectStatement: '選擇語句',
triggerStatement: '觸發器語句',
sqlSecurity: 'SQL 安全',
updateOption: '更新選項',
deleteView: '刪除視圖',
createNewView: '建立新視圖',
deleteTrigger: '刪除觸發器',
createNewTrigger: '建立新觸發器',
currentUser: '當前使用者',
routineBody: '例程主體',
dataAccess: '數據訪問',
thereAreNoParameters: '沒有參數',
createNewParameter: '建立新參數',
createNewRoutine: '建立新存儲例程',
deleteRoutine: '刪除存儲例程',
functionBody: '函數體',
createNewFunction: '建立新函數',
deleteFunction: '刪除函數',
schedulerBody: '調度器主體',
createNewScheduler: '建立新調度器',
deleteScheduler: '刪除調度器',
preserveOnCompletion: '完成時保留',
tableFiller: '表填充器',
fakeDataLanguage: '僞造的數據語言',
queryDuration: '查詢持續時間',
setNull: '設定 NULL',
processesList: '進程列表',
processInfo: '進程信息',
manageUsers: '管理使用者',
createNewSchema: '建立新模式(schema)',
schemaName: '模式名稱',
editSchema: '編輯模式',
deleteSchema: '刪除模式',
noSchema: '沒有模式',
runQuery: '運行查詢',
thereAreNoTableFields: '沒有表的字段',
newTable: '新表',
newView: '新視圖',
newTrigger: '新觸發器',
newRoutine: '新例程',
newFunction: '新函數',
newScheduler: '新調度器',
newTriggerFunction: '新觸發器函數',
thereAreNoQueriesYet: '目前還沒有任何查詢',
searchForQueries: '搜索查詢',
killProcess: '終止進程',
exportSchema: '導出模式(schema)',
importSchema: '導入模式(schema)',
newInsertStmtEvery: '每條新的 INSERT 語句',
processingTableExport: '處理 {table}',
fetchingTableExport: '正在獲取 {table} 數據',
writingTableExport: '正在寫入 {table} 數據',
checkAllTables: '檢查所有表',
uncheckAllTables: '不檢查所有表',
killQuery: '終止查詢',
insertRow: '插入單行 | 插入多行',
commitMode: '提交模式',
autoCommit: '自動提交',
manualCommit: '手動提交',
importQueryErrors: '警告: 發生了 {n} 個錯誤 | 警告: 發生了 {n} 個錯誤',
executedQueries: '{n} 個查詢已執行 | {n} 個查詢已執行',
disableFKChecks: '禁用外鍵檢查',
formatQuery: '格式查詢',
queryHistory: '查詢曆史',
clearQuery: '清除查詢',
fillCell: '填充單元格',
executeSelectedQuery: '執行所選查詢',
noResultsPresent: '沒有結果',
sqlExportOptions: 'SQL 導出選項',
targetTable: '目標表',
switchDatabase: '切換資料庫',
searchForElements: '搜索元素',
searchForSchemas: '搜索模式(schema)',
savedQueries: '已保存的查詢'
},
application: {
// 應用程式相關術語
settings: '設定',
console: '控製臺',
general: '常規',
themes: '主題',
update: '更新',
about: '關於',
language: '語言',
shortcuts: '捷徑',
key: '按鍵 | 按鍵', // 鍵盤按鍵
event: '事件',
light: '明亮',
dark: '暗黑',
autoCompletion: '自動完成',
application: '應用程式',
editor: '編輯器',
changelog: '變更日誌',
small: '小',
medium: '中',
large: '大',
appearance: '外觀',
color: '顔色',
label: '標簽',
icon: '圖示',
fileName: '檔案名稱',
choseFile: '選擇檔案',
data: '數據',
password: '密碼',
required: '依賴',
madeWithJS: '使用 💛 和 JavaScript 製作!',
checkForUpdates: '檢查更新',
noUpdatesAvailable: '無可用更新',
checkingForUpdate: '正在檢查更新',
checkFailure: '檢查失敗,請稍後再試',
updateAvailable: '可用更新',
downloadingUpdate: '正在下載更新',
updateDownloaded: '已下載更新',
restartToInstall: '重啟 Antares 以進行安裝',
includeBetaUpdates: '包含測試版更新',
notificationsTimeout: '通知超時',
openNewTab: '打開一個新標簽',
unsavedChanges: '未保存的變更',
discardUnsavedChanges: '您有一些未保存的變更, 關閉此標簽將放棄這些變更.',
applicationTheme: '應用程式主題',
editorTheme: '編輯器主題',
wrapLongLines: '將長行換行顯示',
markdownSupported: '支援 Markdown',
plantATree: '種植一棵樹',
dataTabPageSize: '資料標簽的頁面大小',
noOpenTabs: '沒有打開的標簽, 請在左側欄上導航或:',
restorePreviousSession: '恢複上一個會話',
closeTab: '關閉標簽',
goToDownloadPage: '轉到下載頁面',
disableBlur: '禁用模糊',
missingOrIncompleteTranslation: '有缺失或不完整的翻譯?',
findOutHowToContribute: '了解如何做出貢獻',
reportABug: '報告錯誤',
nextTab: '下一個標簽',
previousTab: '上一個標簽',
selectTabNumber: '選擇標簽編號 {param}',
toggleConsole: '切換控製臺',
addShortcut: '新增捷徑',
editShortcut: '編輯捷徑',
deleteShortcut: '刪除捷徑',
restoreDefaults: '恢複預設',
restoreDefaultsQuestion: '是否確認恢複預設值?',
registerAShortcut: '註冊捷徑',
invalidShortcutMessage: '無效組合,請繼續鍵入',
shortcutAlreadyExists: '捷徑已存在',
saveContent: '保存內容',
openAllConnections: '打開所有連接',
openSettings: '打開設定',
openScratchpad: '打開草稿欄',
runOrReload: '運行或重新加載',
openFilter: '打開過濾器',
nextResultsPage: '下一個結果頁',
previousResultsPage: '上一個結果頁',
editFolder: '編輯檔案夾',
folderName: '檔案夾名稱',
deleteFolder: '刪除檔案夾',
editConnectionAppearance: '編輯連接的外觀',
defaultCopyType: '預設複製類型',
showTableSize: '在側邊欄顯示表大小',
showTableSizeDescription: '僅限 MySQL/MariaDB. 啓用此選項可能會影響許多表的模式(schema)的性能.',
switchSearchMethod: '切換搜索方法',
phpArray: 'PHP 陣列',
closeAllTabs: '關閉所有標簽',
closeOtherTabs: '關閉其他標簽',
closeTabsToLeft: '關閉左側的標簽',
closeTabsToRight: '關閉右側的標簽',
csvFieldDelimiter: '字段分隔符',
csvLinesTerminator: '行終止符',
csvStringDelimiter: '字符串分隔符',
csvIncludeHeader: '包含頁眉',
csvExportOptions: 'CSV 導出選項',
exportData: '導出數據',
exportDataExplanation: '將保存的連接導出到 Antares. 係統將要求您輸入密碼以加密導出的檔案.',
importData: '導入數據',
importDataExplanation: '導入包含連接的 .antares 檔案. 您需要輸入在導出過程中定義的密碼.',
includeConnectionPasswords: '包含連接密碼',
includeFolders: '包含檔案夾',
encryptionPassword: '加密密碼',
encryptionPasswordError: '加密密碼的長度必須至少為 8 個字符.',
ignoreDuplicates: '忽略重複',
wrongImportPassword: '錯誤的導入密碼',
wrongFileFormat: '錯誤的檔案格式',
dataImportSuccess: '數據已成功導入',
note: '筆記 | 筆記',
thereAreNoNotesYet: '目前還沒有筆記',
addNote: '新增筆記',
editNote: '編輯筆記',
saveAsNote: '另存為筆記',
showArchivedNotes: '顯示歸檔筆記',
hideArchivedNotes: '隱藏歸檔筆記',
tag: '筆記標簽', // Note tag,
saveFile: '保存檔案',
saveFileAs: '將檔案另存為',
openFile: '打開檔案',
openNotes: '打開筆記'
},
faker: {
// Faker.js 方法,用於隨機生成的內容
address: '地址',
commerce: '商業',
company: '公司',
database: '資料庫',
date: '日期',
finance: '財務',
git: 'Git',
hacker: '駭客',
internet: '網際網路',
lorem: 'Lorem',
name: '姓名',
music: '音樂',
phone: '電話',
random: '隨機',
system: '系統',
time: '時間',
vehicle: '車輛',
zipCode: '郵政編碼',
zipCodeByState: '各州的郵政編碼',
city: '城市',
cityPrefix: '城市前綴',
citySuffix: '城市字尾',
streetName: '街道名稱',
streetAddress: '街道地址',
streetSuffix: '街道字尾',
streetPrefix: '街道前綴',
secondaryAddress: '次要地址',
county: '縣',
country: '國家',
countryCode: '國家代碼',
state: '州',
stateAbbr: '州簡稱',
latitude: '緯度',
longitude: '經度',
direction: '\'方向\'',
cardinalDirection: '方位',
ordinalDirection: '順序方向',
nearbyGPSCoordinate: '附近的 GPS 坐標',
timeZone: '時區',
color: '顔色',
department: '部門',
productName: '産品名稱',
price: '價格',
productAdjective: '産品形容詞',
productMaterial: '産品材料',
product: '産品',
productDescription: '産品說明',
suffixes: '字尾',
companyName: '公司名稱',
companySuffix: '公司字尾',
catchPhrase: '流行語',
bs: 'BS',
catchPhraseAdjective: '流行語形容詞',
catchPhraseDescriptor: '流行語說明',
catchPhraseNoun: '流行語名詞',
bsAdjective: 'BS 形容詞',
bsBuzz: 'BS 嗡嗡聲',
bsNoun: 'BS 名稱',
column: '列',
type: '類型',
collation: '排序規則',
engine: '引擎',
past: '過去',
now: '現在',
future: '未來',
between: '之間',
recent: '最近',
soon: '很快',
month: '月',
weekday: '工作日',
account: '帳戶',
accountName: '帳戶名稱',
routingNumber: '路由編號',
mask: '掩碼',
amount: '金額',
transactionType: '交易類型',
currencyCode: '貨幣代碼',
currencyName: '貨幣名稱',
currencySymbol: '貨幣符號',
bitcoinAddress: '位元幣地址',
litecoinAddress: '萊特幣地址',
creditCardNumber: '信用卡號碼',
creditCardCVV: '信用卡CVV',
ethereumAddress: '以太坊地址',
iban: 'Iban',
bic: 'Bic',
transactionDescription: '交易描述',
branch: '分支',
commitEntry: '提交條目',
commitMessage: '提交信息',
commitSha: '提交 SHA',
shortSha: '短的 SHA',
abbreviation: '縮寫',
adjective: '形容詞',
noun: '名詞',
verb: '動詞',
ingverb: '英式動詞',
phrase: '短語',
avatar: '頭像',
email: '電子信箱',
exampleEmail: '示例電子郵件',
userName: '使用者名',
protocol: '協議',
url: '統一資源定位地址',
domainName: '域名',
domainSuffix: '域名字尾',
domainWord: '域詞',
ip: 'Ip',
ipv6: 'Ipv6',
userAgent: '使用者代理',
mac: 'Mac',
password: '密碼',
word: '單詞',
words: '單詞',
sentence: '句子',
slug: 'Slug',
sentences: '句子',
paragraph: '段落',
paragraphs: '段落',
text: '文本',
lines: '行',
genre: '類型',
firstName: '名',
lastName: '姓氏',
middleName: '中間名',
findName: '全名',
jobTitle: '職位名稱',
gender: '性別',
prefix: '前綴',
suffix: '字尾',
title: '頭銜',
jobDescriptor: '工作描述',
jobArea: '工作領域',
jobType: '工作類型',
phoneNumber: '電話號碼',
phoneNumberFormat: '電話號碼格式',
phoneFormats: '電話格式',
number: '號碼',
float: 'Float',
arrayElement: '陣列元素',
arrayElements: '陣列元素',
objectElement: '物件元素',
uuid: 'Uuid',
boolean: '布林',
image: '鏡像',
locale: '區域',
alpha: '阿爾法',
alphaNumeric: 'α 數字',
hexaDecimal: '十六進製',
fileName: '檔案名',
commonFileName: '普通檔案名',
mimeType: 'MIME類型',
commonFileType: '常見檔案類型',
commonFileExt: '常見檔案擴展名',
fileType: '檔案類型',
fileExt: '檔案擴展名',
directoryPath: '目錄路徑',
filePath: '檔案路徑',
semver: 'Semver',
manufacturer: '製造商',
model: '型號',
fuel: 'Fuel',
vin: 'Vin'
}
};

View File

@@ -12,7 +12,7 @@ import { createApp } from 'vue';
import App from '@/App.vue'; import App from '@/App.vue';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { useApplicationStore } from '@/stores/application'; import { useApplicationStore } from '@/stores/application';
import { useConsoleStore } from '@/stores/console'; import { QueryLog, useConsoleStore } from '@/stores/console';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
@@ -37,11 +37,26 @@ i18n.global.locale = locale;
// IPC exceptions // IPC exceptions
ipcRenderer.on('unhandled-exception', (event, error) => { ipcRenderer.on('unhandled-exception', (event, error) => {
useNotificationsStore().addNotification({ status: 'error', message: error.message }); useNotificationsStore().addNotification({ status: 'error', message: error.message });
useConsoleStore().putLog('debug', {
level: 'error',
process: 'main',
message: error.message,
date: new Date()
});
});
ipcRenderer.on('non-blocking-exception', (event, error) => {
useNotificationsStore().addNotification({ status: 'error', message: error.message });
useConsoleStore().putLog('debug', {
level: 'error',
process: 'main',
message: error.message,
date: new Date()
});
}); });
// IPC query logs // IPC query logs
ipcRenderer.on('query-log', (event, logRecord) => { ipcRenderer.on('query-log', (event, logRecord: QueryLog) => {
useConsoleStore().putLog(logRecord); useConsoleStore().putLog('query', logRecord);
}); });
ipcRenderer.on('toggle-console', () => { ipcRenderer.on('toggle-console', () => {

View File

@@ -8,6 +8,10 @@ export default class {
return ipcRenderer.invoke('show-open-dialog', unproxify(options)); return ipcRenderer.invoke('show-open-dialog', unproxify(options));
} }
static showSaveDialog (options: OpenDialogOptions): Promise<OpenDialogReturnValue> {
return ipcRenderer.invoke('show-save-dialog', unproxify(options));
}
static getDownloadPathDirectory (): Promise<string> { static getDownloadPathDirectory (): Promise<string> {
return ipcRenderer.invoke('get-download-dir-path'); return ipcRenderer.invoke('get-download-dir-path');
} }
@@ -27,4 +31,12 @@ export default class {
static unregisterShortcuts () { static unregisterShortcuts () {
return ipcRenderer.invoke('unregister-shortcuts'); return ipcRenderer.invoke('unregister-shortcuts');
} }
static readFile (params: {filePath: string; encoding: string}): Promise<string> {
return ipcRenderer.invoke('read-file', params);
}
static writeFile (path: string, content: unknown) {
return ipcRenderer.invoke('write-file', path, content);
}
} }

View File

@@ -1,18 +1,19 @@
import { ConnectionParams, IpcResponse } from 'common/interfaces/antares'; import { ConnectionParams, IpcResponse } from 'common/interfaces/antares';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import connStringConstruct from '../libs/connStringDecode';
import { unproxify } from '../libs/unproxify'; import { unproxify } from '../libs/unproxify';
export default class { export default class {
static makeTest (params: ConnectionParams & { pgConnString?: string }): Promise<IpcResponse> { static makeTest (params: ConnectionParams & { connString?: string }): Promise<IpcResponse> {
const newParams = connStringConstruct(params) as ConnectionParams; return ipcRenderer.invoke('test-connection', unproxify(params));
return ipcRenderer.invoke('test-connection', unproxify(newParams));
} }
static connect (params: ConnectionParams & { pgConnString?: string }): Promise<IpcResponse> { static connect (params: ConnectionParams & { connString?: string }): Promise<IpcResponse> {
const newParams = connStringConstruct(params) as ConnectionParams; return ipcRenderer.invoke('connect', unproxify(params));
return ipcRenderer.invoke('connect', unproxify(newParams)); }
static abortConnection (uid: string): void {
ipcRenderer.send('abort-connection', uid);
} }
static checkConnection (uid: string): Promise<boolean> { static checkConnection (uid: string): Promise<boolean> {

View File

@@ -36,6 +36,10 @@ export default class {
return ipcRenderer.invoke('get-table-indexes', unproxify(params)); return ipcRenderer.invoke('get-table-indexes', unproxify(params));
} }
static getTableChecks (params: { uid: string; schema: string; table: string }): Promise<IpcResponse> {
return ipcRenderer.invoke('get-table-checks', unproxify(params));
}
static getTableDll (params: { uid: string; schema: string; table: string }): Promise<IpcResponse<string>> { static getTableDll (params: { uid: string; schema: string; table: string }): Promise<IpcResponse<string>> {
return ipcRenderer.invoke('get-table-ddl', unproxify(params)); return ipcRenderer.invoke('get-table-ddl', unproxify(params));
} }

View File

@@ -19,4 +19,20 @@ export default class {
static createView (params: CreateViewParams & { uid: string }): Promise<IpcResponse> { static createView (params: CreateViewParams & { uid: string }): Promise<IpcResponse> {
return ipcRenderer.invoke('create-view', unproxify(params)); return ipcRenderer.invoke('create-view', unproxify(params));
} }
static createMaterializedView (params: CreateViewParams & { uid: string }): Promise<IpcResponse> {
return ipcRenderer.invoke('create-materialized-view', unproxify(params));
}
static getMaterializedViewInformations (params: { uid: string; schema: string; view: string }): Promise<IpcResponse> {
return ipcRenderer.invoke('get-materialized-view-informations', unproxify(params));
}
static dropMaterializedView (params: { uid: string; schema: string; view: string }): Promise<IpcResponse> {
return ipcRenderer.invoke('drop-materialized-view', unproxify(params));
}
static alterMaterializedView (params: { view: AlterViewParams & { uid: string }}): Promise<IpcResponse> {
return ipcRenderer.invoke('alter-materialized-view', unproxify(params));
}
} }

View File

@@ -0,0 +1,9 @@
export const camelize = (text: string) => {
const textArr = text.split('-');
for (let i = 0; i < textArr.length; i++) {
if (i === 0) continue;
textArr[i] = textArr[i].charAt(0).toUpperCase() + textArr[i].slice(1);
}
return textArr.join('');
};

View File

@@ -0,0 +1,18 @@
export const colorShade = (color: string, amount: number) => {
color = color.replaceAll('#', '');
if (color.length === 3) color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let [r, g, b] = color.match(/.{2}/g) as any;
([r, g, b] = [parseInt(r, 16) + amount, parseInt(g, 16) + amount, parseInt(b, 16) + amount]);
r = Math.max(Math.min(255, r), 0).toString(16);
g = Math.max(Math.min(255, g), 0).toString(16);
b = Math.max(Math.min(255, b), 0).toString(16);
const rr = (r.length < 2 ? '0' : '') + r;
const gg = (g.length < 2 ? '0' : '') + g;
const bb = (b.length < 2 ? '0' : '') + b;
return `#${rr}${gg}${bb}`;
};

View File

@@ -1,50 +0,0 @@
import { ConnectionParams } from 'common/interfaces/antares';
import * as formatter from 'pg-connection-string'; // parses a connection string
const formatHost = (host: string) => {
const results = host === 'localhost' ? '127.0.0.1' : host;
return results;
};
const checkForSSl = (conn: string) => {
return conn.includes('ssl=true');
};
const connStringConstruct = (args: ConnectionParams & { pgConnString?: string }): ConnectionParams => {
if (!args.pgConnString)
return args;
if (typeof args.pgConnString !== 'string')
return args;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const stringArgs: any = formatter.parse(args.pgConnString);
const client = args.client || 'pg';
args.client = client;
args.host = formatHost(stringArgs.host);
args.database = stringArgs.database;
args.port = stringArgs.port || '5432';
args.user = stringArgs.user;
args.password = stringArgs.password;
// ssh
args.ssh = stringArgs.ssh || args.ssh;
args.sshHost = stringArgs.sshHost;
args.sshUser = stringArgs.sshUser;
args.sshPass = stringArgs.sshPass;
args.sshKey = stringArgs.sshKey;
args.sshPort = stringArgs.sshPort;
// ssl mode
args.ssl = checkForSSl(args.pgConnString);
args.cert = stringArgs.sslcert;
args.key = stringArgs.sslkey;
args.ca = stringArgs.sslrootcert;
args.ciphers = stringArgs.ciphers;
return args;
};
export default connStringConstruct;

View File

@@ -0,0 +1,16 @@
export const hexToRGBA = (hexCode: string, opacity = 1) => {
let hex = hexCode.replace('#', '');
if (hex.length === 3)
hex = `${hex[0]}${hex[0]}${hex[1]}${hex[1]}${hex[2]}${hex[2]}`;
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
/* Backward compatibility for whole number based opacity values. */
if (opacity > 1 && opacity <= 100)
opacity = opacity / 100;
return `rgba(${r},${g},${b},${opacity})`;
};

View File

@@ -1,5 +1,5 @@
/* Colors */ /* Colors */
$body-bg: #fdfdfd; $body-bg: #f3f3f3;
$body-bg-dark: #1d1d1d; $body-bg-dark: #1d1d1d;
$body-font-color-dark: #fff; $body-font-color-dark: #fff;
$bg-color-dark: #1d1d1d; $bg-color-dark: #1d1d1d;

View File

@@ -1,4 +1,10 @@
/* stylelint-disable */ /* stylelint-disable */
:root {
--primary-color: #e36929;
--primary-color-dark: color-mix(in srgb, var(--primary-color), #000 30%);
--primary-color-shadow: 0 0 0 0.1rem rgba(227, 105, 41, 0.2);
}
@import "~spectre.css/src/variables"; @import "~spectre.css/src/variables";
@import "variables"; @import "variables";
@import "transitions"; @import "transitions";
@@ -16,12 +22,20 @@ body {
user-select: none; user-select: none;
} }
a {
color: var(--primary-color);
&:hover {
color: var(--primary-color-dark)
}
}
::selection, ::selection,
option:hover, option:hover,
option:focus, option:focus,
option:active, option:active,
option:checked { option:checked {
background-color: $primary-color; background-color: var(--primary-color);
color: $light-color; color: $light-color;
} }
@@ -189,6 +203,14 @@ option:checked {
animation: rotation 0.8s infinite linear; animation: rotation 0.8s infinite linear;
} }
.loading {
&::after {
border: 0.1rem solid var(--primary-color);
border-right-color: transparent;
border-top-color: transparent;
}
}
/* Override */ /* Override */
.modal { .modal {
.modal-container, .modal-container,
@@ -234,6 +256,10 @@ option:checked {
} }
&.active { &.active {
a {
border-bottom-color: transparent;
}
.tab-link { .tab-link {
border-color: transparent; border-color: transparent;
} }
@@ -248,7 +274,7 @@ option:checked {
height: 2px; height: 2px;
width: 0; width: 0;
transition: width 0.2s; transition: width 0.2s;
background-color: $primary-color; background-color: var(--primary-color);
position: absolute; position: absolute;
bottom: 0; bottom: 0;
} }
@@ -300,9 +326,23 @@ option:checked {
height: auto; height: auto;
} }
.form-checkbox input:checked + .form-icon, .form-radio input:checked + .form-icon, .form-switch input:checked + .form-icon {
background: var(--primary-color);
border-color: var(--primary-color);
}
.form-checkbox input:focus + .form-icon, .form-radio input:focus + .form-icon, .form-switch input:focus + .form-icon {
box-shadow: 0 0 0 0.1rem var(--primary-color-shadow);
border-color: var(--primary-color);
}
.form-select { .form-select {
cursor: pointer; cursor: pointer;
&:focus {
box-shadow: 0 0 0 0.1rem var(--primary-color-shadow);
}
&.small-select { &.small-select {
height: 21px; height: 21px;
font-size: 0.7rem; font-size: 0.7rem;
@@ -311,7 +351,8 @@ option:checked {
&.select { &.select {
&.select--open { &.select--open {
border-color: $primary-color !important; border-color: var(--primary-color) !important;
box-shadow: 0 0 0 0.1rem var(--primary-color-shadow) !important;
@include control-shadow(); @include control-shadow();
} }
@@ -336,19 +377,28 @@ option:checked {
z-index: 401 !important; z-index: 401 !important;
border: 1px solid transparent; border: 1px solid transparent;
border-radius: $border-radius; border-radius: $border-radius;
box-shadow: 0 8px 17px 0 rgb(0 0 0 / 20%), 0 6px 20px 0 rgb(0 0 0 / 19%); box-shadow:
0 8px 17px 0 rgb(0 0 0 / 20%),
0 6px 20px 0 rgb(0 0 0 / 19%);
} }
.select__option--selected { .select__option--selected {
background: rgba($primary-color, 0.25); background: rgba(var(--primary-color), 0.25);
} }
.select__option--highlight { .select__option--highlight {
background: $primary-color; background: var(--primary-color);
text-shadow: 0 0 15px #000;
} }
.form-input[type="file"] { .form-input {
overflow: hidden; &[type="file"] {
overflow: hidden;
}
&:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 0.1rem var(--primary-color-shadow);
}
} }
.input-group .input-group-addon { .input-group .input-group-addon {
@@ -370,13 +420,34 @@ option:checked {
} }
.btn { .btn {
color: var(--primary-color);
border-color: var(--primary-color);
&:not(.btn-link) {
text-shadow: 0 0 15px #000;
}
&:hover {
border-color: var(--primary-color-dark);
}
&:focus { &:focus {
box-shadow: 0 0 3px 1px rgba($primary-color, 90%); box-shadow: 0 0 3px 1px rgba(var(--primary-color), 90%);
} }
&.btn-success:focus { &.btn-success:focus {
border-color: $primary-color; border-color: var(--primary-color);
box-shadow: 0 0 3px 1px rgba($primary-color, 90%); box-shadow: 0 0 3px 1px rgba(var(--primary-color), 90%);
}
&.btn-primary {
background: var(--primary-color);
border-color: var(--primary-color-dark);
&:hover {
background: var(--primary-color-dark);
border-color: var(--primary-color-dark);
}
} }
} }
@@ -435,7 +506,7 @@ code.sql {
} }
.sql-hl-keyword { .sql-hl-keyword {
color: $primary-color; color: var(--primary-color);
} }
.sql-hl-function { .sql-hl-function {
@@ -456,4 +527,4 @@ code.sql {
.sql-hl-bracket { .sql-hl-bracket {
color: darkorchid; color: darkorchid;
} }

View File

@@ -33,12 +33,41 @@
.menu-item a { .menu-item a {
&:hover { &:hover {
color: $primary-color; color: var(--primary-color);
background: $bg-color-gray; background: $bg-color-gray;
} }
} }
} }
.tab {
.tab-item {
a {
color: $body-font-color-dark;
opacity: .7;
&:hover {
color: $body-font-color-dark;
opacity: 1;
}
}
&.active {
a {
color: $body-font-color-dark;
opacity: 1;
}
.tab-link {
border-color: transparent;
}
&::after {
width: 100%;
}
}
}
}
.btn { .btn {
&.btn-link { &.btn-link {
color: rgba($body-font-color-dark, 0.8); color: rgba($body-font-color-dark, 0.8);
@@ -67,7 +96,7 @@
} }
&.active { &.active {
background-color: $primary-color; background-color: var(--primary-color);
} }
} }
@@ -124,7 +153,7 @@
} }
.form-select:not([multiple], [size]):focus { .form-select:not([multiple], [size]):focus {
border-color: $primary-color; border-color: var(--primary-color);
} }
.select { .select {
@@ -226,6 +255,11 @@
} }
} }
&.result-tabs .tab-item {
background: transparent;
}
.workspace-query-runner .workspace-query-runner-footer .workspace-query-buttons .btn { .workspace-query-runner .workspace-query-runner-footer .workspace-query-buttons .btn {
color: $body-font-color-dark; color: $body-font-color-dark;
} }
@@ -320,11 +354,11 @@
} }
} }
.query-console { .console {
border-top: 1px solid #444; border-top: 1px solid #444;
background-color: $bg-color-dark; background-color: $bg-color-dark;
.query-console-log { .console-log {
&:hover, &:hover,
&:focus { &:focus {
background: $bg-color-gray; background: $bg-color-gray;
@@ -432,7 +466,7 @@
.settingbar-element { .settingbar-element {
.settingbar-element-icon { .settingbar-element-icon {
&.badge-update::after { &.badge-update::after {
background: $primary-color; background: var(--primary-color);
} }
} }
} }
@@ -447,7 +481,7 @@
} }
#footer { #footer {
background: $primary-color; background: var(--primary-color);
box-shadow: 0 0 1px 0 #000; box-shadow: 0 0 1px 0 #000;
.footer-elements { .footer-elements {

View File

@@ -12,6 +12,14 @@
} }
} }
.form-select,
.form-input,
.form-select:not([multiple], [size]),
.form-checkbox .form-icon,
.form-radio .form-icon {
background-color: #f5f5f5;
}
.form-input:disabled, .form-input:disabled,
.form-input.disabled, .form-input.disabled,
.form-select:disabled, .form-select:disabled,
@@ -44,7 +52,43 @@
} }
} }
.tab {
border-bottom: 0.05rem solid #c5c5c5;
.tab-item {
a {
color: $body-font-color;
opacity: .7;
&:hover {
color: $body-font-color;
opacity: 1;
}
}
&.active {
a {
color: $body-font-color;
opacity: 1;
}
.tab-link {
border-color: transparent;
}
&::after {
width: 100%;
}
}
}
}
.btn { .btn {
&.btn-clear:focus, &.btn-clear:hover {
background: #e0e0e0;
opacity: 0.95;
}
&.btn-link { &.btn-link {
color: rgba($body-font-color, 0.8); color: rgba($body-font-color, 0.8);
@@ -72,7 +116,7 @@
} }
&.active { &.active {
background-color: $primary-color; background-color: var(--primary-color);
} }
} }
} }
@@ -129,11 +173,11 @@
} }
} }
.query-console { .console {
border-top: 1px solid darken($bg-color-light-gray, 15%); border-top: 1px solid darken($bg-color-light-gray, 15%);
background-color: $bg-color-light; background-color: $bg-color-light;
.query-console-log { .console-log {
&:hover, &:hover,
&:focus { &:focus {
background: $bg-color-light-gray; background: $bg-color-light-gray;
@@ -181,7 +225,7 @@
.settingbar-element { .settingbar-element {
.settingbar-element-icon { .settingbar-element-icon {
&.badge-update::after { &.badge-update::after {
background: $primary-color; background: var(--primary-color);
} }
} }
} }
@@ -230,6 +274,10 @@
.workspace-tabs { .workspace-tabs {
.tab-block { .tab-block {
.tab-item { .tab-item {
> a {
color: $body-font-color;
}
&.tools-dropdown { &.tools-dropdown {
background-color: $body-bg; background-color: $body-bg;
} }
@@ -241,19 +289,25 @@
.workspace-query-results { .workspace-query-results {
.table { .table {
.th { .th {
background: $body-bg; background: #D8D8D8;
border-color: lighten($bg-color-light-gray, 2%); border-color: transparent;
border-radius: 0;
}
.th:first-child {
border-left: 2px solid transparent;
} }
.tr { .tr {
background-color: lighten($bg-color-light-gray, 2%); background-color: lighten($bg-color-light-gray, 2%);
.td:first-child { .td:first-child {
border-left: 2px solid $body-bg; border-left: 2px solid #0000001f;
} }
.td { .td {
border-color: $body-bg; border-color: #0000001f;
border-radius: 1px;
&:focus, &:focus,
&.selected { &.selected {
@@ -272,7 +326,7 @@
.connection-panel { .connection-panel {
.panel { .panel {
background: rgba($bg-color-light-gray, 100%); background: #e0e0e0;
} }
} }
@@ -343,7 +397,7 @@
} }
#footer { #footer {
background: $primary-color; background: var(--primary-color);
box-shadow: 0 0 1px 0 #000; box-shadow: 0 0 1px 0 #000;
.footer-elements { .footer-elements {

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