mirror of
https://github.com/Fabio286/antares.git
synced 2025-06-05 21:59:22 +02:00
Compare commits
268 Commits
v0.7.20-be
...
v0.7.31-be
Author | SHA1 | Date | |
---|---|---|---|
0f35814ca0 | |||
96ae09feca | |||
e3b30359bf | |||
f3c3284fd1 | |||
27387f18a1 | |||
8e54f7b801 | |||
70aae2f194 | |||
592d7b3517 | |||
2b743a2c79 | |||
|
e493db5112 | ||
|
d3d7ab38c0 | ||
7a66c11868 | |||
8544bb5378 | |||
6709a75298 | |||
f25f6659d5 | |||
8d0ff4953e | |||
|
fbe28f0ff0 | ||
0d8bcf5cd6 | |||
|
47ac729d2f | ||
|
450c4c47f3 | ||
|
110dcd335a | ||
|
0029967619 | ||
34848e8dc3 | |||
c32add76e8 | |||
|
507dc7d55b | ||
4a2b5926f4 | |||
ed90b12a7b | |||
|
00ce76a12e | ||
|
77b3a8a354 | ||
d3ae45ec94 | |||
ad4478a822 | |||
|
ba5dd9ff15 | ||
|
5aab824fe9 | ||
87ab58c50f | |||
e986f287c6 | |||
|
39a30e48dd | ||
46165d2f4f | |||
|
d0e56e4eb6 | ||
|
c803c072d1 | ||
232211811b | |||
|
fb9c258cc1 | ||
8de99dae7b | |||
2bd69c6263 | |||
4d1a81033d | |||
5887eea2ed | |||
6c69583c90 | |||
|
03461522b7 | ||
|
11807e3bb6 | ||
a54b8d719c | |||
d1f68da495 | |||
d666281daa | |||
481ae842dd | |||
|
acf5d459e2 | ||
2ee9cfcf0b | |||
f0d312fb59 | |||
c97ade949c | |||
38af648440 | |||
ccbcffc7f0 | |||
dfa7cf9905 | |||
6365e07534 | |||
24605d01e1 | |||
f083a8a185 | |||
|
f639bc7983 | ||
|
5e51997e5b | ||
|
1e3c9edb50 | ||
60e1e59505 | |||
dba490f226 | |||
b6c5dff15c | |||
d2da8c2446 | |||
b54d2c9f5e | |||
9a0ad80bb5 | |||
2f3f5de8d6 | |||
b2c046fd38 | |||
|
bbc29a6335 | ||
|
b6c337638c | ||
2120a59d41 | |||
2cda4a1fa1 | |||
76c8cd1beb | |||
14aeebed9c | |||
|
1a1118452a | ||
|
4b0f596405 | ||
|
eb749f0f66 | ||
|
d78e59dd09 | ||
7969294a93 | |||
2ae016f0b6 | |||
b4f33bc474 | |||
f185463866 | |||
3fa0bd3cd1 | |||
|
0d3ef39822 | ||
|
a02913f4e5 | ||
f9f993cbcd | |||
|
ebd1a75445 | ||
4201532081 | |||
|
c5cb586358 | ||
|
c111b2c0f5 | ||
b70ed124eb | |||
010147b553 | |||
da8cc39157 | |||
37d44c95ee | |||
4df4c6197d | |||
|
0506b653d7 | ||
5fd9fe48a2 | |||
b6a7124f33 | |||
|
5855ab0921 | ||
c2b602785a | |||
97279742e9 | |||
6cb21ff792 | |||
72bacdeabf | |||
c434855879 | |||
ba0ffcc6f5 | |||
8e7965a0f9 | |||
4aab84fbd5 | |||
|
74e97e660d | ||
|
f5d236b521 | ||
|
e794d207ad | ||
f99a3cc054 | |||
59f7d3c670 | |||
8f2daa0f1c | |||
141d5bb69c | |||
171b6f924a | |||
f7419d8e9c | |||
5b1cd70e25 | |||
3e223b475e | |||
4a38656b7e | |||
25b7ae57c6 | |||
4b5718e9b7 | |||
780d83deaa | |||
e952f9f5f8 | |||
49f1a8ef2e | |||
121aa21a6d | |||
cb25963a67 | |||
3a47607a5f | |||
7494ff6fcf | |||
838491bfd4 | |||
0b9898f3e7 | |||
a973ec3c60 | |||
d0c50f17ca | |||
b4cdd58973 | |||
9947479fdc | |||
4a1697d633 | |||
b7dfd5cb8c | |||
0ec9d3cfc1 | |||
|
4f615b26cf | ||
|
86a1e05197 | ||
3fa9873d20 | |||
37a160a03f | |||
2385a8207c | |||
|
243984e697 | ||
d1bb50b2bb | |||
8501fa2e81 | |||
25123e34ef | |||
bd4502ee47 | |||
b2f9d475a2 | |||
|
93de974b09 | ||
|
949bf4cbcb | ||
|
bb3c87b2cf | ||
40bf9a040a | |||
|
978b55fdb1 | ||
|
098d4e96d6 | ||
957cb9e1a5 | |||
09c274a724 | |||
9bcd874e80 | |||
ece2ee05cc | |||
058fc2fc0b | |||
33bbc0e7e6 | |||
23c59b4d4e | |||
6600197b82 | |||
33203aeb04 | |||
f4f385589f | |||
0565ae1204 | |||
258fbc81f7 | |||
8d8650fbe7 | |||
af2812f2b0 | |||
|
d163cbfac8 | ||
|
4537d96f3e | ||
099a71a189 | |||
e7efb9c616 | |||
a752dcb6a9 | |||
|
c1e58eb695 | ||
|
f7204dc0ae | ||
|
6b56c60b68 | ||
1875e895ae | |||
2064294119 | |||
62e3115860 | |||
9aef287a98 | |||
65ec4c5da6 | |||
e19118982b | |||
11f130d91c | |||
|
0bb5cedda6 | ||
de9dac3e8a | |||
dd5b41716a | |||
86acb390ac | |||
2884ec3dd6 | |||
b542a09c01 | |||
6d94a04b67 | |||
8500fc40a1 | |||
586f901bae | |||
04e4d21e20 | |||
fd3dd03eb2 | |||
d3f71e65ce | |||
90b9b87b1d | |||
e9b42c3edb | |||
259d051a21 | |||
876d5ea481 | |||
6d002efaf5 | |||
58be1abf5f | |||
da56905572 | |||
d698f2798a | |||
9a41511c42 | |||
|
30ada13663 | ||
14eeaccb07 | |||
1a0c5da2f1 | |||
bb36e98beb | |||
8928510fb5 | |||
eb5d3f14f1 | |||
33c127b090 | |||
4e98dc21d8 | |||
20b27343cd | |||
3b9228a723 | |||
ab0f91b448 | |||
0b6307c738 | |||
dbf38fd99c | |||
169fcb13da | |||
97ece32988 | |||
c946c3fcda | |||
cdd2a11f8e | |||
23946ff2ce | |||
0f8d2cb4ef | |||
219f89aa60 | |||
eec29e99cc | |||
171caed8b5 | |||
88ec71c943 | |||
532002ca01 | |||
9a732ea197 | |||
b734b24679 | |||
a52fc3fd92 | |||
bfa3924d57 | |||
08e5a13f72 | |||
eaaf1b756a | |||
84d221aaa7 | |||
ba6063e636 | |||
b055350726 | |||
dbd533b229 | |||
b5b35be45c | |||
6a72f6b4ae | |||
756786d72e | |||
861b704344 | |||
9ce53165e8 | |||
62614dceb9 | |||
8774dd44e6 | |||
f0ae01ca5e | |||
03be777c2a | |||
45a695ac0a | |||
c176841b75 | |||
329246e2d8 | |||
e26809f260 | |||
f13d4e6dce | |||
879de91516 | |||
38b32bfb28 | |||
315d9d84c2 | |||
c3d96cb35b | |||
05bd7672e1 | |||
390bf88bb8 | |||
984aa893d3 | |||
1ac816eaa9 | |||
6f25fcbc05 | |||
bc44465132 | |||
634a442213 |
@@ -266,6 +266,105 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"translation"
|
"translation"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "bagusindrayana",
|
||||||
|
"name": "Bagus Indrayana",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/36830534?v=4",
|
||||||
|
"profile": "https://github.com/bagusindrayana",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "penguinlab",
|
||||||
|
"name": "Naoki Ishikawa",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/10959317?v=4",
|
||||||
|
"profile": "https://github.com/penguinlab",
|
||||||
|
"contributions": [
|
||||||
|
"translation"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "mangas",
|
||||||
|
"name": "Filipe Azevedo",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/1640325?v=4",
|
||||||
|
"profile": "https://fazevedo.dev",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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,
|
||||||
|
@@ -2,4 +2,5 @@ node_modules
|
|||||||
assets
|
assets
|
||||||
out
|
out
|
||||||
dist
|
dist
|
||||||
build
|
build
|
||||||
|
misc
|
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
@@ -1,6 +1,6 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: [fabio286]
|
github: [antares-sql,fabio286]
|
||||||
patreon: #fabio286
|
patreon: #fabio286
|
||||||
open_collective: # Replace with a single Open Collective username
|
open_collective: # Replace with a single Open Collective username
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
ko_fi: # Replace with a single Ko-fi username
|
||||||
@@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
|
|||||||
liberapay: # Replace with a single Liberapay username
|
liberapay: # Replace with a single Liberapay username
|
||||||
issuehunt: # Replace with a single IssueHunt username
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
otechie: # Replace with a single Otechie username
|
otechie: # Replace with a single Otechie username
|
||||||
custom: ['https://paypal.me/fabiodistasio']
|
custom: ['https://paypal.me/fabiodistasio']
|
||||||
|
12
.github/workflows/build-beta.yml
vendored
12
.github/workflows/build-beta.yml
vendored
@@ -15,21 +15,23 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [macos-11, ubuntu-latest, windows-latest]
|
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||||
|
|
||||||
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: 18
|
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
|
||||||
|
12
.github/workflows/build.yml
vendored
12
.github/workflows/build.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [macos-11, ubuntu-latest, windows-latest]
|
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Exit if not on master branch
|
- name: Exit if not on master branch
|
||||||
@@ -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: 18
|
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
|
||||||
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -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.
|
||||||
|
@@ -8,14 +8,14 @@ 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: 18
|
node-version: 20
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm i
|
run: npm i
|
||||||
@@ -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
|
||||||
|
10
.github/workflows/create-artifact-linux.yml
vendored
10
.github/workflows/create-artifact-linux.yml
vendored
@@ -5,15 +5,15 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-20.04
|
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: 16
|
node-version: 20
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm i
|
run: npm i
|
||||||
@@ -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
|
||||||
|
37
.github/workflows/create-artifact-macos.yml
vendored
37
.github/workflows/create-artifact-macos.yml
vendored
@@ -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: 16
|
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
|
||||||
@@ -29,3 +30,31 @@ jobs:
|
|||||||
build
|
build
|
||||||
!build/*-unpacked
|
!build/*-unpacked
|
||||||
!build/.icon-ico
|
!build/.icon-ico
|
||||||
|
build-beta:
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out Git repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: beta
|
||||||
|
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
- name: npm install & build
|
||||||
|
run: |
|
||||||
|
npm install
|
||||||
|
npm install "dmg-license" --save-optional
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
- name: Upload Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: macos-build-beta
|
||||||
|
retention-days: 3
|
||||||
|
path: |
|
||||||
|
build
|
||||||
|
!build/*-unpacked
|
||||||
|
!build/.icon-ico
|
||||||
|
32
.github/workflows/create-artifact-windows-appx.yml
vendored
Normal file
32
.github/workflows/create-artifact-windows-appx.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Create artifact [WINDOWS APPX]
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: windows-2022
|
||||||
|
steps:
|
||||||
|
- name: Check out Git repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm i
|
||||||
|
|
||||||
|
- name: "Build"
|
||||||
|
run: npm run build:appx
|
||||||
|
|
||||||
|
- name: Upload Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: windows-build
|
||||||
|
retention-days: 3
|
||||||
|
path: |
|
||||||
|
build
|
||||||
|
!build/*-unpacked
|
||||||
|
!build/.icon-ico
|
50
.github/workflows/create-generated-sources.yml
vendored
Normal file
50
.github/workflows/create-generated-sources.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
name: Create generated-rources.json
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
# Install flatpak-node-generator
|
||||||
|
- name: Install Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Install pipx
|
||||||
|
uses: CfirTsabari/actions-pipx@v1
|
||||||
|
|
||||||
|
- name: Install flatpak-node-generator
|
||||||
|
run: |
|
||||||
|
cd ../
|
||||||
|
git clone https://github.com/flatpak/flatpak-builder-tools.git
|
||||||
|
cd flatpak-builder-tools/node
|
||||||
|
pipx install .
|
||||||
|
|
||||||
|
# Install Antares
|
||||||
|
- name: Check out Git repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
# - name: Delete old package-lock.json
|
||||||
|
# run: rm package-lock.json
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm i --lockfile-version 2
|
||||||
|
|
||||||
|
- name: Generate generated-sources.json
|
||||||
|
run: flatpak-node-generator npm -r package-lock.json --electron-node-headers
|
||||||
|
|
||||||
|
- name: Upload Artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: generated-sources
|
||||||
|
retention-days: 3
|
||||||
|
path: |
|
||||||
|
generated-sources.json
|
4
.github/workflows/test-builds.yml
vendored
4
.github/workflows/test-builds.yml
vendored
@@ -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
|
||||||
|
|
||||||
|
15
.github/workflows/test-e2e-win.yml
vendored
15
.github/workflows/test-e2e-win.yml
vendored
@@ -1,9 +1,10 @@
|
|||||||
name: Test end-to-end [WINDOWS]
|
name: Test end-to-end
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_dispatch: {}
|
||||||
branches:
|
# push:
|
||||||
- master
|
# branches:
|
||||||
|
# - develop
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
@@ -15,12 +16,12 @@ 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: 16
|
node-version: 20
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm i
|
run: npm i
|
||||||
|
1
.husky/commit-msg
Normal file
1
.husky/commit-msg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
npx --no -- commitlint --edit $1
|
1
.husky/pre-commit
Normal file
1
.husky/pre-commit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
npm run lint
|
@@ -5,14 +5,14 @@
|
|||||||
],
|
],
|
||||||
"fix": true,
|
"fix": true,
|
||||||
"formatter": "verbose",
|
"formatter": "verbose",
|
||||||
|
"customSyntax": "postcss-html",
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"stylelint-scss"
|
"stylelint-scss"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"at-rule-no-unknown": null,
|
"at-rule-no-unknown": null,
|
||||||
"no-descending-specificity": null,
|
"no-descending-specificity": null,
|
||||||
"font-family-no-missing-generic-family-keyword": null,
|
"font-family-no-missing-generic-family-keyword": null
|
||||||
"declaration-colon-newline-after": "always-multi-line"
|
|
||||||
},
|
},
|
||||||
"syntax": "scss"
|
"syntax": "scss"
|
||||||
}
|
}
|
9
.vscode/launch.json
vendored
9
.vscode/launch.json
vendored
@@ -17,15 +17,6 @@
|
|||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"type": "chrome",
|
"type": "chrome",
|
||||||
"webRoot": "${workspaceFolder}"
|
"webRoot": "${workspaceFolder}"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Electron: Worker",
|
|
||||||
"cwd": "${workspaceFolder}",
|
|
||||||
"port": 9224,
|
|
||||||
"request": "attach",
|
|
||||||
"sourceMaps": true,
|
|
||||||
"type": "node",
|
|
||||||
"timeout": 1000000
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"compounds": [
|
"compounds": [
|
||||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -10,7 +10,8 @@
|
|||||||
"translation",
|
"translation",
|
||||||
"Linux",
|
"Linux",
|
||||||
"MacOS",
|
"MacOS",
|
||||||
"deps"
|
"deps",
|
||||||
|
"Flatpak"
|
||||||
],
|
],
|
||||||
"svg.preview.background": "transparent"
|
"svg.preview.background": "transparent"
|
||||||
}
|
}
|
416
CHANGELOG.md
416
CHANGELOG.md
@@ -2,6 +2,422 @@
|
|||||||
|
|
||||||
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.3](https://github.com/antares-sql/antares/compare/v0.7.31-beta.2...v0.7.31-beta.3) (2025-01-31)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* implement a better query splitter for SQL queries, fixes [#926](https://github.com/antares-sql/antares/issues/926) ([96ae09f](https://github.com/antares-sql/antares/commit/96ae09fecad0c1fc8926d5dcf64cc779abe5ed49))
|
||||||
|
* **Linux:** update title bar for better Linux experience ([8e54f7b](https://github.com/antares-sql/antares/commit/8e54f7b80135768a33934bc9336239dee38401a5))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **MySQL:** adjust utf8mb3 encoding to resolve compatibility issue, fixes [#646](https://github.com/antares-sql/antares/issues/646) ([27387f1](https://github.com/antares-sql/antares/commit/27387f18a107fc6c09afec5f85134496ce764355))
|
||||||
|
|
||||||
|
### [0.7.31-beta.2](https://github.com/antares-sql/antares/compare/v0.7.31-beta.1...v0.7.31-beta.2) (2025-01-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add developer tools and refresh buttons to console in development mode ([592d7b3](https://github.com/antares-sql/antares/commit/592d7b35170f8437ebc15221c97985e889fccb40))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fail to fill cell to datetime column(Postgre) fixes [#924](https://github.com/antares-sql/antares/issues/924) ([d3d7ab3](https://github.com/antares-sql/antares/commit/d3d7ab38c029fc5ec23767c6c86c49a96e4e329c))
|
||||||
|
* reorder condition when format the update data ([e493db5](https://github.com/antares-sql/antares/commit/e493db5112478ff121e4e77f69c21747c5d2e032))
|
||||||
|
|
||||||
|
### [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-beta.1](https://github.com/antares-sql/antares/compare/v0.7.23-beta.0...v0.7.23-beta.1) (2024-04-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add the page reference in the export file name, closes [#772](https://github.com/antares-sql/antares/issues/772) ([2064294](https://github.com/antares-sql/antares/commit/2064294119ed9dfab2a9968dfb5b35d52e2ae03b))
|
||||||
|
* move connections out of folder from context menu, related to [#773](https://github.com/antares-sql/antares/issues/773) ([62e3115](https://github.com/antares-sql/antares/commit/62e311586073ae7ee4896305198c7168f637c1af))
|
||||||
|
* move connections to folders from context menu, related to [#773](https://github.com/antares-sql/antares/issues/773) ([9aef287](https://github.com/antares-sql/antares/commit/9aef287a983754158cdbdc9b2a72db9ab82f76c8))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* bad format of timestamp fields on CSV export, fixes 776 ([65ec4c5](https://github.com/antares-sql/antares/commit/65ec4c5da6187a7ab2dfff59326cd12bfa788c3b))
|
||||||
|
|
||||||
|
### [0.7.23-beta.0](https://github.com/antares-sql/antares/compare/v0.7.22...v0.7.23-beta.0) (2024-03-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* CSV export does not escape strings when needed, fixes [#770](https://github.com/antares-sql/antares/issues/770) ([dd5b417](https://github.com/antares-sql/antares/commit/dd5b41716a10cf9500f2c611b232f5b5b0756a68))
|
||||||
|
* query result sort not working with aliased tables, fixes [#765](https://github.com/antares-sql/antares/issues/765) ([de9dac3](https://github.com/antares-sql/antares/commit/de9dac3e8abf3b3261f8c54c88cf2386a5be2207))
|
||||||
|
* shortcut not working on mac os ([0bb5ced](https://github.com/antares-sql/antares/commit/0bb5cedda6a67ccbeea8c127b799f533395101a2))
|
||||||
|
|
||||||
|
### [0.7.22](https://github.com/antares-sql/antares/compare/v0.7.22-beta.2...v0.7.22) (2024-02-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* delete record modal pressing del when editing a field, fixes [#767](https://github.com/antares-sql/antares/issues/767) ([586f901](https://github.com/antares-sql/antares/commit/586f901bae9a80c0e53ac1d804cbae3f05e26d8e))
|
||||||
|
|
||||||
|
### [0.7.22-beta.2](https://github.com/antares-sql/antares/compare/v0.7.22-beta.1...v0.7.22-beta.2) (2024-02-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **MySQL:** option to enable single connection mode ([d3f71e6](https://github.com/antares-sql/antares/commit/d3f71e65cef88838f03f95a4b34e197fb61878f8))
|
||||||
|
|
||||||
|
### [0.7.22-beta.1](https://github.com/antares-sql/antares/compare/v0.7.22-beta.0...v0.7.22-beta.1) (2024-02-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* update dutch translation + fix spelling mistake ([30ada13](https://github.com/antares-sql/antares/commit/30ada13663e88f89beb3dd7291010837059585d5))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* some issues related to previous commit ([259d051](https://github.com/antares-sql/antares/commit/259d051a21e334496d3a52b662f1855ba9a9046d))
|
||||||
|
* unable to edit tables containing SET fields, fixes [#755](https://github.com/antares-sql/antares/issues/755) ([d698f27](https://github.com/antares-sql/antares/commit/d698f2798a2423f86e6d786dd3ab80439b372a08))
|
||||||
|
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
|
||||||
|
* **MySQL:** improvements in connection handling ([876d5ea](https://github.com/antares-sql/antares/commit/876d5ea48185334e9e2fc981c4282a9c42d22b10))
|
||||||
|
|
||||||
|
### [0.7.22-beta.0](https://github.com/antares-sql/antares/compare/v0.7.21...v0.7.22-beta.0) (2024-02-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **UI:** resizable textarea in new/edito note, closes [#747](https://github.com/antares-sql/antares/issues/747) ([1a0c5da](https://github.com/antares-sql/antares/commit/1a0c5da2f14b99d6f5581b2bf6e916d67d097245))
|
||||||
|
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
|
||||||
|
* **UI:** improved notes, fixes [#746](https://github.com/antares-sql/antares/issues/746) ([bb36e98](https://github.com/antares-sql/antares/commit/bb36e98bebc5e1e55735e98d272428df2ab682e8))
|
||||||
|
|
||||||
|
### [0.7.21](https://github.com/antares-sql/antares/compare/v0.7.21-beta.1...v0.7.21) (2024-01-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **SQLite:** enable schema reloat button on sidebar ([20b2734](https://github.com/antares-sql/antares/commit/20b27343cd95998bd83403b7556ea35fcad9fa1b))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **SQLite:** unable to change integer fields length to 0, fixes [#732](https://github.com/antares-sql/antares/issues/732) ([3b9228a](https://github.com/antares-sql/antares/commit/3b9228a7230dcd9f47f5794a83b60d28207bdce1))
|
||||||
|
|
||||||
|
### [0.7.21-beta.1](https://github.com/antares-sql/antares/compare/v0.7.21-beta.0...v0.7.21-beta.1) (2024-01-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **PostgreSQL:** error adding MONEY fields to a table ([0f8d2cb](https://github.com/antares-sql/antares/commit/0f8d2cb4ef5c327f96f788179be0b309689b4ce8))
|
||||||
|
* **PostgreSQL:** exception deleting a table with one or less tabs open ([23946ff](https://github.com/antares-sql/antares/commit/23946ff2cef6d63e1529e2c8c4357d7fdedc3284))
|
||||||
|
* **PostgreSQL:** unhandled error on connection lost, fixes [#740](https://github.com/antares-sql/antares/issues/740) ([cdd2a11](https://github.com/antares-sql/antares/commit/cdd2a11f8e33d6607337989723774d60c7c1a030))
|
||||||
|
|
||||||
|
### [0.7.21-beta.0](https://github.com/antares-sql/antares/compare/v0.7.20...v0.7.21-beta.0) (2023-12-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* ability to edit notes ([08e5a13](https://github.com/antares-sql/antares/commit/08e5a13f723bc3ae95b0f529b79f0b558bc2a377))
|
||||||
|
* buttons to save and access to saved queryes from query tab ([a52fc3f](https://github.com/antares-sql/antares/commit/a52fc3fd923fec30cfdd3d804554e6fe4534c400))
|
||||||
|
* highlithg sql in notes, history and console ([bfa3924](https://github.com/antares-sql/antares/commit/bfa3924d57c2ea2cc2857006d6bd6279865dbc99))
|
||||||
|
* new notes system ([eaaf1b7](https://github.com/antares-sql/antares/commit/eaaf1b756a6b5ffb77f7f07f3e4c0971822d48c3))
|
||||||
|
* open saved queries in a tab ([9a732ea](https://github.com/antares-sql/antares/commit/9a732ea1971d223f3278ad02d3dd77837fecb377))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* JavaScript error at first startup, fixes [#736](https://github.com/antares-sql/antares/issues/736) ([b734b24](https://github.com/antares-sql/antares/commit/b734b246795fb240f6728714be68c22cc221bbe9))
|
||||||
|
|
||||||
|
### [0.7.20](https://github.com/antares-sql/antares/compare/v0.7.20-beta.2...v0.7.20) (2023-12-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* missing update indicator on setting icon ([b055350](https://github.com/antares-sql/antares/commit/b055350726774e05a4e04ea6d890c46f64f2112e))
|
||||||
|
|
||||||
|
### [0.7.20-beta.2](https://github.com/antares-sql/antares/compare/v0.7.20-beta.1...v0.7.20-beta.2) (2023-12-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* communication with worker thread not working ([6a72f6b](https://github.com/antares-sql/antares/commit/6a72f6b4ae3f4c1d6c42ca7a817d2f2c135696a7))
|
||||||
|
|
||||||
|
### [0.7.20-beta.1](https://github.com/antares-sql/antares/compare/v0.7.20-beta.0...v0.7.20-beta.1) (2023-12-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* copy element names on sidebar from context menu, closes [#718](https://github.com/antares-sql/antares/issues/718) ([f13d4e6](https://github.com/antares-sql/antares/commit/f13d4e6dceb70b0c7cb8d56ddfb5f00e938571cc))
|
||||||
|
* logging errors on log file ([315d9d8](https://github.com/antares-sql/antares/commit/315d9d84c2caa29852d68bd189750b2a4028d953))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Flatpak:** import/export schema not working ([e26809f](https://github.com/antares-sql/antares/commit/e26809f260099ba194bf5d00671cae14d438197b))
|
||||||
|
|
||||||
### [0.7.20-beta.0](https://github.com/antares-sql/antares/compare/v0.7.19...v0.7.20-beta.0) (2023-11-15)
|
### [0.7.20-beta.0](https://github.com/antares-sql/antares/compare/v0.7.19...v0.7.20-beta.0) (2023-11-15)
|
||||||
|
|
||||||
|
|
||||||
|
44
README.md
44
README.md
@@ -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
|
||||||
|
|
||||||
  [](https://actions-badge.atrox.dev/fabio286/antares/goto)  [](https://twitter.com/AntaresSQL) [](https://www.treedom.net/en/user/fabio-di-stasio/event/antares-for-the-planet)
|
    [](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,8 +16,8 @@ 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) or [Twitter](https://twitter.com/AntaresSQL).
|
👁 To stay tuned for new releases follow Antares SQL on [Mastodon](https://fosstodon.org/@AntaresSQL).
|
||||||
🌟 Don't forget to **leave a star** if you appreciate this project.
|
🌟 Don't forget to **leave a star** if you appreciate this project.
|
||||||
|
|
||||||
🗳️ Polls:
|
🗳️ Polls:
|
||||||
@@ -35,6 +35,7 @@ We are actively working on it, hoping to provide new cool features, improvements
|
|||||||
- Fake table data filler to generate tons of data for test purpose.
|
- Fake table data filler to generate tons of data for test purpose.
|
||||||
- Query suggestions and auto complete.
|
- Query suggestions and auto complete.
|
||||||
- Query history: search through the last 1000 queries.
|
- Query history: search through the last 1000 queries.
|
||||||
|
- Save queries, notes or todo.
|
||||||
- SSH tunnel support.
|
- SSH tunnel support.
|
||||||
- Manual commit mode.
|
- Manual commit mode.
|
||||||
- Import and export database dumps.
|
- Import and export database dumps.
|
||||||
@@ -59,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
|
||||||
|
|
||||||
@@ -70,17 +71,6 @@ On macOS you can run `.dmg` distribution following [this guide](https://support.
|
|||||||
[<img height='56' alt='Download on Flathub' src='https://dl.flathub.org/assets/badges/flathub-badge-en.svg'/>](https://flathub.org/apps/it.fabiodistasio.AntaresSQL) [](https://snapcraft.io/antares) [](https://aur.archlinux.org/packages/antares-sql-bin) [<img src="https://developer.microsoft.com/store/badges/images/English_get-it-from-MS.png" style="height: 56px">](https://www.microsoft.com/p/antares-sql-client/9nhtb9sq51r1?cid=storebadge&ocid=badge&rtc=1&activetab=pivot:overviewtab)
|
[<img height='56' alt='Download on Flathub' src='https://dl.flathub.org/assets/badges/flathub-badge-en.svg'/>](https://flathub.org/apps/it.fabiodistasio.AntaresSQL) [](https://snapcraft.io/antares) [](https://aur.archlinux.org/packages/antares-sql-bin) [<img src="https://developer.microsoft.com/store/badges/images/English_get-it-from-MS.png" style="height: 56px">](https://www.microsoft.com/p/antares-sql-client/9nhtb9sq51r1?cid=storebadge&ocid=badge&rtc=1&activetab=pivot:overviewtab)
|
||||||
🚀 **[Other Downloads](https://github.com/antares-sql/antares/releases/latest)**
|
🚀 **[Other Downloads](https://github.com/antares-sql/antares/releases/latest)**
|
||||||
|
|
||||||
## Coming soon
|
|
||||||
|
|
||||||
This is a roadmap with major features will come in near future.
|
|
||||||
|
|
||||||
- Database tools.
|
|
||||||
- Users management (add/edit/delete).
|
|
||||||
- More context menu shortcuts.
|
|
||||||
- More keyboard shortcuts.
|
|
||||||
- Support for other databases.
|
|
||||||
- Apple Silicon distribution
|
|
||||||
|
|
||||||
## Currently supported
|
## Currently supported
|
||||||
|
|
||||||
### Databases
|
### Databases
|
||||||
@@ -89,6 +79,7 @@ This is a roadmap with major features will come in near future.
|
|||||||
- [x] PostgreSQL
|
- [x] PostgreSQL
|
||||||
- [x] SQLite
|
- [x] SQLite
|
||||||
- [x] Firebird SQL
|
- [x] Firebird SQL
|
||||||
|
- [ ] DuckDB
|
||||||
- [ ] SQL Server
|
- [ ] SQL Server
|
||||||
- [ ] More...
|
- [ ] More...
|
||||||
|
|
||||||
@@ -108,9 +99,9 @@ This is a roadmap with major features will come in near future.
|
|||||||
|
|
||||||
## 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/antares-sql/antares/projects/1)
|
- 🚧 [Project Board](https://github.com/orgs/antares-sql/projects/3/views/2)
|
||||||
|
|
||||||
## Contributors ✨
|
## Contributors ✨
|
||||||
|
|
||||||
@@ -157,6 +148,21 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Lawondyss"><img src="https://avatars.githubusercontent.com/u/272130?v=4?s=100" width="100px;" alt="Ladislav Vondráček"/><br /><sub><b>Ladislav Vondráček</b></sub></a><br /><a href="#translation-Lawondyss" title="Translation">🌍</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Lawondyss"><img src="https://avatars.githubusercontent.com/u/272130?v=4?s=100" width="100px;" alt="Ladislav Vondráček"/><br /><sub><b>Ladislav Vondráček</b></sub></a><br /><a href="#translation-Lawondyss" title="Translation">🌍</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zvlad"><img src="https://avatars.githubusercontent.com/u/9055134?v=4?s=100" width="100px;" alt="Vladyslav"/><br /><sub><b>Vladyslav</b></sub></a><br /><a href="#translation-zvlad" title="Translation">🌍</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zvlad"><img src="https://avatars.githubusercontent.com/u/9055134?v=4?s=100" width="100px;" alt="Vladyslav"/><br /><sub><b>Vladyslav</b></sub></a><br /><a href="#translation-zvlad" title="Translation">🌍</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bagusindrayana"><img src="https://avatars.githubusercontent.com/u/36830534?v=4?s=100" width="100px;" alt="Bagus Indrayana"/><br /><sub><b>Bagus Indrayana</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=bagusindrayana" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/penguinlab"><img src="https://avatars.githubusercontent.com/u/10959317?v=4?s=100" width="100px;" alt="Naoki Ishikawa"/><br /><sub><b>Naoki Ishikawa</b></sub></a><br /><a href="#translation-penguinlab" title="Translation">🌍</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://fazevedo.dev"><img src="https://avatars.githubusercontent.com/u/1640325?v=4?s=100" width="100px;" alt="Filipe Azevedo"/><br /><sub><b>Filipe Azevedo</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=mangas" title="Code">💻</a></td>
|
||||||
|
<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>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
33
commitlint.config.js
Normal file
33
commitlint.config.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ['@commitlint/config-conventional'],
|
||||||
|
rules: {
|
||||||
|
// TODO Add Scope Enum Here
|
||||||
|
// 'scope-enum': [2, 'always', ['yourscope', 'yourscope']],
|
||||||
|
'type-enum': [
|
||||||
|
2,
|
||||||
|
'always',
|
||||||
|
[
|
||||||
|
'feat',
|
||||||
|
'fix',
|
||||||
|
'docs',
|
||||||
|
'chore',
|
||||||
|
'style',
|
||||||
|
'refactor',
|
||||||
|
'build',
|
||||||
|
'ci',
|
||||||
|
'test',
|
||||||
|
'revert',
|
||||||
|
'perf'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'subject-case': [
|
||||||
|
2,
|
||||||
|
'never',
|
||||||
|
[
|
||||||
|
'upper-case',
|
||||||
|
'pascal-case',
|
||||||
|
'start-case'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
13109
package-lock.json
generated
13109
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
85
package.json
85
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "antares",
|
"name": "antares",
|
||||||
"productName": "Antares",
|
"productName": "Antares",
|
||||||
"version": "0.7.20-beta.0",
|
"version": "0.7.31-beta.3",
|
||||||
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
|
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "https://github.com/antares-sql/antares.git",
|
"repository": "https://github.com/antares-sql/antares.git",
|
||||||
@@ -25,7 +25,8 @@
|
|||||||
"lint": "eslint . --ext .js,.ts,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
|
"lint": "eslint . --ext .js,.ts,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
|
||||||
"lint:fix": "eslint . --ext .js,.ts,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
|
"lint:fix": "eslint . --ext .js,.ts,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
|
||||||
"contributors:add": "all-contributors add",
|
"contributors:add": "all-contributors add",
|
||||||
"contributors:generate": "all-contributors generate"
|
"contributors:generate": "all-contributors generate",
|
||||||
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"author": "Fabio Di Stasio <info@fabiodistasio.it>",
|
"author": "Fabio Di Stasio <info@fabiodistasio.it>",
|
||||||
"main": "./dist/main.js",
|
"main": "./dist/main.js",
|
||||||
@@ -118,44 +119,69 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/remote": "~2.0.1",
|
"@electron/remote": "~2.1.2",
|
||||||
"@fabio286/ssh2-promise": "~1.0.4-b",
|
"@fabio286/ssh2-promise": "~1.0.4-b",
|
||||||
"@faker-js/faker": "~6.1.2",
|
"@faker-js/faker": "~6.1.2",
|
||||||
"@jamescoyle/vue-icon": "~0.1.2",
|
"@jamescoyle/vue-icon": "~0.1.2",
|
||||||
"@mdi/js": "~7.2.96",
|
"@mdi/js": "~7.2.96",
|
||||||
"@turf/helpers": "~6.5.0",
|
"@turf/helpers": "~6.5.0",
|
||||||
|
"@vue/compiler-sfc": "~3.2.33",
|
||||||
"@vueuse/core": "~10.4.1",
|
"@vueuse/core": "~10.4.1",
|
||||||
"ace-builds": "~1.24.1",
|
"ace-builds": "~1.34.1",
|
||||||
"better-sqlite3": "~9.1.1",
|
"babel-loader": "~8.2.3",
|
||||||
"electron-log": "~4.4.1",
|
"better-sqlite3": "~10.0.0",
|
||||||
|
"chalk": "~4.1.2",
|
||||||
|
"cpu-features": "^0.0.10",
|
||||||
|
"cross-env": "~7.0.2",
|
||||||
|
"css-loader": "~6.5.0",
|
||||||
|
"electron-log": "~5.0.1",
|
||||||
"electron-store": "~8.1.0",
|
"electron-store": "~8.1.0",
|
||||||
"electron-updater": "~4.6.5",
|
"electron-updater": "~4.6.5",
|
||||||
"electron-window-state": "~5.0.3",
|
"electron-window-state": "~5.0.3",
|
||||||
"encoding": "~0.1.13",
|
"encoding": "~0.1.13",
|
||||||
|
"file-loader": "~6.2.0",
|
||||||
"floating-vue": "~2.0.0-beta.20",
|
"floating-vue": "~2.0.0-beta.20",
|
||||||
|
"html-webpack-plugin": "~5.5.0",
|
||||||
"json2php": "~0.0.7",
|
"json2php": "~0.0.7",
|
||||||
"leaflet": "~1.7.1",
|
"leaflet": "~1.7.1",
|
||||||
"marked": "~4.0.19",
|
"marked": "~12.0.0",
|
||||||
"moment": "~2.29.4",
|
"mini-css-extract-plugin": "~2.4.5",
|
||||||
"mysql2": "~3.5.2",
|
"moment": "~2.30.1",
|
||||||
"node-firebird": "~1.1.4",
|
"mysql2": "~3.9.7",
|
||||||
"pg": "~8.11.1",
|
"node-firebird": "~1.1.8",
|
||||||
"pg-connection-string": "~2.5.0",
|
"node-loader": "~2.0.0",
|
||||||
|
"pg": "~8.11.5",
|
||||||
"pg-query-stream": "~4.2.3",
|
"pg-query-stream": "~4.2.3",
|
||||||
"pgsql-ast-parser": "~7.2.1",
|
"pgsql-ast-parser": "~7.2.1",
|
||||||
"pinia": "~2.1.6",
|
"pinia": "~2.1.7",
|
||||||
|
"postcss-html": "~1.5.0",
|
||||||
|
"progress-webpack-plugin": "~1.0.12",
|
||||||
|
"rimraf": "~3.0.2",
|
||||||
|
"sass": "~1.42.1",
|
||||||
|
"sass-loader": "~12.3.0",
|
||||||
"source-map-support": "~0.5.20",
|
"source-map-support": "~0.5.20",
|
||||||
"spectre.css": "~0.5.9",
|
"spectre.css": "~0.5.9",
|
||||||
"sql-formatter": "~13.0.0",
|
"sql-formatter": "~13.0.0",
|
||||||
|
"sql-highlight": "~4.4.0",
|
||||||
|
"style-loader": "~3.3.1",
|
||||||
|
"tree-kill": "~1.2.2",
|
||||||
|
"ts-loader": "~9.2.8",
|
||||||
|
"typescript": "~4.6.3",
|
||||||
|
"unzip-crx-3": "~0.2.0",
|
||||||
"v-mask": "~2.3.0",
|
"v-mask": "~2.3.0",
|
||||||
"vue": "~3.3.4",
|
"vue": "~3.4.27",
|
||||||
"vue-i18n": "~9.2.2",
|
"vue-i18n": "~9.13.1",
|
||||||
"vuedraggable": "~4.1.0"
|
"vue-loader": "~16.8.3",
|
||||||
|
"vuedraggable": "~4.1.0",
|
||||||
|
"webpack": "^5.91.0",
|
||||||
|
"webpack-cli": "~4.9.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "~7.15.7",
|
"@babel/eslint-parser": "~7.15.7",
|
||||||
"@babel/preset-env": "~7.15.8",
|
"@babel/preset-env": "~7.15.8",
|
||||||
"@babel/preset-typescript": "~7.16.7",
|
"@babel/preset-typescript": "~7.16.7",
|
||||||
|
"@commitlint/cli": "~19.0.3",
|
||||||
|
"@commitlint/config-conventional": "~19.0.3",
|
||||||
"@playwright/test": "~1.28.1",
|
"@playwright/test": "~1.28.1",
|
||||||
"@types/better-sqlite3": "~7.5.0",
|
"@types/better-sqlite3": "~7.5.0",
|
||||||
"@types/leaflet": "~1.7.9",
|
"@types/leaflet": "~1.7.9",
|
||||||
@@ -165,14 +191,9 @@
|
|||||||
"@types/ssh2": "~1.11.6",
|
"@types/ssh2": "~1.11.6",
|
||||||
"@typescript-eslint/eslint-plugin": "~5.18.0",
|
"@typescript-eslint/eslint-plugin": "~5.18.0",
|
||||||
"@typescript-eslint/parser": "~5.18.0",
|
"@typescript-eslint/parser": "~5.18.0",
|
||||||
"@vue/compiler-sfc": "~3.2.33",
|
|
||||||
"all-contributors-cli": "~6.20.0",
|
"all-contributors-cli": "~6.20.0",
|
||||||
"babel-loader": "~8.2.3",
|
"electron": "~30.0.8",
|
||||||
"chalk": "~4.1.2",
|
"electron-builder": "~24.13.3",
|
||||||
"cross-env": "~7.0.2",
|
|
||||||
"css-loader": "~6.5.0",
|
|
||||||
"electron": "~22.3.27",
|
|
||||||
"electron-builder": "~24.6.4",
|
|
||||||
"eslint": "~7.32.0",
|
"eslint": "~7.32.0",
|
||||||
"eslint-config-standard": "~16.0.3",
|
"eslint-config-standard": "~16.0.3",
|
||||||
"eslint-plugin-import": "~2.24.2",
|
"eslint-plugin-import": "~2.24.2",
|
||||||
@@ -180,32 +201,16 @@
|
|||||||
"eslint-plugin-promise": "~5.2.0",
|
"eslint-plugin-promise": "~5.2.0",
|
||||||
"eslint-plugin-simple-import-sort": "~10.0.0",
|
"eslint-plugin-simple-import-sort": "~10.0.0",
|
||||||
"eslint-plugin-vue": "~8.0.3",
|
"eslint-plugin-vue": "~8.0.3",
|
||||||
"file-loader": "~6.2.0",
|
"husky": "~9.0.11",
|
||||||
"html-webpack-plugin": "~5.5.0",
|
|
||||||
"mini-css-extract-plugin": "~2.4.5",
|
|
||||||
"node-loader": "~2.0.0",
|
|
||||||
"playwright": "~1.28.1",
|
"playwright": "~1.28.1",
|
||||||
"playwright-core": "~1.28.1",
|
"playwright-core": "~1.28.1",
|
||||||
"postcss-html": "~1.5.0",
|
|
||||||
"progress-webpack-plugin": "~1.0.12",
|
|
||||||
"rimraf": "~3.0.2",
|
|
||||||
"sass": "~1.42.1",
|
|
||||||
"sass-loader": "~12.3.0",
|
|
||||||
"standard-version": "~9.3.1",
|
"standard-version": "~9.3.1",
|
||||||
"style-loader": "~3.3.1",
|
|
||||||
"stylelint": "^15.11.0",
|
"stylelint": "^15.11.0",
|
||||||
"stylelint-config-recommended-vue": "~1.5.0",
|
"stylelint-config-recommended-vue": "~1.5.0",
|
||||||
"stylelint-config-standard": "~34.0.0",
|
"stylelint-config-standard": "~34.0.0",
|
||||||
"stylelint-scss": "~5.3.0",
|
"stylelint-scss": "~5.3.0",
|
||||||
"tree-kill": "~1.2.2",
|
|
||||||
"ts-loader": "~9.2.8",
|
|
||||||
"ts-node": "~10.9.1",
|
"ts-node": "~10.9.1",
|
||||||
"typescript": "~4.6.3",
|
|
||||||
"unzip-crx-3": "~0.2.0",
|
|
||||||
"vue-eslint-parser": "~8.3.0",
|
"vue-eslint-parser": "~8.3.0",
|
||||||
"vue-loader": "~16.8.3",
|
|
||||||
"webpack": "~5.72.0",
|
|
||||||
"webpack-cli": "~4.9.1",
|
|
||||||
"webpack-dev-server": "~4.11.1",
|
"webpack-dev-server": "~4.11.1",
|
||||||
"xvfb-maybe": "~0.2.1"
|
"xvfb-maybe": "~0.2.1"
|
||||||
}
|
}
|
||||||
|
@@ -63,43 +63,39 @@ async function restartElectron () {
|
|||||||
if (!manualRestart) process.exit(0);
|
if (!manualRestart) process.exit(0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function startWorkers () {
|
||||||
|
const compiler = webpack(workersConfig);
|
||||||
|
const { name } = compiler;
|
||||||
|
|
||||||
function startMain () {
|
compiler.hooks.afterEmit.tap('afterEmit', () => {
|
||||||
const webpackSetup = webpack([mainConfig, workersConfig]);
|
console.log(chalk.gray(`\nCompiled ${name} script!`));
|
||||||
|
console.log(chalk.gray(`\nWatching file changes for ${name} script...`));
|
||||||
webpackSetup.compilers.forEach((compiler) => {
|
|
||||||
const { name } = compiler;
|
|
||||||
|
|
||||||
switch (name) {
|
|
||||||
case 'workers':
|
|
||||||
compiler.hooks.afterEmit.tap('afterEmit', async () => {
|
|
||||||
console.log(chalk.gray(`\nCompiled ${name} script!`));
|
|
||||||
console.log(
|
|
||||||
chalk.gray(`\nWatching file changes for ${name} script...`)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'main':
|
|
||||||
default:
|
|
||||||
compiler.hooks.afterEmit.tap('afterEmit', async () => {
|
|
||||||
console.log(chalk.gray(`\nCompiled ${name} script!`));
|
|
||||||
|
|
||||||
manualRestart = true;
|
|
||||||
await restartElectron();
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
manualRestart = false;
|
|
||||||
}, 2500);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
chalk.gray(`\nWatching file changes for ${name} script...`)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
webpackSetup.watch({ aggregateTimeout: 500 }, err => {
|
compiler.watch({ aggregateTimeout: 500 }, err => {
|
||||||
|
if (err) console.error(chalk.red(err));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startMain () {
|
||||||
|
const compiler = webpack(mainConfig);
|
||||||
|
const { name } = compiler;
|
||||||
|
|
||||||
|
compiler.hooks.afterEmit.tap('afterEmit', async () => {
|
||||||
|
console.log(chalk.gray(`\nCompiled ${name} script!`));
|
||||||
|
|
||||||
|
manualRestart = true;
|
||||||
|
await restartElectron();
|
||||||
|
startWorkers();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
manualRestart = false;
|
||||||
|
}, 2500);
|
||||||
|
|
||||||
|
console.log(chalk.gray(`\nWatching file changes for ${name} script...`));
|
||||||
|
});
|
||||||
|
|
||||||
|
compiler.watch({ aggregateTimeout: 500 }, err => {
|
||||||
if (err) console.error(chalk.red(err));
|
if (err) console.error(chalk.red(err));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -115,6 +111,7 @@ function startRenderer (callback) {
|
|||||||
|
|
||||||
const server = new WebpackDevServer(compiler, {
|
const server = new WebpackDevServer(compiler, {
|
||||||
port: 9080,
|
port: 9080,
|
||||||
|
hot: true,
|
||||||
client: {
|
client: {
|
||||||
overlay: true,
|
overlay: true,
|
||||||
logging: 'warn'
|
logging: 'warn'
|
||||||
|
@@ -42,6 +42,7 @@ const downloadFile = url => {
|
|||||||
await unzip(filePath, destFolder);
|
await unzip(filePath, destFolder);
|
||||||
fs.unlinkSync(filePath);
|
fs.unlinkSync(filePath);
|
||||||
fs.unlinkSync(`${destFolder}/package.json`);// <- Avoid to display annoyng npm script in vscode
|
fs.unlinkSync(`${destFolder}/package.json`);// <- Avoid to display annoyng npm script in vscode
|
||||||
|
process.exit();
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
@@ -19,6 +19,7 @@ export const defaults: Customizations = {
|
|||||||
sshConnection: false,
|
sshConnection: false,
|
||||||
fileConnection: false,
|
fileConnection: false,
|
||||||
cancelQueries: false,
|
cancelQueries: false,
|
||||||
|
singleConnectionMode: false,
|
||||||
// Tools
|
// Tools
|
||||||
processesList: false,
|
processesList: false,
|
||||||
usersManagement: false,
|
usersManagement: false,
|
||||||
@@ -54,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,
|
||||||
|
@@ -29,6 +29,7 @@ export const customizations: Customizations = {
|
|||||||
sslConnection: true,
|
sslConnection: true,
|
||||||
sshConnection: true,
|
sshConnection: true,
|
||||||
cancelQueries: true,
|
cancelQueries: true,
|
||||||
|
singleConnectionMode: true,
|
||||||
// Tools
|
// Tools
|
||||||
processesList: true,
|
processesList: true,
|
||||||
// Structure
|
// Structure
|
||||||
@@ -46,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,
|
||||||
|
@@ -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,
|
||||||
|
@@ -66,7 +66,7 @@ export default [
|
|||||||
group: 'monetary',
|
group: 'monetary',
|
||||||
types: [
|
types: [
|
||||||
{
|
{
|
||||||
name: 'money',
|
name: 'MONEY',
|
||||||
length: false,
|
length: false,
|
||||||
unsigned: true
|
unsigned: true
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +34,7 @@ export interface ClientParams {
|
|||||||
| { databasePath: string; readonly: boolean };
|
| { databasePath: string; readonly: boolean };
|
||||||
poolSize?: number;
|
poolSize?: number;
|
||||||
logger?: () => void;
|
logger?: () => void;
|
||||||
|
querySplitter?: (sql: string, clieng?: string) => string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,10 +53,12 @@ export interface ConnectionParams {
|
|||||||
password: string;
|
password: string;
|
||||||
ask: boolean;
|
ask: boolean;
|
||||||
readonly: boolean;
|
readonly: boolean;
|
||||||
|
singleConnectionMode: boolean;
|
||||||
ssl: boolean;
|
ssl: boolean;
|
||||||
cert?: string;
|
cert?: string;
|
||||||
key?: string;
|
key?: string;
|
||||||
ca?: string;
|
ca?: string;
|
||||||
|
connString?: string;
|
||||||
untrustedConnection: boolean;
|
untrustedConnection: boolean;
|
||||||
ciphers?: string;
|
ciphers?: string;
|
||||||
ssh: boolean;
|
ssh: boolean;
|
||||||
@@ -158,6 +161,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;
|
||||||
@@ -165,6 +175,7 @@ export interface CreateTableParams {
|
|||||||
fields: TableField[];
|
fields: TableField[];
|
||||||
foreigns: TableForeign[];
|
foreigns: TableForeign[];
|
||||||
indexes: TableIndex[];
|
indexes: TableIndex[];
|
||||||
|
checks?: TableCheck[];
|
||||||
options: TableOptions;
|
options: TableOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,6 +203,11 @@ export interface AlterTableParams {
|
|||||||
changes: TableForeign[];
|
changes: TableForeign[];
|
||||||
deletions: TableForeign[];
|
deletions: TableForeign[];
|
||||||
};
|
};
|
||||||
|
checkChanges?: {
|
||||||
|
additions: TableCheck[];
|
||||||
|
changes: TableCheck[];
|
||||||
|
deletions: TableCheck[];
|
||||||
|
};
|
||||||
options: TableOptions;
|
options: TableOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,7 +379,7 @@ export interface QueryBuilderObject {
|
|||||||
offset: number;
|
offset: number;
|
||||||
join: string[];
|
join: string[];
|
||||||
update: string[];
|
update: string[];
|
||||||
insert: {[key: string]: string | boolean | number }[];
|
insert: Record<string, string | boolean | number>[];
|
||||||
delete: boolean;
|
delete: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -19,6 +19,7 @@ export interface Customizations {
|
|||||||
sshConnection?: boolean;
|
sshConnection?: boolean;
|
||||||
fileConnection?: boolean;
|
fileConnection?: boolean;
|
||||||
cancelQueries?: boolean;
|
cancelQueries?: boolean;
|
||||||
|
singleConnectionMode?: boolean;
|
||||||
// Tools
|
// Tools
|
||||||
processesList?: boolean;
|
processesList?: boolean;
|
||||||
usersManagement?: boolean;
|
usersManagement?: boolean;
|
||||||
@@ -27,6 +28,7 @@ export interface Customizations {
|
|||||||
schemas?: boolean;
|
schemas?: boolean;
|
||||||
tables?: boolean;
|
tables?: boolean;
|
||||||
views?: boolean;
|
views?: boolean;
|
||||||
|
materializedViews?: boolean;
|
||||||
triggers?: boolean;
|
triggers?: boolean;
|
||||||
triggerFunctions?: boolean;
|
triggerFunctions?: boolean;
|
||||||
routines?: boolean;
|
routines?: boolean;
|
||||||
@@ -41,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;
|
||||||
|
@@ -13,7 +13,7 @@ export interface ExportOptions {
|
|||||||
includeContent: boolean;
|
includeContent: boolean;
|
||||||
includeDropStatement: boolean;
|
includeDropStatement: boolean;
|
||||||
}[];
|
}[];
|
||||||
includes: {[key: string]: boolean};
|
includes: Record<string, boolean>;
|
||||||
outputFormat: 'sql' | 'sql.zip';
|
outputFormat: 'sql' | 'sql.zip';
|
||||||
outputFile: string;
|
outputFile: string;
|
||||||
sqlInsertAfter: number;
|
sqlInsertAfter: number;
|
||||||
|
@@ -18,7 +18,7 @@ export interface TableDeleteParams {
|
|||||||
primary?: string;
|
primary?: string;
|
||||||
field: string;
|
field: string;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
rows: {[key: string]: any};
|
rows: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TableFilterOperator = '=' | '!=' | '>' | '<' | '>=' | '<=' | 'IN' | 'NOT IN' | 'LIKE' | 'NOT LIKE' | 'RLIKE' | 'NOT RLIKE' | 'BETWEEN' | 'IS NULL' | 'IS NOT NULL'
|
export type TableFilterOperator = '=' | '!=' | '>' | '<' | '>=' | '<=' | 'IN' | 'NOT IN' | 'LIKE' | 'NOT LIKE' | 'RLIKE' | 'NOT RLIKE' | 'BETWEEN' | 'IS NULL' | 'IS NOT NULL'
|
||||||
@@ -35,17 +35,16 @@ export interface InsertRowsParams {
|
|||||||
uid: string;
|
uid: string;
|
||||||
schema: string;
|
schema: string;
|
||||||
table: string;
|
table: string;
|
||||||
row: {[key: string]: {
|
row: Record<string, {
|
||||||
group: string;
|
group: string;
|
||||||
method: string;
|
method: string;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
params: any;
|
params: any;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
value: any;
|
value: any;
|
||||||
length: number;
|
length: number;
|
||||||
};
|
}>;
|
||||||
};
|
|
||||||
repeat: number;
|
repeat: number;
|
||||||
fields: {[key: string]: string};
|
fields: Record<string, string>;
|
||||||
locale: UsableLocale;
|
locale: UsableLocale;
|
||||||
}
|
}
|
||||||
|
86
src/common/libs/querySplitter.ts
Normal file
86
src/common/libs/querySplitter.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { ClientCode } from 'common/interfaces/antares';
|
||||||
|
|
||||||
|
export const querySplitter =(sql: string, dbType: ClientCode): string[] => {
|
||||||
|
const queries: string[] = [];
|
||||||
|
let currentQuery = '';
|
||||||
|
let insideBlock = false;
|
||||||
|
let insideString = false;
|
||||||
|
let stringDelimiter: string | null = null;
|
||||||
|
let insideDollarTag = false;
|
||||||
|
let dollarTagDelimiter: string | null = null;
|
||||||
|
|
||||||
|
// Regex patterns for BEGIN-END blocks, dollar tags in PostgreSQL, and semicolons
|
||||||
|
const beginRegex = /\bBEGIN\b/i;
|
||||||
|
const endRegex = /\bEND\b;/i;
|
||||||
|
const dollarTagRegex = /\$(\w+)?\$/; // Matches $tag$ or $$
|
||||||
|
|
||||||
|
// Split on semicolons, keeping semicolons attached to the lines
|
||||||
|
const lines = sql.split(/(?<=;)/);
|
||||||
|
|
||||||
|
for (let line of lines) {
|
||||||
|
line = line.trim();
|
||||||
|
|
||||||
|
if (!line) continue;
|
||||||
|
|
||||||
|
for (let i = 0; i < line.length; i++) {
|
||||||
|
const char = line[i];
|
||||||
|
|
||||||
|
// Handle string boundaries
|
||||||
|
if ((char === '\'' || char === '"') && (!insideString || char === stringDelimiter)) {
|
||||||
|
if (!insideString) {
|
||||||
|
insideString = true;
|
||||||
|
stringDelimiter = char;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
insideString = false;
|
||||||
|
stringDelimiter = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentQuery += char;
|
||||||
|
|
||||||
|
if (dbType === 'pg') {
|
||||||
|
// Handle dollar-quoted blocks in PostgreSQL
|
||||||
|
if (!insideString && line.slice(i).match(dollarTagRegex)) {
|
||||||
|
const match = line.slice(i).match(dollarTagRegex);
|
||||||
|
if (match) {
|
||||||
|
const tag = match[0];
|
||||||
|
if (!insideDollarTag) {
|
||||||
|
insideDollarTag = true;
|
||||||
|
dollarTagDelimiter = tag;
|
||||||
|
currentQuery += tag;
|
||||||
|
i += tag.length - 1;
|
||||||
|
}
|
||||||
|
else if (dollarTagDelimiter === tag) {
|
||||||
|
insideDollarTag = false;
|
||||||
|
dollarTagDelimiter = null;
|
||||||
|
currentQuery += tag;
|
||||||
|
i += tag.length - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check BEGIN-END blocks
|
||||||
|
if (!insideString && !insideDollarTag) {
|
||||||
|
if (beginRegex.test(line))
|
||||||
|
insideBlock = true;
|
||||||
|
|
||||||
|
if (insideBlock && endRegex.test(line))
|
||||||
|
insideBlock = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the query if we encounter a semicolon outside a BEGIN-END block, outside a string, and outside dollar tags
|
||||||
|
if (!insideBlock && !insideString && !insideDollarTag && /;\s*$/.test(line)) {
|
||||||
|
queries.push(currentQuery.trim());
|
||||||
|
currentQuery = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any remaining query
|
||||||
|
if (currentQuery.trim())
|
||||||
|
queries.push(currentQuery.trim());
|
||||||
|
|
||||||
|
return queries;
|
||||||
|
};
|
@@ -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,18 +41,21 @@ 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: {[key: string]: string} = {
|
const CHARS_ESCAPE_MAP: Record<string, string> = {
|
||||||
'\0': '\\0',
|
'\0': '\\0',
|
||||||
'\b': '\\b',
|
'\b': '\\b',
|
||||||
'\t': '\\t',
|
'\t': '\\t',
|
||||||
'\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))
|
||||||
@@ -153,9 +166,9 @@ export const valueToSqlString = (args: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const jsonToSqlInsert = (args: {
|
export const jsonToSqlInsert = (args: {
|
||||||
json: { [key: string]: any}[];
|
json: Record<string, any>[];
|
||||||
client: ClientCode;
|
client: ClientCode;
|
||||||
fields: { [key: string]: {type: string; datePrecision: number}};
|
fields: Record<string, {type: string; datePrecision: number}>;
|
||||||
table: string;
|
table: string;
|
||||||
options?: {sqlInsertAfter: number; sqlInsertDivider: 'bytes' | 'rows'};
|
options?: {sqlInsertAfter: number; sqlInsertDivider: 'bytes' | 'rows'};
|
||||||
}) => {
|
}) => {
|
||||||
@@ -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}'`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@@ -1,26 +1,34 @@
|
|||||||
export const shortcutEvents: { [key: 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({
|
||||||
|
@@ -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';
|
||||||
@@ -36,9 +37,15 @@ export default () => {
|
|||||||
name: 'session',
|
name: 'session',
|
||||||
fileExtension: ''
|
fileExtension: ''
|
||||||
});
|
});
|
||||||
const encrypted = sessionStore.get('key') as string;
|
|
||||||
const key = safeStorage.decryptString(Buffer.from(encrypted, 'utf-8'));
|
try {
|
||||||
event.returnValue = key;
|
const encrypted = sessionStore.get('key') as string;
|
||||||
|
const key = safeStorage.decryptString(Buffer.from(encrypted, 'utf-8'));
|
||||||
|
event.returnValue = key;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
event.returnValue = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('show-open-dialog', (event, options) => {
|
ipcMain.handle('show-open-dialog', (event, options) => {
|
||||||
@@ -46,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');
|
||||||
@@ -74,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() };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@@ -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: {[key: string]: antares.Client}) => {
|
export default (connections: Record<string, antares.Client>) => {
|
||||||
ipcMain.handle('test-connection', async (event, conn: antares.ConnectionParams) => {
|
ipcMain.handle('test-connection', async (event, conn: antares.ConnectionParams) => {
|
||||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
|
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: {[key: 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: {[key: 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: {[key: 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: '',
|
||||||
@@ -146,22 +175,40 @@ export default (connections: {[key: string]: antares.Client}) => {
|
|||||||
uid: conn.uid,
|
uid: conn.uid,
|
||||||
client: conn.client,
|
client: conn.client,
|
||||||
params,
|
params,
|
||||||
poolSize: 5
|
poolSize: conn.singleConnectionMode ? 0 : 5
|
||||||
});
|
});
|
||||||
|
|
||||||
await connection.connect();
|
await connection.connect();
|
||||||
|
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' };
|
||||||
|
|
||||||
|
@@ -3,7 +3,7 @@ import { ipcMain } from 'electron';
|
|||||||
|
|
||||||
import { validateSender } from '../libs/misc/validateSender';
|
import { validateSender } from '../libs/misc/validateSender';
|
||||||
|
|
||||||
export default (connections: {[key: string]: antares.Client}) => {
|
export default (connections: Record<string, antares.Client>) => {
|
||||||
ipcMain.handle('get-databases', async (event, uid) => {
|
ipcMain.handle('get-databases', async (event, uid) => {
|
||||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
|
@@ -3,7 +3,7 @@ import { ipcMain } from 'electron';
|
|||||||
|
|
||||||
import { validateSender } from '../libs/misc/validateSender';
|
import { validateSender } from '../libs/misc/validateSender';
|
||||||
|
|
||||||
export default (connections: {[key: string]: antares.Client}) => {
|
export default (connections: Record<string, antares.Client>) => {
|
||||||
ipcMain.handle('get-function-informations', async (event, params) => {
|
ipcMain.handle('get-function-informations', async (event, params) => {
|
||||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
|
@@ -13,7 +13,7 @@ import updates from './updates';
|
|||||||
import users from './users';
|
import users from './users';
|
||||||
import views from './views';
|
import views from './views';
|
||||||
|
|
||||||
const connections: {[key: string]: antares.Client} = {};
|
const connections: Record<string, antares.Client> = {};
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
connection(connections);
|
connection(connections);
|
||||||
|
@@ -3,7 +3,7 @@ import { ipcMain } from 'electron';
|
|||||||
|
|
||||||
import { validateSender } from '../libs/misc/validateSender';
|
import { validateSender } from '../libs/misc/validateSender';
|
||||||
|
|
||||||
export default (connections: {[key: string]: antares.Client}) => {
|
export default (connections: Record<string, antares.Client>) => {
|
||||||
ipcMain.handle('get-routine-informations', async (event, params) => {
|
ipcMain.handle('get-routine-informations', async (event, params) => {
|
||||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
|
@@ -3,7 +3,7 @@ import { ipcMain } from 'electron';
|
|||||||
|
|
||||||
import { validateSender } from '../libs/misc/validateSender';
|
import { validateSender } from '../libs/misc/validateSender';
|
||||||
|
|
||||||
export default (connections: {[key: string]: antares.Client}) => {
|
export default (connections: Record<string, antares.Client>) => {
|
||||||
ipcMain.handle('get-scheduler-informations', async (event, params) => {
|
ipcMain.handle('get-scheduler-informations', async (event, params) => {
|
||||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
|
@@ -1,17 +1,14 @@
|
|||||||
import { ChildProcess, fork } from 'child_process';
|
|
||||||
import * as antares from 'common/interfaces/antares';
|
import * as antares from 'common/interfaces/antares';
|
||||||
import * as workers from 'common/interfaces/workers';
|
import * as workers from 'common/interfaces/workers';
|
||||||
import { dialog, ipcMain } from 'electron';
|
import { dialog, ipcMain } from 'electron';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import { Worker } from 'worker_threads';
|
||||||
|
|
||||||
import { validateSender } from '../libs/misc/validateSender';
|
import { validateSender } from '../libs/misc/validateSender';
|
||||||
|
|
||||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
export default (connections: Record<string, antares.Client>) => {
|
||||||
|
let exporter: Worker = null;
|
||||||
export default (connections: {[key: string]: antares.Client}) => {
|
let importer: Worker = null;
|
||||||
let exporter: ChildProcess = null;
|
|
||||||
let importer: ChildProcess = null;
|
|
||||||
|
|
||||||
ipcMain.handle('create-schema', async (event, params) => {
|
ipcMain.handle('create-schema', async (event, params) => {
|
||||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
@@ -202,7 +199,7 @@ export default (connections: {[key: string]: antares.Client}) => {
|
|||||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
if (exporter !== null) {
|
if (exporter !== null) {
|
||||||
exporter.kill();
|
exporter.terminate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,11 +224,12 @@ export default (connections: {[key: string]: antares.Client}) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init exporter process
|
// Init exporter thread
|
||||||
exporter = fork(isDevelopment ? './dist/exporter.js' : path.resolve(__dirname, './exporter.js'), [], {
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
execArgv: isDevelopment ? ['--inspect=9224'] : undefined
|
// @ts-ignore
|
||||||
});
|
exporter = new Worker(new URL('../workers/exporter', import.meta.url));
|
||||||
exporter.send({
|
|
||||||
|
exporter.postMessage({
|
||||||
type: 'init',
|
type: 'init',
|
||||||
client: {
|
client: {
|
||||||
name: type,
|
name: type,
|
||||||
@@ -242,32 +240,34 @@ export default (connections: {[key: string]: antares.Client}) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Exporter message listener
|
// Exporter message listener
|
||||||
exporter.on('message', ({ type, payload }: workers.WorkerIpcMessage) => {
|
exporter.on('message', (message: workers.WorkerIpcMessage) => {
|
||||||
|
const { type, payload } = message;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'export-progress':
|
case 'export-progress':
|
||||||
event.sender.send('export-progress', payload);
|
event.sender.send('export-progress', payload);
|
||||||
break;
|
break;
|
||||||
case 'end':
|
case 'end':
|
||||||
setTimeout(() => { // Ensures that writing process has finished
|
setTimeout(() => { // Ensures that writing thread has finished
|
||||||
exporter.kill();
|
exporter?.terminate();
|
||||||
exporter = null;
|
exporter = null;
|
||||||
}, 2000);
|
}, 500);
|
||||||
resolve({ status: 'success', response: payload });
|
resolve({ status: 'success', response: payload });
|
||||||
break;
|
break;
|
||||||
case 'cancel':
|
case 'cancel':
|
||||||
exporter.kill();
|
exporter?.terminate();
|
||||||
exporter = null;
|
exporter = null;
|
||||||
resolve({ status: 'error', response: 'Operation cancelled' });
|
resolve({ status: 'error', response: 'Operation cancelled' });
|
||||||
break;
|
break;
|
||||||
case 'error':
|
case 'error':
|
||||||
exporter.kill();
|
exporter?.terminate();
|
||||||
exporter = null;
|
exporter = null;
|
||||||
resolve({ status: 'error', response: payload });
|
resolve({ status: 'error', response: payload });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
exporter.on('exit', code => {
|
exporter.on('close', code => {
|
||||||
exporter = null;
|
exporter = null;
|
||||||
resolve({ status: 'error', response: `Operation ended with code: ${code}` });
|
resolve({ status: 'error', response: `Operation ended with code: ${code}` });
|
||||||
});
|
});
|
||||||
@@ -291,7 +291,7 @@ export default (connections: {[key: string]: antares.Client}) => {
|
|||||||
|
|
||||||
if (result.response === 1) {
|
if (result.response === 1) {
|
||||||
willAbort = true;
|
willAbort = true;
|
||||||
exporter.send({ type: 'cancel' });
|
exporter.postMessage({ type: 'cancel' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,7 +302,7 @@ export default (connections: {[key: string]: antares.Client}) => {
|
|||||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
if (importer !== null) {
|
if (importer !== null) {
|
||||||
importer.kill();
|
importer.terminate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,18 +310,21 @@ export default (connections: {[key: string]: antares.Client}) => {
|
|||||||
(async () => {
|
(async () => {
|
||||||
const dbConfig = await connections[options.uid].getDbConfig();
|
const dbConfig = await connections[options.uid].getDbConfig();
|
||||||
|
|
||||||
// Init importer process
|
// Init importer thread
|
||||||
importer = fork(isDevelopment ? './dist/importer.js' : path.resolve(__dirname, './importer.js'), [], {
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
execArgv: isDevelopment ? ['--inspect=9224'] : undefined
|
// @ts-ignore
|
||||||
});
|
importer = new Worker(new URL('../workers/importer', import.meta.url));
|
||||||
importer.send({
|
|
||||||
|
importer.postMessage({
|
||||||
type: 'init',
|
type: 'init',
|
||||||
dbConfig,
|
dbConfig,
|
||||||
options
|
options
|
||||||
});
|
});
|
||||||
|
|
||||||
// Importer message listener
|
// Importer message listener
|
||||||
importer.on('message', ({ type, payload }: workers.WorkerIpcMessage) => {
|
importer.on('message', (message: workers.WorkerIpcMessage) => {
|
||||||
|
const { type, payload } = message;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'import-progress':
|
case 'import-progress':
|
||||||
event.sender.send('import-progress', payload);
|
event.sender.send('import-progress', payload);
|
||||||
@@ -331,23 +334,28 @@ export default (connections: {[key: string]: antares.Client}) => {
|
|||||||
break;
|
break;
|
||||||
case 'end':
|
case 'end':
|
||||||
setTimeout(() => { // Ensures that writing process has finished
|
setTimeout(() => { // Ensures that writing process has finished
|
||||||
importer?.kill();
|
importer?.terminate();
|
||||||
importer = null;
|
importer = null;
|
||||||
}, 2000);
|
}, 2000);
|
||||||
resolve({ status: 'success', response: payload });
|
resolve({ status: 'success', response: payload });
|
||||||
break;
|
break;
|
||||||
case 'cancel':
|
case 'cancel':
|
||||||
importer.kill();
|
importer.terminate();
|
||||||
importer = null;
|
importer = null;
|
||||||
resolve({ status: 'error', response: 'Operation cancelled' });
|
resolve({ status: 'error', response: 'Operation cancelled' });
|
||||||
break;
|
break;
|
||||||
case 'error':
|
case 'error':
|
||||||
importer.kill();
|
importer.terminate();
|
||||||
importer = null;
|
importer = null;
|
||||||
resolve({ status: 'error', response: payload });
|
resolve({ status: 'error', response: payload });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
importer.on('close', code => {
|
||||||
|
importer = null;
|
||||||
|
resolve({ status: 'error', response: `Operation ended with code: ${code}` });
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -368,7 +376,7 @@ export default (connections: {[key: string]: antares.Client}) => {
|
|||||||
|
|
||||||
if (result.response === 1) {
|
if (result.response === 1) {
|
||||||
willAbort = true;
|
willAbort = true;
|
||||||
importer.send({ type: 'cancel' });
|
importer.postMessage({ type: 'cancel' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,14 +3,14 @@ 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';
|
||||||
|
|
||||||
import { validateSender } from '../libs/misc/validateSender';
|
import { validateSender } from '../libs/misc/validateSender';
|
||||||
|
|
||||||
export default (connections: {[key: string]: antares.Client}) => {
|
export default (connections: Record<string, antares.Client>) => {
|
||||||
ipcMain.handle('get-table-columns', async (event, params) => {
|
ipcMain.handle('get-table-columns', async (event, params) => {
|
||||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
@@ -87,6 +87,19 @@ export default (connections: {[key: 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: {[key: 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]}`;
|
||||||
@@ -249,7 +263,7 @@ export default (connections: {[key: string]: antares.Client}) => {
|
|||||||
|
|
||||||
if (params.primary) {
|
if (params.primary) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const idString = params.rows.map((row: {[key: string]: any}) => {
|
const idString = params.rows.map((row: Record<string, any>) => {
|
||||||
const fieldName = Object.keys(row)[0].includes('.') ? `${params.table}.${params.primary}` : params.primary;
|
const fieldName = Object.keys(row)[0].includes('.') ? `${params.table}.${params.primary}` : params.primary;
|
||||||
|
|
||||||
return typeof row[fieldName] === 'string'
|
return typeof row[fieldName] === 'string'
|
||||||
@@ -304,10 +318,10 @@ export default (connections: {[key: string]: antares.Client}) => {
|
|||||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
try { // TODO: move to client classes
|
try { // TODO: move to client classes
|
||||||
const rows: {[key: string]: string | number | boolean | Date | Buffer}[] = [];
|
const rows: Record<string, string | number | boolean | Date | Buffer>[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < +params.repeat; i++) {
|
for (let i = 0; i < +params.repeat; i++) {
|
||||||
const insertObj: {[key: string]: string | number | boolean | Date | Buffer} = {};
|
const insertObj: Record<string, string | number | boolean | Date | Buffer> = {};
|
||||||
|
|
||||||
for (const key in params.row) {
|
for (const key in params.row) {
|
||||||
const type = params.fields[key];
|
const type = params.fields[key];
|
||||||
@@ -367,7 +381,7 @@ export default (connections: {[key: string]: antares.Client}) => {
|
|||||||
insertObj[key] = escapedParam;
|
insertObj[key] = escapedParam;
|
||||||
}
|
}
|
||||||
else { // Faker value
|
else { // Faker value
|
||||||
const parsedParams: {[key: string]: string | number | boolean | Date | Buffer} = {};
|
const parsedParams: Record<string, string | number | boolean | Date | Buffer> = {};
|
||||||
let fakeValue;
|
let fakeValue;
|
||||||
|
|
||||||
if (params.locale)
|
if (params.locale)
|
||||||
@@ -437,12 +451,12 @@ export default (connections: {[key: string]: antares.Client}) => {
|
|||||||
if (description)
|
if (description)
|
||||||
query.select(`LEFT(${description}, 20) AS foreign_description`);
|
query.select(`LEFT(${description}, 20) AS foreign_description`);
|
||||||
|
|
||||||
const results = await query.run<{[key: string]: string}>();
|
const results = await query.run<Record<string, string>>();
|
||||||
|
|
||||||
const parsedResults: {[key: string]: string}[] = [];
|
const parsedResults: Record<string, string>[] = [];
|
||||||
|
|
||||||
for (const row of results.rows) {
|
for (const row of results.rows) {
|
||||||
const remappedRow: {[key: string]: string} = {};
|
const remappedRow: Record<string, string> = {};
|
||||||
|
|
||||||
for (const key in row)
|
for (const key in row)
|
||||||
remappedRow[key.toLowerCase()] = row[key];// Thanks Firebird -.-
|
remappedRow[key.toLowerCase()] = row[key];// Thanks Firebird -.-
|
||||||
|
@@ -3,7 +3,7 @@ import { ipcMain } from 'electron';
|
|||||||
|
|
||||||
import { validateSender } from '../libs/misc/validateSender';
|
import { validateSender } from '../libs/misc/validateSender';
|
||||||
|
|
||||||
export default (connections: {[key: string]: antares.Client}) => {
|
export default (connections: Record<string, antares.Client>) => {
|
||||||
ipcMain.handle('get-trigger-informations', async (event, params) => {
|
ipcMain.handle('get-trigger-informations', async (event, params) => {
|
||||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { ipcMain } from 'electron';
|
import { ipcMain } from 'electron';
|
||||||
|
import * as log from 'electron-log/main';
|
||||||
import * as Store from 'electron-store';
|
import * as Store from 'electron-store';
|
||||||
import { autoUpdater } from 'electron-updater';
|
import { autoUpdater } from 'electron-updater';
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@ export default () => {
|
|||||||
mainWindow.reply('update-downloaded');
|
mainWindow.reply('update-downloaded');
|
||||||
});
|
});
|
||||||
|
|
||||||
// autoUpdater.logger = require('electron-log');
|
log.transports.file.level = 'info';
|
||||||
// autoUpdater.logger.transports.console.format = '{h}:{i}:{s} {text}';
|
// log.transports.console.format = '{h}:{i}:{s} {text}';
|
||||||
// autoUpdater.logger.transports.file.level = 'info';
|
autoUpdater.logger = log;
|
||||||
};
|
};
|
||||||
|
@@ -3,7 +3,7 @@ import { ipcMain } from 'electron';
|
|||||||
|
|
||||||
import { validateSender } from '../libs/misc/validateSender';
|
import { validateSender } from '../libs/misc/validateSender';
|
||||||
|
|
||||||
export default (connections: {[key: string]: antares.Client}) => {
|
export default (connections: Record<string, antares.Client>) => {
|
||||||
ipcMain.handle('get-users', async (event, uid) => {
|
ipcMain.handle('get-users', async (event, uid) => {
|
||||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
|
@@ -3,7 +3,7 @@ import { ipcMain } from 'electron';
|
|||||||
|
|
||||||
import { validateSender } from '../libs/misc/validateSender';
|
import { validateSender } from '../libs/misc/validateSender';
|
||||||
|
|
||||||
export default (connections: {[key: string]: antares.Client}) => {
|
export default (connections: Record<string, antares.Client>) => {
|
||||||
ipcMain.handle('get-view-informations', async (event, params) => {
|
ipcMain.handle('get-view-informations', async (event, params) => {
|
||||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
@@ -51,4 +51,52 @@ export default (connections: {[key: string]: antares.Client}) => {
|
|||||||
return { status: 'error', response: err.toString() };
|
return { status: 'error', response: err.toString() };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-materialized-view-informations', async (event, params) => {
|
||||||
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await connections[params.uid].getMaterializedViewInformations(params);
|
||||||
|
return { status: 'success', response: result };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('drop-materialized-view', async (event, params) => {
|
||||||
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connections[params.uid].dropMaterializedView(params);
|
||||||
|
return { status: 'success' };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('alter-materialized-view', async (event, params) => {
|
||||||
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connections[params.uid].alterView(params);
|
||||||
|
return { status: 'success' };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('create-materialized-view', async (event, params) => {
|
||||||
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connections[params.uid].createMaterializedView(params);
|
||||||
|
return { status: 'success' };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@@ -70,23 +70,29 @@ export class ShortcutRegister {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private setLocalShortcuts () {
|
private setLocalShortcuts () {
|
||||||
|
const isMenuVisible = process.platform === 'darwin';
|
||||||
|
const submenu = [];
|
||||||
for (const shortcut of this.shortcuts) {
|
for (const shortcut of this.shortcuts) {
|
||||||
if (shortcut.os.includes(process.platform)) {
|
if (shortcut.os.includes(process.platform)) {
|
||||||
for (const key of shortcut.keys) {
|
for (const key of shortcut.keys) {
|
||||||
try {
|
try {
|
||||||
this._menu.append(new MenuItem({
|
submenu.push({
|
||||||
label: '.',
|
label: String(shortcut.event),
|
||||||
visible: false,
|
accelerator: key,
|
||||||
submenu: [{
|
visible: isMenuVisible,
|
||||||
label: String(key),
|
click: () => {
|
||||||
accelerator: key,
|
if (shortcut.isFunction) {
|
||||||
visible: false,
|
if (shortcut.event in this) {
|
||||||
click: () => {
|
type exporterMethods = 'setFullScreen' | 'setZoomIn' | 'setZoomOut' | 'setZoomReset';
|
||||||
this._mainWindow.webContents.send(shortcut.event);
|
this[shortcut.event as exporterMethods]();
|
||||||
if (isDevelopment) console.log('LOCAL EVENT:', shortcut);
|
}
|
||||||
}
|
}
|
||||||
}]
|
else
|
||||||
}));
|
this._mainWindow.webContents.send(shortcut.event);
|
||||||
|
|
||||||
|
if (isDevelopment) console.log('LOCAL EVENT:', shortcut);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (isDevelopment) console.log(error);
|
if (isDevelopment) console.log(error);
|
||||||
@@ -96,6 +102,11 @@ export class ShortcutRegister {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this._menu.append(new MenuItem({
|
||||||
|
label: 'Shortcut',
|
||||||
|
visible: isMenuVisible,
|
||||||
|
submenu
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private setGlobalShortcuts () {
|
private setGlobalShortcuts () {
|
||||||
@@ -118,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();
|
||||||
|
@@ -2,16 +2,9 @@ import * as antares from 'common/interfaces/antares';
|
|||||||
import mysql from 'mysql2/promise';
|
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');
|
||||||
|
import { querySplitter } from 'common/libs/querySplitter';
|
||||||
|
|
||||||
const queryLogger = ({ sql, cUid }: {sql: string; cUid: string}) => {
|
import { ipcLogger, LoggerLevel } from '../misc/ipcLogger';
|
||||||
// Remove comments, newlines and multiple spaces
|
|
||||||
const escapedSql = sql.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') console.log(escapedSql);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* As Simple As Possible Query Builder Core
|
* As Simple As Possible Query Builder Core
|
||||||
@@ -22,7 +15,8 @@ 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 _querySplitter: (sql: string, client: antares.ClientCode) => string[];
|
||||||
protected _queryDefaults: antares.QueryBuilderObject;
|
protected _queryDefaults: antares.QueryBuilderObject;
|
||||||
protected _query: antares.QueryBuilderObject;
|
protected _query: antares.QueryBuilderObject;
|
||||||
|
|
||||||
@@ -31,7 +25,8 @@ 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._querySplitter = args.querySplitter || querySplitter;
|
||||||
|
|
||||||
this._queryDefaults = {
|
this._queryDefaults = {
|
||||||
schema: '',
|
schema: '',
|
||||||
@@ -136,7 +131,7 @@ export abstract class BaseClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
insert (arr: {[key: string]: any}[]) {
|
insert (arr: Record<string, any>[]) {
|
||||||
this._query.insert = [...this._query.insert, ...arr];
|
this._query.insert = [...this._query.insert, ...arr];
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -178,6 +173,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 +233,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');
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,7 @@ export class FirebirdSQLClient extends BaseClient {
|
|||||||
protected _connection?: firebird.Database | firebird.ConnectionPool;
|
protected _connection?: firebird.Database | firebird.ConnectionPool;
|
||||||
_params: firebird.Options;
|
_params: firebird.Options;
|
||||||
|
|
||||||
private _types: {[key: number]: string} ={
|
private _types: Record<number, string> ={
|
||||||
452: 'CHAR', // Array of char
|
452: 'CHAR', // Array of char
|
||||||
448: 'VARCHAR',
|
448: 'VARCHAR',
|
||||||
500: 'SMALLINT',
|
500: 'SMALLINT',
|
||||||
@@ -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();
|
||||||
@@ -241,10 +245,10 @@ export class FirebirdSQLClient extends BaseClient {
|
|||||||
name: db.name,
|
name: db.name,
|
||||||
size: schemaSize,
|
size: schemaSize,
|
||||||
tables: remappedTables,
|
tables: remappedTables,
|
||||||
functions: [],
|
functions: [] as null[],
|
||||||
procedures: remappedProcedures,
|
procedures: remappedProcedures,
|
||||||
triggers: remappedTriggers,
|
triggers: remappedTriggers,
|
||||||
schedulers: []
|
schedulers: [] as null[]
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -333,7 +337,7 @@ export class FirebirdSQLClient extends BaseClient {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
name: field.FIELD_NAME.trim(),
|
name: field.FIELD_NAME.trim(),
|
||||||
key: null,
|
key: null as null,
|
||||||
type: fieldType,
|
type: fieldType,
|
||||||
schema: schema,
|
schema: schema,
|
||||||
table: table,
|
table: table,
|
||||||
@@ -342,14 +346,14 @@ export class FirebirdSQLClient extends BaseClient {
|
|||||||
datePrecision: field.FIELD_NAME.trim() === 'TIMESTAMP' ? 4 : null,
|
datePrecision: field.FIELD_NAME.trim() === 'TIMESTAMP' ? 4 : null,
|
||||||
charLength: ![...NUMBER, ...FLOAT].includes(fieldType) ? field.FIELD_LENGTH : null,
|
charLength: ![...NUMBER, ...FLOAT].includes(fieldType) ? field.FIELD_LENGTH : null,
|
||||||
nullable: !field.NOT_NULL,
|
nullable: !field.NOT_NULL,
|
||||||
unsigned: null,
|
unsigned: null as null,
|
||||||
zerofill: null,
|
zerofill: null as null,
|
||||||
order: field.FIELD_POSITION+1,
|
order: field.FIELD_POSITION+1,
|
||||||
default: defaultValue,
|
default: defaultValue,
|
||||||
charset: field.CHARSET,
|
charset: field.CHARSET,
|
||||||
collation: null,
|
collation: null as null,
|
||||||
autoIncrement: false,
|
autoIncrement: false,
|
||||||
onUpdate: null,
|
onUpdate: null as null,
|
||||||
comment: field.DESCRIPTION?.trim()
|
comment: field.DESCRIPTION?.trim()
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -453,7 +457,7 @@ export class FirebirdSQLClient extends BaseClient {
|
|||||||
table: table,
|
table: table,
|
||||||
field: field.FKCOLUMN_NAME.trim(),
|
field: field.FKCOLUMN_NAME.trim(),
|
||||||
position: field.KEY_SEQ,
|
position: field.KEY_SEQ,
|
||||||
constraintPosition: null,
|
constraintPosition: null as null,
|
||||||
constraintName: field.FK_NAME.trim(),
|
constraintName: field.FK_NAME.trim(),
|
||||||
refSchema: schema,
|
refSchema: schema,
|
||||||
refTable: field.PKTABLE_NAME.trim(),
|
refTable: field.PKTABLE_NAME.trim(),
|
||||||
@@ -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,
|
||||||
@@ -1037,9 +1041,7 @@ export class FirebirdSQLClient extends BaseClient {
|
|||||||
const resultsArr = [];
|
const resultsArr = [];
|
||||||
let paramsArr = [];
|
let paramsArr = [];
|
||||||
const queries = args.split
|
const queries = args.split
|
||||||
? sql.split(/((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm)
|
? this._querySplitter(sql, 'firebird')
|
||||||
.filter(Boolean)
|
|
||||||
.map(q => q.trim())
|
|
||||||
: [sql];
|
: [sql];
|
||||||
|
|
||||||
let connection: firebird.Database | firebird.Transaction;
|
let connection: firebird.Database | firebird.Transaction;
|
||||||
|
@@ -4,7 +4,9 @@ import dataTypes from 'common/data-types/mysql';
|
|||||||
import * as antares from 'common/interfaces/antares';
|
import * as antares from 'common/interfaces/antares';
|
||||||
import * as mysql from 'mysql2/promise';
|
import * as mysql from 'mysql2/promise';
|
||||||
|
|
||||||
|
import * as EncodingToCharset from '../../../../node_modules/mysql2/lib/constants/encoding_charset.js';
|
||||||
import { BaseClient } from './BaseClient';
|
import { BaseClient } from './BaseClient';
|
||||||
|
EncodingToCharset.utf8mb3 = 192; // To fix https://github.com/sidorares/node-mysql2/issues/1398 until not included in mysql2
|
||||||
|
|
||||||
export class MySQLClient extends BaseClient {
|
export class MySQLClient extends BaseClient {
|
||||||
private _schema?: string;
|
private _schema?: string;
|
||||||
@@ -12,10 +14,11 @@ export class MySQLClient extends BaseClient {
|
|||||||
private _connectionsToCommit: Map<string, mysql.Connection | mysql.PoolConnection>;
|
private _connectionsToCommit: Map<string, mysql.Connection | mysql.PoolConnection>;
|
||||||
private _keepaliveTimer: NodeJS.Timer;
|
private _keepaliveTimer: NodeJS.Timer;
|
||||||
private _keepaliveMs: number;
|
private _keepaliveMs: number;
|
||||||
|
private sqlMode?: string[];
|
||||||
_connection?: mysql.Connection | mysql.Pool;
|
_connection?: mysql.Connection | mysql.Pool;
|
||||||
_params: mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean};
|
_params: mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean};
|
||||||
|
|
||||||
private types: {[key: number]: string} = {
|
private types: Record<number, string> = {
|
||||||
0: 'DECIMAL',
|
0: 'DECIMAL',
|
||||||
1: 'TINYINT',
|
1: 'TINYINT',
|
||||||
2: 'SMALLINT',
|
2: 'SMALLINT',
|
||||||
@@ -58,6 +61,10 @@ export class MySQLClient extends BaseClient {
|
|||||||
this._keepaliveMs = 10*60*1000;
|
this._keepaliveMs = 10*60*1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get isPool () {
|
||||||
|
return 'getConnection' in this._connection;
|
||||||
|
}
|
||||||
|
|
||||||
private _getType (field: mysql.FieldPacket & { columnType?: number; columnLength?: number }) {
|
private _getType (field: mysql.FieldPacket & { columnType?: number; columnLength?: number }) {
|
||||||
let name = this.types[field.columnType];
|
let name = this.types[field.columnType];
|
||||||
let length = field.columnLength;
|
let length = field.columnLength;
|
||||||
@@ -156,6 +163,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
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -181,9 +190,36 @@ export class MySQLClient extends BaseClient {
|
|||||||
|
|
||||||
async connect () {
|
async connect () {
|
||||||
if (!this._poolSize)
|
if (!this._poolSize)
|
||||||
this._connection = await this.getConnection();
|
this._connection = await this.getSingleConnection();
|
||||||
else
|
else
|
||||||
this._connection = await this.getConnectionPool();
|
this._connection = await this.getConnectionPool();
|
||||||
|
|
||||||
|
// ANSI_QUOTES check
|
||||||
|
const [response] = await this._connection.query<mysql.RowDataPacket[]>('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\'');
|
||||||
|
this.sqlMode = response[0]?.Value?.split(',');
|
||||||
|
const hasAnsiQuotes = this.sqlMode.includes('ANSI') || this.sqlMode.includes('ANSI_QUOTES');
|
||||||
|
|
||||||
|
if (hasAnsiQuotes)
|
||||||
|
await this._connection.query(`SET SESSION sql_mode = '${this.sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`);
|
||||||
|
|
||||||
|
if (this._params.readonly)
|
||||||
|
await this._connection.query('SET SESSION TRANSACTION READ ONLY');
|
||||||
|
|
||||||
|
if (this._poolSize) {
|
||||||
|
const hasAnsiQuotes = this.sqlMode.includes('ANSI') || this.sqlMode.includes('ANSI_QUOTES');
|
||||||
|
|
||||||
|
this._connection.on('connection', conn => {
|
||||||
|
if (this._params.readonly)
|
||||||
|
conn.query('SET SESSION TRANSACTION READ ONLY');
|
||||||
|
|
||||||
|
if (hasAnsiQuotes)
|
||||||
|
conn.query(`SET SESSION sql_mode = '${this.sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ping () {
|
||||||
|
return this.select('1+1').run();
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy () {
|
destroy () {
|
||||||
@@ -196,29 +232,19 @@ export class MySQLClient extends BaseClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getConnection () {
|
async getSingleConnection () {
|
||||||
const dbConfig = await this.getDbConfig();
|
const dbConfig = await this.getDbConfig();
|
||||||
const connection = await mysql.createConnection({
|
const connection = await mysql.createConnection({
|
||||||
...dbConfig,
|
...dbConfig,
|
||||||
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();
|
||||||
|
// }
|
||||||
});
|
});
|
||||||
|
|
||||||
// ANSI_QUOTES check
|
|
||||||
const [response] = await connection.query<mysql.RowDataPacket[]>('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\'');
|
|
||||||
const sqlMode: string[] = response[0]?.Value?.split(',');
|
|
||||||
const hasAnsiQuotes = sqlMode.includes('ANSI') || sqlMode.includes('ANSI_QUOTES');
|
|
||||||
|
|
||||||
if (this._params.readonly)
|
|
||||||
await connection.query('SET SESSION TRANSACTION READ ONLY');
|
|
||||||
|
|
||||||
if (hasAnsiQuotes)
|
|
||||||
await connection.query(`SET SESSION sql_mode = '${sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`);
|
|
||||||
|
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,31 +253,14 @@ export class MySQLClient extends BaseClient {
|
|||||||
const connection = mysql.createPool({
|
const connection = mysql.createPool({
|
||||||
...dbConfig,
|
...dbConfig,
|
||||||
connectionLimit: this._poolSize,
|
connectionLimit: this._poolSize,
|
||||||
typeCast: (field, next) => {
|
enableKeepAlive: true,
|
||||||
if (field.type === 'DATETIME')
|
dateStrings: true
|
||||||
return field.string();
|
// typeCast: (field, next) => {
|
||||||
else
|
// if (field.type === 'DATETIME')
|
||||||
return next();
|
// return field.string();
|
||||||
}
|
// else
|
||||||
});
|
// return next();
|
||||||
|
// }
|
||||||
// ANSI_QUOTES check
|
|
||||||
const [res] = await connection.query<mysql.RowDataPacket[]>('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\'');
|
|
||||||
const sqlMode: string[] = res[0]?.Value?.split(',');
|
|
||||||
const hasAnsiQuotes = sqlMode.includes('ANSI') || sqlMode.includes('ANSI_QUOTES');
|
|
||||||
|
|
||||||
if (hasAnsiQuotes)
|
|
||||||
await connection.query(`SET SESSION sql_mode = '${sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`);
|
|
||||||
|
|
||||||
if (this._params.readonly)
|
|
||||||
await connection.query('SET SESSION TRANSACTION READ ONLY');
|
|
||||||
|
|
||||||
connection.on('connection', conn => {
|
|
||||||
if (this._params.readonly)
|
|
||||||
conn.query('SET SESSION TRANSACTION READ ONLY');
|
|
||||||
|
|
||||||
if (hasAnsiQuotes)
|
|
||||||
conn.query(`SET SESSION sql_mode = '${sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this._keepaliveTimer = setInterval(async () => {
|
this._keepaliveTimer = setInterval(async () => {
|
||||||
@@ -261,6 +270,43 @@ export class MySQLClient extends BaseClient {
|
|||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getConnection (args?: antares.QueryParams, retry?: boolean): Promise<mysql.Pool | mysql.PoolConnection | mysql.Connection> {
|
||||||
|
let connection;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (args && !args.autocommit && args.tabUid) { // autocommit OFF
|
||||||
|
if (this._connectionsToCommit.has(args.tabUid))
|
||||||
|
connection = this._connectionsToCommit.get(args.tabUid);
|
||||||
|
else {
|
||||||
|
connection = await this.getSingleConnection();
|
||||||
|
await connection.query('SET SESSION autocommit=0');
|
||||||
|
this._connectionsToCommit.set(args.tabUid, connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else// autocommit ON
|
||||||
|
connection = this.isPool ? await (this._connection as mysql.Pool).getConnection() : this._connection;
|
||||||
|
|
||||||
|
if (args && args.tabUid && this.isPool) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
this._runningConnections.set(args.tabUid, (connection as any).connection.connectionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args && args.schema)
|
||||||
|
await connection.query(`USE \`${args.schema}\``);
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error.code === 'ECONNRESET' && !retry) {
|
||||||
|
this.destroy();
|
||||||
|
await this.connect();
|
||||||
|
return this.getConnection(args, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new Error(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async keepAlive () {
|
private async keepAlive () {
|
||||||
try {
|
try {
|
||||||
const connection = await (this._connection as mysql.Pool).getConnection();
|
const connection = await (this._connection as mysql.Pool).getConnection();
|
||||||
@@ -312,10 +358,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`');
|
||||||
@@ -582,7 +639,7 @@ export class MySQLClient extends BaseClient {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.reduce((acc: {[key: string]: { name: string; type: string; length: string; default: string}}, curr) => {
|
.reduce((acc: Record<string, { name: string; type: string; length: string; default: string}>, curr) => {
|
||||||
acc[curr.name] = curr;
|
acc[curr.name] = curr;
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
@@ -591,7 +648,7 @@ export class MySQLClient extends BaseClient {
|
|||||||
return rows.map((field) => {
|
return rows.map((field) => {
|
||||||
const numLengthMatch = field.COLUMN_TYPE.match(/int\(([^)]+)\)/);
|
const numLengthMatch = field.COLUMN_TYPE.match(/int\(([^)]+)\)/);
|
||||||
const numLength = numLengthMatch ? +numLengthMatch.pop() : field.NUMERIC_PRECISION || null;
|
const numLength = numLengthMatch ? +numLengthMatch.pop() : field.NUMERIC_PRECISION || null;
|
||||||
const enumValues = /(enum)/.test(field.COLUMN_TYPE)
|
const enumValues = /(enum|set)/.test(field.COLUMN_TYPE)
|
||||||
? field.COLUMN_TYPE.match(/\(([^)]+)\)/)[0].slice(1, -1)
|
? field.COLUMN_TYPE.match(/\(([^)]+)\)/)[0].slice(1, -1)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
@@ -621,7 +678,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()
|
||||||
: '',
|
: '',
|
||||||
@@ -636,6 +693,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 {
|
||||||
@@ -812,11 +897,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}\``;
|
||||||
|
|
||||||
@@ -857,7 +944,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);
|
||||||
}
|
}
|
||||||
@@ -871,6 +964,7 @@ export class MySQLClient extends BaseClient {
|
|||||||
changes,
|
changes,
|
||||||
indexChanges,
|
indexChanges,
|
||||||
foreignChanges,
|
foreignChanges,
|
||||||
|
checkChanges,
|
||||||
options
|
options
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
@@ -878,6 +972,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}'`);
|
||||||
@@ -923,6 +1018,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);
|
||||||
@@ -934,9 +1035,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'}`);
|
||||||
});
|
});
|
||||||
@@ -967,6 +1068,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}\``);
|
||||||
@@ -985,7 +1093,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(', '));
|
||||||
@@ -1625,7 +1737,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,
|
||||||
@@ -1643,33 +1755,10 @@ export class MySQLClient extends BaseClient {
|
|||||||
const resultsArr: antares.QueryResult[] = [];
|
const resultsArr: antares.QueryResult[] = [];
|
||||||
let paramsArr = [];
|
let paramsArr = [];
|
||||||
const queries = args.split
|
const queries = args.split
|
||||||
? sql.split(/((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm)
|
? this._querySplitter(sql, 'mysql')
|
||||||
.filter(Boolean)
|
|
||||||
.map(q => q.trim())
|
|
||||||
: [sql];
|
: [sql];
|
||||||
|
|
||||||
let connection: mysql.Connection | mysql.Pool | mysql.PoolConnection;
|
const connection = await this.getConnection(args);
|
||||||
const isPool = 'getConnection' in this._connection;
|
|
||||||
|
|
||||||
if (!args.autocommit && args.tabUid) { // autocommit OFF
|
|
||||||
if (this._connectionsToCommit.has(args.tabUid))
|
|
||||||
connection = this._connectionsToCommit.get(args.tabUid);
|
|
||||||
else {
|
|
||||||
connection = await this.getConnection();
|
|
||||||
await connection.query('SET SESSION autocommit=0');
|
|
||||||
this._connectionsToCommit.set(args.tabUid, connection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else// autocommit ON
|
|
||||||
connection = isPool ? await (this._connection as mysql.Pool).getConnection() : this._connection;
|
|
||||||
|
|
||||||
if (args.tabUid && isPool) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
this._runningConnections.set(args.tabUid, (connection as any).connection.connectionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.schema)
|
|
||||||
await connection.query(`USE \`${args.schema}\``);
|
|
||||||
|
|
||||||
for (const query of queries) {
|
for (const query of queries) {
|
||||||
if (!query) continue;
|
if (!query) continue;
|
||||||
@@ -1682,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;
|
||||||
|
|
||||||
@@ -1729,7 +1819,7 @@ export class MySQLClient extends BaseClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
if (isPool && args.autocommit) {
|
if (this.isPool && args.autocommit) {
|
||||||
(connection as mysql.PoolConnection).release();
|
(connection as mysql.PoolConnection).release();
|
||||||
this._runningConnections.delete(args.tabUid);
|
this._runningConnections.delete(args.tabUid);
|
||||||
}
|
}
|
||||||
@@ -1741,7 +1831,7 @@ export class MySQLClient extends BaseClient {
|
|||||||
keysArr = keysArr ? [...keysArr, ...response] : response;
|
keysArr = keysArr ? [...keysArr, ...response] : response;
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
if (isPool && args.autocommit) {
|
if (this.isPool && args.autocommit) {
|
||||||
(connection as mysql.PoolConnection).release();
|
(connection as mysql.PoolConnection).release();
|
||||||
this._runningConnections.delete(args.tabUid);
|
this._runningConnections.delete(args.tabUid);
|
||||||
}
|
}
|
||||||
@@ -1753,13 +1843,13 @@ 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
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
if (isPool && args.autocommit) {
|
if (this.isPool && args.autocommit) {
|
||||||
(connection as mysql.PoolConnection).release();
|
(connection as mysql.PoolConnection).release();
|
||||||
this._runningConnections.delete(args.tabUid);
|
this._runningConnections.delete(args.tabUid);
|
||||||
}
|
}
|
||||||
@@ -1776,7 +1866,7 @@ export class MySQLClient extends BaseClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPool && args.autocommit) {
|
if (this.isPool && args.autocommit) {
|
||||||
(connection as mysql.PoolConnection).release();
|
(connection as mysql.PoolConnection).release();
|
||||||
this._runningConnections.delete(args.tabUid);
|
this._runningConnections.delete(args.tabUid);
|
||||||
}
|
}
|
||||||
|
@@ -88,8 +88,8 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
private _keepaliveTimer: NodeJS.Timer;
|
private _keepaliveTimer: NodeJS.Timer;
|
||||||
private _keepaliveMs: number;
|
private _keepaliveMs: number;
|
||||||
protected _connection?: pg.Client | pg.Pool;
|
protected _connection?: pg.Client | pg.Pool;
|
||||||
private types: {[key: string]: string} = {};
|
private types: Record<string, string> = {};
|
||||||
private _arrayTypes: {[key: string]: string} = {
|
private _arrayTypes: Record<string, string> = {
|
||||||
_int2: 'SMALLINT',
|
_int2: 'SMALLINT',
|
||||||
_int4: 'INTEGER',
|
_int4: 'INTEGER',
|
||||||
_int8: 'BIGINT',
|
_int8: 'BIGINT',
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,9 +239,17 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
await this.keepAlive();
|
await this.keepAlive();
|
||||||
}, this._keepaliveMs);
|
}, this._keepaliveMs);
|
||||||
|
|
||||||
|
connection.on('error', err => { // Intercepts errors and converts to rejections
|
||||||
|
Promise.reject(err);
|
||||||
|
});
|
||||||
|
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ping () {
|
||||||
|
return this.select('1+1').run();
|
||||||
|
}
|
||||||
|
|
||||||
destroy () {
|
destroy () {
|
||||||
this._connection.end();
|
this._connection.end();
|
||||||
clearInterval(this._keepaliveTimer);
|
clearInterval(this._keepaliveTimer);
|
||||||
@@ -327,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;
|
||||||
@@ -335,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,
|
||||||
@@ -370,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,
|
||||||
@@ -426,7 +466,7 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
procedures: remappedProcedures,
|
procedures: remappedProcedures,
|
||||||
triggers: remappedTriggers,
|
triggers: remappedTriggers,
|
||||||
triggerFunctions: remappedTriggerFunctions,
|
triggerFunctions: remappedTriggerFunctions,
|
||||||
schedulers: []
|
schedulers: [] as null[]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -461,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;
|
||||||
@@ -481,7 +532,7 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
name: field.column_name,
|
name: field.column_name,
|
||||||
key: null,
|
key: null as null,
|
||||||
type: type.toUpperCase(),
|
type: type.toUpperCase(),
|
||||||
isArray,
|
isArray,
|
||||||
schema: field.table_schema,
|
schema: field.table_schema,
|
||||||
@@ -491,15 +542,15 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
datePrecision: field.datetime_precision,
|
datePrecision: field.datetime_precision,
|
||||||
charLength: field.character_maximum_length,
|
charLength: field.character_maximum_length,
|
||||||
nullable: field.is_nullable.includes('YES'),
|
nullable: field.is_nullable.includes('YES'),
|
||||||
unsigned: null,
|
unsigned: null as null,
|
||||||
zerofill: null,
|
zerofill: null as null,
|
||||||
order: field.ordinal_position,
|
order: field.ordinal_position,
|
||||||
default: field.column_default,
|
default: field.column_default,
|
||||||
charset: field.character_set_name,
|
charset: field.character_set_name,
|
||||||
collation: field.collation_name,
|
collation: field.collation_name,
|
||||||
autoIncrement: false,
|
autoIncrement: false,
|
||||||
onUpdate: null,
|
onUpdate: null as null,
|
||||||
comment: ''
|
comment: field.column_comment
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -558,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
|
||||||
@@ -603,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;
|
||||||
@@ -652,7 +675,7 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
let createSql = '';
|
let createSql = '';
|
||||||
const sequences = [];
|
const sequences = [];
|
||||||
const columnsSql = [];
|
const columnsSql = [];
|
||||||
const arrayTypes: {[key: string]: string} = {
|
const arrayTypes: Record<string, string> = {
|
||||||
_int2: 'smallint',
|
_int2: 'smallint',
|
||||||
_int4: 'integer',
|
_int4: 'integer',
|
||||||
_int8: 'bigint',
|
_int8: 'bigint',
|
||||||
@@ -673,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}`;
|
||||||
@@ -700,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];
|
||||||
@@ -716,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;
|
||||||
}
|
}
|
||||||
@@ -836,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}"`;
|
||||||
|
|
||||||
@@ -851,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
|
||||||
@@ -871,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);
|
||||||
}
|
}
|
||||||
@@ -897,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 => {
|
||||||
@@ -910,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
|
||||||
@@ -962,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
|
||||||
@@ -1009,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}`;
|
||||||
|
|
||||||
@@ -1048,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}`;
|
||||||
|
|
||||||
@@ -1062,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('.');
|
||||||
|
|
||||||
@@ -1151,9 +1252,9 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
return results.rows.map(async row => {
|
return results.rows.map(async row => {
|
||||||
if (!row.pg_get_functiondef) {
|
if (!row.pg_get_functiondef) {
|
||||||
return {
|
return {
|
||||||
definer: null,
|
definer: null as null,
|
||||||
sql: '',
|
sql: '',
|
||||||
parameters: [],
|
parameters: [] as null[],
|
||||||
name: routine,
|
name: routine,
|
||||||
comment: '',
|
comment: '',
|
||||||
security: 'DEFINER',
|
security: 'DEFINER',
|
||||||
@@ -1202,8 +1303,8 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
name: routine,
|
name: routine,
|
||||||
comment: '',
|
comment: '',
|
||||||
security: row.pg_get_functiondef.includes('SECURITY DEFINER') ? 'DEFINER' : 'INVOKER',
|
security: row.pg_get_functiondef.includes('SECURITY DEFINER') ? 'DEFINER' : 'INVOKER',
|
||||||
deterministic: null,
|
deterministic: null as null,
|
||||||
dataAccess: null,
|
dataAccess: null as null,
|
||||||
language: row.pg_get_functiondef.match(/(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm)[0]
|
language: row.pg_get_functiondef.match(/(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm)[0]
|
||||||
};
|
};
|
||||||
})[0];
|
})[0];
|
||||||
@@ -1267,9 +1368,9 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
return results.rows.map(async row => {
|
return results.rows.map(async row => {
|
||||||
if (!row.pg_get_functiondef) {
|
if (!row.pg_get_functiondef) {
|
||||||
return {
|
return {
|
||||||
definer: null,
|
definer: null as null,
|
||||||
sql: '',
|
sql: '',
|
||||||
parameters: [],
|
parameters: [] as null[],
|
||||||
name: func,
|
name: func,
|
||||||
comment: '',
|
comment: '',
|
||||||
security: 'DEFINER',
|
security: 'DEFINER',
|
||||||
@@ -1317,8 +1418,8 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
name: func,
|
name: func,
|
||||||
comment: '',
|
comment: '',
|
||||||
security: row.pg_get_functiondef.includes('SECURITY DEFINER') ? 'DEFINER' : 'INVOKER',
|
security: row.pg_get_functiondef.includes('SECURITY DEFINER') ? 'DEFINER' : 'INVOKER',
|
||||||
deterministic: null,
|
deterministic: null as null,
|
||||||
dataAccess: null,
|
dataAccess: null as null,
|
||||||
language: row.pg_get_functiondef.match(/(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm)[0],
|
language: row.pg_get_functiondef.match(/(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm)[0],
|
||||||
returns: row.pg_get_functiondef.match(/(?<=RETURNS )(.*)(?<=[\S+\n\r\s])/gm)[0].replace('SETOF ', '').toUpperCase()
|
returns: row.pg_get_functiondef.match(/(?<=RETURNS )(.*)(?<=[\S+\n\r\s])/gm)[0].replace('SETOF ', '').toUpperCase()
|
||||||
};
|
};
|
||||||
@@ -1547,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,
|
||||||
@@ -1564,9 +1665,7 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
const resultsArr: antares.QueryResult[] = [];
|
const resultsArr: antares.QueryResult[] = [];
|
||||||
let paramsArr = [];
|
let paramsArr = [];
|
||||||
const queries = args.split
|
const queries = args.split
|
||||||
? sql.split(/(?!\B'[^']*);(?![^']*'\B)/gm)
|
? this._querySplitter(sql, 'pg')
|
||||||
.filter(Boolean)
|
|
||||||
.map(q => q.trim())
|
|
||||||
: [sql];
|
: [sql];
|
||||||
|
|
||||||
let connection: pg.Client | pg.PoolClient;
|
let connection: pg.Client | pg.PoolClient;
|
||||||
|
@@ -35,6 +35,10 @@ export class SQLiteClient extends BaseClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ping () {
|
||||||
|
return this.select('1+1').run();
|
||||||
|
}
|
||||||
|
|
||||||
destroy () {
|
destroy () {
|
||||||
this._connection.close();
|
this._connection.close();
|
||||||
}
|
}
|
||||||
@@ -120,10 +124,10 @@ export class SQLiteClient extends BaseClient {
|
|||||||
name: db.name,
|
name: db.name,
|
||||||
size: schemaSize,
|
size: schemaSize,
|
||||||
tables: remappedTables,
|
tables: remappedTables,
|
||||||
functions: [],
|
functions: [] as null[],
|
||||||
procedures: [],
|
procedures: [] as null[],
|
||||||
triggers: remappedTriggers,
|
triggers: remappedTriggers,
|
||||||
schedulers: []
|
schedulers: [] as null[]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -162,22 +166,22 @@ export class SQLiteClient extends BaseClient {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
name: field.name,
|
name: field.name,
|
||||||
key: null,
|
key: null as null,
|
||||||
type: type.trim(),
|
type: type.trim(),
|
||||||
schema: schema,
|
schema: schema,
|
||||||
table: table,
|
table: table,
|
||||||
numPrecision: [...NUMBER, ...FLOAT].includes(type) ? length : null,
|
numLength: [...NUMBER, ...FLOAT].includes(type) ? length : null,
|
||||||
datePrecision: null,
|
datePrecision: null as null,
|
||||||
charLength: ![...NUMBER, ...FLOAT].includes(type) ? length : null,
|
charLength: ![...NUMBER, ...FLOAT].includes(type) ? length : null,
|
||||||
nullable: !field.notnull,
|
nullable: !field.notnull,
|
||||||
unsigned: null,
|
unsigned: null as null,
|
||||||
zerofill: null,
|
zerofill: null as null,
|
||||||
order: typeof field.cid === 'string' ? +field.cid + 1 : field.cid + 1,
|
order: typeof field.cid === 'string' ? +field.cid + 1 : field.cid + 1,
|
||||||
default: field.dflt_value,
|
default: field.dflt_value,
|
||||||
charset: null,
|
charset: null as null,
|
||||||
collation: null,
|
collation: null as null,
|
||||||
autoIncrement: false,
|
autoIncrement: false,
|
||||||
onUpdate: null,
|
onUpdate: null as null,
|
||||||
comment: ''
|
comment: ''
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -263,7 +267,7 @@ export class SQLiteClient extends BaseClient {
|
|||||||
table: table,
|
table: table,
|
||||||
field: field.from,
|
field: field.from,
|
||||||
position: field.id + 1,
|
position: field.id + 1,
|
||||||
constraintPosition: null,
|
constraintPosition: null as null,
|
||||||
constraintName: field.id,
|
constraintName: field.id,
|
||||||
refSchema: schema,
|
refSchema: schema,
|
||||||
refTable: field.table,
|
refTable: field.table,
|
||||||
@@ -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,
|
||||||
@@ -625,9 +629,7 @@ export class SQLiteClient extends BaseClient {
|
|||||||
const resultsArr = [];
|
const resultsArr = [];
|
||||||
let paramsArr = [];
|
let paramsArr = [];
|
||||||
const queries = args.split
|
const queries = args.split
|
||||||
? sql.split(/((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm)
|
? this._querySplitter(sql, 'sqlite')
|
||||||
.filter(Boolean)
|
|
||||||
.map(q => q.trim())
|
|
||||||
: [sql];
|
: [sql];
|
||||||
|
|
||||||
let connection: sqlite.Database;
|
let connection: sqlite.Database;
|
||||||
@@ -658,7 +660,7 @@ export class SQLiteClient extends BaseClient {
|
|||||||
let queryAllResult: any[];
|
let queryAllResult: any[];
|
||||||
let affectedRows;
|
let affectedRows;
|
||||||
let fields;
|
let fields;
|
||||||
const detectedTypes: {[key: string]: string} = {};
|
const detectedTypes: Record<string, string> = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stmt = connection.prepare(query);
|
const stmt = connection.prepare(query);
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import * as exporter from 'common/interfaces/exporter';
|
import * as exporter from 'common/interfaces/exporter';
|
||||||
import { valueToSqlString } from 'common/libs/sqlUtils';
|
import { valueToSqlString } from 'common/libs/sqlUtils';
|
||||||
import * as mysql from 'mysql2/promise';
|
|
||||||
|
|
||||||
import { MySQLClient } from '../../clients/MySQLClient';
|
import { MySQLClient } from '../../clients/MySQLClient';
|
||||||
import { SqlExporter } from './SqlExporter';
|
import { SqlExporter } from './SqlExporter';
|
||||||
@@ -334,12 +333,11 @@ CREATE TABLE \`${view.Name}\`(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _queryStream (sql: string) {
|
async _queryStream (sql: string) {
|
||||||
if (process.env.NODE_ENV === 'development') console.log('EXPORTER:', sql);
|
const connection = await this._client.getConnection();
|
||||||
const isPool = 'getConnection' in this._client._connection;
|
|
||||||
const connection = isPool ? await (this._client._connection as mysql.Pool).getConnection() : this._client._connection;
|
|
||||||
// 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 as mysql.PoolConnection).release();
|
// 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);
|
||||||
@@ -357,7 +355,7 @@ CREATE TABLE \`${view.Name}\`(
|
|||||||
escapeAndQuote (val: string) {
|
escapeAndQuote (val: string) {
|
||||||
// eslint-disable-next-line no-control-regex
|
// eslint-disable-next-line no-control-regex
|
||||||
const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g;
|
const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g;
|
||||||
const CHARS_ESCAPE_MAP: {[key: string]: string} = {
|
const CHARS_ESCAPE_MAP: Record<string, string> = {
|
||||||
'\0': '\\0',
|
'\0': '\\0',
|
||||||
'\b': '\\b',
|
'\b': '\\b',
|
||||||
'\t': '\\t',
|
'\t': '\\t',
|
||||||
|
@@ -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: {[key: string]: string} = {
|
|
||||||
_int2: 'smallint',
|
|
||||||
_int4: 'integer',
|
|
||||||
_int8: 'bigint',
|
|
||||||
_float4: 'real',
|
|
||||||
_float8: 'double precision',
|
|
||||||
_char: '"char"',
|
|
||||||
_varchar: 'character varying'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Table columns
|
|
||||||
const { rows } = await this._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(`
|
||||||
@@ -425,7 +317,6 @@ SET row_security = off;\n\n\n`;
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _queryStream (sql: string) {
|
async _queryStream (sql: string) {
|
||||||
if (process.env.NODE_ENV === 'development') console.log('EXPORTER:', sql);
|
|
||||||
const connection = await this._client.getConnection();
|
const connection = await this._client.getConnection();
|
||||||
const query = new QueryStream(sql, null);
|
const query = new QueryStream(sql, null);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@@ -441,7 +332,7 @@ SET row_security = off;\n\n\n`;
|
|||||||
escapeAndQuote (val: string) {
|
escapeAndQuote (val: string) {
|
||||||
// eslint-disable-next-line no-control-regex
|
// eslint-disable-next-line no-control-regex
|
||||||
const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g;
|
const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g;
|
||||||
const CHARS_ESCAPE_MAP: {[key: string]: string} = {
|
const CHARS_ESCAPE_MAP: Record<string, string> = {
|
||||||
'\0': '\\0',
|
'\0': '\\0',
|
||||||
'\b': '\\b',
|
'\b': '\\b',
|
||||||
'\t': '\\t',
|
'\t': '\\t',
|
||||||
|
@@ -33,8 +33,8 @@ export default class MySQLImporter extends BaseImporter {
|
|||||||
parser.on('error', reject);
|
parser.on('error', reject);
|
||||||
|
|
||||||
parser.on('close', async () => {
|
parser.on('close', async () => {
|
||||||
console.log('TOTAL QUERIES', queryCount);
|
// console.log('TOTAL QUERIES', queryCount);
|
||||||
console.log('import end');
|
// console.log('import end');
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -33,8 +33,8 @@ export default class PostgreSQLImporter extends BaseImporter {
|
|||||||
parser.on('error', reject);
|
parser.on('error', reject);
|
||||||
|
|
||||||
parser.on('close', async () => {
|
parser.on('close', async () => {
|
||||||
console.log('TOTAL QUERIES', queryCount);
|
// console.log('TOTAL QUERIES', queryCount);
|
||||||
console.log('import end');
|
// console.log('import end');
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
20
src/main/libs/misc/ipcLogger.ts
Normal file
20
src/main/libs/misc/ipcLogger.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export type LoggerLevel = 'query' | 'error'
|
||||||
|
|
||||||
|
export const ipcLogger = ({ content, cUid, level }: {content: string; cUid: string; level: LoggerLevel}) => {
|
||||||
|
if (level === 'error') {
|
||||||
|
if (process.type !== undefined) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
@@ -6,7 +6,7 @@ const isWindows = process.platform === 'win32';
|
|||||||
const indexPath = path.resolve(__dirname, 'index.html').split(path.sep).join('/');
|
const indexPath = path.resolve(__dirname, 'index.html').split(path.sep).join('/');
|
||||||
|
|
||||||
export function validateSender (frame: WebFrameMain) {
|
export function validateSender (frame: WebFrameMain) {
|
||||||
if (process.windowsStore) return true; // TEMP HOTFIX
|
if (isWindows) return true; // TEMP HOTFIX
|
||||||
const frameUrl = new URL(frame.url);
|
const frameUrl = new URL(frame.url);
|
||||||
const prefix = isWindows ? 'file:///' : 'file://';
|
const prefix = isWindows ? 'file:///' : 'file://';
|
||||||
const framePath = frameUrl.href.replace(prefix, '');
|
const framePath = frameUrl.href.replace(prefix, '');
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import * as remoteMain from '@electron/remote/main';
|
import * as remoteMain from '@electron/remote/main';
|
||||||
import { app, BrowserWindow, ipcMain, nativeImage, safeStorage } from 'electron';
|
import { app, BrowserWindow, ipcMain, nativeImage, safeStorage } from 'electron';
|
||||||
|
import * as log from 'electron-log/main';
|
||||||
import * as Store from 'electron-store';
|
import * as Store from 'electron-store';
|
||||||
import * as windowStateKeeper from 'electron-window-state';
|
import * as windowStateKeeper from 'electron-window-state';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
@@ -8,6 +9,7 @@ import ipcHandlers from './ipc-handlers';
|
|||||||
import { OsMenu, ShortcutRegister } from './libs/ShortcutRegister';
|
import { OsMenu, ShortcutRegister } from './libs/ShortcutRegister';
|
||||||
|
|
||||||
Store.initRenderer();
|
Store.initRenderer();
|
||||||
|
log.errorHandler.startCatching();
|
||||||
const settingsStore = new Store({ name: 'settings' });
|
const settingsStore = new Store({ name: 'settings' });
|
||||||
const appTheme = settingsStore.get('application_theme');
|
const appTheme = settingsStore.get('application_theme');
|
||||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||||
@@ -41,7 +43,8 @@ async function createMainWindow () {
|
|||||||
spellcheck: false
|
spellcheck: false
|
||||||
},
|
},
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
titleBarStyle: isLinux ? 'default' :'hidden',
|
frame: !isLinux,
|
||||||
|
titleBarStyle: 'hidden',
|
||||||
titleBarOverlay: isWindows
|
titleBarOverlay: isWindows
|
||||||
? {
|
? {
|
||||||
color: appTheme === 'dark' ? '#3f3f3f' : '#fff',
|
color: appTheme === 'dark' ? '#3f3f3f' : '#fff',
|
||||||
@@ -125,7 +128,7 @@ app.on('ready', async () => {
|
|||||||
if (isWindows)
|
if (isWindows)
|
||||||
mainWindow.show();
|
mainWindow.show();
|
||||||
|
|
||||||
// if (isDevelopment)
|
// if (isDevelopment && !isWindows)
|
||||||
// mainWindow.webContents.openDevTools();
|
// mainWindow.webContents.openDevTools();
|
||||||
|
|
||||||
process.on('uncaughtException', error => {
|
process.on('uncaughtException', error => {
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
import * as antares from 'common/interfaces/antares';
|
import * as antares from 'common/interfaces/antares';
|
||||||
|
import * as log from 'electron-log/main';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
import { parentPort } from 'worker_threads';
|
||||||
|
|
||||||
import { MySQLClient } from '../libs/clients/MySQLClient';
|
import { MySQLClient } from '../libs/clients/MySQLClient';
|
||||||
import { PostgreSQLClient } from '../libs/clients/PostgreSQLClient';
|
import { PostgreSQLClient } from '../libs/clients/PostgreSQLClient';
|
||||||
@@ -8,63 +10,78 @@ import MysqlExporter from '../libs/exporters/sql/MysqlExporter';
|
|||||||
import PostgreSQLExporter from '../libs/exporters/sql/PostgreSQLExporter';
|
import PostgreSQLExporter from '../libs/exporters/sql/PostgreSQLExporter';
|
||||||
let exporter: antares.Exporter;
|
let exporter: antares.Exporter;
|
||||||
|
|
||||||
process.on('message', async ({ type, client, tables, options }: any) => {
|
log.transports.file.fileName = 'workers.log';
|
||||||
|
log.transports.console = null;
|
||||||
|
log.errorHandler.startCatching();
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const exportHandler = async (data: any) => {
|
||||||
|
const { type, client, tables, options } = data;
|
||||||
|
|
||||||
if (type === 'init') {
|
if (type === 'init') {
|
||||||
const connection = await ClientsFactory.getClient({
|
try {
|
||||||
client: client.name,
|
const connection = await ClientsFactory.getClient({
|
||||||
params: client.config,
|
client: client.name,
|
||||||
poolSize: 5
|
params: client.config,
|
||||||
}) as MySQLClient | PostgreSQLClient;
|
poolSize: 5
|
||||||
await connection.connect();
|
}) as MySQLClient | PostgreSQLClient;
|
||||||
|
await connection.connect();
|
||||||
|
|
||||||
switch (client.name) {
|
switch (client.name) {
|
||||||
case 'mysql':
|
case 'mysql':
|
||||||
case 'maria':
|
case 'maria':
|
||||||
exporter = new MysqlExporter(connection as MySQLClient, tables, options);
|
exporter = new MysqlExporter(connection as MySQLClient, tables, options);
|
||||||
break;
|
break;
|
||||||
case 'pg':
|
case 'pg':
|
||||||
exporter = new PostgreSQLExporter(connection as PostgreSQLClient, tables, options);
|
exporter = new PostgreSQLExporter(connection as PostgreSQLClient, tables, options);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
process.send({
|
parentPort.postMessage({
|
||||||
|
type: 'error',
|
||||||
|
payload: `"${client.name}" exporter not aviable`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
exporter.once('error', err => {
|
||||||
|
log.error(err.toString());
|
||||||
|
parentPort.postMessage({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
payload: `"${client.name}" exporter not aviable`
|
payload: err.toString()
|
||||||
});
|
});
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
exporter.once('error', err => {
|
exporter.once('end', () => {
|
||||||
console.error(err);
|
parentPort.postMessage({
|
||||||
process.send({
|
type: 'end',
|
||||||
|
payload: { cancelled: exporter.isCancelled }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
exporter.once('cancel', () => {
|
||||||
|
fs.unlinkSync(exporter.outputFile);
|
||||||
|
parentPort.postMessage({ type: 'cancel' });
|
||||||
|
});
|
||||||
|
|
||||||
|
exporter.on('progress', state => {
|
||||||
|
parentPort.postMessage({
|
||||||
|
type: 'export-progress',
|
||||||
|
payload: state
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
exporter.run();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
log.error(err.toString());
|
||||||
|
parentPort.postMessage({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
payload: err.toString()
|
payload: err.toString()
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
exporter.once('end', () => {
|
|
||||||
process.send({
|
|
||||||
type: 'end',
|
|
||||||
payload: { cancelled: exporter.isCancelled }
|
|
||||||
});
|
|
||||||
connection.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
exporter.once('cancel', () => {
|
|
||||||
fs.unlinkSync(exporter.outputFile);
|
|
||||||
process.send({ type: 'cancel' });
|
|
||||||
});
|
|
||||||
|
|
||||||
exporter.on('progress', state => {
|
|
||||||
process.send({
|
|
||||||
type: 'export-progress',
|
|
||||||
payload: state
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
exporter.run();
|
|
||||||
}
|
}
|
||||||
else if (type === 'cancel')
|
else if (type === 'cancel')
|
||||||
exporter.cancel();
|
exporter.cancel();
|
||||||
});
|
};
|
||||||
|
|
||||||
process.on('beforeExit', console.log);
|
parentPort.on('message', exportHandler);
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
import SSHConfig from '@fabio286/ssh2-promise/lib/sshConfig';
|
import SSHConfig from '@fabio286/ssh2-promise/lib/sshConfig';
|
||||||
import * as antares from 'common/interfaces/antares';
|
import * as antares from 'common/interfaces/antares';
|
||||||
import { ImportOptions } from 'common/interfaces/importer';
|
import { ImportOptions } from 'common/interfaces/importer';
|
||||||
|
import * as log from 'electron-log/main';
|
||||||
import * as mysql from 'mysql2';
|
import * as mysql from 'mysql2';
|
||||||
import * as pg from 'pg';
|
import * as pg from 'pg';
|
||||||
|
import { parentPort } from 'worker_threads';
|
||||||
|
|
||||||
import { MySQLClient } from '../libs/clients/MySQLClient';
|
import { MySQLClient } from '../libs/clients/MySQLClient';
|
||||||
import { PostgreSQLClient } from '../libs/clients/PostgreSQLClient';
|
import { PostgreSQLClient } from '../libs/clients/PostgreSQLClient';
|
||||||
@@ -11,14 +13,18 @@ import MySQLImporter from '../libs/importers/sql/MySQLlImporter';
|
|||||||
import PostgreSQLImporter from '../libs/importers/sql/PostgreSQLImporter';
|
import PostgreSQLImporter from '../libs/importers/sql/PostgreSQLImporter';
|
||||||
let importer: antares.Importer;
|
let importer: antares.Importer;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
log.transports.file.fileName = 'workers.log';
|
||||||
process.on('message', async ({ type, dbConfig, options }: {
|
log.transports.console = null;
|
||||||
|
log.errorHandler.startCatching();
|
||||||
|
|
||||||
|
const importHandler = async (data: {
|
||||||
type: string;
|
type: string;
|
||||||
dbConfig: mysql.ConnectionOptions & { schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean }
|
dbConfig: mysql.ConnectionOptions & { schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean }
|
||||||
| pg.ClientConfig & { schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean }
|
| pg.ClientConfig & { schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean }
|
||||||
| { databasePath: string; readonly: boolean };
|
| { databasePath: string; readonly: boolean };
|
||||||
options: ImportOptions;
|
options: ImportOptions;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { type, dbConfig, options } = data;
|
||||||
if (type === 'init') {
|
if (type === 'init') {
|
||||||
try {
|
try {
|
||||||
const connection = await ClientsFactory.getClient({
|
const connection = await ClientsFactory.getClient({
|
||||||
@@ -41,7 +47,7 @@ process.on('message', async ({ type, dbConfig, options }: {
|
|||||||
importer = new PostgreSQLImporter(pool as unknown as pg.PoolClient, options);
|
importer = new PostgreSQLImporter(pool as unknown as pg.PoolClient, options);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
process.send({
|
parentPort.postMessage({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
payload: `"${options.type}" importer not aviable`
|
payload: `"${options.type}" importer not aviable`
|
||||||
});
|
});
|
||||||
@@ -49,33 +55,33 @@ process.on('message', async ({ type, dbConfig, options }: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
importer.once('error', err => {
|
importer.once('error', err => {
|
||||||
console.error(err);
|
log.error(err.toString());
|
||||||
process.send({
|
parentPort.postMessage({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
payload: err.toString()
|
payload: err.toString()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
importer.once('end', () => {
|
importer.once('end', () => {
|
||||||
process.send({
|
parentPort.postMessage({
|
||||||
type: 'end',
|
type: 'end',
|
||||||
payload: { cancelled: importer.isCancelled }
|
payload: { cancelled: importer.isCancelled }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
importer.once('cancel', () => {
|
importer.once('cancel', () => {
|
||||||
process.send({ type: 'cancel' });
|
parentPort.postMessage({ type: 'cancel' });
|
||||||
});
|
});
|
||||||
|
|
||||||
importer.on('progress', state => {
|
importer.on('progress', state => {
|
||||||
process.send({
|
parentPort.postMessage({
|
||||||
type: 'import-progress',
|
type: 'import-progress',
|
||||||
payload: state
|
payload: state
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
importer.on('query-error', state => {
|
importer.on('query-error', state => {
|
||||||
process.send({
|
parentPort.postMessage({
|
||||||
type: 'query-error',
|
type: 'query-error',
|
||||||
payload: state
|
payload: state
|
||||||
});
|
});
|
||||||
@@ -84,8 +90,8 @@ process.on('message', async ({ type, dbConfig, options }: {
|
|||||||
importer.run();
|
importer.run();
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.error(err);
|
log.error(err.toString());
|
||||||
process.send({
|
parentPort.postMessage({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
payload: err.toString()
|
payload: err.toString()
|
||||||
});
|
});
|
||||||
@@ -93,20 +99,6 @@ process.on('message', async ({ type, dbConfig, options }: {
|
|||||||
}
|
}
|
||||||
else if (type === 'cancel')
|
else if (type === 'cancel')
|
||||||
importer.cancel();
|
importer.cancel();
|
||||||
});
|
};
|
||||||
|
|
||||||
process.on('uncaughtException', (err) => {
|
parentPort.on('message', importHandler);
|
||||||
console.error(err);
|
|
||||||
process.send({
|
|
||||||
type: 'error',
|
|
||||||
payload: err.toString()
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on('unhandledRejection', (err) => {
|
|
||||||
console.error(err);
|
|
||||||
process.send({
|
|
||||||
type: 'error',
|
|
||||||
payload: err.toString()
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
@@ -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">
|
||||||
|
@@ -56,8 +56,8 @@ const { t } = useI18n();
|
|||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
size: {
|
size: {
|
||||||
type: String as PropType<'small' | 'medium' | '400' | 'large'>,
|
type: String as PropType<'small' | 'medium' | '400' | 'large' | 'resize'>,
|
||||||
validator: (prop: string) => ['small', 'medium', '400', 'large'].includes(prop),
|
validator: (prop: string) => ['small', 'medium', '400', 'large', 'resize'].includes(prop),
|
||||||
default: 'small'
|
default: 'small'
|
||||||
},
|
},
|
||||||
hideFooter: {
|
hideFooter: {
|
||||||
@@ -88,6 +88,8 @@ const modalSizeClass = computed(() => {
|
|||||||
return 'modal-sm';
|
return 'modal-sm';
|
||||||
if (props.size === '400')
|
if (props.size === '400')
|
||||||
return 'modal-400';
|
return 'modal-400';
|
||||||
|
if (props.size === 'resize')
|
||||||
|
return 'modal-resize';
|
||||||
else if (props.size === 'large')
|
else if (props.size === 'large')
|
||||||
return 'modal-lg';
|
return 'modal-lg';
|
||||||
else return '';
|
else return '';
|
||||||
@@ -120,6 +122,12 @@ onBeforeUnmount(() => {
|
|||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-resize .modal-container {
|
||||||
|
max-width: 95vw;
|
||||||
|
max-height: 95vh;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.modal.modal-sm .modal-container {
|
.modal.modal-sm .modal-container {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
@@ -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(() => {
|
||||||
|
@@ -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%);
|
||||||
}
|
}
|
||||||
|
@@ -280,7 +280,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
if (props.searchable)
|
if (props.searchable)
|
||||||
searchInput.value.focus();
|
searchInput.value.focus();
|
||||||
|
|
||||||
else
|
else
|
||||||
el.value.focus();
|
el.value.focus();
|
||||||
|
|
||||||
@@ -366,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(() => {
|
||||||
|
@@ -4,7 +4,11 @@
|
|||||||
:id="`editor-${id}`"
|
:id="`editor-${id}`"
|
||||||
class="editor"
|
class="editor"
|
||||||
:class="editorClass"
|
:class="editorClass"
|
||||||
:style="{height: `${height}px`}"
|
:style="{
|
||||||
|
height: `${height}px`,
|
||||||
|
width: width ? `${width}px` : null,
|
||||||
|
resize: resizable ? 'both' : 'none'
|
||||||
|
}"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -17,7 +21,7 @@ import 'ace-builds/webpack-resolver';
|
|||||||
|
|
||||||
import { uidGen } from 'common/libs/uidGen';
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { onMounted, watch } from 'vue';
|
import { PropType, onMounted, watch } from 'vue';
|
||||||
|
|
||||||
import { useSettingsStore } from '@/stores/settings';
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
|
|
||||||
@@ -25,10 +29,12 @@ const props = defineProps({
|
|||||||
modelValue: String,
|
modelValue: String,
|
||||||
mode: { type: String, default: 'text' },
|
mode: { type: String, default: 'text' },
|
||||||
editorClass: { type: String, default: '' },
|
editorClass: { type: String, default: '' },
|
||||||
|
resizable: { type: Boolean, default: false },
|
||||||
autoFocus: { type: Boolean, default: false },
|
autoFocus: { type: Boolean, default: false },
|
||||||
readOnly: { type: Boolean, default: false },
|
readOnly: { type: Boolean, default: false },
|
||||||
showLineNumbers: { type: Boolean, default: true },
|
showLineNumbers: { type: Boolean, default: true },
|
||||||
height: { type: Number, default: 200 }
|
height: { type: Number, default: 200 },
|
||||||
|
width: { type: [Number, Boolean] as PropType<number|false>, default: false }
|
||||||
});
|
});
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
@@ -132,8 +138,10 @@ onMounted(() => {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.editor-wrapper {
|
.editor-wrapper {
|
||||||
.editor {
|
.editor {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
height: 100%;
|
||||||
|
max-width: 90vw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -54,7 +54,7 @@ const updateWindow = () => {
|
|||||||
const totalScrollHeight = props.items.length * props.itemHeight;
|
const totalScrollHeight = props.items.length * props.itemHeight;
|
||||||
const offset = 50;
|
const offset = 50;
|
||||||
|
|
||||||
const scrollTop = localScrollElement.value.scrollTop;
|
const scrollTop = localScrollElement.value?.scrollTop;
|
||||||
|
|
||||||
const firstVisibleIndex = Math.floor(scrollTop / props.itemHeight);
|
const firstVisibleIndex = Math.floor(scrollTop / props.itemHeight);
|
||||||
const lastVisibleIndex = firstVisibleIndex + visibleItemsCount;
|
const lastVisibleIndex = firstVisibleIndex + visibleItemsCount;
|
||||||
|
312
src/renderer/components/DebugConsole.vue
Normal file
312
src/renderer/components/DebugConsole.vue
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
<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>
|
||||||
|
<div class="d-flex">
|
||||||
|
<div
|
||||||
|
v-if="isDevelopment"
|
||||||
|
class="c-hand mr-2"
|
||||||
|
@click="openDevTools()"
|
||||||
|
>
|
||||||
|
<BaseIcon icon-name="mdiBugPlayOutline" :size="22" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="isDevelopment"
|
||||||
|
class="c-hand mr-2"
|
||||||
|
@click="reload()"
|
||||||
|
>
|
||||||
|
<BaseIcon icon-name="mdiRefresh" :size="22" />
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-clear mr-1" @click="resizeConsole(0)" />
|
||||||
|
</div>
|
||||||
|
</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 { getCurrentWindow } from '@electron/remote';
|
||||||
|
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 w = ref(getCurrentWindow());
|
||||||
|
const isDevelopment = ref(process.env.NODE_ENV === 'development');
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openDevTools = () => {
|
||||||
|
w.value.webContents.openDevTools();
|
||||||
|
};
|
||||||
|
|
||||||
|
const reload = () => {
|
||||||
|
w.value.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
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>
|
@@ -113,7 +113,7 @@ const selectedGroup: Ref<string> = ref('manual');
|
|||||||
const selectedMethod: Ref<string> = ref('');
|
const selectedMethod: Ref<string> = ref('');
|
||||||
const selectedValue: Ref<string> = ref('');
|
const selectedValue: Ref<string> = ref('');
|
||||||
const debounceTimeout: Ref<NodeJS.Timeout> = ref(null);
|
const debounceTimeout: Ref<NodeJS.Timeout> = ref(null);
|
||||||
const methodParams: Ref<{[key: string]: string}> = ref({});
|
const methodParams: Ref<Record<string, string>> = ref({});
|
||||||
const enumArray: Ref<string[]> = ref(null);
|
const enumArray: Ref<string[]> = ref(null);
|
||||||
|
|
||||||
const fakerGroups = computed(() => {
|
const fakerGroups = computed(() => {
|
||||||
|
@@ -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,6 +178,14 @@
|
|||||||
/>
|
/>
|
||||||
SSH
|
SSH
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="connection.readonly" class="chip bg-dark mt-2">
|
||||||
|
<BaseIcon
|
||||||
|
icon-name="mdiLock"
|
||||||
|
class="mr-1"
|
||||||
|
:size="18"
|
||||||
|
/>
|
||||||
|
Read-only
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -201,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';
|
||||||
|
|
||||||
@@ -210,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);
|
||||||
|
|
||||||
@@ -228,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('');
|
||||||
@@ -236,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
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@@ -253,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()) ||
|
||||||
@@ -352,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 {
|
||||||
|
@@ -73,7 +73,7 @@ const props = defineProps({
|
|||||||
const emit = defineEmits(['confirm', 'close']);
|
const emit = defineEmits(['confirm', 'close']);
|
||||||
|
|
||||||
const firstInput: Ref<HTMLInputElement[]> = ref(null);
|
const firstInput: Ref<HTMLInputElement[]> = ref(null);
|
||||||
const values: Ref<{[key: string]: string}> = ref({});
|
const values: Ref<Record<string, string>> = ref({});
|
||||||
|
|
||||||
const inParameters = computed(() => {
|
const inParameters = computed(() => {
|
||||||
return props.localRoutine.parameters.filter(param => param.context === 'IN');
|
return props.localRoutine.parameters.filter(param => param.context === 'IN');
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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';
|
||||||
@@ -327,7 +328,7 @@ const tables: Ref<{
|
|||||||
}[]> = ref([]);
|
}[]> = ref([]);
|
||||||
const options: Ref<Partial<ExportOptions>> = ref({
|
const options: Ref<Partial<ExportOptions>> = ref({
|
||||||
schema: selectedSchema.value,
|
schema: selectedSchema.value,
|
||||||
includes: {} as {[key: string]: boolean},
|
includes: {} as Record<string, boolean>,
|
||||||
outputFormat: 'sql' as 'sql' | 'sql.zip',
|
outputFormat: 'sql' as 'sql' | 'sql.zip',
|
||||||
sqlInsertAfter: 250,
|
sqlInsertAfter: 250,
|
||||||
sqlInsertDivider: 'bytes' as 'bytes' | 'rows'
|
sqlInsertDivider: 'bytes' as 'bytes' | 'rows'
|
||||||
@@ -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':
|
||||||
|
@@ -142,7 +142,7 @@ const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
|||||||
const { trapRef } = useFocusTrap({ disableAutofocus: true });
|
const { trapRef } = useFocusTrap({ disableAutofocus: true });
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const localRow: Ref<{[key: string]: any}> = ref({});
|
const localRow: Ref<Record<string, any>> = ref({});
|
||||||
const fieldsToExclude = ref([]);
|
const fieldsToExclude = ref([]);
|
||||||
const nInserts = ref(1);
|
const nInserts = ref(1);
|
||||||
const isInserting = ref(false);
|
const isInserting = ref(false);
|
||||||
@@ -225,7 +225,7 @@ const insertRows = async () => {
|
|||||||
delete rowToInsert[key];
|
delete rowToInsert[key];
|
||||||
});
|
});
|
||||||
|
|
||||||
const fieldTypes: {[key: string]: string} = {};
|
const fieldTypes: Record<string, string> = {};
|
||||||
props.fields.forEach(field => {
|
props.fields.forEach(field => {
|
||||||
fieldTypes[field.name] = field.type;
|
fieldTypes[field.name] = field.type;
|
||||||
});
|
});
|
||||||
@@ -290,7 +290,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
||||||
const rowObj: {[key: string]: unknown} = {};
|
const rowObj: Record<string, unknown> = {};
|
||||||
|
|
||||||
if (!props.rowToDuplicate) {
|
if (!props.rowToDuplicate) {
|
||||||
// Set default values
|
// Set default values
|
||||||
@@ -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];
|
||||||
|
@@ -75,7 +75,7 @@
|
|||||||
<code
|
<code
|
||||||
class="cut-text"
|
class="cut-text"
|
||||||
:title="query.sql"
|
:title="query.sql"
|
||||||
v-html="highlightWord(query.sql)"
|
v-html="highlight(query.sql, {html: true})"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="tile-bottom-content">
|
<div class="tile-bottom-content">
|
||||||
@@ -115,7 +115,7 @@
|
|||||||
<BaseIcon icon-name="mdiHistory" :size="48" />
|
<BaseIcon icon-name="mdiHistory" :size="48" />
|
||||||
</div>
|
</div>
|
||||||
<p class="empty-title h5">
|
<p class="empty-title h5">
|
||||||
{{ t('database.thereIsNoQueriesYet') }}
|
{{ t('database.thereAreNoQueriesYet') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -126,13 +126,26 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ConnectionParams } from 'common/interfaces/antares';
|
import { ConnectionParams } from 'common/interfaces/antares';
|
||||||
import { Component, computed, ComputedRef, onBeforeUnmount, onMounted, onUpdated, Prop, Ref, ref, watch } from 'vue';
|
import { highlight } from 'sql-highlight';
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
computed,
|
||||||
|
ComputedRef,
|
||||||
|
onBeforeUnmount,
|
||||||
|
onMounted,
|
||||||
|
onUpdated,
|
||||||
|
Prop,
|
||||||
|
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 BaseVirtualScroll from '@/components/BaseVirtualScroll.vue';
|
import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue';
|
||||||
import { useFilters } from '@/composables/useFilters';
|
import { useFilters } from '@/composables/useFilters';
|
||||||
import { useFocusTrap } from '@/composables/useFocusTrap';
|
import { useFocusTrap } from '@/composables/useFocusTrap';
|
||||||
|
import { copyText } from '@/libs/copyText';
|
||||||
import { useConnectionsStore } from '@/stores/connections';
|
import { useConnectionsStore } from '@/stores/connections';
|
||||||
import { HistoryRecord, useHistoryStore } from '@/stores/history';
|
import { HistoryRecord, useHistoryStore } from '@/stores/history';
|
||||||
|
|
||||||
@@ -162,7 +175,7 @@ const localSearchTerm = ref('');
|
|||||||
|
|
||||||
const connectionName = computed(() => getConnectionName(props.connection.uid));
|
const connectionName = computed(() => getConnectionName(props.connection.uid));
|
||||||
const history: ComputedRef<HistoryRecord[]> = computed(() => (getHistoryByWorkspace(props.connection.uid) || []));
|
const history: ComputedRef<HistoryRecord[]> = computed(() => (getHistoryByWorkspace(props.connection.uid) || []));
|
||||||
const filteredHistory = computed(() => history.value.filter(q => q.sql.toLowerCase().search(searchTerm.value.toLowerCase()) >= 0));
|
const filteredHistory = computed(() => history.value.filter(q => q.sql.toLowerCase().search(localSearchTerm.value.toLowerCase()) >= 0));
|
||||||
|
|
||||||
watch(searchTerm, () => {
|
watch(searchTerm, () => {
|
||||||
clearTimeout(searchTermInterval.value);
|
clearTimeout(searchTermInterval.value);
|
||||||
@@ -173,7 +186,7 @@ watch(searchTerm, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const copyQuery = (sql: string) => {
|
const copyQuery = (sql: string) => {
|
||||||
navigator.clipboard.writeText(sql);
|
copyText(sql);
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteQuery = (query: HistoryRecord[]) => {
|
const deleteQuery = (query: HistoryRecord[]) => {
|
||||||
@@ -197,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('<', '<').replaceAll('>', '>');
|
|
||||||
|
|
||||||
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')
|
||||||
@@ -274,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;
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
124
src/renderer/components/ModalNoteEdit.vue
Normal file
124
src/renderer/components/ModalNoteEdit.vue
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
<template>
|
||||||
|
<ConfirmModal
|
||||||
|
size="resize"
|
||||||
|
:disable-autofocus="true"
|
||||||
|
:close-on-confirm="!!localNote.note.length"
|
||||||
|
:confirm-text="t('general.save')"
|
||||||
|
@confirm="updateNote"
|
||||||
|
@hide="$emit('hide')"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="d-flex">
|
||||||
|
<BaseIcon
|
||||||
|
icon-name="mdiNoteEditOutline"
|
||||||
|
class="mr-1"
|
||||||
|
:size="24"
|
||||||
|
/> {{ t('application.editNote') }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #body>
|
||||||
|
<form class="form">
|
||||||
|
<div class="form-group columns">
|
||||||
|
<div class="column col-8">
|
||||||
|
<label class="form-label">{{ t('connection.connection') }}</label>
|
||||||
|
<BaseSelect
|
||||||
|
v-model="localNote.cUid"
|
||||||
|
class="form-select"
|
||||||
|
:options="connectionOptions"
|
||||||
|
option-track-by="code"
|
||||||
|
option-label="name"
|
||||||
|
@change="null"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="column col-4">
|
||||||
|
<label class="form-label">{{ t('application.tag') }}</label>
|
||||||
|
<BaseSelect
|
||||||
|
v-model="localNote.type"
|
||||||
|
class="form-select"
|
||||||
|
:options="noteTags"
|
||||||
|
option-track-by="code"
|
||||||
|
option-label="name"
|
||||||
|
@change="null"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">{{ t('general.content') }} <small
|
||||||
|
v-if="localNote.type !== 'query'"
|
||||||
|
style="line-height: 1;"
|
||||||
|
class="text-gray"
|
||||||
|
>({{ t('application.markdownSupported') }})</small></label>
|
||||||
|
<BaseTextEditor
|
||||||
|
v-model="localNote.note"
|
||||||
|
:mode="editorMode"
|
||||||
|
:show-line-numbers="false"
|
||||||
|
:auto-focus="true"
|
||||||
|
:height="400"
|
||||||
|
:width="640"
|
||||||
|
:resizable="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
</ConfirmModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { inject, onBeforeMount, PropType, Ref, ref, watch } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||||
|
import BaseIcon from '@/components/BaseIcon.vue';
|
||||||
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
import BaseTextEditor from '@/components/BaseTextEditor.vue';
|
||||||
|
import { ConnectionNote, TagCode, useScratchpadStore } from '@/stores/scratchpad';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { editNote } = useScratchpadStore();
|
||||||
|
|
||||||
|
const emit = defineEmits(['hide']);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
note: {
|
||||||
|
type: Object as PropType<ConnectionNote>,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const noteTags = inject<{code: TagCode; name: string}[]>('noteTags');
|
||||||
|
const connectionOptions = inject<{code: string; name: string}[]>('connectionOptions');
|
||||||
|
|
||||||
|
const editorMode = ref('markdown');
|
||||||
|
const localNote: Ref<ConnectionNote> = ref({
|
||||||
|
uid: 'dummy',
|
||||||
|
cUid: null,
|
||||||
|
title: undefined,
|
||||||
|
note: '',
|
||||||
|
date: new Date(),
|
||||||
|
type: 'note',
|
||||||
|
isArchived: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateNote = () => {
|
||||||
|
if (localNote.value.note) {
|
||||||
|
if (!localNote.value.title)// Set a default title
|
||||||
|
localNote.value.title = `${localNote.value.type.toLocaleUpperCase()}: ${localNote.value.uid}`;
|
||||||
|
|
||||||
|
localNote.value.date = new Date();
|
||||||
|
editNote(localNote.value);
|
||||||
|
emit('hide');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(() => localNote.value.type, () => {
|
||||||
|
if (localNote.value.type === 'query')
|
||||||
|
editorMode.value = 'sql';
|
||||||
|
else
|
||||||
|
editorMode.value = 'markdown';
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
localNote.value = JSON.parse(JSON.stringify(props.note));
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
122
src/renderer/components/ModalNoteNew.vue
Normal file
122
src/renderer/components/ModalNoteNew.vue
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<template>
|
||||||
|
<ConfirmModal
|
||||||
|
size="resize"
|
||||||
|
:disable-autofocus="true"
|
||||||
|
:close-on-confirm="!!newNote.note.length"
|
||||||
|
:confirm-text="t('general.save')"
|
||||||
|
@confirm="createNote"
|
||||||
|
@hide="$emit('hide')"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="d-flex">
|
||||||
|
<BaseIcon
|
||||||
|
icon-name="mdiNotePlusOutline"
|
||||||
|
class="mr-1"
|
||||||
|
:size="24"
|
||||||
|
/> {{ t('application.addNote') }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #body>
|
||||||
|
<form class="form">
|
||||||
|
<div class="form-group columns">
|
||||||
|
<div class="column col-8">
|
||||||
|
<label class="form-label">{{ t('connection.connection') }}</label>
|
||||||
|
<BaseSelect
|
||||||
|
v-model="newNote.cUid"
|
||||||
|
class="form-select"
|
||||||
|
:options="connectionOptions"
|
||||||
|
option-track-by="code"
|
||||||
|
option-label="name"
|
||||||
|
@change="null"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="column col-4">
|
||||||
|
<label class="form-label">{{ t('application.tag') }}</label>
|
||||||
|
<BaseSelect
|
||||||
|
v-model="newNote.type"
|
||||||
|
class="form-select"
|
||||||
|
:options="noteTags"
|
||||||
|
option-track-by="code"
|
||||||
|
option-label="name"
|
||||||
|
@change="null"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">{{ t('general.content') }} <small
|
||||||
|
v-if="newNote.type !== 'query'"
|
||||||
|
style="line-height: 1;"
|
||||||
|
class="text-gray"
|
||||||
|
>({{ t('application.markdownSupported') }})</small></label>
|
||||||
|
<BaseTextEditor
|
||||||
|
v-model="newNote.note"
|
||||||
|
:mode="editorMode"
|
||||||
|
:show-line-numbers="false"
|
||||||
|
:auto-focus="true"
|
||||||
|
:height="400"
|
||||||
|
:width="640"
|
||||||
|
:resizable="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
</ConfirmModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
|
import { inject, Ref, ref, watch } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||||
|
import BaseIcon from '@/components/BaseIcon.vue';
|
||||||
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
import BaseTextEditor from '@/components/BaseTextEditor.vue';
|
||||||
|
import { ConnectionNote, TagCode, useScratchpadStore } from '@/stores/scratchpad';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { addNote } = useScratchpadStore();
|
||||||
|
|
||||||
|
const emit = defineEmits(['hide']);
|
||||||
|
|
||||||
|
const noteTags = inject<{code: TagCode; name: string}[]>('noteTags');
|
||||||
|
const selectedConnection = inject<Ref<null | string>>('selectedConnection');
|
||||||
|
const selectedTag = inject<Ref<TagCode>>('selectedTag');
|
||||||
|
const connectionOptions = inject<{code: string; name: string}[]>('connectionOptions');
|
||||||
|
|
||||||
|
const editorMode = ref('markdown');
|
||||||
|
|
||||||
|
const newNote: Ref<ConnectionNote> = ref({
|
||||||
|
uid: uidGen('N'),
|
||||||
|
cUid: null,
|
||||||
|
title: undefined,
|
||||||
|
note: '',
|
||||||
|
date: new Date(),
|
||||||
|
type: 'note',
|
||||||
|
isArchived: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const createNote = () => {
|
||||||
|
if (newNote.value.note) {
|
||||||
|
if (!newNote.value.title)// Set a default title
|
||||||
|
newNote.value.title = `${newNote.value.type.toLocaleUpperCase()}: ${newNote.value.uid}`;
|
||||||
|
|
||||||
|
newNote.value.date = new Date();
|
||||||
|
addNote(newNote.value);
|
||||||
|
emit('hide');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(() => newNote.value.type, () => {
|
||||||
|
if (newNote.value.type === 'query')
|
||||||
|
editorMode.value = 'sql';
|
||||||
|
else
|
||||||
|
editorMode.value = 'markdown';
|
||||||
|
});
|
||||||
|
|
||||||
|
newNote.value.cUid = selectedConnection.value;
|
||||||
|
|
||||||
|
if (selectedTag.value !== 'all')
|
||||||
|
newNote.value.type = selectedTag.value;
|
||||||
|
|
||||||
|
</script>
|
@@ -161,6 +161,7 @@ import ModalProcessesListContext from '@/components/ModalProcessesListContext.vu
|
|||||||
import ModalProcessesListRow from '@/components/ModalProcessesListRow.vue';
|
import ModalProcessesListRow from '@/components/ModalProcessesListRow.vue';
|
||||||
import { useFocusTrap } from '@/composables/useFocusTrap';
|
import { useFocusTrap } from '@/composables/useFocusTrap';
|
||||||
import Schema from '@/ipc-api/Schema';
|
import Schema from '@/ipc-api/Schema';
|
||||||
|
import { copyText } from '@/libs/copyText';
|
||||||
import { useConnectionsStore } from '@/stores/connections';
|
import { useConnectionsStore } from '@/stores/connections';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
|
|
||||||
@@ -322,13 +323,13 @@ const closeContext = () => {
|
|||||||
const copyCell = () => {
|
const copyCell = () => {
|
||||||
const row = results.value.find(row => Number(row.id) === selectedRow.value);
|
const row = results.value.find(row => Number(row.id) === selectedRow.value);
|
||||||
const valueToCopy = row[selectedCell.value.field];
|
const valueToCopy = row[selectedCell.value.field];
|
||||||
navigator.clipboard.writeText(valueToCopy);
|
copyText(valueToCopy);
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyRow = () => {
|
const copyRow = () => {
|
||||||
const row = results.value.find(row => Number(row.id) === selectedRow.value);
|
const row = results.value.find(row => Number(row.id) === selectedRow.value);
|
||||||
const rowToCopy = JSON.parse(JSON.stringify(row));
|
const rowToCopy = JSON.parse(JSON.stringify(row));
|
||||||
navigator.clipboard.writeText(JSON.stringify(rowToCopy));
|
copyText(JSON.stringify(rowToCopy));
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeModal = () => emit('close');
|
const closeModal = () => emit('close');
|
||||||
|
@@ -67,7 +67,7 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits(['select-row', 'contextmenu', 'stop-refresh']);
|
const emit = defineEmits(['select-row', 'contextmenu', 'stop-refresh']);
|
||||||
|
|
||||||
const isInlineEditor: Ref<{[key: string]: boolean}> = ref({});
|
const isInlineEditor: Ref<Record<string, boolean>> = ref({});
|
||||||
const isInfoModal = ref(false);
|
const isInfoModal = ref(false);
|
||||||
const editorMode = ref('sql');
|
const editorMode = ref('sql');
|
||||||
|
|
||||||
|
@@ -166,19 +166,6 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group column col-12 mb-0">
|
|
||||||
<div class="col-5 col-sm-12">
|
|
||||||
<label class="form-label">
|
|
||||||
{{ t('application.disableScratchpad') }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="col-3 col-sm-12">
|
|
||||||
<label class="form-switch d-inline-block" @click.prevent="toggleDisableScratchpad">
|
|
||||||
<input type="checkbox" :checked="disableScratchpad">
|
|
||||||
<i class="form-icon" />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group column col-12">
|
<div class="form-group column col-12">
|
||||||
<div class="col-5 col-sm-12">
|
<div class="col-5 col-sm-12">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
@@ -422,14 +409,6 @@
|
|||||||
class="d-inline mr-1"
|
class="d-inline mr-1"
|
||||||
:size="16"
|
:size="16"
|
||||||
/> Mastodon</a> • <a
|
/> Mastodon</a> • <a
|
||||||
class="c-hand"
|
|
||||||
:style="'align-items: center; display: inline-flex;'"
|
|
||||||
@click="openOutside('https://twitter.com/AntaresSQL')"
|
|
||||||
><BaseIcon
|
|
||||||
icon-name="mdiTwitter"
|
|
||||||
class="d-inline mr-1"
|
|
||||||
:size="16"
|
|
||||||
/> Twitter</a> • <a
|
|
||||||
class="c-hand"
|
class="c-hand"
|
||||||
:style="'align-items: center; display: inline-flex;'"
|
:style="'align-items: center; display: inline-flex;'"
|
||||||
@click="openOutside('https://antares-sql.app/')"
|
@click="openOutside('https://antares-sql.app/')"
|
||||||
@@ -499,7 +478,6 @@ const {
|
|||||||
restoreTabs,
|
restoreTabs,
|
||||||
showTableSize,
|
showTableSize,
|
||||||
disableBlur,
|
disableBlur,
|
||||||
disableScratchpad,
|
|
||||||
applicationTheme,
|
applicationTheme,
|
||||||
editorTheme,
|
editorTheme,
|
||||||
editorFontSize
|
editorFontSize
|
||||||
@@ -512,7 +490,6 @@ const {
|
|||||||
changePageSize,
|
changePageSize,
|
||||||
changeRestoreTabs,
|
changeRestoreTabs,
|
||||||
changeDisableBlur,
|
changeDisableBlur,
|
||||||
changeDisableScratchpad,
|
|
||||||
changeAutoComplete,
|
changeAutoComplete,
|
||||||
changeLineWrap,
|
changeLineWrap,
|
||||||
changeExecuteSelected,
|
changeExecuteSelected,
|
||||||
@@ -635,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) => {
|
||||||
@@ -671,10 +648,6 @@ const toggleDisableBlur = () => {
|
|||||||
changeDisableBlur(!disableBlur.value);
|
changeDisableBlur(!disableBlur.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleDisableScratchpad = () => {
|
|
||||||
changeDisableScratchpad(!disableScratchpad.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleAutoComplete = () => {
|
const toggleAutoComplete = () => {
|
||||||
changeAutoComplete(!selectedAutoComplete.value);
|
changeAutoComplete(!selectedAutoComplete.value);
|
||||||
};
|
};
|
||||||
@@ -730,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -758,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 {
|
||||||
|
@@ -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)));
|
||||||
|
@@ -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')
|
||||||
|
@@ -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;
|
||||||
|
@@ -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>
|
||||||
|
352
src/renderer/components/ScratchpadNote.vue
Normal file
352
src/renderer/components/ScratchpadNote.vue
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="tile my-2"
|
||||||
|
tabindex="0"
|
||||||
|
@click="$emit('select-note', note.uid)"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
v-if="isBig"
|
||||||
|
class="tile-compress c-hand"
|
||||||
|
:icon-name="isSelected ? 'mdiChevronUp' : 'mdiChevronDown'"
|
||||||
|
:size="36"
|
||||||
|
@click.stop="$emit('toggle-note', note.uid)"
|
||||||
|
/>
|
||||||
|
<div class="tile-icon">
|
||||||
|
<BaseIcon
|
||||||
|
:icon-name="note.type === 'query'
|
||||||
|
? 'mdiHeartOutline'
|
||||||
|
: note.type === 'todo'
|
||||||
|
? note.isArchived
|
||||||
|
? 'mdiCheckboxMarkedOutline'
|
||||||
|
: 'mdiCheckboxBlankOutline'
|
||||||
|
: 'mdiNoteEditOutline'"
|
||||||
|
:size="36"
|
||||||
|
/>
|
||||||
|
<div class="tile-icon-type">
|
||||||
|
{{ note.type }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tile-content">
|
||||||
|
<div class="tile-content-message" :class="[{'opened': isSelected}]">
|
||||||
|
<code
|
||||||
|
v-if="note.type === 'query'"
|
||||||
|
ref="noteParagraph"
|
||||||
|
class="tile-paragraph sql"
|
||||||
|
v-html="highlight(note.note, {html: true})"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
ref="noteParagraph"
|
||||||
|
class="tile-paragraph"
|
||||||
|
v-html="parseMarkdown(highlightWord(note.note))"
|
||||||
|
/>
|
||||||
|
<div v-if="isBig && !isSelected" class="tile-paragraph-overlay" />
|
||||||
|
</div>
|
||||||
|
<div class="tile-bottom-content">
|
||||||
|
<small class="tile-subtitle">{{ getConnectionName(note.cUid) || t('general.all') }} · {{ formatDate(note.date) }}</small>
|
||||||
|
<div class="tile-history-buttons">
|
||||||
|
<button
|
||||||
|
v-if="note.type === 'todo' && !note.isArchived"
|
||||||
|
class="btn btn-link pl-1"
|
||||||
|
@click.stop="$emit('archive-note', note.uid)"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
icon-name="mdiCheck"
|
||||||
|
class="pr-1"
|
||||||
|
:size="22"
|
||||||
|
/> {{ t('general.archive') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="note.type === 'todo' && note.isArchived"
|
||||||
|
class="btn btn-link pl-1"
|
||||||
|
@click.stop="$emit('restore-note', note.uid)"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
icon-name="mdiRestore"
|
||||||
|
class="pr-1"
|
||||||
|
:size="22"
|
||||||
|
/> {{ t('general.undo') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="note.type === 'query'"
|
||||||
|
class="btn btn-link pl-1"
|
||||||
|
@click.stop="$emit('select-query', note.note)"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
icon-name="mdiOpenInApp"
|
||||||
|
class="pr-1"
|
||||||
|
:size="22"
|
||||||
|
/> {{ t('general.select') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="note.type === 'query'"
|
||||||
|
class="btn btn-link pl-1"
|
||||||
|
@click.stop="copyText(note.note)"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
icon-name="mdiContentCopy"
|
||||||
|
class="pr-1"
|
||||||
|
:size="22"
|
||||||
|
/> {{ t('general.copy') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if=" !note.isArchived"
|
||||||
|
class="btn btn-link pl-1"
|
||||||
|
@click.stop="$emit('edit-note')"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
icon-name="mdiPencil"
|
||||||
|
class="pr-1"
|
||||||
|
:size="22"
|
||||||
|
/> {{ t('general.edit') }}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-link pl-1" @click.stop="$emit('delete-note', note.uid)">
|
||||||
|
<BaseIcon
|
||||||
|
icon-name="mdiDeleteForever"
|
||||||
|
class="pr-1"
|
||||||
|
:size="22"
|
||||||
|
/> {{ t('general.delete') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useElementBounding } from '@vueuse/core';
|
||||||
|
import { marked } from 'marked';
|
||||||
|
import { highlight } from 'sql-highlight';
|
||||||
|
import { computed, PropType, Ref, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import BaseIcon from '@/components/BaseIcon.vue';
|
||||||
|
import { useFilters } from '@/composables/useFilters';
|
||||||
|
import { copyText } from '@/libs/copyText';
|
||||||
|
import { useConnectionsStore } from '@/stores/connections';
|
||||||
|
import { ConnectionNote } from '@/stores/scratchpad';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
note: {
|
||||||
|
type: Object as PropType<ConnectionNote>,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
searchTerm: {
|
||||||
|
type: String,
|
||||||
|
default: () => ''
|
||||||
|
},
|
||||||
|
selectedNote: {
|
||||||
|
type: String,
|
||||||
|
default: () => ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { formatDate } = useFilters();
|
||||||
|
const { getConnectionName } = useConnectionsStore();
|
||||||
|
|
||||||
|
defineEmits([
|
||||||
|
'edit-note',
|
||||||
|
'delete-note',
|
||||||
|
'select-note',
|
||||||
|
'toggle-note',
|
||||||
|
'archive-note',
|
||||||
|
'restore-note',
|
||||||
|
'select-query'
|
||||||
|
]);
|
||||||
|
|
||||||
|
const noteParagraph: Ref<HTMLDivElement> = ref(null);
|
||||||
|
const noteHeight = ref(useElementBounding(noteParagraph)?.height);
|
||||||
|
|
||||||
|
const isSelected = computed(() => props.selectedNote === props.note.uid);
|
||||||
|
const isBig = computed(() => noteHeight.value > 75);
|
||||||
|
|
||||||
|
const parseMarkdown = (text: string) => {
|
||||||
|
const renderer = {
|
||||||
|
listitem (text: string) {
|
||||||
|
return `<li>${text.replace(/ *\([^)]*\) */g, '')}</li>`;
|
||||||
|
},
|
||||||
|
link (href: string, title: string, text: string) {
|
||||||
|
return `<a>${text}</a>`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
marked.use({ renderer });
|
||||||
|
|
||||||
|
return marked(text);
|
||||||
|
};
|
||||||
|
|
||||||
|
const highlightWord = (string: string) => {
|
||||||
|
string = string.replaceAll('<', '<').replaceAll('>', '>');
|
||||||
|
|
||||||
|
if (props.searchTerm) {
|
||||||
|
const regexp = new RegExp(`(${props.searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
||||||
|
return string.replace(regexp, '<span class="text-primary text-bold">$1</span>');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return string;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.tile {
|
||||||
|
border-radius: $border-radius;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
transition: none;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
.tile-content {
|
||||||
|
.tile-bottom-content {
|
||||||
|
.tile-history-buttons {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile-compress {
|
||||||
|
position: absolute;
|
||||||
|
right: 2px;
|
||||||
|
top: 0px;
|
||||||
|
opacity: .7;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile-icon {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-left: 0.3rem;
|
||||||
|
margin-right: 0.3rem;
|
||||||
|
margin-top: 0.6rem;
|
||||||
|
width: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: .8;
|
||||||
|
|
||||||
|
.tile-icon-type {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile-content {
|
||||||
|
padding: 0.3rem;
|
||||||
|
padding-left: 0.1rem;
|
||||||
|
min-height: 75px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.tile-content-message{
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:not(.opened) {
|
||||||
|
max-height: 36px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile-paragraph-overlay {
|
||||||
|
height: 36px;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
code, pre {
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 100%;
|
||||||
|
opacity: 0.8;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: break-spaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile-subtitle {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile-bottom-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.tile-history-buttons {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
height: 1rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark {
|
||||||
|
.tile {
|
||||||
|
.tile-paragraph-overlay {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(255,0,0,0) 70%,
|
||||||
|
$body-bg-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
.tile-paragraph-overlay {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(255,0,0,0)70%,
|
||||||
|
#323232);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover{
|
||||||
|
.tile-paragraph-overlay {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(255,0,0,0) 70%,
|
||||||
|
$bg-color-light-dark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light {
|
||||||
|
.tile {
|
||||||
|
.tile-paragraph-overlay {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(255,0,0,0) 70%,
|
||||||
|
#FFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
.tile-paragraph-overlay {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(255,0,0,0) 70%,
|
||||||
|
$bg-color-light-gray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="scss">
|
||||||
|
.tile-paragraph {
|
||||||
|
white-space: initial;
|
||||||
|
word-break: break-word;
|
||||||
|
user-select: text;
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6, p, li {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@@ -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 = [];
|
||||||
});
|
});
|
||||||
|
@@ -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>
|
||||||
|
@@ -15,6 +15,64 @@
|
|||||||
:size="18"
|
:size="18"
|
||||||
/> {{ t('connection.disconnect') }}</span>
|
/> {{ t('connection.disconnect') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="!contextConnection.isFolder" class="context-element">
|
||||||
|
<span class="d-flex">
|
||||||
|
<BaseIcon
|
||||||
|
class="text-light mt-1 mr-1"
|
||||||
|
icon-name="mdiFolderMove"
|
||||||
|
:size="18"
|
||||||
|
/> {{ t('general.moveTo') }}</span>
|
||||||
|
<BaseIcon
|
||||||
|
class="text-light ml-1"
|
||||||
|
icon-name="mdiChevronRight"
|
||||||
|
:size="18"
|
||||||
|
/>
|
||||||
|
<div class="context-submenu">
|
||||||
|
<div class="context-element" @click.stop="moveToFolder(null)">
|
||||||
|
<span class="d-flex">
|
||||||
|
<BaseIcon
|
||||||
|
class="text-light mt-1 mr-1"
|
||||||
|
icon-name="mdiFolderPlus"
|
||||||
|
:size="18"
|
||||||
|
/> {{ t('application.newFolder') }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="folder in filteredFolders"
|
||||||
|
:key="folder.uid"
|
||||||
|
class="context-element"
|
||||||
|
@click.stop="moveToFolder(folder.uid)"
|
||||||
|
>
|
||||||
|
<span class="d-flex">
|
||||||
|
<BaseIcon
|
||||||
|
class="text-light mt-1 mr-1"
|
||||||
|
icon-name="mdiFolder"
|
||||||
|
:size="18"
|
||||||
|
:style="`color: ${folder.color}!important`"
|
||||||
|
/> {{ folder.name || t('general.folder') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="isInFolder"
|
||||||
|
class="context-element"
|
||||||
|
@click="outOfFolder"
|
||||||
|
>
|
||||||
|
<span class="d-flex">
|
||||||
|
<BaseIcon
|
||||||
|
class="text-light mt-1 mr-1"
|
||||||
|
icon-name="mdiFolderOff"
|
||||||
|
:size="18"
|
||||||
|
/> {{ t('application.outOfFolder') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="context-element" @click.stop="showAppearanceModal">
|
||||||
|
<span class="d-flex">
|
||||||
|
<BaseIcon
|
||||||
|
class="text-light mt-1 mr-1"
|
||||||
|
icon-name="mdiBrushVariant"
|
||||||
|
:size="18"
|
||||||
|
/> {{ t('application.appearance') }}</span>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="!contextConnection.isFolder"
|
v-if="!contextConnection.isFolder"
|
||||||
class="context-element"
|
class="context-element"
|
||||||
@@ -27,14 +85,6 @@
|
|||||||
:size="18"
|
:size="18"
|
||||||
/> {{ t('general.duplicate') }}</span>
|
/> {{ t('general.duplicate') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="context-element" @click.stop="showAppearanceModal">
|
|
||||||
<span class="d-flex">
|
|
||||||
<BaseIcon
|
|
||||||
class="text-light mt-1 mr-1"
|
|
||||||
icon-name="mdiBrushVariant"
|
|
||||||
:size="18"
|
|
||||||
/> {{ t('application.appearance') }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="context-element" @click="showConfirmModal">
|
<div class="context-element" @click="showConfirmModal">
|
||||||
<span class="d-flex">
|
<span class="d-flex">
|
||||||
<BaseIcon
|
<BaseIcon
|
||||||
@@ -79,6 +129,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { uidGen } from 'common/libs/uidGen';
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
import { computed, Prop, ref } from 'vue';
|
import { computed, Prop, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
@@ -98,9 +149,14 @@ const {
|
|||||||
getConnectionByUid,
|
getConnectionByUid,
|
||||||
getConnectionName,
|
getConnectionName,
|
||||||
addConnection,
|
addConnection,
|
||||||
deleteConnection
|
deleteConnection,
|
||||||
|
addFolder,
|
||||||
|
addToFolder,
|
||||||
|
removeFromFolders
|
||||||
} = connectionsStore;
|
} = connectionsStore;
|
||||||
|
|
||||||
|
const { getFolders: folders } = storeToRefs(connectionsStore);
|
||||||
|
|
||||||
const workspacesStore = useWorkspacesStore();
|
const workspacesStore = useWorkspacesStore();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -121,6 +177,8 @@ const isConnectionEdit = ref(false);
|
|||||||
|
|
||||||
const connectionName = computed(() => props.contextConnection.name || getConnectionName(props.contextConnection.uid) || t('general.folder', 1));
|
const connectionName = computed(() => props.contextConnection.name || getConnectionName(props.contextConnection.uid) || t('general.folder', 1));
|
||||||
const isConnected = computed(() => getWorkspace(props.contextConnection.uid)?.connectionStatus === 'connected');
|
const isConnected = computed(() => getWorkspace(props.contextConnection.uid)?.connectionStatus === 'connected');
|
||||||
|
const filteredFolders = computed(() => folders.value.filter(f => !f.connections.includes(props.contextConnection.uid)));
|
||||||
|
const isInFolder = computed(() => folders.value.some(f => f.connections.includes(props.contextConnection.uid)));
|
||||||
|
|
||||||
const confirmDeleteConnection = () => {
|
const confirmDeleteConnection = () => {
|
||||||
if (isConnected.value)
|
if (isConnected.value)
|
||||||
@@ -129,6 +187,27 @@ const confirmDeleteConnection = () => {
|
|||||||
closeContext();
|
closeContext();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const moveToFolder = (folderUid?: string) => {
|
||||||
|
if (!folderUid) {
|
||||||
|
addFolder({
|
||||||
|
connections: [props.contextConnection.uid]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
addToFolder({
|
||||||
|
folder: folderUid,
|
||||||
|
connection: props.contextConnection.uid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
closeContext();
|
||||||
|
};
|
||||||
|
|
||||||
|
const outOfFolder = () => {
|
||||||
|
removeFromFolders(props.contextConnection.uid);
|
||||||
|
closeContext();
|
||||||
|
};
|
||||||
|
|
||||||
const duplicateConnection = () => {
|
const duplicateConnection = () => {
|
||||||
let connectionCopy = getConnectionByUid(props.contextConnection.uid);
|
let connectionCopy = getConnectionByUid(props.contextConnection.uid);
|
||||||
connectionCopy = {
|
connectionCopy = {
|
||||||
|
@@ -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">
|
||||||
|
@@ -32,7 +32,7 @@ const { removeNotification } = notificationsStore;
|
|||||||
const { notifications } = storeToRefs(notificationsStore);
|
const { notifications } = storeToRefs(notificationsStore);
|
||||||
const { notificationsTimeout } = storeToRefs(settingsStore);
|
const { notificationsTimeout } = storeToRefs(settingsStore);
|
||||||
|
|
||||||
const timeouts: Ref<{[key: string]: NodeJS.Timeout}> = ref({});
|
const timeouts: Ref<Record<string, NodeJS.Timeout>> = ref({});
|
||||||
|
|
||||||
const latestNotifications = computed(() => notifications.value.slice(0, 10));
|
const latestNotifications = computed(() => notifications.value.slice(0, 10));
|
||||||
|
|
||||||
|
@@ -1,66 +1,368 @@
|
|||||||
<template>
|
<template>
|
||||||
<ConfirmModal
|
<Teleport to="#window-content">
|
||||||
:confirm-text="t('application.update')"
|
<div class="modal active">
|
||||||
:cancel-text="t('general.close')"
|
<a class="modal-overlay" @click.stop="hideScratchpad" />
|
||||||
size="large"
|
<div ref="trapRef" class="modal-container p-0 pb-4">
|
||||||
:hide-footer="true"
|
<div class="modal-header pl-2">
|
||||||
@hide="hideScratchpad"
|
<div class="modal-title h6">
|
||||||
>
|
<div class="d-flex">
|
||||||
<template #header>
|
<BaseIcon
|
||||||
<div class="d-flex">
|
icon-name="mdiNotebookOutline"
|
||||||
<BaseIcon
|
class="mr-1"
|
||||||
icon-name="mdiNotebookEditOutline"
|
:size="24"
|
||||||
class="mr-1"
|
/>
|
||||||
:size="24"
|
<span>{{ t('application.note', 2) }}</span>
|
||||||
/> {{ t('application.scratchpad') }}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<a class="btn btn-clear c-hand" @click.stop="hideScratchpad" />
|
||||||
<template #body>
|
</div>
|
||||||
<div>
|
<div class="modal-body p-0 workspace-query-results">
|
||||||
<div>
|
<div
|
||||||
<TextEditor
|
ref="noteFilters"
|
||||||
v-model="localNotes"
|
class="d-flex p-vcentered p-2"
|
||||||
editor-class="textarea-editor"
|
style="gap: 0 10px"
|
||||||
mode="markdown"
|
>
|
||||||
:auto-focus="true"
|
<div style="flex: 1;">
|
||||||
:show-line-numbers="false"
|
<BaseSelect
|
||||||
/>
|
v-model="localConnection"
|
||||||
|
class="form-select"
|
||||||
|
:options="connectionOptions"
|
||||||
|
option-track-by="code"
|
||||||
|
option-label="name"
|
||||||
|
@change="null"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="btn-group btn-group-block text-uppercase">
|
||||||
|
<div
|
||||||
|
v-for="tag in [{ code: 'all', name: t('general.all') }, ...noteTags]"
|
||||||
|
:key="tag.code"
|
||||||
|
class="btn"
|
||||||
|
:class="[selectedTag === tag.code ? 'btn-primary': 'btn-dark']"
|
||||||
|
@click="setTag(tag.code)"
|
||||||
|
>
|
||||||
|
{{ tag.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<div
|
||||||
|
class="btn px-1 tooltip tooltip-left s-rounded archived-button"
|
||||||
|
:class="[showArchived ? 'btn-primary' : 'btn-link']"
|
||||||
|
:data-tooltip="showArchived ? t('application.hideArchivedNotes') : t('application.showArchivedNotes')"
|
||||||
|
@click="showArchived = !showArchived"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
:icon-name="!showArchived ? 'mdiArchiveEyeOutline' : 'mdiArchiveCancelOutline'"
|
||||||
|
class=""
|
||||||
|
:size="24"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-show="filteredNotes.length || searchTerm.length"
|
||||||
|
ref="searchForm"
|
||||||
|
class="form-group has-icon-right m-0 p-2"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-model="searchTerm"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
:placeholder="t('general.search')"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
v-if="!searchTerm"
|
||||||
|
icon-name="mdiMagnify"
|
||||||
|
class="form-icon pr-2"
|
||||||
|
:size="18"
|
||||||
|
/>
|
||||||
|
<BaseIcon
|
||||||
|
v-else
|
||||||
|
icon-name="mdiBackspace"
|
||||||
|
class="form-icon c-hand pr-2"
|
||||||
|
:size="18"
|
||||||
|
@click="searchTerm = ''"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="filteredNotes.length"
|
||||||
|
ref="tableWrapper"
|
||||||
|
class="vscroll px-2"
|
||||||
|
:style="{'height': resultsSize+'px'}"
|
||||||
|
>
|
||||||
|
<div ref="table">
|
||||||
|
<BaseVirtualScroll
|
||||||
|
ref="resultTable"
|
||||||
|
:items="filteredNotes"
|
||||||
|
:item-height="83"
|
||||||
|
:visible-height="resultsSize"
|
||||||
|
:scroll-element="scrollElement"
|
||||||
|
>
|
||||||
|
<template #default="{ items }">
|
||||||
|
<ScratchpadNote
|
||||||
|
v-for="note in items"
|
||||||
|
:key="note.uid"
|
||||||
|
:search-term="searchTerm"
|
||||||
|
:note="note"
|
||||||
|
:selected-note="selectedNote"
|
||||||
|
@select-note="selectedNote = note.uid"
|
||||||
|
@toggle-note="toggleNote"
|
||||||
|
@edit-note="startEditNote(note)"
|
||||||
|
@delete-note="deleteNote"
|
||||||
|
@archive-note="archiveNote"
|
||||||
|
@restore-note="restoreNote"
|
||||||
|
@select-query="selectQuery"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</BaseVirtualScroll>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="empty">
|
||||||
|
<div class="empty-icon">
|
||||||
|
<BaseIcon icon-name="mdiNoteSearch" :size="48" />
|
||||||
|
</div>
|
||||||
|
<p class="empty-title h5">
|
||||||
|
{{ t('application.thereAreNoNotesYet') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="btn btn-primary p-0 add-button tooltip tooltip-left"
|
||||||
|
:data-tooltip="t('application.addNote')"
|
||||||
|
@click="isAddModal = true"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
icon-name="mdiPlus"
|
||||||
|
:size="48"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<small class="text-gray">{{ t('application.markdownSupported') }}</small>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</ConfirmModal>
|
</Teleport>
|
||||||
|
<ModalNoteNew v-if="isAddModal" @hide="isAddModal = false" />
|
||||||
|
<ModalNoteEdit
|
||||||
|
v-if="isEditModal"
|
||||||
|
:note="noteToEdit"
|
||||||
|
@hide="closeEditModal"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { Ref, ref, watch } from 'vue';
|
import {
|
||||||
|
Component,
|
||||||
|
computed,
|
||||||
|
ComputedRef,
|
||||||
|
onBeforeUnmount,
|
||||||
|
onMounted,
|
||||||
|
onUpdated,
|
||||||
|
provide,
|
||||||
|
Ref,
|
||||||
|
ref,
|
||||||
|
watch
|
||||||
|
} from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
|
||||||
import BaseIcon from '@/components/BaseIcon.vue';
|
import BaseIcon from '@/components/BaseIcon.vue';
|
||||||
import TextEditor from '@/components/BaseTextEditor.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue';
|
||||||
|
import ModalNoteEdit from '@/components/ModalNoteEdit.vue';
|
||||||
|
import ModalNoteNew from '@/components/ModalNoteNew.vue';
|
||||||
|
import ScratchpadNote from '@/components/ScratchpadNote.vue';
|
||||||
import { useApplicationStore } from '@/stores/application';
|
import { useApplicationStore } from '@/stores/application';
|
||||||
import { useScratchpadStore } from '@/stores/scratchpad';
|
import { useConnectionsStore } from '@/stores/connections';
|
||||||
|
import { ConnectionNote, TagCode, useScratchpadStore } from '@/stores/scratchpad';
|
||||||
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const applicationStore = useApplicationStore();
|
const applicationStore = useApplicationStore();
|
||||||
const scratchpadStore = useScratchpadStore();
|
const scratchpadStore = useScratchpadStore();
|
||||||
|
const workspacesStore = useWorkspacesStore();
|
||||||
|
|
||||||
const { notes } = storeToRefs(scratchpadStore);
|
const { connectionNotes, selectedTag } = storeToRefs(scratchpadStore);
|
||||||
const { changeNotes } = scratchpadStore;
|
const { changeNotes } = scratchpadStore;
|
||||||
const { hideScratchpad } = applicationStore;
|
const { hideScratchpad } = applicationStore;
|
||||||
|
const { getConnectionName } = useConnectionsStore();
|
||||||
|
const { connections } = storeToRefs(useConnectionsStore());
|
||||||
|
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||||
|
const { getWorkspaceTab, getWorkspace, newTab, updateTabContent } = workspacesStore;
|
||||||
|
|
||||||
const localNotes = ref(notes.value);
|
const localConnection = ref(null);
|
||||||
const debounceTimeout: Ref<NodeJS.Timeout> = ref(null);
|
const table: Ref<HTMLDivElement> = ref(null);
|
||||||
|
const resultTable: Ref<Component & { updateWindow: () => void }> = ref(null);
|
||||||
|
const tableWrapper: Ref<HTMLDivElement> = ref(null);
|
||||||
|
const noteFilters: Ref<HTMLInputElement> = ref(null);
|
||||||
|
const searchForm: Ref<HTMLInputElement> = ref(null);
|
||||||
|
const resultsSize = ref(1000);
|
||||||
|
const searchTermInterval: Ref<NodeJS.Timeout> = ref(null);
|
||||||
|
const scrollElement: Ref<HTMLDivElement> = ref(null);
|
||||||
|
const searchTerm = ref('');
|
||||||
|
const localSearchTerm = ref('');
|
||||||
|
const showArchived = ref(false);
|
||||||
|
const isAddModal = ref(false);
|
||||||
|
const isEditModal = ref(false);
|
||||||
|
const noteToEdit: Ref<ConnectionNote> = ref(null);
|
||||||
|
const selectedNote = ref(null);
|
||||||
|
|
||||||
watch(localNotes, () => {
|
const noteTags: ComputedRef<{code: TagCode; name: string}[]> = computed(() => [
|
||||||
clearTimeout(debounceTimeout.value);
|
{ code: 'note', name: t('application.note') },
|
||||||
|
{ code: 'todo', name: 'TODO' },
|
||||||
|
{ code: 'query', name: 'Query' }
|
||||||
|
]);
|
||||||
|
const filteredNotes = computed(() => connectionNotes.value.filter(n => (
|
||||||
|
(n.type === selectedTag.value || selectedTag.value === 'all') &&
|
||||||
|
(n.cUid === localConnection.value || localConnection.value === null) &&
|
||||||
|
(!n.isArchived || showArchived.value) &&
|
||||||
|
(n.note.toLowerCase().search(localSearchTerm.value.toLowerCase()) >= 0)
|
||||||
|
)));
|
||||||
|
const connectionOptions = computed(() => {
|
||||||
|
return [
|
||||||
|
{ code: null, name: t('general.all') },
|
||||||
|
...connections.value.map(c => ({ code: c.uid, name: getConnectionName(c.uid) }))
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
debounceTimeout.value = setTimeout(() => {
|
provide('noteTags', noteTags);
|
||||||
changeNotes(localNotes.value);
|
provide('connectionOptions', connectionOptions);
|
||||||
|
provide('selectedConnection', localConnection);
|
||||||
|
provide('selectedTag', selectedTag);
|
||||||
|
|
||||||
|
const resizeResults = () => {
|
||||||
|
if (resultTable.value) {
|
||||||
|
const el = tableWrapper.value.parentElement;
|
||||||
|
|
||||||
|
if (el)
|
||||||
|
resultsSize.value = el.offsetHeight - searchForm.value.offsetHeight - noteFilters.value.offsetHeight;
|
||||||
|
|
||||||
|
resultTable.value.updateWindow();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshScroller = () => resizeResults();
|
||||||
|
|
||||||
|
const setTag = (tag: string) => {
|
||||||
|
selectedTag.value = tag;
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleNote = (uid: string) => {
|
||||||
|
selectedNote.value = selectedNote.value !== uid ? uid : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const startEditNote = (note: ConnectionNote) => {
|
||||||
|
isEditModal.value = true;
|
||||||
|
noteToEdit.value = note;
|
||||||
|
};
|
||||||
|
|
||||||
|
const archiveNote = (uid: string) => {
|
||||||
|
const remappedNotes = connectionNotes.value.map(n => {
|
||||||
|
if (n.uid === uid)
|
||||||
|
n.isArchived = true;
|
||||||
|
return n;
|
||||||
|
});
|
||||||
|
changeNotes(remappedNotes);
|
||||||
|
};
|
||||||
|
|
||||||
|
const restoreNote = (uid: string) => {
|
||||||
|
const remappedNotes = connectionNotes.value.map(n => {
|
||||||
|
if (n.uid === uid)
|
||||||
|
n.isArchived = false;
|
||||||
|
return n;
|
||||||
|
});
|
||||||
|
changeNotes(remappedNotes);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteNote = (uid: string) => {
|
||||||
|
const otherNotes = connectionNotes.value.filter(n => n.uid !== uid);
|
||||||
|
changeNotes(otherNotes);
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectQuery = (query: string) => {
|
||||||
|
const workspace = getWorkspace(selectedWorkspace.value);
|
||||||
|
const selectedTab = getWorkspaceTab(workspace.selectedTab);
|
||||||
|
|
||||||
|
if (workspace.connectionStatus !== 'connected') return;
|
||||||
|
|
||||||
|
if (selectedTab.type === 'query') {
|
||||||
|
updateTabContent({
|
||||||
|
tab: selectedTab.uid,
|
||||||
|
uid: selectedWorkspace.value,
|
||||||
|
type: 'query',
|
||||||
|
content: query,
|
||||||
|
schema: workspace.breadcrumbs.schema
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newTab({
|
||||||
|
uid: selectedWorkspace.value,
|
||||||
|
type: 'query',
|
||||||
|
content: query,
|
||||||
|
autorun: false,
|
||||||
|
schema: workspace.breadcrumbs.schema
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hideScratchpad();
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeEditModal = () => {
|
||||||
|
isEditModal.value = false;
|
||||||
|
noteToEdit.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(searchTerm, () => {
|
||||||
|
clearTimeout(searchTermInterval.value);
|
||||||
|
|
||||||
|
searchTermInterval.value = setTimeout(() => {
|
||||||
|
localSearchTerm.value = searchTerm.value;
|
||||||
}, 200);
|
}, 200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onUpdated(() => {
|
||||||
|
if (table.value)
|
||||||
|
refreshScroller();
|
||||||
|
|
||||||
|
if (tableWrapper.value)
|
||||||
|
scrollElement.value = tableWrapper.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
resizeResults();
|
||||||
|
window.addEventListener('resize', resizeResults);
|
||||||
|
|
||||||
|
if (selectedWorkspace.value && selectedWorkspace.value !== 'NEW')
|
||||||
|
localConnection.value = selectedWorkspace.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('resize', resizeResults);
|
||||||
|
clearInterval(searchTermInterval.value);
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.vscroll {
|
||||||
|
height: 1000px;
|
||||||
|
overflow: auto;
|
||||||
|
overflow-anchor: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-button{
|
||||||
|
border: none;
|
||||||
|
height: 48px;
|
||||||
|
width: 48px;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: fixed;
|
||||||
|
margin-top: -40px;
|
||||||
|
margin-left: 580px;
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
.archived-button {
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@@ -59,17 +59,16 @@
|
|||||||
<div class="settingbar-bottom-elements">
|
<div class="settingbar-bottom-elements">
|
||||||
<ul class="settingbar-elements">
|
<ul class="settingbar-elements">
|
||||||
<li
|
<li
|
||||||
v-if="!disableScratchpad"
|
|
||||||
v-tooltip="{
|
v-tooltip="{
|
||||||
strategy: 'fixed',
|
strategy: 'fixed',
|
||||||
placement: 'right',
|
placement: 'right',
|
||||||
content: t('application.scratchpad')
|
content: t('application.note', 2)
|
||||||
}"
|
}"
|
||||||
class="settingbar-element btn btn-link"
|
class="settingbar-element btn btn-link"
|
||||||
@click="showScratchpad"
|
@click="showScratchpad()"
|
||||||
>
|
>
|
||||||
<BaseIcon
|
<BaseIcon
|
||||||
icon-name="mdiNotebookEditOutline"
|
icon-name="mdiNotebookOutline"
|
||||||
class="settingbar-element-icon text-light"
|
class="settingbar-element-icon text-light"
|
||||||
:size="24"
|
:size="24"
|
||||||
/>
|
/>
|
||||||
@@ -84,12 +83,15 @@
|
|||||||
@click="showSettingModal('general')"
|
@click="showSettingModal('general')"
|
||||||
>
|
>
|
||||||
<div class="settingbar-element-icon-wrapper">
|
<div class="settingbar-element-icon-wrapper">
|
||||||
<BaseIcon
|
<div
|
||||||
icon-name="mdiCog"
|
|
||||||
class="settingbar-element-icon text-light"
|
class="settingbar-element-icon text-light"
|
||||||
:class="{ 'badge badge-update': hasUpdates }"
|
:class="{ 'badge badge-update': hasUpdates }"
|
||||||
:size="24"
|
>
|
||||||
/>
|
<BaseIcon
|
||||||
|
icon-name="mdiCog"
|
||||||
|
:size="24"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -108,7 +110,6 @@ import SettingBarConnections from '@/components/SettingBarConnections.vue';
|
|||||||
import SettingBarContext from '@/components/SettingBarContext.vue';
|
import SettingBarContext from '@/components/SettingBarContext.vue';
|
||||||
import { useApplicationStore } from '@/stores/application';
|
import { useApplicationStore } from '@/stores/application';
|
||||||
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
|
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
|
||||||
import { useSettingsStore } from '@/stores/settings';
|
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
@@ -117,12 +118,10 @@ localStorage.setItem('opened-folders', '[]');
|
|||||||
const applicationStore = useApplicationStore();
|
const applicationStore = useApplicationStore();
|
||||||
const connectionsStore = useConnectionsStore();
|
const connectionsStore = useConnectionsStore();
|
||||||
const workspacesStore = useWorkspacesStore();
|
const workspacesStore = useWorkspacesStore();
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
|
|
||||||
const { updateStatus } = storeToRefs(applicationStore);
|
const { updateStatus } = storeToRefs(applicationStore);
|
||||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||||
const { connectionsOrder } = storeToRefs(connectionsStore);
|
const { connectionsOrder } = storeToRefs(connectionsStore);
|
||||||
const { disableScratchpad } = storeToRefs(settingsStore);
|
|
||||||
|
|
||||||
const { showSettingModal, showScratchpad } = applicationStore;
|
const { showSettingModal, showScratchpad } = applicationStore;
|
||||||
const { updateConnectionsOrder, initConnectionsOrder } = connectionsStore;
|
const { updateConnectionsOrder, initConnectionsOrder } = connectionsStore;
|
||||||
@@ -187,7 +186,7 @@ if (!connectionsArr.value.length)
|
|||||||
.settingbar-top-elements {
|
.settingbar-top-elements {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: overlay;
|
overflow-y: overlay;
|
||||||
// max-height: calc((100vh - 3.5rem) - #{$excluding-size});
|
width: 100%;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: 3px;
|
width: 3px;
|
||||||
@@ -234,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;
|
||||||
@@ -266,7 +266,7 @@ if (!connectionsArr.value.length)
|
|||||||
.settingbar-element-icon {
|
.settingbar-element-icon {
|
||||||
&.badge::after {
|
&.badge::after {
|
||||||
top: 10px;
|
top: 10px;
|
||||||
right: -6px;
|
right: -3px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="!isLinux"
|
|
||||||
id="titlebar"
|
id="titlebar"
|
||||||
@dblclick="toggleFullScreen"
|
@dblclick="toggleFullScreen"
|
||||||
>
|
>
|
||||||
@@ -21,16 +20,39 @@
|
|||||||
class="titlebar-element"
|
class="titlebar-element"
|
||||||
@click="openDevTools"
|
@click="openDevTools"
|
||||||
>
|
>
|
||||||
<BaseIcon icon-name="mdiCodeTags" :size="24" />
|
<BaseIcon icon-name="mdiBugPlayOutline" :size="18" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="isDevelopment"
|
v-if="isDevelopment"
|
||||||
class="titlebar-element"
|
class="titlebar-element"
|
||||||
@click="reload"
|
@click="reload"
|
||||||
>
|
>
|
||||||
<BaseIcon icon-name="mdiRefresh" :size="24" />
|
<BaseIcon icon-name="mdiRefresh" :size="18" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isWindows" :style="'width: 140px;'" />
|
<div v-if="isWindows" :style="'width: 140px;'" />
|
||||||
|
<div v-if="isLinux" class="d-flex">
|
||||||
|
<div
|
||||||
|
v-if="isDevelopment"
|
||||||
|
class="titlebar-element"
|
||||||
|
@click="minimize"
|
||||||
|
>
|
||||||
|
<BaseIcon icon-name="mdiWindowMinimize" :size="18" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="isDevelopment"
|
||||||
|
class="titlebar-element"
|
||||||
|
@click="toggleFullScreen"
|
||||||
|
>
|
||||||
|
<BaseIcon :icon-name="isMaximized ? 'mdiWindowRestore' : 'mdiWindowMaximize'" :size="18" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="isDevelopment"
|
||||||
|
class="titlebar-element"
|
||||||
|
@click="closeApp"
|
||||||
|
>
|
||||||
|
<BaseIcon icon-name="mdiClose" :size="18" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -74,6 +96,18 @@ const windowTitle = computed(() => {
|
|||||||
return [connectionName, ...breadcrumbs].join(' • ');
|
return [connectionName, ...breadcrumbs].join(' • ');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const openDevTools = () => {
|
||||||
|
w.value.webContents.openDevTools();
|
||||||
|
};
|
||||||
|
|
||||||
|
const reload = () => {
|
||||||
|
w.value.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const minimize = () => {
|
||||||
|
w.value.minimize();
|
||||||
|
};
|
||||||
|
|
||||||
const toggleFullScreen = () => {
|
const toggleFullScreen = () => {
|
||||||
if (isMaximized.value)
|
if (isMaximized.value)
|
||||||
w.value.unmaximize();
|
w.value.unmaximize();
|
||||||
@@ -81,12 +115,8 @@ const toggleFullScreen = () => {
|
|||||||
w.value.maximize();
|
w.value.maximize();
|
||||||
};
|
};
|
||||||
|
|
||||||
const openDevTools = () => {
|
const closeApp = () => {
|
||||||
w.value.webContents.openDevTools();
|
ipcRenderer.send('close-app');
|
||||||
};
|
|
||||||
|
|
||||||
const reload = () => {
|
|
||||||
w.value.reload();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onResize = () => {
|
const onResize = () => {
|
||||||
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user