Compare commits
682 Commits
Author | SHA1 | Date | |
---|---|---|---|
88cd097ec0 | |||
f12e6a96dd | |||
|
6fcae957e1 | ||
|
1cd2d8abf3 | ||
ed3c5fe559 | |||
|
9601c59392 | ||
|
14d20a30c1 | ||
e9079adb25 | |||
8f3efabb69 | |||
db628f7722 | |||
e4bd747381 | |||
88eb113e53 | |||
9109b2c328 | |||
dd070d008d | |||
ee415da127 | |||
f0d368e3e3 | |||
4be55f3fe9 | |||
fd00ea42ee | |||
50b4411e9a | |||
|
cd0a5dc034 | ||
|
9fd427c4ff | ||
f3759b6541 | |||
191a354020 | |||
|
7dc314eecf | ||
|
330a80fe70 | ||
|
b2ce533b82 | ||
|
12ce6b1135 | ||
|
71c5829702 | ||
1c4d5b05b3 | |||
e85197a2cc | |||
|
abf2b92e6e | ||
|
3be826df4b | ||
9db6bfd255 | |||
|
110adf90de | ||
|
40e4fbda15 | ||
8e983ad2cb | |||
|
6305752ad1 | ||
cc02e2c5a8 | |||
|
f4a63eae2a | ||
763be8532d | |||
|
a6f5645a22 | ||
|
bbe13f27dc | ||
f444746f46 | |||
b4af645941 | |||
a5c8daa5b8 | |||
1a9fc37285 | |||
f0351e5b94 | |||
9bda4e71b7 | |||
7c00055034 | |||
4479a9600b | |||
|
7a6bd8bdbd | ||
ad713fcf35 | |||
251795e2d2 | |||
45cda7a7cc | |||
b7039553cc | |||
ddee68b4c2 | |||
573ac6d42e | |||
265f28b4d9 | |||
1990d9a3d4 | |||
748d44977e | |||
4051eff382 | |||
4276586e11 | |||
832fb0fb03 | |||
328ab61757 | |||
95d15de1bd | |||
7dcd4441c4 | |||
d81e0911ab | |||
5bfff649e9 | |||
76743e8f7c | |||
4ed2f9a939 | |||
c5eb73ed3f | |||
fa3f3e1fd8 | |||
2c7c97852f | |||
48ebf23bd1 | |||
64deedc5ad | |||
9f033fb994 | |||
401cb49687 | |||
1356011ba3 | |||
0cfd7938ee | |||
745b55a942 | |||
eef7c1dcec | |||
aa8fc545d7 | |||
a780c7e0ff | |||
|
9a1bb0599f | ||
|
d847870f67 | ||
cd24371576 | |||
|
6ef565cf07 | ||
46b45c8ab6 | |||
f28531a225 | |||
8fb1f0803e | |||
020ce36312 | |||
b4545b178f | |||
d9a3eab015 | |||
2ab49c218d | |||
|
8f9385d508 | ||
|
4e9f8d16ee | ||
0c002918eb | |||
3d56ec7b49 | |||
48c3e6afc4 | |||
|
50a5c8fe1e | ||
b0d1f115c9 | |||
a59f77f618 | |||
e7a1858091 | |||
648a51efe8 | |||
38962c4807 | |||
|
43e4cdd4cf | ||
|
91046c1ac1 | ||
|
63f8b9b6a1 | ||
|
ed476d9b5c | ||
|
f41d8c0480 | ||
e3b54a8be1 | |||
|
c2c0394624 | ||
8a6f5eac59 | |||
a5fdcc1a85 | |||
1df21da47c | |||
8da0224876 | |||
359e14a9eb | |||
813aa320d9 | |||
aaa5549609 | |||
35cb7e1dc4 | |||
992a033cb2 | |||
5267b37eaf | |||
8fe30d8b6d | |||
37ccb6b00d | |||
e8af2d24a8 | |||
d7f1aa97af | |||
c46224635a | |||
cc99491fe4 | |||
fe8435531e | |||
5d48fe08c7 | |||
4437d44486 | |||
9fe3680bbb | |||
da1947e4ef | |||
37a848df9d | |||
|
9816965e18 | ||
98165cacaa | |||
7ad3096b4e | |||
7d345cf795 | |||
f40e9c592e | |||
7671c585f5 | |||
93b4a7063b | |||
3efeb45c46 | |||
3fc227d2de | |||
604b371920 | |||
fd321beece | |||
9fe4e6b9e3 | |||
|
57bf90481b | ||
|
6062a32c1c | ||
94c899eb82 | |||
44a4ca75bd | |||
|
c8e1605b08 | ||
60e5556a3e | |||
f2fcc98839 | |||
c54438d6d3 | |||
b3f10220b3 | |||
d19f475fc2 | |||
795db96319 | |||
b5fee79e90 | |||
|
5532ddbda9 | ||
3369d3dc2d | |||
fd25f881f9 | |||
5ca3a22dc5 | |||
5c668249cf | |||
39b9a59143 | |||
|
d25c62b4da | ||
|
8cf738bac8 | ||
534659f9ae | |||
c00fd1381f | |||
17c6686163 | |||
bc0c5a76ba | |||
2b16c0ece4 | |||
|
ba1416dce2 | ||
13adf0a767 | |||
cacab55f55 | |||
|
3189d625e3 | ||
|
f71ed39b88 | ||
409ed54608 | |||
d9d3bf2bc9 | |||
9e9de7b5c5 | |||
|
b2a5b40c03 | ||
729aa9781a | |||
cd1ebacf89 | |||
a08074b446 | |||
0cd182546b | |||
89fdd210ca | |||
|
0de2321920 | ||
|
e2cf6e8c21 | ||
|
430490ad93 | ||
|
a35566f273 | ||
3679121c25 | |||
|
7657d05edf | ||
|
1ddf8f0dbe | ||
9506bb862a | |||
4cfab365c2 | |||
8d0aa73d1e | |||
30b487c37f | |||
aef17be36c | |||
ea65d8eee7 | |||
|
7dc33c78aa | ||
|
69cd083054 | ||
|
91788054e6 | ||
|
968a67ce3d | ||
f9ee7d0450 | |||
|
0e15c39797 | ||
adf407c1ba | |||
8a86344484 | |||
d2d0c3ca41 | |||
|
9046b858b1 | ||
46987faea8 | |||
|
dd25827e40 | ||
4069ade36d | |||
c8594c0549 | |||
7359c3b5bd | |||
4195b8416f | |||
9407a29922 | |||
2fcd080bd4 | |||
26446fb7ed | |||
2480c76a08 | |||
1f0ec57789 | |||
|
76e5849c78 | ||
db1641b74f | |||
165c54f663 | |||
|
a5ca3ea204 | ||
|
add95292ad | ||
7a63608f54 | |||
7ea7b369ab | |||
258fffa958 | |||
0849c5131f | |||
|
cce59d0ca8 | ||
94a53fec6c | |||
13aa47cd44 | |||
85f625daf7 | |||
7de3bb9346 | |||
e43a0ba0b4 | |||
|
638cd4bfb7 | ||
3959333662 | |||
abd46aa322 | |||
d4888ad8fb | |||
6bfc229b77 | |||
d31b051f4b | |||
95b60df8fc | |||
ed5189fdc1 | |||
265ed66d25 | |||
09c07acd5c | |||
3c5a69adc9 | |||
0203f69e95 | |||
9a2498862c | |||
|
3a26d4f509 | ||
ce08cbd8b5 | |||
|
d91ffcccca | ||
6115eb9409 | |||
3fd26a0523 | |||
e217d5181b | |||
dcf368b350 | |||
21e3e79ddf | |||
2918c3cb92 | |||
3ad190b18c | |||
d5b2bde2ea | |||
a42348ef5c | |||
8b93c49778 | |||
0842e00098 | |||
6cef02bebb | |||
|
334c7a31d2 | ||
bc82289d54 | |||
c9fa941578 | |||
4048df3c7b | |||
198368605b | |||
8f0ac26b69 | |||
b35fc5b78b | |||
622b519cbb | |||
71e2c911ae | |||
756d49b259 | |||
14c64c537c | |||
97b3563e25 | |||
e834fe31ac | |||
065de3a0a2 | |||
1573de5b1f | |||
04fc1bbee0 | |||
dea378014d | |||
3abff36136 | |||
70354aa828 | |||
372049ae64 | |||
5d271be062 | |||
07ee1ae828 | |||
cbe0e2980a | |||
0fba50c6ec | |||
420255cdd4 | |||
a8a47ed5f7 | |||
425f0663f9 | |||
e106d100b5 | |||
d7fdf53932 | |||
62f7e57d0c | |||
14577d14bb | |||
f5f2a697e8 | |||
a0105cf1c3 | |||
77c5d28032 | |||
d1d8592f79 | |||
1f828f69a0 | |||
adc5477673 | |||
1e543aa6b0 | |||
c41e059b0b | |||
e6ef5ffa56 | |||
d67d122270 | |||
b837e2fc68 | |||
a73a2f483e | |||
0a9983d30d | |||
58b91ebfe0 | |||
e78ca2417e | |||
e1855a262d | |||
6b725b1d40 | |||
f6faad98f8 | |||
320aa8ba04 | |||
9f0280b991 | |||
04fa320820 | |||
f7c3aa883d | |||
f7a74df009 | |||
003c02b1fb | |||
ef21ea7448 | |||
525c964c62 | |||
7845e3e501 | |||
0c29e0d566 | |||
d38097d056 | |||
c87b8dc738 | |||
ed6e7fa72d | |||
f0fa7c81b7 | |||
5bb4e496f2 | |||
01057332b0 | |||
ab382dfbcd | |||
88c4cdc8e2 | |||
15ff211a41 | |||
5c855a520a | |||
7488bc7a17 | |||
|
fd85cf43a2 | ||
a87079cd17 | |||
14d5842056 | |||
f19f9e23a2 | |||
0252a064d9 | |||
439356a019 | |||
c6897af22d | |||
7e40dbfba3 | |||
27a153ef43 | |||
56fcc2650b | |||
9622dbec6b | |||
a0ab63bdb5 | |||
8cd76e711c | |||
9af71a6e34 | |||
e7b3c28826 | |||
7570b0add8 | |||
|
1801bef019 | ||
0db5ebd7bf | |||
5c21b67b3d | |||
08d5b1b329 | |||
61a42d51f5 | |||
d96907ca2d | |||
75bbd5f66e | |||
faa07a077c | |||
7ca69e51ae | |||
d868c772b9 | |||
a69bdeb20d | |||
51db2795bb | |||
2d278aa14e | |||
f74bca7bb4 | |||
978a7c5f5b | |||
0110756204 | |||
7df0cf8389 | |||
3aef7e953e | |||
a975df38dd | |||
a0a025e450 | |||
7cc5e7b67f | |||
e2ebb04a90 | |||
e579f37438 | |||
3829b94bf7 | |||
5c8ee66f43 | |||
b77e77f3bb | |||
1d5614278c | |||
8a20befd09 | |||
4133fc452f | |||
690a4541f9 | |||
71910ca5c8 | |||
cce5adbac7 | |||
e6d67fcb0c | |||
e83b80afc9 | |||
8742fa10f0 | |||
ab54eb086f | |||
308f69f12e | |||
2f30bafa33 | |||
|
1716077182 | ||
5562e73e75 | |||
8a7cc2a14f | |||
9ca059d979 | |||
c9ccf786cd | |||
5e9c88a7fd | |||
e0e6483d1f | |||
66227569f4 | |||
faa799c8ea | |||
acc1eeb094 | |||
865dad80df | |||
f502620779 | |||
f61d7eeaf4 | |||
e71c7568c0 | |||
79f033e524 | |||
310cfaa3c2 | |||
6f93e1f9ab | |||
04bdd085a5 | |||
c568acae14 | |||
7a46a119d8 | |||
f1636f1528 | |||
f839d5cd3c | |||
8f4f1d393c | |||
ab17c6d4bf | |||
b4b2f3d0cf | |||
7a766f04e6 | |||
f6b77d1243 | |||
840241c3cc | |||
0f10c9e824 | |||
1e37f2a96f | |||
0d6137195d | |||
d26f168250 | |||
5efcad4d3f | |||
0a872e7023 | |||
0e425a9c6d | |||
434711a360 | |||
854472c7a3 | |||
9a7cd90d5c | |||
026400d242 | |||
4bd8cbbf6c | |||
1382bc9300 | |||
ae103e5477 | |||
6b0b8b19d7 | |||
475397ca34 | |||
7a62131cc7 | |||
c7663be338 | |||
496490b14a | |||
|
672573d8c2 | ||
d923453919 | |||
0435279604 | |||
79cef61fea | |||
dec334737f | |||
84c3692e95 | |||
5dc0b0bea4 | |||
19d16e46bb | |||
3baf6fa173 | |||
29e2d92b5b | |||
bebba64d06 | |||
9dfe7cca22 | |||
35ef070725 | |||
370ad6a536 | |||
5822b3df43 | |||
5208ec171b | |||
6e332da425 | |||
bf3367b41d | |||
ecfb732c26 | |||
fd4f032a6f | |||
1b09909126 | |||
04cd806954 | |||
773cb36ca1 | |||
ccacd3e2c3 | |||
15948b30c9 | |||
3dcb5d4f14 | |||
|
64d93d7c40 | ||
|
2d85295093 | ||
|
cd58e2d8ca | ||
d87495822e | |||
|
8720bcdad6 | ||
1df57dc705 | |||
86240fb53c | |||
1d363f755e | |||
0d77aee3eb | |||
a41cf1ab56 | |||
5ceddb8e00 | |||
16e17b39b6 | |||
20cba6ee9b | |||
9ffd443a66 | |||
f82dbd24dc | |||
6eb2977568 | |||
cafb65560a | |||
532d963019 | |||
1b0a63ff31 | |||
c22187c305 | |||
dcccb544f9 | |||
7d2ace9456 | |||
2584c9b9c2 | |||
a6b75ad0dc | |||
90fd9db917 | |||
c0f54b9514 | |||
cd31413256 | |||
b33199ea59 | |||
dea5ec7513 | |||
be816e8588 | |||
1e938adc5d | |||
8735a0c5f9 | |||
3dde1c109e | |||
d0b3e1b1b8 | |||
c20bff7bcb | |||
9f5ec0276c | |||
55932fe115 | |||
d374372e20 | |||
bb5f44681f | |||
49a4e1cb7b | |||
c2f76e490a | |||
e349dd5eab | |||
280697698e | |||
0783f8b57e | |||
c981244d7a | |||
dcb135dd01 | |||
99f7511c4d | |||
fe4c8e12b3 | |||
21728a663d | |||
9ca03f4625 | |||
affb7288b0 | |||
614e0d3275 | |||
feef5e30ee | |||
82c25711b6 | |||
2ca2988832 | |||
e3f259c6e8 | |||
e7401cc96e | |||
13b9840f3d | |||
d20414b692 | |||
22a8c25717 | |||
db47b4040a | |||
e89911b185 | |||
fccfe92453 | |||
d465e18dba | |||
ffb1712a59 | |||
9f6a183d9b | |||
1f80a64fe1 | |||
fc651149b9 | |||
964570247f | |||
8a6c59f7ce | |||
4d844fe2c9 | |||
d892fa6fb3 | |||
8c9e4f6e96 | |||
966ca60c89 | |||
9bbe218f90 | |||
a1c6be372b | |||
7d0c929fb8 | |||
25d72e3952 | |||
b232a3bb5f | |||
e9a26c1bc0 | |||
76c5c0c680 | |||
ddfb713124 | |||
0081a4167c | |||
239cb4488f | |||
fb5adbe676 | |||
9cd51c8d8b | |||
8dfaa3b7be | |||
5d7efa75b7 | |||
4862d51fba | |||
07f60c3917 | |||
049143d143 | |||
db4430609e | |||
71b4310117 | |||
201fad9265 | |||
45351faeae | |||
b4ead6992c | |||
b1ea32b680 | |||
39ca1974bc | |||
777b73fa6f | |||
4494e637f7 | |||
219da0aba4 | |||
7e8167154f | |||
3aa2159a1a | |||
76d92cd106 | |||
c8545a250b | |||
dbab06fcb8 | |||
b54fefbf25 | |||
9a1bf32128 | |||
110b0b414c | |||
2f58007af4 | |||
9b60bfff8d | |||
3b37b7432e | |||
7c4ca999ce | |||
94c4952319 | |||
014257147e | |||
970de4962b | |||
b5a828309f | |||
5b21d17f3a | |||
2c6e35288f | |||
bcadac6e95 | |||
|
18a93ef1aa | ||
6c62052b47 | |||
9d5ebefdce | |||
34ebc6b72d | |||
288ff4c1a1 | |||
a176174b8d | |||
0f69d1dbb7 | |||
0386bbac50 | |||
b0576acdf6 | |||
9a190854fe | |||
2aace28e80 | |||
02c03e3d26 | |||
a0d85520fb | |||
ede6fe81ce | |||
4e72bb1587 | |||
15417e8a77 | |||
88ab7c5a62 | |||
5940b0b842 | |||
574d493908 | |||
af96647603 | |||
ab70ff239e | |||
bacf458936 | |||
379d6c169a | |||
|
36352f8028 | ||
04fcaf2f6e | |||
8b914446b0 | |||
a11bac504c | |||
b9ed8dd610 | |||
1cf6485896 | |||
4bc9bbfb34 | |||
4923128236 | |||
8ff6e70145 | |||
afcf1c86ed | |||
0200ae4a0f | |||
dbe7b9dd23 | |||
ceab4ef243 | |||
1e7d4ca347 | |||
c0a32c040e | |||
f150508547 | |||
49d71722e2 | |||
59a50bc014 | |||
41d75b127c | |||
0cbea9d100 | |||
e351c903a8 | |||
6e55f27b23 | |||
27fd9ec203 | |||
0ec2710872 | |||
3bcd02fc4e | |||
aa33850286 | |||
82fdc0bcd7 | |||
d695c9f8d2 | |||
b32132ad84 | |||
3126625461 | |||
ab307f82b1 | |||
0df2b836b1 | |||
6611aad840 | |||
8c4aaec167 | |||
b7053bdf80 | |||
b6b7be098a | |||
56f2a27f00 | |||
dcf469ebed | |||
d94b49febf | |||
3b4f1475df | |||
155154b43d | |||
a95b8d188c | |||
cb1fce6f99 | |||
0014f48079 | |||
fc35f271d7 | |||
65ad0e954d | |||
8cafade8b1 | |||
d13b708377 | |||
206597e5b8 | |||
c5458159d1 | |||
1476e899d1 | |||
797ab70e7c | |||
f81312aeb0 | |||
3ed5ea023e | |||
9291a7a7b4 | |||
5cfdc9b92d | |||
15b08d7ea8 | |||
acebe435ff | |||
5712b80022 | |||
d38583262e | |||
e0e2131981 | |||
7470bddd70 | |||
33d1fa2290 | |||
a4122b4eaa | |||
e6602d1bfa | |||
f8cf90a89e | |||
41505bde65 | |||
8ebc3bce92 | |||
45e9cdc591 | |||
3cbfc0e148 | |||
a47e9e1b1f | |||
e95d29c7c3 | |||
e954f04828 | |||
|
85c800f85b | ||
|
e0482244d7 | ||
27769f204f | |||
dfb24c65f3 | |||
0fe71572a5 | |||
db577bfef0 | |||
0805b96a75 |
163
.all-contributorsrc
Normal file
@@ -0,0 +1,163 @@
|
||||
{
|
||||
"projectName": "antares",
|
||||
"projectOwner": "Fabio286",
|
||||
"repoType": "github",
|
||||
"repoHost": "https://github.com",
|
||||
"files": [
|
||||
"README.md"
|
||||
],
|
||||
"imageSize": 100,
|
||||
"commit": false,
|
||||
"commitConvention": "angular",
|
||||
"contributors": [
|
||||
{
|
||||
"login": "Fabio286",
|
||||
"name": "Fabio Di Stasio",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/31471771?v=4",
|
||||
"profile": "https://fabiodistasio.it/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"translation",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "toriphes",
|
||||
"name": "Giulio Ganci",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/4192159?v=4",
|
||||
"profile": "https://www.linkedin.com/in/giulioganci/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "digitalgopnik",
|
||||
"name": "Christian Ratz",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/2630316?v=4",
|
||||
"profile": "https://christianratz.de/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "reverb6821",
|
||||
"name": "Giuseppe Gigliotti",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/55198803?v=4",
|
||||
"profile": "https://reverb6821.github.io/",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Mohd-PH",
|
||||
"name": "Mohd-PH",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/9362157?v=4",
|
||||
"profile": "https://github.com/Mohd-PH",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "hongkfui",
|
||||
"name": "hongkfui",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/37477191?v=4",
|
||||
"profile": "https://github.com/hongkfui",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "MrAnyx",
|
||||
"name": "Robin",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/44176707?v=4",
|
||||
"profile": "https://github.com/MrAnyx",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "daeleduardo",
|
||||
"name": "Daniel Eduardo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/8599078?v=4",
|
||||
"profile": "https://github.com/daeleduardo",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "datlechin",
|
||||
"name": "Ngô Quốc Đạt",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/56961917?v=4",
|
||||
"profile": "https://ngoquocdat.com/",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "IsamuSugi",
|
||||
"name": "Isamu Sugiura",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7746658?v=4",
|
||||
"profile": "https://github.com/IsamuSugi",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Occhioverde",
|
||||
"name": "Riccardo Sacchetto",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/18429412?v=4",
|
||||
"profile": "http://rsacchetto.nexxontech.it/",
|
||||
"contributions": [
|
||||
"platform"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "kilianstallz",
|
||||
"name": "Kilian Stallinger",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/5290318?v=4",
|
||||
"profile": "https://kilianstallinger.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "wenj91",
|
||||
"name": "文杰",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/12549338?v=4",
|
||||
"profile": "https://github.com/wenj91",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "goYou",
|
||||
"name": "goYou",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/62732795?v=4",
|
||||
"profile": "https://github.com/goYou",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "raliqala",
|
||||
"name": "Topollo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/30502407?v=4",
|
||||
"profile": "https://github.com/raliqala",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "SmileYzn",
|
||||
"name": "Cleverson",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/5851851?v=4",
|
||||
"profile": "https://github.com/SmileYzn",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
"skipCi": true
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
/node_modules
|
||||
/assets/vendor
|
||||
/out
|
||||
/dist
|
||||
node_modules
|
||||
assets
|
||||
out
|
||||
dist
|
47
.eslintrc
@@ -9,22 +9,25 @@
|
||||
"plugin:vue/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint",
|
||||
"parser": "@babel/eslint-parser",
|
||||
"ecmaVersion": 9,
|
||||
"sourceType": "module"
|
||||
"sourceType": "module",
|
||||
"requireConfigFile": false
|
||||
},
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
3,
|
||||
{ "SwitchCase": 1 }
|
||||
{
|
||||
"SwitchCase": 1
|
||||
}
|
||||
],
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"brace-style": [
|
||||
"error",
|
||||
"error",
|
||||
"stroustrup"
|
||||
],
|
||||
"quotes": [
|
||||
@@ -36,26 +39,36 @@
|
||||
"always"
|
||||
],
|
||||
"curly": [
|
||||
"error",
|
||||
"error",
|
||||
"multi-or-nest"
|
||||
],
|
||||
"no-console": "off",
|
||||
"no-undef": "off",
|
||||
"vue/no-side-effects-in-computed-properties": "off",
|
||||
"vue/multi-word-component-names": "off",
|
||||
"vue/require-default-prop": "off",
|
||||
"vue/comment-directive": "off",
|
||||
"vue/no-v-html": "off",
|
||||
"vue/html-indent": ["error", 3, {
|
||||
"attribute": 1,
|
||||
"baseIndent": 1,
|
||||
"closeBracket": 0,
|
||||
"ignores": []
|
||||
}],
|
||||
"vue/max-attributes-per-line": ["error", {
|
||||
"singleline": 2,
|
||||
"multiline": {
|
||||
"max": 1,
|
||||
"allowFirstLine": false
|
||||
"vue/html-indent": [
|
||||
"error",
|
||||
3,
|
||||
{
|
||||
"attribute": 1,
|
||||
"baseIndent": 1,
|
||||
"closeBracket": 0,
|
||||
"ignores": []
|
||||
}
|
||||
}]
|
||||
],
|
||||
"vue/max-attributes-per-line": [
|
||||
"error",
|
||||
{
|
||||
"singleline": {
|
||||
"max": 2
|
||||
},
|
||||
"multiline": {
|
||||
"max": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
7
.gitattributes
vendored
@@ -1 +1,6 @@
|
||||
* text eol=lf
|
||||
* text eol=lf
|
||||
*.jpg binary
|
||||
*.png binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.icns binary
|
2
.github/FUNDING.yml
vendored
@@ -1,7 +1,7 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [fabio286]
|
||||
patreon: fabio286
|
||||
patreon: #fabio286
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
|
21
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -3,7 +3,7 @@ name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
assignees: Fabio286
|
||||
|
||||
---
|
||||
|
||||
@@ -12,6 +12,7 @@ A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
@@ -23,16 +24,16 @@ A clear and concise description of what you expected to happen.
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
**Application (please complete the following information):**
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
- Client [e.g. MySQL]
|
||||
- Version [e.g. 0.14.0]
|
||||
- Distribution: [e.g. exe, Linux Store, AppImage, dmg]
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
|
||||
- OS: [e.g. Windows 11]
|
||||
- Version [e.g. 21H2]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -3,7 +3,7 @@ name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
assignees: Fabio286
|
||||
|
||||
---
|
||||
|
||||
|
32
.github/workflows/build-linux.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Build/release [linux]
|
||||
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm i
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test
|
||||
|
||||
- name: Build/release Electron app
|
||||
uses: samuelmeuli/action-electron-builder@v1
|
||||
with:
|
||||
github_token: ${{ secrets.github_token }}
|
||||
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
32
.github/workflows/build-mac.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Build/release [mac]
|
||||
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest]
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm i
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test
|
||||
|
||||
- name: Build/release Electron app
|
||||
uses: samuelmeuli/action-electron-builder@v1
|
||||
with:
|
||||
github_token: ${{ secrets.github_token }}
|
||||
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
32
.github/workflows/build-win.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Build/release [windows]
|
||||
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-2019]
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm i
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test
|
||||
|
||||
- name: Build/release Electron app
|
||||
uses: samuelmeuli/action-electron-builder@v1
|
||||
with:
|
||||
github_token: ${{ secrets.github_token }}
|
||||
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
5
.github/workflows/codeql-analysis.yml
vendored
@@ -31,11 +31,6 @@ jobs:
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
|
12
.gitignore
vendored
@@ -1,9 +1,9 @@
|
||||
.DS_Store
|
||||
dist/
|
||||
node_modules/
|
||||
dist
|
||||
build
|
||||
node_modules
|
||||
thumbs.db
|
||||
.idea/
|
||||
.vscode
|
||||
TODO.md
|
||||
NOTES.md
|
||||
*.txt
|
||||
package-lock.json
|
||||
package-lock.json
|
||||
*.heapsnapshot
|
@@ -8,7 +8,9 @@
|
||||
"stylelint-scss"
|
||||
],
|
||||
"rules": {
|
||||
"at-rule-no-unknown": null
|
||||
"at-rule-no-unknown": null,
|
||||
"no-descending-specificity": null,
|
||||
"declaration-colon-newline-after": "always-multi-line"
|
||||
},
|
||||
"syntax": "scss"
|
||||
}
|
47
.travis.yml
@@ -1,47 +0,0 @@
|
||||
language: node_js
|
||||
node_js: 12
|
||||
|
||||
before_install:
|
||||
- npm install
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- app/node_modules
|
||||
- $HOME/.cache/electron
|
||||
- $HOME/.cache/electron-builder
|
||||
- $HOME/.npm/_prebuilds
|
||||
|
||||
env:
|
||||
global:
|
||||
- ELECTRON_CACHE=$HOME/.cache/electron
|
||||
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: Test
|
||||
script:
|
||||
- npm test
|
||||
|
||||
- stage: Deploy Linux & Windows
|
||||
if: tag IS present
|
||||
os: linux
|
||||
services: docker
|
||||
script:
|
||||
- docker run --rm --env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|_TOKEN|_KEY|AWS_|STRIP|BUILD_') -v ${PWD}:/project -v ~/.cache/electron:/root/.cache/electron -v ~/.cache/electron-builder:/root/.cache/electron-builder electronuserland/builder:wine /bin/bash -c "npm run build -- --linux --win -p always"
|
||||
before_cache:
|
||||
- rm -rf $HOME/.cache/electron-builder/wine
|
||||
|
||||
- stage: Deploy Mac
|
||||
if: tag IS present
|
||||
os: osx
|
||||
osx_image: xcode10.2
|
||||
script:
|
||||
- npm run build -- -p always
|
||||
|
||||
- stage: Deploy ARM Linux
|
||||
if: tag IS present
|
||||
os: linux
|
||||
arch: arm64
|
||||
script:
|
||||
- npm run build -- --linux AppImage -p always
|
7
.versionrc.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"types": [
|
||||
{"type":"feat","section":"Features"},
|
||||
{"type":"perf","section":"Improvements"},
|
||||
{"type":"fix","section":"Bug Fixes"}
|
||||
]
|
||||
}
|
39
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Electron: Main",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"port": 9222,
|
||||
"protocol": "inspector",
|
||||
"request": "attach",
|
||||
"sourceMaps": true,
|
||||
"type": "node",
|
||||
"timeout": 1000000
|
||||
},
|
||||
{
|
||||
"name": "Electron: Renderer",
|
||||
"port": 9223,
|
||||
"request": "attach",
|
||||
"sourceMaps": true,
|
||||
"type": "chrome",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"name": "Electron: Worker",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"port": 9224,
|
||||
"protocol": "inspector",
|
||||
"request": "attach",
|
||||
"sourceMaps": true,
|
||||
"type": "node",
|
||||
"timeout": 1000000
|
||||
},
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Electron: All",
|
||||
"configurations": ["Electron: Main", "Electron: Renderer"]
|
||||
}
|
||||
]
|
||||
}
|
11
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"conventionalCommits.scopes": [
|
||||
"UI",
|
||||
"core",
|
||||
"MySQL",
|
||||
"PostgreSQL",
|
||||
"SQLite",
|
||||
"Windows"
|
||||
],
|
||||
"svg.preview.background": "transparent"
|
||||
}
|
894
CHANGELOG.md
@@ -2,6 +2,900 @@
|
||||
|
||||
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.5.1](https://github.com/Fabio286/antares/compare/v0.5.0...v0.5.1) (2022-03-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* export database as zip sql file ([8f3efab](https://github.com/Fabio286/antares/commit/8f3efabb6962c55c23a43c8da1433185dbc3fb41))
|
||||
* **UI:** option to disable blur effects, closes [#209](https://github.com/Fabio286/antares/issues/209) ([e9079ad](https://github.com/Fabio286/antares/commit/e9079adb25ec28e9546acd54bc2565b8d6e28120))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* numeric scale displayed on non decimal fields ([db628f7](https://github.com/Fabio286/antares/commit/db628f77226b161c3df31b7450b92a6e58754ab7))
|
||||
* **UI:** connection buttons out of screen on small displays, closes [#213](https://github.com/Fabio286/antares/issues/213) ([f12e6a9](https://github.com/Fabio286/antares/commit/f12e6a96dd66140b06c55eda775af48a666627dd))
|
||||
|
||||
## [0.5.0](https://github.com/Fabio286/antares/compare/v0.4.4...v0.5.0) (2022-03-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* delete dump file when the export is canceled ([d25c62b](https://github.com/Fabio286/antares/commit/d25c62b4da9480e040d0bfac8b76732a4c69a5f1))
|
||||
* initial db export implementation ([0de2321](https://github.com/Fabio286/antares/commit/0de232192076d1de827424c593ac9dff63903531))
|
||||
* initial mysql import support ([4e9f8d1](https://github.com/Fabio286/antares/commit/4e9f8d16ee3c204d5f0c2bed081206f8b38207a6))
|
||||
* mysql export for trigger, views, schedulers, functions and routines ([b2a5b40](https://github.com/Fabio286/antares/commit/b2a5b40c03d56bced5a7968c3454f36060e56dd0))
|
||||
* **MySQL:** enhance export characters escaping ([3be826d](https://github.com/Fabio286/antares/commit/3be826df4b02ff0df0aa922d96755b31b7155784))
|
||||
* **MySQL:** support to multi spatial fields export ([4be55f3](https://github.com/Fabio286/antares/commit/4be55f3fe9bb48324b780734762f2ff6da2ccb61))
|
||||
* **PostgreSQL:** :sparkles: Postgress connection string feature for local and server connection string ([6305752](https://github.com/Fabio286/antares/commit/6305752ad117cc29c04bce3ce3df321f743cdc44))
|
||||
* **PostgreSQL:** :sparkles: Postgress connection string feature for local and server connection string ([f4a63ea](https://github.com/Fabio286/antares/commit/f4a63eae2aca2a84647a5027137614950aef1eac))
|
||||
* **UI:** auto-refresh schema at the end of the import process ([abf2b92](https://github.com/Fabio286/antares/commit/abf2b92e6e66b6668e698c5addf4e3c00ae5157b))
|
||||
* **UI:** better real-time import stats ([a6f5645](https://github.com/Fabio286/antares/commit/a6f5645a226454cc2c415311ac321ba3d4db4454))
|
||||
* **UI:** toggle tables checkbox by column on export modal ([1c4d5b0](https://github.com/Fabio286/antares/commit/1c4d5b05b3f94b3e7bef930aa7f89bdaa596c0b9))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MySQL:** exception exporting empty procedures/functions ([ee415da](https://github.com/Fabio286/antares/commit/ee415da127d6d0de95aac901a2a01af863736344))
|
||||
* **MySQL:** export crash with large databases ([8cf738b](https://github.com/Fabio286/antares/commit/8cf738bac85698fddd0504eef7844279e8c11f44))
|
||||
* **MySQL:** missing functions and procedures definer escapes on exporter ([f0351e5](https://github.com/Fabio286/antares/commit/f0351e5b94830f9f52256096c2601b0ca9cd811d))
|
||||
* **MySQL:** missing initial delimiter for exported procedures ([1a9fc37](https://github.com/Fabio286/antares/commit/1a9fc3728580f789727256d7893ca4bb90c16a50))
|
||||
* **MySQL:** procedures exportation ([4276586](https://github.com/Fabio286/antares/commit/4276586e1141500401ff1ab570b29e485f459987))
|
||||
* sql parser hangs during import ([7a6bd8b](https://github.com/Fabio286/antares/commit/7a6bd8bdbd69e3b5fe265d0bb0be844699dd77c2))
|
||||
* wrong schema and table size on explore bar ([4479a96](https://github.com/Fabio286/antares/commit/4479a9600b5e59ef1bcf9135d661b4d7900a4bde))
|
||||
* wrong soft sort algorithm for numeric fields, closes [#199](https://github.com/Fabio286/antares/issues/199) ([763be85](https://github.com/Fabio286/antares/commit/763be8532d2b61d0b4d45e72343f6a2e5fee1db9))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* avoid to load schema elements if already loaded in export modal ([d9d3bf2](https://github.com/Fabio286/antares/commit/d9d3bf2bc9d39ce8eec5dffbecbf767fbcf47782))
|
||||
* **MySQL:** import performance improvement ([f444746](https://github.com/Fabio286/antares/commit/f444746f465ed0e8bd2e4c007faf17e167814278))
|
||||
* **MySQL:** import tasks managed with async queue ([bbe13f2](https://github.com/Fabio286/antares/commit/bbe13f27dc29f997898f8c13f36b5d582770b21d))
|
||||
* **MySQL:** improved several field types support on exporter ([1990d9a](https://github.com/Fabio286/antares/commit/1990d9a3d441f0e2075ac7e893d5b166275c48c0))
|
||||
* **MySQL:** prevent memory leak on large dump import ([f3759b6](https://github.com/Fabio286/antares/commit/f3759b65411a40d92b98208176cdf8e6dd8230ce))
|
||||
* **PostgreSQL:** :zap: Postgres connection update, better error handling and connection string accommodation. ([330a80f](https://github.com/Fabio286/antares/commit/330a80fe70b81f466f5e883029f42087b4b5c411))
|
||||
* split the export select query to avoid running out of memory ([409ed54](https://github.com/Fabio286/antares/commit/409ed54608ad402b63fcc26a6e724bc447ba89d2))
|
||||
* use fork() for the export process ([748d449](https://github.com/Fabio286/antares/commit/748d44977e76c6c8d6344df52e8e3ccfab84f670))
|
||||
* use fork() for the import process ([573ac6d](https://github.com/Fabio286/antares/commit/573ac6d42ef833f250d102e5b30ae6cf5877f330))
|
||||
|
||||
### [0.4.4](https://github.com/Fabio286/antares/compare/v0.4.3...v0.4.4) (2022-02-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* execution notification for ROLLBACK and COMMIT ([76743e8](https://github.com/Fabio286/antares/commit/76743e8f7c02b824cb21540bfbcbe66ba43de8fa))
|
||||
* **MySQL:** manual commit mode ([4ed2f9a](https://github.com/Fabio286/antares/commit/4ed2f9a93937b4293436a64318b7d6ae3a0d93c2))
|
||||
* **PostgreSQL:** manual commit mode ([d81e091](https://github.com/Fabio286/antares/commit/d81e0911ab82fb75745ab11e67e867a00d8ac273))
|
||||
* reminder for uncommitted changes closing a tab ([5bfff64](https://github.com/Fabio286/antares/commit/5bfff649e92f6fe5aba4b16aa4c8d5a5a70b31b2))
|
||||
* **SQLite:** manual commit mode ([7dcd444](https://github.com/Fabio286/antares/commit/7dcd4441c49fafc0f47e12c2129708fe1092e1a4))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* bigint support, closes [#197](https://github.com/Fabio286/antares/issues/197) ([b703955](https://github.com/Fabio286/antares/commit/b7039553ccaac4fd59e521530c4a053922854130))
|
||||
* **MySQL:** default value not displayed for DECIMAL fields ([fa3f3e1](https://github.com/Fabio286/antares/commit/fa3f3e1fd8101f19f18df71e90d60fd37cdddaee))
|
||||
* zero-padded bit fields beyond length ([265f28b](https://github.com/Fabio286/antares/commit/265f28b4d94cde4608a1d6d3d306824134808ec2))
|
||||
|
||||
### [0.4.3](https://github.com/Fabio286/antares/compare/v0.4.2...v0.4.3) (2022-01-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add Simplified Chinese translation ([6ef565c](https://github.com/Fabio286/antares/commit/6ef565cf078cb3f5b7bcdc226894cddeb6239db9))
|
||||
* **MySQL:** spatial fields support ([#165](https://github.com/Fabio286/antares/issues/165)) ([48ebf23](https://github.com/Fabio286/antares/commit/48ebf23bd1574408f429f2e1200ce878352007f6))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* cell copy returns "undefined" in some conditions, closes [#170](https://github.com/Fabio286/antares/issues/170) ([8fb1f08](https://github.com/Fabio286/antares/commit/8fb1f0803efd9df0b66521e73bb6e1a229cf9691))
|
||||
* indexes and foreign keys not cleared after deletion of related field, closes [#182](https://github.com/Fabio286/antares/issues/182) ([9f033fb](https://github.com/Fabio286/antares/commit/9f033fb994916b4fb165e81e55e86127ca817791))
|
||||
* **PostgreSQL:** schema different than public not automatically selected, closes [#172](https://github.com/Fabio286/antares/issues/172) ([46b45c8](https://github.com/Fabio286/antares/commit/46b45c8ab64fb6837a532c4f8342167e4fd794bb))
|
||||
* scale on numeric fields that doesn't support it ([0cfd793](https://github.com/Fabio286/antares/commit/0cfd7938ee7d607dbad66ae452d0200223a6bab2))
|
||||
* **Windows:** temporary fix to Windows 7 style frame on app startup, closes [#169](https://github.com/Fabio286/antares/issues/169) ([1356011](https://github.com/Fabio286/antares/commit/1356011ba3b7dd72e12cb252a8787ce48a364fd4))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* support of scale in field's length setting ([eef7c1d](https://github.com/Fabio286/antares/commit/eef7c1dcecc6593ab0e69ed678187a57fe0a4fb6))
|
||||
|
||||
### [0.4.2](https://github.com/Fabio286/antares/compare/v0.4.1...v0.4.2) (2022-01-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **MySQL:** ability to cancel queries ([a59f77f](https://github.com/Fabio286/antares/commit/a59f77f618aea6156fc80fb832d3efcb9848411f))
|
||||
* **PostgreSQL:** ability to cancel queries ([0c00291](https://github.com/Fabio286/antares/commit/0c002918eb0226f6b3f21ed62117495f86396fb1))
|
||||
* save window state ([8f9385d](https://github.com/Fabio286/antares/commit/8f9385d50815635d091758ecd5d00884e3297ca0))
|
||||
* **UI:** textarea autofocus selecting a query tab, closes [#166](https://github.com/Fabio286/antares/issues/166) ([b4545b1](https://github.com/Fabio286/antares/commit/b4545b178f795712c781a3f4fc35eec31b5ad902))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **SQLite:** exception with some fields ([e7a1858](https://github.com/Fabio286/antares/commit/e7a18580915e7739bfa97948c6a0c4fc90a7e78a))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* hash for foreign key default names ([48c3e6a](https://github.com/Fabio286/antares/commit/48c3e6afc43c51f70a16703f1a71194f43da7a3e))
|
||||
* **MySQL:** support to ANSI_QUOTES sql_mode, closes [#158](https://github.com/Fabio286/antares/issues/158) ([d9a3eab](https://github.com/Fabio286/antares/commit/d9a3eab015302e9f23112f659658073ab3242191))
|
||||
|
||||
### [0.4.1](https://github.com/Fabio286/antares/compare/v0.4.0...v0.4.1) (2021-12-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* language format detection for text fields ([a5fdcc1](https://github.com/Fabio286/antares/commit/a5fdcc1a85aa188ff1b9a15b1a768aced026f360))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* cell disappear on edit in one column tables ([aaa5549](https://github.com/Fabio286/antares/commit/aaa5549609664665bd4513632d621cb249b379c1))
|
||||
* false positive with Windows Defender ([992a033](https://github.com/Fabio286/antares/commit/992a033cb2bede3d1eb52e19482d810f6692de1e))
|
||||
* **MySQL:** wrong datetime fields default in table filler in some cases ([8da0224](https://github.com/Fabio286/antares/commit/8da022487650039b7f34a9c86a7bd9045eba65e2))
|
||||
* **MySQL:** wrong value for fields "on update" in some conditions ([359e14a](https://github.com/Fabio286/antares/commit/359e14a9ebd48f86069ba7762fe00a7056f52d47))
|
||||
* select all rows with ctrl+a when editing a cell ([35cb7e1](https://github.com/Fabio286/antares/commit/35cb7e1dc48d3a74e9d106cb1a37f454c1b4a4d1))
|
||||
* **SQLite:** update rows with a text primary key ([d7f1aa9](https://github.com/Fabio286/antares/commit/d7f1aa97af32a4c51fc7022498bd47e15fa08430))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **UI:** avoid columns size change when editing cells or scrolling results ([813aa32](https://github.com/Fabio286/antares/commit/813aa320d9ab799efea38a7110b7c0bdf7549123))
|
||||
* **UI:** disable save button in table creation when no fields are added ([e8af2d2](https://github.com/Fabio286/antares/commit/e8af2d24a869f7667c069936648808952d2062ab))
|
||||
|
||||
## [0.4.0](https://github.com/Fabio286/antares/compare/v0.3.9...v0.4.0) (2021-11-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **MySQL:** read-only mode ([4437d44](https://github.com/Fabio286/antares/commit/4437d44486c4f20b0bec4bf89d56016b08e36e79))
|
||||
* **PostgreSQL:** read-only mode ([5d48fe0](https://github.com/Fabio286/antares/commit/5d48fe08c77755ed18b3f7a9ea834268e317e7ef))
|
||||
* **SQLite:** cell update in data tabs ([604b371](https://github.com/Fabio286/antares/commit/604b3719204f7473ce4846624f08f8be9eec8b8f))
|
||||
* **SQLite:** connection add/edit masks ([c54438d](https://github.com/Fabio286/antares/commit/c54438d6d3bad38bc76dfcd61f58929fe30279cb))
|
||||
* **SQLite:** keys support ([fd321be](https://github.com/Fabio286/antares/commit/fd321beece075d3ad23fdd8541f9beb5727045a5))
|
||||
* **SQLite:** readonly mode ([3fc227d](https://github.com/Fabio286/antares/commit/3fc227d2de53aae115226ad3c965bfb6e9f3eca6))
|
||||
* **SQLite:** table data visualization ([f2fcc98](https://github.com/Fabio286/antares/commit/f2fcc9883972402eab4d51ef2a9796638dde2d3d))
|
||||
* **SQLite:** tables management ([3efeb45](https://github.com/Fabio286/antares/commit/3efeb45c460f178b794de72367f8d542fd8ddd56))
|
||||
* **SQLite:** triggers management ([f40e9c5](https://github.com/Fabio286/antares/commit/f40e9c592eeffd204aba21a0a0767a0c523fca49))
|
||||
* **SQLite:** views management ([7671c58](https://github.com/Fabio286/antares/commit/7671c585f5f8049bd863db190d4fc60d8f0c6c66))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **SQLite:** hide schema creation ([98165ca](https://github.com/Fabio286/antares/commit/98165cacaa158c85ead0490d3caf579e2a17319f))
|
||||
* **UI:** hide tools menu if no tools available ([da1947e](https://github.com/Fabio286/antares/commit/da1947e4efa7f0a26d6a231fadf750be055fbdd5))
|
||||
* **UI:** notifications timeout anomalies ([cc99491](https://github.com/Fabio286/antares/commit/cc99491fe4a15812368f6c928b8c7801d7b255aa))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **SQLite:** improvements in data visualization ([94c899e](https://github.com/Fabio286/antares/commit/94c899eb8288b41a5962ac3d24365227e1f9f485))
|
||||
* **SQLite:** improvements in field length detection ([93b4a70](https://github.com/Fabio286/antares/commit/93b4a7063beeb5a7001cb06a74f05b23105212f5))
|
||||
* update italian traslation ([9fe3680](https://github.com/Fabio286/antares/commit/9fe3680bbb17c192cffa85348e68794ab49beb81))
|
||||
|
||||
### [0.3.9](https://github.com/Fabio286/antares/compare/v0.3.8...v0.3.9) (2021-11-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* added macos basic shortcusts and menu ([430490a](https://github.com/Fabio286/antares/commit/430490ad93f3148962ced1f13a5330c79cd86b3b))
|
||||
* **MySQL:** enable/disable schedulers from contextual menu ([5ca3a22](https://github.com/Fabio286/antares/commit/5ca3a22dc538b27a4bf6402f1288c4b9f5bc5a90))
|
||||
* **MySQL:** scheduler status indicator in explore bar ([5c66824](https://github.com/Fabio286/antares/commit/5c668249cf102cd9d601f9f7b4943c7155775217))
|
||||
* **PostgreSQL:** enable/disable triggers from contextual menu ([534659f](https://github.com/Fabio286/antares/commit/534659f9aee12eb5ac477f91bfe5d764387dc17e))
|
||||
* schema size in explore bar ([fd25f88](https://github.com/Fabio286/antares/commit/fd25f881f95779709156cbad93a41d6b391f1a45))
|
||||
* **UI:** double click on the title bar will toggle window fullscreen size ([a35566f](https://github.com/Fabio286/antares/commit/a35566f273322602abe434b8bd30817ba8885900))
|
||||
* **UI:** improved topbar look&feel on MacOS ([7657d05](https://github.com/Fabio286/antares/commit/7657d05edfbeaed6a14eb337fc562da5126e6ba0))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* copy&paste and basic usability on macOS ([1ddf8f0](https://github.com/Fabio286/antares/commit/1ddf8f0dbe22f94d6bffddf70636706d2d142ecf))
|
||||
* **PostgreSQL:** bigint fetched as string instead of number, closes [#134](https://github.com/Fabio286/antares/issues/134) ([39b9a59](https://github.com/Fabio286/antares/commit/39b9a59143b457a96f0711a3b8588c92dd80e28d))
|
||||
* row selection problems after a deletion fail, closes [#128](https://github.com/Fabio286/antares/issues/128) ([89fdd21](https://github.com/Fabio286/antares/commit/89fdd210ca48fc9ae399b195ea796c8523619627))
|
||||
* temporary solution on MacOS for unsigned app updates ([c00fd13](https://github.com/Fabio286/antares/commit/c00fd1381f451ba7aace7047b28b904ddcaf18f0))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **UI:** improved function and routine parameters modals ([d19f475](https://github.com/Fabio286/antares/commit/d19f475fc28c0367ada569cb634769fa618b48b4))
|
||||
|
||||
### [0.3.8](https://github.com/Fabio286/antares/compare/v0.3.7...v0.3.8) (2021-10-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **UI:** CTRL+A to select all result rows ([4069ade](https://github.com/Fabio286/antares/commit/4069ade36df43e58f198dd1305778b5811824315))
|
||||
* **UI:** ctrl|cmd+t, ctrl|cmd+w shortcut to open/close workspace tabs ([9046b85](https://github.com/Fabio286/antares/commit/9046b858b1e4608af4c01bc4d69e1a49d4009c07))
|
||||
* **UI:** hide filter bar if there are no more rows in it ([9178805](https://github.com/Fabio286/antares/commit/91788054e6302e83cb4a7501ad6c3f72809cb3bb))
|
||||
* **UI:** multi column table filters ([0e15c39](https://github.com/Fabio286/antares/commit/0e15c39797fe34f7a649f85ee62204682d45c98a))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **PostgreSQL:** issue with uppercase characters in table field names ([aef17be](https://github.com/Fabio286/antares/commit/aef17be36cfcf3a6325a954e80f973623e250405))
|
||||
* query failure when a filter with a numeric value is used ([69cd083](https://github.com/Fabio286/antares/commit/69cd083054cae50d64475b9f1f5d7ebd39093e39))
|
||||
* regression during resize results table on filters change ([7dc33c7](https://github.com/Fabio286/antares/commit/7dc33c78aa4152264cc6833437be9af9b8621867))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **UI:** resize results table on filters change ([f9ee7d0](https://github.com/Fabio286/antares/commit/f9ee7d0450a1386800223d7b96849e06ae02aece))
|
||||
|
||||
### [0.3.7](https://github.com/Fabio286/antares/compare/v0.3.6...v0.3.7) (2021-10-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support to SSH private keys with passphrase, closes [#118](https://github.com/Fabio286/antares/issues/118) ([9407a29](https://github.com/Fabio286/antares/commit/9407a29922812ab6aa3cf67569ba2f509433657c))
|
||||
* **UI:** auto detect system theme as default app theme ([7a63608](https://github.com/Fabio286/antares/commit/7a63608f54e387d45e655855666041f5602b54b1))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* closing ask credential modal during a connection doesn't stops loading, closes [#114](https://github.com/Fabio286/antares/issues/114) ([26446fb](https://github.com/Fabio286/antares/commit/26446fb7ed04216283736072d442786350252dbb))
|
||||
* **PostgreSQL:** issue with uppercase characters in table and field names, closes [#116](https://github.com/Fabio286/antares/issues/116) ([2fcd080](https://github.com/Fabio286/antares/commit/2fcd080bd47367a21590ea5a754410a975959bdd))
|
||||
* **UI:** window reload pressing enter in schema creation modal, closes [#113](https://github.com/Fabio286/antares/issues/113) ([db1641b](https://github.com/Fabio286/antares/commit/db1641b74fcd218f1f1d24163cba70b024cc6bd7))
|
||||
|
||||
### [0.3.6](https://github.com/Fabio286/antares/compare/v0.3.5...v0.3.6) (2021-09-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* copy cell/row or kill connections on context menu from processes list ([85f625d](https://github.com/Fabio286/antares/commit/85f625daf7026815dac6223a29c5a6479830edbb))
|
||||
* processes list exportation ([13aa47c](https://github.com/Fabio286/antares/commit/13aa47cd4441aa47c93038dbd91d6a0e54f6a60c))
|
||||
* workspace query history ([3959333](https://github.com/Fabio286/antares/commit/39593336626e6d9f3d3b65d2a4081388900e37d6))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* adding a connection default values not change when switching clients, closes [#101](https://github.com/Fabio286/antares/issues/101) ([d4888ad](https://github.com/Fabio286/antares/commit/d4888ad8fba3c8e8ec2d6b6d9a78bb212d83eeed))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **core:** better communication of internal exceptions ([abd46aa](https://github.com/Fabio286/antares/commit/abd46aa32256f822e52eaac2fc698da378b8163f))
|
||||
|
||||
### [0.3.5](https://github.com/Fabio286/antares/compare/v0.3.4...v0.3.5) (2021-09-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MySQL:** connections stuck at startup if 5 or more tabs are restored ([95b60df](https://github.com/Fabio286/antares/commit/95b60df8fc634b96a4c2c5c48dc6b10848888978))
|
||||
|
||||
### [0.3.4](https://github.com/Fabio286/antares/compare/v0.3.3...v0.3.4) (2021-09-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* new create function tabs ([0203f69](https://github.com/Fabio286/antares/commit/0203f69e95093d25a7ef3e66df1c70f76edcedf2))
|
||||
* new create routine tabs ([3fd26a0](https://github.com/Fabio286/antares/commit/3fd26a05238368ae375197c79d42162a5910bb07))
|
||||
* new create scheduler tabs ([3c5a69a](https://github.com/Fabio286/antares/commit/3c5a69adc9cecdc4b8f46097676005f2a60f06cf))
|
||||
* new create trigger function tabs ([09c07ac](https://github.com/Fabio286/antares/commit/09c07acd5c2a6ed2b75640dbb83e782ed432bc30))
|
||||
* new create trigger tabs ([e217d51](https://github.com/Fabio286/antares/commit/e217d5181b37ec6304151120b4a2aba9455c6a84))
|
||||
* start search when typing with focus on the left bar ([265ed66](https://github.com/Fabio286/antares/commit/265ed66d25d35be99ed0a6b1668dab9f246ed71e))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **UI:** context menu of tables cut if close to bottom edge ([21e3e79](https://github.com/Fabio286/antares/commit/21e3e79ddf9e292bcfc5881b8fa76a1dc58b207c))
|
||||
|
||||
### [0.3.3](https://github.com/Fabio286/antares/compare/v0.3.2...v0.3.3) (2021-08-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* new create table tabs ([c9fa941](https://github.com/Fabio286/antares/commit/c9fa9415787c3953043db5876a99b3664c69c071))
|
||||
* new create view tabs ([8b93c49](https://github.com/Fabio286/antares/commit/8b93c497784ea431f9747c5afb53f6ef075ea9d6))
|
||||
* new table empty state ([0842e00](https://github.com/Fabio286/antares/commit/0842e00098ba420412937aa52276ee33bda53693))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MySQL:** editing a view causes error for missing database in some conditions ([4048df3](https://github.com/Fabio286/antares/commit/4048df3c7bc2d42a60f7a57c9a4b8b5b445fcd43))
|
||||
* table options not loaded on restored setting tabs at startup ([622b519](https://github.com/Fabio286/antares/commit/622b519cbb5fbe4e38a4baffb8eab169b21eed21))
|
||||
* **UI:** multiple temp tabs opened switching to tables from other elements ([b35fc5b](https://github.com/Fabio286/antares/commit/b35fc5b78bdbeff1422ef088441b17c8b0df663c))
|
||||
* **UI:** no round borders on left of file upload inputs ([d5b2bde](https://github.com/Fabio286/antares/commit/d5b2bde2eaf8ff3e14f49cc26acdcb201b4cb15c))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **UI:** element options in setting tabs accessible directly ([71e2c91](https://github.com/Fabio286/antares/commit/71e2c911ae23e86543b2af1fa885981ff271777d))
|
||||
* **UI:** improved view setting tab ([1983686](https://github.com/Fabio286/antares/commit/198368605b084bd58fd6f7ca0b19895ba23a45e6))
|
||||
* **UI:** primary app color on selected text backgrouns ([756d49b](https://github.com/Fabio286/antares/commit/756d49b2596dea58376c6afa8e0bad0cd62b146c))
|
||||
* **UI:** visual improvements of tables ([bc82289](https://github.com/Fabio286/antares/commit/bc82289d54550a93300fe66d7a660aa70db2fd23))
|
||||
|
||||
### [0.3.2](https://github.com/Fabio286/antares/compare/v0.3.1...v0.3.2) (2021-08-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* contextual menu option to duplicate table fields ([3abff36](https://github.com/Fabio286/antares/commit/3abff3613618ddc86e1d6c898f83bcb360e8e5d9))
|
||||
* **UI:** automatic scroll on selected tab ([e834fe3](https://github.com/Fabio286/antares/commit/e834fe31ac8b73f249e3ad8654e97da834b28cfe))
|
||||
* **UI:** automatic scroll to selected tab element in left bar ([04fc1bb](https://github.com/Fabio286/antares/commit/04fc1bbee0cdc3b02288ff5d3b2c402633cae1f1))
|
||||
* **UI:** button to clear sidebar search input ([1573de5](https://github.com/Fabio286/antares/commit/1573de5b1f545328523aaaaac541f5a8046617be))
|
||||
* **UI:** query tab name based on content ([065de3a](https://github.com/Fabio286/antares/commit/065de3a0a28a9e6667df4e41ac1c5fa5561d2171))
|
||||
* **UI:** shortcuts info on empty query tab ([70354aa](https://github.com/Fabio286/antares/commit/70354aa828121004f70e91d65cb9f0a4811dce57))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* tab selected when clicking closing cross ([07ee1ae](https://github.com/Fabio286/antares/commit/07ee1ae828bb5f9971a263f2e39e73c760fed50a))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* approximate table total updated on table refresh ([dea3780](https://github.com/Fabio286/antares/commit/dea378014dd45bf90f51a599b4a801e0bc22059f))
|
||||
* **UI:** loading animation on tables and table context menu improvements ([372049a](https://github.com/Fabio286/antares/commit/372049ae64232e8e61a974edc8be5b319c5c0811))
|
||||
* update italian translation ([cbe0e29](https://github.com/Fabio286/antares/commit/cbe0e2980a9d4562941aec37a57cc936a46f41d8))
|
||||
|
||||
### [0.3.1](https://github.com/Fabio286/antares/compare/v0.3.0...v0.3.1) (2021-07-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **UI:** tabs or explorebar elements selected with mouse wheel or right button ([a8a47ed](https://github.com/Fabio286/antares/commit/a8a47ed5f7d5d8cbbdd6c33ef8307d9dce5b193b))
|
||||
|
||||
## [0.3.0](https://github.com/Fabio286/antares/compare/v0.2.1...v0.3.0) (2021-07-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* close tabs if element deleted ([320aa8b](https://github.com/Fabio286/antares/commit/320aa8ba04d464f628b938b812165cd1ec786492))
|
||||
* new data tabs ([ab382df](https://github.com/Fabio286/antares/commit/ab382dfbcd89f8ab38a8cbdb4d87e1705618fd0d))
|
||||
* new function, procedure and scheduler tabs ([0a9983d](https://github.com/Fabio286/antares/commit/0a9983d30d6aeb9aee1c68b31a31f8c605deea45))
|
||||
* new trigger function tabs ([58b91eb](https://github.com/Fabio286/antares/commit/58b91ebfe0ab200d76d4cfd442b9f2970ae97384))
|
||||
* new trigger setting tabs ([f6faad9](https://github.com/Fabio286/antares/commit/f6faad98f88222500dfb7265b74c1be914b894cd))
|
||||
* new unsaved change reminder ([f7a74df](https://github.com/Fabio286/antares/commit/f7a74df0097867a82a2a2d8b3c278a81897d7898))
|
||||
* new view setting tabs ([003c02b](https://github.com/Fabio286/antares/commit/003c02b1fbe5aba7f53f3faa5610b0c2f7706793))
|
||||
* option to restore session on startup ([adc5477](https://github.com/Fabio286/antares/commit/adc5477673603cd63fabf77a53db5397e3774e0e))
|
||||
* option to select schema in query tabs ([a73a2f4](https://github.com/Fabio286/antares/commit/a73a2f483ef6927def4ba635e306277c34ae4b53))
|
||||
* **UI:** empty workspace view ([e1855a2](https://github.com/Fabio286/antares/commit/e1855a262dc24363fc143a38a70154938308bd71))
|
||||
* rename tabs if element is renamed ([ef21ea7](https://github.com/Fabio286/antares/commit/ef21ea74481839ba67ca79d40d5f47e1c12aebc0))
|
||||
* **MySQL:** improved schema detection for queries ([5bb4e49](https://github.com/Fabio286/antares/commit/5bb4e496f289f3ae8a46f24d1a2fbb896d9da86e))
|
||||
* **UI:** close temp data tabs ([88c4cdc](https://github.com/Fabio286/antares/commit/88c4cdc8e2a60ffbe26b0f831184c3a6dd9a1637))
|
||||
* **UI:** display schema in data tabs ([0105733](https://github.com/Fabio286/antares/commit/01057332b090997c107bf395bb1fc3b9195e8218))
|
||||
* **UI:** new table settings tabs ([7845e3e](https://github.com/Fabio286/antares/commit/7845e3e501bb7f890d07b42d4f3eb5f9f4bc8586))
|
||||
* **UI:** sortable tabs ([d38097d](https://github.com/Fabio286/antares/commit/d38097d0567020b5265b5a0b347f5e1f38e0b1d4))
|
||||
* **UI:** temporary table data tabs ([a87079c](https://github.com/Fabio286/antares/commit/a87079cd179033cebb6fd228ad7f1b991f3b6c46))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* clear empty indexes and foreign keys on confirm respective modals ([04fa320](https://github.com/Fabio286/antares/commit/04fa320820df1f70a4ef05e4a6e4cbcd4081d047))
|
||||
* enabled copy context on non editable rows ([525c964](https://github.com/Fabio286/antares/commit/525c964c623e7574f6abe732a2b315b8805d5eee))
|
||||
* issues with trigger temp tabs ([6b725b1](https://github.com/Fabio286/antares/commit/6b725b1d40753ad89bfad6f1df57c6fb737e5262))
|
||||
* manual page input not disabled when only one page is available ([62f7e57](https://github.com/Fabio286/antares/commit/62f7e57d0ccf6041b5469bec6a1864aa5b045609))
|
||||
* new field default value unknown instead 'noval' ([77c5d28](https://github.com/Fabio286/antares/commit/77c5d280325792e20befc845f3a6834837131e39))
|
||||
* reload twice after element rename ([1e543aa](https://github.com/Fabio286/antares/commit/1e543aa6b0b63e9134bf544682a26bd98573b794))
|
||||
* solved a vulnerability in table names ([5c855a5](https://github.com/Fabio286/antares/commit/5c855a520a6bc66cc00b0b8afc6d2c03c75c0fab))
|
||||
* sort order of tables is lost switching pages ([14577d1](https://github.com/Fabio286/antares/commit/14577d14bb337898a1fd0fdee1c3760812b9d21f))
|
||||
* wrong editor height with some conditions ([d7fdf53](https://github.com/Fabio286/antares/commit/d7fdf53932a4d0609a88641c40d451c71b12c242))
|
||||
* **UI:** multiple trigger tabs open on single click on explore bar ([e78ca24](https://github.com/Fabio286/antares/commit/e78ca2417e8f996fa0507c5a5586a75393dfe8ee))
|
||||
* **UI:** not disabled buttons during save table setting tabs ([a0105cf](https://github.com/Fabio286/antares/commit/a0105cf1c37144ea27b212029d39275918f4f95e))
|
||||
* tab won't open after table or view creation ([f7c3aa8](https://github.com/Fabio286/antares/commit/f7c3aa883dfcad0a28222c53cf22ab49d381559c))
|
||||
* wrong loaded schema change ([c41e059](https://github.com/Fabio286/antares/commit/c41e059b0ba6d8c6d545c3b86a21a9a2abb6f537))
|
||||
* **UI:** table icon in view data tabs ([f0fa7c8](https://github.com/Fabio286/antares/commit/f0fa7c81b7aa2a05833ce0b243afed39db98d66b))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **UI:** improvements in setting bar connections sort ([0c29e0d](https://github.com/Fabio286/antares/commit/0c29e0d566c792ffd1b2b7045124e170b9c51985))
|
||||
|
||||
### [0.2.1](https://github.com/Fabio286/antares/compare/v0.2.0...v0.2.1) (2021-07-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **UI:** contextual menu shortcuts to create new elements on folders ([0252a06](https://github.com/Fabio286/antares/commit/0252a064d99df09b01c020c7d10c76dd43d4ede8))
|
||||
* context menu option to duplicate connections ([439356a](https://github.com/Fabio286/antares/commit/439356a01993a6a248f5a7d7df305ac5e0e63775))
|
||||
* **MySQL:** possibility to set a default schema in connection parameters ([c6897af](https://github.com/Fabio286/antares/commit/c6897af22d04ed930289a55124b3e8d080fbae3a))
|
||||
* **UI:** new connection add panel ([8cd76e7](https://github.com/Fabio286/antares/commit/8cd76e711c9328254d498b4f9afa221311f5a487))
|
||||
* **UI:** new connection edit panel ([9af71a6](https://github.com/Fabio286/antares/commit/9af71a6e343deda1bf79d8410511cf861af3c304))
|
||||
* SSH Tunnel functionality ([#81](https://github.com/Fabio286/antares/issues/81)) ([1801bef](https://github.com/Fabio286/antares/commit/1801bef019cee77a99df7e3822145ad952465abb))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* clear corrupted configurations to avoid exceptions ([56fcc26](https://github.com/Fabio286/antares/commit/56fcc2650b93ece398118f39f027dc9520dd8a6a))
|
||||
* **UI:** connection tab indicator when scrolling ([a0ab63b](https://github.com/Fabio286/antares/commit/a0ab63bdb533aac9b2bdc2ee07a3b1f2b70ea227))
|
||||
* avoid to trigger schema loading multiple times ([7570b0a](https://github.com/Fabio286/antares/commit/7570b0add8cb9130f15cf8cb807a96dbfd2837d0))
|
||||
|
||||
## [0.2.0](https://github.com/Fabio286/antares/compare/v0.1.13...v0.2.0) (2021-07-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* contextual option to duplicate tables ([08d5b1b](https://github.com/Fabio286/antares/commit/08d5b1b3299adc5c53419f50892aee3b58e6fe72))
|
||||
* **PostgreSQL:** trigger functions support ([75bbd5f](https://github.com/Fabio286/antares/commit/75bbd5f66e38a971482acff9dde636cf89858c8e))
|
||||
* **PostgreSQL:** triggers creation ([7df0cf8](https://github.com/Fabio286/antares/commit/7df0cf83895ba784e30efa7814433cf329175364))
|
||||
* **UI:** active tab animation ([978a7c5](https://github.com/Fabio286/antares/commit/978a7c5f5b314f2ba4067c86471755fdcc341b3f))
|
||||
* **UI:** context option to copy cell or row value ([d868c77](https://github.com/Fabio286/antares/commit/d868c772b9e7c70e173d1f80ad8de51aa40991ef))
|
||||
* **UI:** resizer border mouse hover animation ([a69bdeb](https://github.com/Fabio286/antares/commit/a69bdeb20db7d67613dace554ae37a0c5e112ec2))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **PostgreSQL:** error opening setting tab for some stored routines ([d96907c](https://github.com/Fabio286/antares/commit/d96907ca2de344114e3edae52843f6258468934b))
|
||||
* **UI:** contextual sub-menu alignment when close to the lower edge of the window ([61a42d5](https://github.com/Fabio286/antares/commit/61a42d51f55ec3010c4ee67bad2f699ff14c9f7c))
|
||||
* fields default not correctly set in table filler ([faa07a0](https://github.com/Fabio286/antares/commit/faa07a077c899af5c71124e0c20869876e57fd9a))
|
||||
* rows loses internal id after export ([2d278aa](https://github.com/Fabio286/antares/commit/2d278aa14e0e5080725196538b7265ec00a7aebb))
|
||||
* **UI:** editor blinking on first connection with a dark theme ([0110756](https://github.com/Fabio286/antares/commit/0110756204b79388535a74017b303e1ba41e8fd8))
|
||||
* unhandled exception in connection test ([a975df3](https://github.com/Fabio286/antares/commit/a975df38dd471ab0ac8645a1021470a4600e6598))
|
||||
* vulnerability in server error toast messages ([3aef7e9](https://github.com/Fabio286/antares/commit/3aef7e953ea82a9105d470cc62c68aacfc97f9d9))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* improved contextual menu appearance ([f74bca7](https://github.com/Fabio286/antares/commit/f74bca7bb4fc6802143446a44663fbf12319bf29))
|
||||
* **UI:** increased application border-radius ([a0a025e](https://github.com/Fabio286/antares/commit/a0a025e4502141e95394ca9717b1ec65df2728c1))
|
||||
|
||||
### [0.1.13](https://github.com/Fabio286/antares/compare/v0.1.12...v0.1.13) (2021-06-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **PostgreSQL:** alter trigger support ([5c8ee66](https://github.com/Fabio286/antares/commit/5c8ee66f432585db7bd72103fa814558bf406bcc))
|
||||
* **UI:** ability to manually insert page number in DATA tabs, closes [#65](https://github.com/Fabio286/antares/issues/65) ([e2ebb04](https://github.com/Fabio286/antares/commit/e2ebb04a9047d25187889644aa625fe675de808b))
|
||||
* **UI:** option to change query editors font size, closes [#77](https://github.com/Fabio286/antares/issues/77) ([e579f37](https://github.com/Fabio286/antares/commit/e579f374381b329422b646b8f7ab5acf298db981))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MySQL:** pool connections not released after MySQL errors, causing endless load animation ([4133fc4](https://github.com/Fabio286/antares/commit/4133fc452fc2e961eb587590e010f4968675db7e))
|
||||
* **UI:** various fixes in displaying content with small window size ([8a20bef](https://github.com/Fabio286/antares/commit/8a20befd0941cb2e6d5e29552606454bd871b092))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* remove comments from queries before execution ([690a454](https://github.com/Fabio286/antares/commit/690a4541f96eaf831dea07b777a421729173b654))
|
||||
|
||||
### [0.1.12](https://github.com/Fabio286/antares/compare/v0.1.11...v0.1.12) (2021-06-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **PostgreSQL:** trigger rename and delete ([cce5adb](https://github.com/Fabio286/antares/commit/cce5adbac75170186956211b54f4db5be25aba07))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* internal exceptions ([8742fa1](https://github.com/Fabio286/antares/commit/8742fa10f08c92a98325df50be9a7df8e36b0f2c))
|
||||
* page offset not reset when selected table changes ([5562e73](https://github.com/Fabio286/antares/commit/5562e73e75c659051a49d61041d1eee9d66ff6e2))
|
||||
* **MySQL:** view's data tab doesn't work with some views, closes [#71](https://github.com/Fabio286/antares/issues/71) ([9ca059d](https://github.com/Fabio286/antares/commit/9ca059d979161c0132f64db372b4ed7ecc844b2d))
|
||||
* **UI:** unable to browse view's result pages ([8a7cc2a](https://github.com/Fabio286/antares/commit/8a7cc2a14fa289c2f3d9e9eb1a5435ffbbaab006))
|
||||
|
||||
### [0.1.11](https://github.com/Fabio286/antares/compare/v0.1.10...v0.1.11) (2021-06-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **UI:** prevent canc key to trigger delete modal when editing a row ([5e9c88a](https://github.com/Fabio286/antares/commit/5e9c88a7fd656dd69b92ba4384ca81f4e8cdd422))
|
||||
* table row loses internal id after cell update ([6622756](https://github.com/Fabio286/antares/commit/66227569f4c032298e20d44c253d30109ffde0b2))
|
||||
* **MySQL:** missing schema altering tables in some conditions ([faa799c](https://github.com/Fabio286/antares/commit/faa799c8ea2329e7ca1b54c9414a060bc8a2a840))
|
||||
* empty offset in cell update queries ([acc1eeb](https://github.com/Fabio286/antares/commit/acc1eeb0948db35f2c9a4d85d70fb654b41b15e5))
|
||||
|
||||
### [0.1.10](https://github.com/Fabio286/antares/compare/v0.1.9...v0.1.10) (2021-05-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* key shortcuts to change DATA tab page ([f61d7ee](https://github.com/Fabio286/antares/commit/f61d7eeaf42406eb88b3285ac4606794f56e88d3))
|
||||
* option to set DATA tab page size ([e71c756](https://github.com/Fabio286/antares/commit/e71c7568c09bf8ad184753d3d0f14046f1e386d0))
|
||||
* prev/next buttons to browse the results pages of data tab ([79f033e](https://github.com/Fabio286/antares/commit/79f033e5245e0e744c35540d25a30b1eadc4d603))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MySQL:** wrong schema in view data tab select, closes [#71](https://github.com/Fabio286/antares/issues/71) ([310cfaa](https://github.com/Fabio286/antares/commit/310cfaa3c2324b1159a46be566b6af2555dc9732))
|
||||
* wrong detection of field default expressions in some cases ([04bdd08](https://github.com/Fabio286/antares/commit/04bdd085a56240273133586d23196e52341d5f27))
|
||||
|
||||
### [0.1.9](https://github.com/Fabio286/antares/compare/v0.1.8...v0.1.9) (2021-05-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MySQL:** can't access tables having UNIQUE KEY, closes [#69](https://github.com/Fabio286/antares/issues/69) ([f1636f1](https://github.com/Fabio286/antares/commit/f1636f1528b5765423535f84027fdc853c58ce80))
|
||||
|
||||
### [0.1.8](https://github.com/Fabio286/antares/compare/v0.1.7...v0.1.8) (2021-05-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* internal id not removed before row update ([0f10c9e](https://github.com/Fabio286/antares/commit/0f10c9e824b679cfebd8a31085236da3d38a7953))
|
||||
* unable to add new ENUM fields ([1e37f2a](https://github.com/Fabio286/antares/commit/1e37f2a96f660a36f2739fea57ca298059e9c31a))
|
||||
* unable to delete table rows ([0d61371](https://github.com/Fabio286/antares/commit/0d6137195da9e0e9afe68f6d3ebcd50e913a888e))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* improved the way how field default value are handled ([7a766f0](https://github.com/Fabio286/antares/commit/7a766f04e668868e8844aac1d7d445bc2da21558))
|
||||
|
||||
### [0.1.7](https://github.com/Fabio286/antares/compare/v0.1.6...v0.1.7) (2021-05-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* row loses internal id after cell update ([854472c](https://github.com/Fabio286/antares/commit/854472c7a3d8442b1eede12f978cad5aa684094e))
|
||||
* **MySQL:** connection loses schema in some conditions ([6b0b8b1](https://github.com/Fabio286/antares/commit/6b0b8b19d7176ef8647d6f401a33315f8732fdf3))
|
||||
* issue with ENUM and SET fields on table filler modal ([475397c](https://github.com/Fabio286/antares/commit/475397ca34c5b7d8925e2d97d32216d5b80d8211))
|
||||
* issue with ENUM and SET length when creating a new field ([7a62131](https://github.com/Fabio286/antares/commit/7a62131cc707aa1c98bb12513de76229472b7c38))
|
||||
* multiple row select on sorted tables not work properly ([c7663be](https://github.com/Fabio286/antares/commit/c7663be338ccb5ff31e241e824593531ad95bd32))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **core:** increased connection pool size to improve performance ([ae103e5](https://github.com/Fabio286/antares/commit/ae103e5477ad190c541d0f29e277c41de6947063))
|
||||
* **MySQL:** improved connections pool handling ([434711a](https://github.com/Fabio286/antares/commit/434711a360bd7539d2ec3adec92e7ccc96fa828e))
|
||||
|
||||
### [0.1.6](https://github.com/Fabio286/antares/compare/v0.1.5...v0.1.6) (2021-05-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **MySQL:** ENUM and SET fields support, closes [#61](https://github.com/Fabio286/antares/issues/61) ([bebba64](https://github.com/Fabio286/antares/commit/bebba64d06532990405763284e27cb768dc050f7))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* better detection and handling of field default type ([3baf6fa](https://github.com/Fabio286/antares/commit/3baf6fa1736a405c95fb02d17c11514861ca9e04))
|
||||
* no quotes around strings in field default custom value ([29e2d92](https://github.com/Fabio286/antares/commit/29e2d92b5bf66ff9e80bb1fee1274967c4418601))
|
||||
* **UI:** data type figure twice on type select ([9dfe7cc](https://github.com/Fabio286/antares/commit/9dfe7cca2234ce6de512bb4c21205c2217e7f765))
|
||||
* support to mDNS/zeroconf in snap build, closes [#58](https://github.com/Fabio286/antares/issues/58) ([35ef070](https://github.com/Fabio286/antares/commit/35ef070725f5779923bf3ad428b44accddf22dbe))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* italian translation updated ([5dc0b0b](https://github.com/Fabio286/antares/commit/5dc0b0bea40f9d5a5944af2f1c5dc6ff3e60c396))
|
||||
|
||||
### [0.1.5](https://github.com/Fabio286/antares/compare/v0.1.4...v0.1.5) (2021-04-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MySQL:** multiple queries non properly split in some cases ([5208ec1](https://github.com/Fabio286/antares/commit/5208ec171b44da0e6bfa93f15bfedd03ef2aa868))
|
||||
* % character not properly escaped, closes [#60](https://github.com/Fabio286/antares/issues/60) ([ecfb732](https://github.com/Fabio286/antares/commit/ecfb732c265a5485e131e75f3d20ff07d9409753))
|
||||
* semicolon inside strings breaks queries, closes [#59](https://github.com/Fabio286/antares/issues/59) ([1b09909](https://github.com/Fabio286/antares/commit/1b0990912627a3a4a4e8d62b4593f8a7aa3a7fe5))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **UI:** new application icon ([5822b3d](https://github.com/Fabio286/antares/commit/5822b3df432e0a2b305d0ff37a20dc466c3a3992))
|
||||
|
||||
### [0.1.4](https://github.com/Fabio286/antares/compare/v0.1.3...v0.1.4) (2021-04-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* query results export ([1d363f7](https://github.com/Fabio286/antares/commit/1d363f755e025d0fc6fec61cbd47ff87a8f25728))
|
||||
* **UI:** canc press to delete selected rows ([20cba6e](https://github.com/Fabio286/antares/commit/20cba6ee9bc1daa902b04d6e2ddcb31d04fbf805))
|
||||
* **UI:** ctrl+s shortcut to save changes ([16e17b3](https://github.com/Fabio286/antares/commit/16e17b39b6c8b561cc018d02afee2276190ce304))
|
||||
* **UI:** format and clear queries ([9ffd443](https://github.com/Fabio286/antares/commit/9ffd443a66303f88fc4529896f6d1d7917454f7a))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* launch from shortcut of procedures or functions with parameters without name dont works ([f82dbd2](https://github.com/Fabio286/antares/commit/f82dbd24dcef7b4d8d127a604e256b3f79a6c617))
|
||||
* wrong changelog in some cases ([a41cf1a](https://github.com/Fabio286/antares/commit/a41cf1ab5662f5f5fdedff4a9e1c626c23071377))
|
||||
* **UI:** data type not listed in selection if not present in global types ([6eb2977](https://github.com/Fabio286/antares/commit/6eb2977568987b9440b62ae7dbd7183338bfcc9b))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **UI:** improved connection status indicator ([5ceddb8](https://github.com/Fabio286/antares/commit/5ceddb8e00f3bc1984b8e47de270dc39b367903f))
|
||||
|
||||
### [0.1.3](https://github.com/Fabio286/antares/compare/v0.1.2...v0.1.3) (2021-04-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **PostgreSQL:** functions management ([cd31413](https://github.com/Fabio286/antares/commit/cd3141325681ea572c06b8998dd7bd334ceb3236))
|
||||
* **PostgreSQL:** procedure language select ([b33199e](https://github.com/Fabio286/antares/commit/b33199ea59c60b467601f333857494aa40adf4e8))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MySQL:** invalid JavaScript datetime values not shown ([dcccb54](https://github.com/Fabio286/antares/commit/dcccb544f9ec24ad693c9e81fb4bcfbdbb7cc4e1))
|
||||
* approximate row count shown for results less than 1000 ([a6b75ad](https://github.com/Fabio286/antares/commit/a6b75ad0dc0d884332464c277e8542b2698630b9))
|
||||
* field apparently loses index or foreign key on rename in table editor ([7d2ace9](https://github.com/Fabio286/antares/commit/7d2ace94562f8da307b15b83c89d919727d800c8))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **MySQL:** improved the way to get routine and functions parameters ([90fd9db](https://github.com/Fabio286/antares/commit/90fd9db917c40262f2bc2501ab86f5feba3d8db4))
|
||||
* **UI:** improved table fields suggestion in query editor ([c22187c](https://github.com/Fabio286/antares/commit/c22187c3053aef368a351cc35e2f1d407ecde209))
|
||||
|
||||
### [0.1.2](https://github.com/Fabio286/antares/compare/v0.1.1...v0.1.2) (2021-04-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* in-app last release changelog ([1e938ad](https://github.com/Fabio286/antares/commit/1e938adc5d8eb5ad16ab16342375eecd88f68d20))
|
||||
* **PostgreSQL:** edit timezone in cell editor ([8735a0c](https://github.com/Fabio286/antares/commit/8735a0c5f9e5b6b3bcaadf37ce158aa7beae2c48))
|
||||
* **PostgreSQL:** procedures management ([3dde1c1](https://github.com/Fabio286/antares/commit/3dde1c109e23342d94362626ef7350dc123ea859))
|
||||
* **PostgreSQL:** support of arrays in table settings ([d0b3e1b](https://github.com/Fabio286/antares/commit/d0b3e1b1b8be9d2c038d70e16d4478671315de8f))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* cell edit doesn't properly use primary or unique index to update if both present, closes [#51](https://github.com/Fabio286/antares/issues/51) ([55932fe](https://github.com/Fabio286/antares/commit/55932fe11583bd5ff48f82b8408965adba4f5071))
|
||||
* deletion of rows from query results ([c20bff7](https://github.com/Fabio286/antares/commit/c20bff7bcbe340ac99ebcacaba3359edd61c068a))
|
||||
* no foreign key select when cell value is NULL, closes [#50](https://github.com/Fabio286/antares/issues/50) ([9f5ec02](https://github.com/Fabio286/antares/commit/9f5ec0276c92904975fdaea34b4c845c92bfe8d4))
|
||||
* wrong datetime conversion when updating a row without an unique key ([d374372](https://github.com/Fabio286/antares/commit/d374372e208318d7e50b258a8041145bdf7992c5))
|
||||
* **PostgreSQL:** issue with selected schema different than public ([49a4e1c](https://github.com/Fabio286/antares/commit/49a4e1cb7b24642641265d5830d3fee370cceeb4))
|
||||
* **UI:** white readonly inputs with dark theme ([bb5f446](https://github.com/Fabio286/antares/commit/bb5f44681f87aacf2cd2f60a6d958c5732289790))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **UI:** improved setting modal rendering ([be816e8](https://github.com/Fabio286/antares/commit/be816e85888b4f3d26cbb9caac0adbc4dde0ea94))
|
||||
|
||||
### [0.1.1](https://github.com/Fabio286/antares/compare/v0.1.0...v0.1.1) (2021-04-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* scratchpad to save persistent notes ([e349dd5](https://github.com/Fabio286/antares/commit/e349dd5eaba608591257f2799b830805e4936c27))
|
||||
* **PostgreSQL:** foreign keys management ([fe4c8e1](https://github.com/Fabio286/antares/commit/fe4c8e12b39dd3cdfc233f07e3fe2ff0676252b0))
|
||||
* **PostgreSQL:** indexes management ([9ca03f4](https://github.com/Fabio286/antares/commit/9ca03f462560b634970a19d3d97b878d60509acc))
|
||||
* **PostgreSQL:** table fields edit ([e3f259c](https://github.com/Fabio286/antares/commit/e3f259c6e8327d71bd7dd0a9c33d957dc6ca1fb8))
|
||||
* **PostgreSQL:** tables addition ([feef5e3](https://github.com/Fabio286/antares/commit/feef5e30eec915cbb219223cc428bd4e98d2e9c5))
|
||||
* **PostgreSQL:** unique keys management ([614e0d3](https://github.com/Fabio286/antares/commit/614e0d32758c13b59139d349d4682a5bafc3ca88))
|
||||
* **PostgreSQL:** views management ([99f7511](https://github.com/Fabio286/antares/commit/99f7511c4d5fab4030b30d5134cd03248167faea))
|
||||
* **UI:** light theme ([2806976](https://github.com/Fabio286/antares/commit/280697698ea5fae6d54326970c823878888c196c))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **UI:** editor theme preview not properly loaded in some cases ([c981244](https://github.com/Fabio286/antares/commit/c981244d7aa93ca18ca2de44bf8df06f253b9d20))
|
||||
* fields of ref. table not automatically loaded in foreign keys modal ([e7401cc](https://github.com/Fabio286/antares/commit/e7401cc96e76e00100a88eea9f40541fd8027adb))
|
||||
* hide update tab for Windows Store distributions ([dcb135d](https://github.com/Fabio286/antares/commit/dcb135dd015b8f8c6cfb44021211bb8cf3089192))
|
||||
|
||||
## [0.1.0](https://github.com/Fabio286/antares/compare/v0.0.20...v0.1.0) (2021-03-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **PostgreSQL:** database in connection parameters ([9645702](https://github.com/Fabio286/antares/commit/964570247ff5b7b8317419730eec5bed4f0f0580))
|
||||
* **PostgreSQL:** edit array and text search fields ([fc65114](https://github.com/Fabio286/antares/commit/fc651149b95399c52d2d63e946731e9c1b0303a9))
|
||||
* **PostgreSQL:** insert and edit blob fields ([1f80a64](https://github.com/Fabio286/antares/commit/1f80a64fe1400baacca26f1a762c5aeb4ef6350d))
|
||||
* **PostgreSQL:** partial postgre implementation ([d892fa6](https://github.com/Fabio286/antares/commit/d892fa6fb3e86fbb96887d8eb67319ae855260a1))
|
||||
* **PostgreSQL:** support to microseconds ([d465e18](https://github.com/Fabio286/antares/commit/d465e18dba8ea3aa00726e33f9b1f70ca4c0683c))
|
||||
* **UI:** support to boolean fields ([ffb1712](https://github.com/Fabio286/antares/commit/ffb1712a593d1421793011e48a17369b884ea3c0))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update or delete rows with more than one primary key ([22a8c25](https://github.com/Fabio286/antares/commit/22a8c25717a4d4b285855426098a3a2846ce7448))
|
||||
* **MySQL:** handle NEWDECIMAL data type ([a1c6be3](https://github.com/Fabio286/antares/commit/a1c6be372b570cf13e89ef7ecf9b7a7c033a9293))
|
||||
* **PostgreSQL:** issue getting foreign keys informations ([db47b40](https://github.com/Fabio286/antares/commit/db47b4040a5282a6ac0711b1926c4c2ac867999e))
|
||||
* remove last char from datetime and time if is a dot ([e89911b](https://github.com/Fabio286/antares/commit/e89911b1851c19813d4acf2c79adfbc2ac7c1112))
|
||||
* **PostgreSQL:** single quote escape ([9f6a183](https://github.com/Fabio286/antares/commit/9f6a183d9b293dfe9ad9f3759f2375f05f37db8e))
|
||||
* **PostgreSQL:** various issues in query results ([fccfe92](https://github.com/Fabio286/antares/commit/fccfe92453325cd54c0331cc5670af0a56822c5b))
|
||||
* schema content not loaded if selected with right click ([8a6c59f](https://github.com/Fabio286/antares/commit/8a6c59f7ce7d051315b04cea38a96e4739b5b9d3))
|
||||
|
||||
### [0.0.20](https://github.com/Fabio286/antares/compare/v0.0.19...v0.0.20) (2021-03-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **UI:** loader layers on query and data tabs ([b232a3b](https://github.com/Fabio286/antares/commit/b232a3bb5ff7e38c83aa33e8b96ec7202bc4881e))
|
||||
* **UI:** row markers in sql editors ([ddfb713](https://github.com/Fabio286/antares/commit/ddfb7131248a47fa2055ccfb72e223986a17f986))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **UI:** avoid unnecessary updates when cell content not change ([e9a26c1](https://github.com/Fabio286/antares/commit/e9a26c1bc0641b7087d8143cc948405850d7552f))
|
||||
* **UI:** row mark not applied on first click ([25d72e3](https://github.com/Fabio286/antares/commit/25d72e39529884c09cf1286ff64ba00c8a5c7b24))
|
||||
* **UI:** table rows lose internal id after an update ([76c5c0c](https://github.com/Fabio286/antares/commit/76c5c0c680521b4a20de1f12bab25314ac084d5c))
|
||||
|
||||
### [0.0.19](https://github.com/Fabio286/antares/compare/v0.0.18...v0.0.19) (2021-03-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **UI:** modal that shows process query ([07f60c3](https://github.com/Fabio286/antares/commit/07f60c39173e9db452909d74573c3aecf4b6466b))
|
||||
* processes list tool ([049143d](https://github.com/Fabio286/antares/commit/049143d143d8187bfcb6377f2bf374c471c28046))
|
||||
* **MySQL:** support to new mysql8 authentication, closes [#45](https://github.com/Fabio286/antares/issues/45) ([db44306](https://github.com/Fabio286/antares/commit/db4430609e22816f4a3a8ecdbf61e9b51cde2579))
|
||||
* context menu shortcut to set NULL a table cell ([71b4310](https://github.com/Fabio286/antares/commit/71b43101172c27d810d533c936534bcf089dbca2))
|
||||
* **UI:** esc key to cancel cell edit ([45351fa](https://github.com/Fabio286/antares/commit/45351faeaea6ad4af8280da6c0ec1b4ded0f86fe))
|
||||
* setting to enable beta updates (future use) ([b1ea32b](https://github.com/Fabio286/antares/commit/b1ea32b68024593481120e2ef67642e127554888))
|
||||
* **UI:** query duration calc ([777b73f](https://github.com/Fabio286/antares/commit/777b73fa6f9d6f7806e0d3d07589dfaee0c40786))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MySQL:** wrong TIMESTAMP fields length ([201fad9](https://github.com/Fabio286/antares/commit/201fad9265e01e19ae4c8dc16bff84ee9dc9c894))
|
||||
* **UI:** modal processes list does not regain size on window resize ([5d7efa7](https://github.com/Fabio286/antares/commit/5d7efa75b76f59b85654603b4df8035a36af0576))
|
||||
* **UI:** wrong height in scrolling tables in some cases ([4862d51](https://github.com/Fabio286/antares/commit/4862d51fba863e055ca6735586b0abe4894037eb))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **UI:** big performance improvement in tables rendering ([39ca197](https://github.com/Fabio286/antares/commit/39ca1974bcea84c9047779893ed3f458300474e3))
|
||||
* **UI:** improvements of date time inputs ([b4ead69](https://github.com/Fabio286/antares/commit/b4ead6992c8ddc172380a97e7aa125e0dd078f1e))
|
||||
|
||||
### [0.0.18](https://github.com/Fabio286/antares/compare/v0.0.17...v0.0.18) (2021-02-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **UI:** context menu for input and textarea tags ([b54fefb](https://github.com/Fabio286/antares/commit/b54fefbf255c91f3cdd0c52b917b239ea40dae16))
|
||||
* **UI:** html, xml, json, svg and yaml editor modes in long text fields edit ([9a1bf32](https://github.com/Fabio286/antares/commit/9a1bf3212850d3783ac6382f8a980eb25c4f4226))
|
||||
* **UI:** run procedures/functions from sidebar context menu ([219da0a](https://github.com/Fabio286/antares/commit/219da0aba46839295807a01056199d42584e0af9))
|
||||
* **UI:** run routines/functions from settings tab ([7e81671](https://github.com/Fabio286/antares/commit/7e8167154ffd64e46d78a50cd13cf90cee61370c))
|
||||
* **UI:** search filter in explore bar ([2f58007](https://github.com/Fabio286/antares/commit/2f58007af4f32207fe4be9fc15bb43916e8e0abc))
|
||||
* **UI:** sticky schema names in explore bar ([110b0b4](https://github.com/Fabio286/antares/commit/110b0b414cef25395ae28493032c2048307e4783))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MySQL:** issue obtaining routine/function parameters ([3aa2159](https://github.com/Fabio286/antares/commit/3aa2159a1ae3b6785646fc7ac1e866cb0277d087))
|
||||
* issue managing function/routine parameters ([76d92cd](https://github.com/Fabio286/antares/commit/76d92cd106b0409e8752c43d7d587d09dd3e1e32))
|
||||
* **UI:** data tab opened when non-table element is selected ([dbab06f](https://github.com/Fabio286/antares/commit/dbab06fcb80ccdae85215ceae9ba24af3e7ff263))
|
||||
* **UI:** elements from previous selected schemas in query suggestions ([c8545a2](https://github.com/Fabio286/antares/commit/c8545a250bc696c339e418a69a2942d5bac10cbf))
|
||||
* disabled sort for fields without a name property ([3b37b74](https://github.com/Fabio286/antares/commit/3b37b7432e969e6315ebd6ed74905ec3980a049c))
|
||||
* prevents F5 shortcut to run in non-selected workspaces ([7c4ca99](https://github.com/Fabio286/antares/commit/7c4ca999ce64af8077d29e419f430d5beaaead93))
|
||||
* support of bit fields in table filler ([94c4952](https://github.com/Fabio286/antares/commit/94c49523197284510361ebbb6984816a3bb1b243))
|
||||
|
||||
### [0.0.17](https://github.com/Fabio286/antares/compare/v0.0.16...v0.0.17) (2021-02-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Added french language ([18a93ef](https://github.com/Fabio286/antares/commit/18a93ef1aae530e69c9062bbeb08a3beec206eda))
|
||||
* fake table data generator ([a176174](https://github.com/Fabio286/antares/commit/a176174b8d7dc232920f4cd7c5e3f8e4c58d51a0))
|
||||
* min and max option for random floats and numbers ([6c62052](https://github.com/Fabio286/antares/commit/6c62052b4764731c774fef90784342447b36deb7))
|
||||
* support to fake data locales ([970de49](https://github.com/Fabio286/antares/commit/970de4962b3bffec271bfa6d4747e6fb9d408ba6))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **UI:** file uploader in table filler ([b5a8283](https://github.com/Fabio286/antares/commit/b5a828309f067636eae2120032f07e01233706e2))
|
||||
* **UI:** no foreign key select editing query results ([5b21d17](https://github.com/Fabio286/antares/commit/5b21d17f3a8f2482c3aafe85f26c34cd3c0a1fcf))
|
||||
* cut faker text based on field length ([288ff4c](https://github.com/Fabio286/antares/commit/288ff4c1a1c77f4b8b86b24649d805836366fdd3))
|
||||
* wrong date or time detection in field default ([9d5ebef](https://github.com/Fabio286/antares/commit/9d5ebefdced999af595f3d8dc2fac2b18fa8258b))
|
||||
* **UI:** better text on ssl file selectors ([9a19085](https://github.com/Fabio286/antares/commit/9a190854fe3c73ed4e7f89545ff259e15ee9f947))
|
||||
* **UI:** wrong length for char fields on table header ([0f69d1d](https://github.com/Fabio286/antares/commit/0f69d1dbb7958e45059b6b738c845abea1ad3225))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **core:** bulk inserts support ([b0576ac](https://github.com/Fabio286/antares/commit/b0576acdf65d41c1c8e0b0bca6cf6522dcb372be))
|
||||
|
||||
### [0.0.16](https://github.com/Fabio286/antares/compare/v0.0.15...v0.0.16) (2021-02-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* MySQL and MariaDB auto detection ([02c03e3](https://github.com/Fabio286/antares/commit/02c03e3d266052872ac8edeb159ec8182f41c6a5))
|
||||
* **UI:** enanched file upload input ([a0d8552](https://github.com/Fabio286/antares/commit/a0d85520fb0669d5eec4475f5e255adb1e2a0159))
|
||||
* support to ssl connections ([4e72bb1](https://github.com/Fabio286/antares/commit/4e72bb15874324214aa0f5b89057cdd565744468))
|
||||
* **UI:** database version in app footer ([15417e8](https://github.com/Fabio286/antares/commit/15417e8a776c6aa9f90762d198d70b26163bb2df))
|
||||
* **UI:** resize query editor area ([88ab7c5](https://github.com/Fabio286/antares/commit/88ab7c5a62654c6a6026b63464224226d33b1950))
|
||||
* delete rows from tables without a primary key ([574d493](https://github.com/Fabio286/antares/commit/574d4939083577ffcb8e7c65f572c364eb8415fb))
|
||||
* edit rows from tables without a primary key ([5940b0b](https://github.com/Fabio286/antares/commit/5940b0b84207093da141b5c617c41e0a18fcb1d7))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* compatibility with electron-store 7 ([bacf458](https://github.com/Fabio286/antares/commit/bacf45893676cb0744907703f6534ffd472bd1dd))
|
||||
* edit bit fields ([ede6fe8](https://github.com/Fabio286/antares/commit/ede6fe81cefc91cdce2bbb0cd7cd6f85bbca99b8))
|
||||
|
||||
### [0.0.15](https://github.com/Fabio286/antares/compare/v0.0.14...v0.0.15) (2021-01-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* functions and schedulers in query suggestions ([8ff6e70](https://github.com/Fabio286/antares/commit/8ff6e70145ed2a207ae8b23a2c688258382a5d74))
|
||||
* loading animation in properties tabs ([1cf6485](https://github.com/Fabio286/antares/commit/1cf64858964f4894913db42f7c268013bb06e40b))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* error retriving dato of some schedulers ([b9ed8dd](https://github.com/Fabio286/antares/commit/b9ed8dd610e3be1489e01cf53f7d632cb1bd6ac5))
|
||||
* unable to call stored routines from query tabs ([4923128](https://github.com/Fabio286/antares/commit/4923128236131482ca948ae8052c294bd9269ed0))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* better fields type detection ([4bc9bbf](https://github.com/Fabio286/antares/commit/4bc9bbfb34ebdc51061f718cdf9cbca8507fa0f4))
|
||||
* big performance improvement in database structure loading ([a11bac5](https://github.com/Fabio286/antares/commit/a11bac504cd4ee865ea6c614a15ee809dc38202e))
|
||||
|
||||
### [0.0.14](https://github.com/Fabio286/antares/compare/v0.0.13...v0.0.14) (2021-01-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* export data tables to json or csv file ([0cbea9d](https://github.com/Fabio286/antares/commit/0cbea9d1007304a5b9cf893d165b4b4104266651))
|
||||
* functions creation ([49d7172](https://github.com/Fabio286/antares/commit/49d71722e26172232f7b54c6568e1e588ce0d049))
|
||||
* functions delete ([59a50bc](https://github.com/Fabio286/antares/commit/59a50bc014facc9643f9153cff61dc9d5a8605a9))
|
||||
* functions edit ([41d75b1](https://github.com/Fabio286/antares/commit/41d75b127cbcf1481fd259a14e6e7688638e18a4))
|
||||
* scheduler edit ([ceab4ef](https://github.com/Fabio286/antares/commit/ceab4ef243881ba64517fb95320844a21fce4849))
|
||||
* schedulers creation ([dbe7b9d](https://github.com/Fabio286/antares/commit/dbe7b9dd239248e806377ae6236b477456f175a3))
|
||||
* schedulers delete ([1e7d4ca](https://github.com/Fabio286/antares/commit/1e7d4ca347f4b9337ff266ec78bb4bbc6dd20d4d))
|
||||
* triggers and stored routines in sql suggestions ([e351c90](https://github.com/Fabio286/antares/commit/e351c903a8a8d7e908d6a7d54c0491438ac6f024))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* error with empty functions/procedures ([f150508](https://github.com/Fabio286/antares/commit/f1505085477a760a768a7d245c9517a858c1379c))
|
||||
* removed internal row _id from exported files ([c0a32c0](https://github.com/Fabio286/antares/commit/c0a32c040e653729ef80d580d6dd1796d1b2adcd))
|
||||
|
||||
### [0.0.13](https://github.com/Fabio286/antares/compare/v0.0.12...v0.0.13) (2021-01-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* option to toggle line wrap mode ([d94b49f](https://github.com/Fabio286/antares/commit/d94b49febf54b0200127859f2a8ed7ef591e56ab))
|
||||
* select definer in view creation/edit ([ab307f8](https://github.com/Fabio286/antares/commit/ab307f82b1d78c5f9571233090b6678a964bd674))
|
||||
* stored routines creation ([3bcd02f](https://github.com/Fabio286/antares/commit/3bcd02fc4ea9a4b780305212f906d6d78c7a8dae))
|
||||
* stored routines delete ([aa33850](https://github.com/Fabio286/antares/commit/aa3385028685417860b3ce985cc7a74f9da377ad))
|
||||
* stored routines edit ([82fdc0b](https://github.com/Fabio286/antares/commit/82fdc0bcd7514b321c1c9852a773adacf81baf87))
|
||||
* triggers creation ([d695c9f](https://github.com/Fabio286/antares/commit/d695c9f8d2418a6a4523a7a242fa1a8cba80e035))
|
||||
* triggers delete ([b32132a](https://github.com/Fabio286/antares/commit/b32132ad84d5798555b80eec3c624b681c37c339))
|
||||
* triggers edit ([3126625](https://github.com/Fabio286/antares/commit/3126625461f4b6d68d641b6b0eda8fcd390bb636))
|
||||
* views creation ([8c4aaec](https://github.com/Fabio286/antares/commit/8c4aaec167f58333a343b52927205b68137ad408))
|
||||
* views deletion ([dcf469e](https://github.com/Fabio286/antares/commit/dcf469ebed6252b4a496800206e0c34cd83b1f5e))
|
||||
* views edit ([56f2a27](https://github.com/Fabio286/antares/commit/56f2a27f0059cc10316204210db078a97408973c))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* breadcrumb not change after table rename ([b6b7be0](https://github.com/Fabio286/antares/commit/b6b7be098ad5ab4d55bfe05a7f862f045c1f54da))
|
||||
* unable to rename views ([b7053bd](https://github.com/Fabio286/antares/commit/b7053bdf8036d027e1685d6b5080d6b927a80e08))
|
||||
* wrong new stored routine modal icon ([0ec2710](https://github.com/Fabio286/antares/commit/0ec2710872c692b3feac076a3250d3b760af4009))
|
||||
* wrong or duplicate fields in some queries ([0df2b83](https://github.com/Fabio286/antares/commit/0df2b836b15436a2397d6a2202bd049b5cd53de4))
|
||||
|
||||
### [0.0.12](https://github.com/Fabio286/antares/compare/v0.0.11...v0.0.12) (2020-12-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* better security connections credentials storage ([fc35f27](https://github.com/Fabio286/antares/commit/fc35f271d7fe384cd786ce33547c0ef17135ddd8))
|
||||
* option to change editor theme ([a95b8d1](https://github.com/Fabio286/antares/commit/a95b8d188cfcc8f563ad73b4f0b676d068775d36))
|
||||
* option to toggle editor auto completion ([155154b](https://github.com/Fabio286/antares/commit/155154b43d0cd02ae875ded3ce865a37a999da5c))
|
||||
* query editor auto-completer for tables and columns ([cb1fce6](https://github.com/Fabio286/antares/commit/cb1fce6f998ea7332886820910e245ab19416a9d))
|
||||
|
||||
### [0.0.11](https://github.com/Fabio286/antares/compare/v0.0.10...v0.0.11) (2020-12-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* auto focus on first input in modals ([1476e89](https://github.com/Fabio286/antares/commit/1476e899d164562f12342ced8c76903b9bdcfa55))
|
||||
* foreign keys management ([206597e](https://github.com/Fabio286/antares/commit/206597e5b891e13e6f7635075bd11599355ab778))
|
||||
* improved data table sorts ([5712b80](https://github.com/Fabio286/antares/commit/5712b8002203b32027f0e820f98a61e2ec965e79))
|
||||
* query tabs auto focus ([f81312a](https://github.com/Fabio286/antares/commit/f81312aeb02ef55affd2ae9e81a9b4cb4c2e6da2))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* data tab sort not maintained at refresh ([15b08d7](https://github.com/Fabio286/antares/commit/15b08d7ea858cb28111c7b548af9a13b1bf0da91))
|
||||
* deletion of rows with non-numeric ID ([d385832](https://github.com/Fabio286/antares/commit/d38583262e672a2b47c5ad0aca0f13c129830a7b))
|
||||
* file field editor not show ([9291a7a](https://github.com/Fabio286/antares/commit/9291a7a7b41e7aeb9b65c7f32e496f523e482272))
|
||||
* improved changes dedection in props tab ([acebe43](https://github.com/Fabio286/antares/commit/acebe435ff6fa1581692fbf7ee1bc23b334e3947))
|
||||
* some properties do not reset after fields changes ([3ed5ea0](https://github.com/Fabio286/antares/commit/3ed5ea023e1852d724b2b59ab156f8722876f85b))
|
||||
* unable to switch tabs when no table selected ([c545815](https://github.com/Fabio286/antares/commit/c5458159d1e30cecf6615086c074d98b4b599637))
|
||||
* wrong field type detection ([5cfdc9b](https://github.com/Fabio286/antares/commit/5cfdc9b92d4b778a7863b02fd64e6445236c89bc))
|
||||
|
||||
### [0.0.10](https://github.com/Fabio286/antares/compare/v0.0.9...v0.0.10) (2020-12-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* approximate totals in table tata tab ([e95d29c](https://github.com/Fabio286/antares/commit/e95d29c7c37e24e7cc14b466f9b539fa667042c2))
|
||||
* create new tables ([e6602d1](https://github.com/Fabio286/antares/commit/e6602d1bfa9ca10c6bb078ee80ddc94fb338763d))
|
||||
* display all keys in properties tab ([27769f2](https://github.com/Fabio286/antares/commit/27769f204f731d20c7ba2f838c02b7c2f28fa0c3))
|
||||
* drop and truncate tables ([a4122b4](https://github.com/Fabio286/antares/commit/a4122b4eaaa5b30d97ba5a93df8c9d21c30bc40b))
|
||||
* index management ([41505bd](https://github.com/Fabio286/antares/commit/41505bde6547c0af3c3413248ad8a0d182838bb1))
|
||||
* tables options edit ([0805b96](https://github.com/Fabio286/antares/commit/0805b96a75e439a7d65e8341ecc86fa938679a9f))
|
||||
* unsaved changes reminder ([33d1fa2](https://github.com/Fabio286/antares/commit/33d1fa22905f477924292135b0dcfefe168ee641))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* index deletion issue ([f8cf90a](https://github.com/Fabio286/antares/commit/f8cf90a89e7367c95e164b7dc669506df392b700))
|
||||
* some problems with properties and data tabs when changing database from sidebar ([0fe7157](https://github.com/Fabio286/antares/commit/0fe71572a5e74c17a5c66237351bb0b02c33e824))
|
||||
* sqlEscaper function wrong quotes conversion ([dfb24c6](https://github.com/Fabio286/antares/commit/dfb24c65f3c395d78d27a2f29e9aa8eeb427cff7))
|
||||
|
||||
### [0.0.9](https://github.com/EStarium/antares/compare/v0.0.8...v0.0.9) (2020-11-13)
|
||||
|
||||
|
||||
|
107
CONTRIBUTING.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Contributors Guide
|
||||
|
||||
Antares SQL is an application based on [Electron.js](https://www.electronjs.org/) that uses [Vue.js](https://vuejs.org/) and [Spectre.css](https://picturepan2.github.io/spectre/) as frontend frameworks.
|
||||
For the build process it takes advantage of [electron-builder](https://www.electron.build/).
|
||||
This application uses [Vuex](https://vuex.vuejs.org/) as application state manager and [electron-store](https://github.com/sindresorhus/electron-store) to save the various settings on disc.
|
||||
This guide aims to provide useful information and guidelines to everyone wants to contribute with this open-source project.
|
||||
For every other question related to this project please [contact me](https://github.com/Fabio286).
|
||||
|
||||
## Project Structure
|
||||
|
||||
The main files of the application are located inside `src` folder and are groupped in three subfolders.
|
||||
|
||||
### `common`
|
||||
|
||||
This folder contains small libraries, classes and objects. The purpose of `common` folder is to group together utilities used by **renderer** and **main** processes.
|
||||
Noteworthy is the `customizations` folder that contains clients related customizations. Those settings are merged with `default.js` that lists every option.
|
||||
Client related customizations are stored on Vuex and can be accessed by `customizations` property of current workspace object, or importing `common/customizations`.
|
||||
|
||||
An use case of customizations object can be the following:
|
||||
|
||||
```js
|
||||
computed: {
|
||||
defaultEngine () {
|
||||
if (this.workspace.customizations.engines)
|
||||
return this.workspace.engines.find(engine => engine.isDefault).name;
|
||||
return '';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this case the computed property `defaultEngine` returns the default engine for MySQL client, or an empty string with PostgreSQL that doesn't have engines.
|
||||
Customization properties are also useful **if some features are ready for one client but not others**.
|
||||
|
||||
### `main`
|
||||
|
||||
Inside this folder are located all files required by main process.
|
||||
`ipc-handlers` subfolder includes all IPC handlers for events sent from renderer process.
|
||||
`libs` subfolder includes classes related to clients and **query and connection logics**.
|
||||
**Everything above client's class level should be "client agnostic"** with a neutral and uniformed api interface
|
||||
|
||||
### `renderer`
|
||||
|
||||
In this folder is located the structure of Vue frontend application.
|
||||
|
||||
## Build
|
||||
|
||||
The command to build Antares SQL locally is `npm run build:local`.
|
||||
`build` command (without `:local`) is used exclusively by the GitHub Action.
|
||||
|
||||
## Conventions
|
||||
|
||||
### Electron
|
||||
|
||||
- **kebab-case** for IPC event names.
|
||||
|
||||
### Vue
|
||||
|
||||
- **PascalCase** for file names (with .vue extension) and including components inside others (`<MyComponent/>`).
|
||||
- "**Base**" prefix for [base component names](https://vuejs.org/v2/style-guide/#Base-component-names-strongly-recommended).
|
||||
- "**The**" prefix for [single-instance component names](https://vuejs.org/v2/style-guide/#Single-instance-component-names-strongly-recommended).
|
||||
- [Tightly coupled component names](https://vuejs.org/v2/style-guide/#Tightly-coupled-component-names-strongly-recommended).
|
||||
- [Order of words in component names](https://vuejs.org/v2/style-guide/#Order-of-words-in-component-names-strongly-recommended).
|
||||
- **kebab-case** in templates for property and event names.
|
||||
|
||||
### Vuex
|
||||
|
||||
- **snake_case** for state names.
|
||||
- **camelCase** for getter and action names.
|
||||
- **SNAKE_CASE (all caps)** for mutation names.
|
||||
|
||||
### Code Style
|
||||
|
||||
The project includes [ESlint](https://eslint.org/) and [StyleLint](https://stylelint.io/) config files with style rules. I recommend to set the lint on-save option in your code editor.
|
||||
Alternatively you can launch following commands to lint the project.
|
||||
|
||||
Check if all the style rules have been followed:
|
||||
|
||||
```console
|
||||
npm run lint
|
||||
```
|
||||
|
||||
Apply style rules globally if possible:
|
||||
|
||||
```console
|
||||
npm run lint:fix
|
||||
```
|
||||
|
||||
### Other recommendations
|
||||
|
||||
Please, use if possible **template literals** to compose strings and **avoid unnecessary dependencies**.
|
||||
|
||||
### Commits
|
||||
|
||||
The commit style adopted for this project is [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
|
||||
Basicly it's important to have **single scoped commits with a prefix** that follows this style because Antares SQL uses [standard-version](https://github.com/conventional-changelog/standard-version) to generate new releases and [CHANGELOG.md](https://github.com/Fabio286/antares/blob/master/CHANGELOG.md) file to track all notable changes.
|
||||
For Visual Studio Code users may be useful [Conventional Commits](https://marketplace.visualstudio.com/items?itemName=vivaxy.vscode-conventional-commits) extension.
|
||||
|
||||
## Debug
|
||||
|
||||
**Debug mode**:
|
||||
|
||||
```console
|
||||
npm run debug
|
||||
```
|
||||
|
||||
After running the debug mode Antares will listen on port 9222 (main process) for a debugger.
|
||||
On **Visual Studio Code** just launch "*Electron: Main*" configurations after running Antares in debug mode.
|
144
README.md
@@ -1,71 +1,88 @@
|
||||
|
||||
<!-- markdownlint-disable -->
|
||||
<p align="center">
|
||||
<img width="800" src="https://raw.githubusercontent.com/Fabio286/antares/master/docs/screen-alpha.png">
|
||||
<img width="800" src="https://raw.githubusercontent.com/Fabio286/antares/master/docs/gh-logo.png">
|
||||
</p>
|
||||
<!-- markdownlint-restore -->
|
||||
|
||||
# Antares SQL Client
|
||||
|
||||
 [](https://travis-ci.com/EStarium/antares)  
|
||||
  [](https://actions-badge.atrox.dev/fabio286/antares/goto) [](https://snapcraft.io/antares) [](https://snapcraft.io/antares) [](https://twitter.com/AntaresSQL) [](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.
|
||||
My target is to support as many databases as possible, and all major operating systems, including the ARM versions.
|
||||
Our target is to support as many databases as possible, and all major operating systems, including the ARM versions.
|
||||
|
||||
**At the moment this application is an alpha, it lacks many features, and isn't ready as a main SQL client**. However i'm actively working on it (yes, i'm a lone dev), hoping to provide all essential features as soon as possible.
|
||||
**At the moment this application is in development state, many features will come in future updates**, and supports only MySQL/MariaDB, PostgreSQL and SQLite.
|
||||
At the moment, 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.
|
||||
|
||||
🔗 If you are curious to try this early state of Antares you can download and install the [latest release](https://github.com/EStarium/antares/releases).
|
||||
👁 To stay tuned for new releases watch this repo on **Release only** channel.
|
||||
🔗 If you are curious to try Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases/latest).
|
||||
👁 To stay tuned for new releases [follow Antares SQL](https://twitter.com/AntaresSQL) on Twitter.
|
||||
🌟 Don't forget to **leave a star** if you appreciate this project.
|
||||
|
||||
## Philosophy
|
||||
|
||||
Why am I developing an SQL client when there are a lot of them on the market?
|
||||
The main goal is to develop a totally free, full featured, cross platform and open source alternative, empowered by JavaScript's ecosystem.
|
||||
An application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons or submenu.
|
||||
|
||||
## How to contribute
|
||||
|
||||
- [Translate Antares](https://github.com/EStarium/antares/wiki/Translate-Antares)
|
||||
|
||||
## Current main features
|
||||
## Current key features
|
||||
|
||||
- Multiple database connections at same time.
|
||||
- Database management (add/edit/delete).
|
||||
- Tables fields management (add/edit/delete).
|
||||
- Tables content management (add/edit/delete).
|
||||
- Run queries on multiple tabs.
|
||||
- Query suggestions.
|
||||
- Native dark theme.
|
||||
- Multi language.
|
||||
- Auto updates.
|
||||
- Full tables management, including indexes and foreign keys.
|
||||
- Views, triggers, stored routines, functions and schedulers management (add/edit/delete).
|
||||
- A modern and friendly tab system; keep open every kind of tab you need in your workspace.
|
||||
- Fake table data filler to generate tons of data for test purpose.
|
||||
- Query suggestions and auto complete.
|
||||
- Query history: search through the last 1000 queries.
|
||||
- SSH tunnel support.
|
||||
- Manual commit mode.
|
||||
- Import and export database dumps.
|
||||
- Dark and light theme.
|
||||
- Editor themes.
|
||||
|
||||
## Philosophy
|
||||
|
||||
Why are we developing an SQL client when there are a lot of them on the market?
|
||||
The main goal is to develop a totally free, full featured, cross platform and open source alternative, empowered by JavaScript's ecosystem.
|
||||
A modern application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons, nested tabs or submenu; productivity comes first.
|
||||
|
||||
## Installation
|
||||
|
||||
Based on your operating system you can have one or more distribution formats to choose based on your preferences.
|
||||
Since Antares SQL is a free software we haven't a budget to spend in annual licenses or certificates. This can result that on some platforms you need some additional passages to install this app.
|
||||
|
||||
### Linux
|
||||
|
||||
On Linux you can simply download and run `.AppImage` distributions, install from Snap Store or from AUR.
|
||||
|
||||
### Windows
|
||||
|
||||
On Windows you can choose between Microsoft Store and download `.exe` distribution. The latter lacks of a certificate, so to install you need to click on "More info" and then "Run anyway" on SmartScreen prompt.
|
||||
|
||||
### MacOS
|
||||
|
||||
On macOS you can run `.dmg` distribution following [this guide](https://support.apple.com/guide/mac-help/mh40616/mac) to install apps from unknown developers.
|
||||
|
||||
## Download
|
||||
|
||||
[](https://snapcraft.io/antares) [](https://aur.archlinux.org/packages/antares-sql/) [](https://www.microsoft.com/p/antares-sql-client/9nhtb9sq51r1?cid=storebadge&ocid=badge&rtc=1&activetab=pivot:overviewtab)
|
||||
🚀 **[Other Downloads](https://github.com/Fabio286/antares/releases/latest)**
|
||||
|
||||
## Coming soon
|
||||
|
||||
This is a roadmap with major features will come in near future.
|
||||
|
||||
- Tables management (add/edit/delete).
|
||||
- Database tools.
|
||||
- Users management (add/edit/delete).
|
||||
- Stored procedures, views, schedulers and triggers support.
|
||||
- More secure password storage.
|
||||
- Database tools (variables, process list...).
|
||||
- Support for other databases.
|
||||
- Improvements of query editor area.
|
||||
- Improvements of query suggestions.
|
||||
- Query history.
|
||||
- More context menu shortcuts.
|
||||
- More keyboard shortcuts.
|
||||
- Query logs console.
|
||||
- Fake data filler.
|
||||
- Import/export and migration.
|
||||
- SSL and SSH tunnel support.
|
||||
- Themes.
|
||||
- Support for other databases.
|
||||
- Apple Silicon distribution
|
||||
|
||||
## Currently supported
|
||||
|
||||
### Databases
|
||||
|
||||
- [x] MySQL/MariaDB
|
||||
- [ ] PostgreSQL
|
||||
- [x] PostgreSQL
|
||||
- [x] SQLite
|
||||
- [ ] MSSQL
|
||||
- [ ] SQLite
|
||||
- [ ] OracleDB
|
||||
- [ ] More...
|
||||
|
||||
@@ -75,7 +92,7 @@ This is a roadmap with major features will come in near future.
|
||||
|
||||
- [x] Windows
|
||||
- [x] Linux
|
||||
- [x] MacOS (needs tests)
|
||||
- [x] MacOS
|
||||
|
||||
#### • ARM
|
||||
|
||||
@@ -83,8 +100,47 @@ This is a roadmap with major features will come in near future.
|
||||
- [x] Linux
|
||||
- [ ] MacOS
|
||||
|
||||
## Translations
|
||||
## How to contribute
|
||||
|
||||
[Giuseppe Gigliotti](https://github.com/ReverbOD) / [Italian Translation](https://github.com/EStarium/antares/pull/20)
|
||||
[Mohd-PH](https://github.com/Mohd-PH) / [Arabic Translation](https://github.com/EStarium/antares/pull/29)
|
||||
[hongkfui](https://github.com/hongkfui) / [Spanish Translation](https://github.com/EStarium/antares/pull/32)
|
||||
- 🌍 [Translate Antares](https://github.com/Fabio286/antares/wiki/Translate-Antares)
|
||||
- 📖 [Contributors Guide](https://github.com/Fabio286/antares/wiki/Contributors-Guide)
|
||||
- 🚧 [Project Board](https://github.com/users/Fabio286/projects/1)
|
||||
|
||||
## Contributors ✨
|
||||
|
||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="https://fabiodistasio.it/"><img src="https://avatars.githubusercontent.com/u/31471771?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fabio Di Stasio</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=Fabio286" title="Code">💻</a> <a href="#translation-Fabio286" title="Translation">🌍</a> <a href="https://github.com/Fabio286/antares/commits?author=Fabio286" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/giulioganci/"><img src="https://avatars.githubusercontent.com/u/4192159?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Giulio Ganci</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=toriphes" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://christianratz.de/"><img src="https://avatars.githubusercontent.com/u/2630316?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Christian Ratz</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=digitalgopnik" title="Code">💻</a> <a href="#translation-digitalgopnik" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://reverb6821.github.io/"><img src="https://avatars.githubusercontent.com/u/55198803?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Giuseppe Gigliotti</b></sub></a><br /><a href="#translation-reverb6821" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/Mohd-PH"><img src="https://avatars.githubusercontent.com/u/9362157?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mohd-PH</b></sub></a><br /><a href="#translation-Mohd-PH" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/hongkfui"><img src="https://avatars.githubusercontent.com/u/37477191?v=4?s=100" width="100px;" alt=""/><br /><sub><b>hongkfui</b></sub></a><br /><a href="#translation-hongkfui" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/MrAnyx"><img src="https://avatars.githubusercontent.com/u/44176707?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Robin</b></sub></a><br /><a href="#translation-MrAnyx" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/daeleduardo"><img src="https://avatars.githubusercontent.com/u/8599078?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Eduardo</b></sub></a><br /><a href="#translation-daeleduardo" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://ngoquocdat.com/"><img src="https://avatars.githubusercontent.com/u/56961917?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ngô Quốc Đạt</b></sub></a><br /><a href="#translation-datlechin" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/IsamuSugi"><img src="https://avatars.githubusercontent.com/u/7746658?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Isamu Sugiura</b></sub></a><br /><a href="#translation-IsamuSugi" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="http://rsacchetto.nexxontech.it/"><img src="https://avatars.githubusercontent.com/u/18429412?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Riccardo Sacchetto</b></sub></a><br /><a href="#platform-Occhioverde" title="Packaging/porting to new platform">📦</a></td>
|
||||
<td align="center"><a href="https://kilianstallinger.com"><img src="https://avatars.githubusercontent.com/u/5290318?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kilian Stallinger</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=kilianstallz" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/wenj91"><img src="https://avatars.githubusercontent.com/u/12549338?v=4?s=100" width="100px;" alt=""/><br /><sub><b>文杰</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=wenj91" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/goYou"><img src="https://avatars.githubusercontent.com/u/62732795?v=4?s=100" width="100px;" alt=""/><br /><sub><b>goYou</b></sub></a><br /><a href="#translation-goYou" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/raliqala"><img src="https://avatars.githubusercontent.com/u/30502407?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Topollo</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=raliqala" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/SmileYzn"><img src="https://avatars.githubusercontent.com/u/5851851?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cleverson</b></sub></a><br /><a href="#translation-SmileYzn" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
BIN
assets/appx/Square150x150Logo.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
assets/appx/Square44x44Logo.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
assets/appx/StoreLogo.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
assets/appx/Wide310x150Logo.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
assets/icon.icns
Normal file
BIN
assets/icon.ico
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
build/icon.icns
BIN
build/icon.ico
Before Width: | Height: | Size: 12 KiB |
77
docs/aur-badge.svg
Normal file
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
style="isolation:isolate" viewBox="0 0 182 56" width="182px" height="56px">
|
||||
<defs>
|
||||
<clipPath id="_clipPath_tR1uglJ1Zei76xP861DY1TsjAiQWS9qF">
|
||||
<rect width="182" height="56" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#_clipPath_tR1uglJ1Zei76xP861DY1TsjAiQWS9qF)">
|
||||
<path
|
||||
d="M 2.5 0.5 L 180.5 0.5 C 181.604 0.5 182.5 1.396 182.5 2.5 L 182.5 54.5 C 182.5 55.604 181.604 56.5 180.5 56.5 L 2.5 56.5 C 1.396 56.5 0.5 55.604 0.5 54.5 L 0.5 2.5 C 0.5 1.396 1.396 0.5 2.5 0.5 Z"
|
||||
style="stroke:none;fill:#252525;stroke-miterlimit:10;" />
|
||||
<path
|
||||
d="M 2.5 0.5 L 180.5 0.5 C 181.604 0.5 182.5 1.396 182.5 2.5 L 182.5 54.5 C 182.5 55.604 181.604 56.5 180.5 56.5 L 2.5 56.5 C 1.396 56.5 0.5 55.604 0.5 54.5 L 0.5 2.5 C 0.5 1.396 1.396 0.5 2.5 0.5 Z"
|
||||
style="fill:none;stroke:#CDCDCD;stroke-width:1;stroke-miterlimit:2;" />
|
||||
<g>
|
||||
<g>
|
||||
<path
|
||||
d=" M 60.898 13.777 C 58.555 13.774 56.61 14.254 55.858 14.516 L 55.083 18.697 C 55.081 18.712 58.937 17.669 60.635 17.73 C 63.447 17.831 63.706 18.805 63.656 20.119 C 63.704 20.196 62.931 18.931 60.498 18.889 C 57.43 18.836 53.098 19.976 53.104 24.608 C 53.022 29.818 56.997 31.351 59.704 31.379 C 62.138 31.335 63.279 30.458 63.904 29.988 C 64.726 29.129 65.665 28.265 66.561 27.229 C 65.714 28.77 64.978 29.835 64.213 30.651 L 64.213 31.339 L 67.913 30.716 L 67.938 20.66 C 67.901 19.237 68.754 13.791 60.898 13.777 Z M 60.367 22.533 C 61.9 22.554 63.659 23.31 63.662 25.129 C 63.669 26.784 61.589 27.674 60.235 27.66 C 58.881 27.646 57.085 26.596 57.077 24.982 C 57.103 23.54 58.771 22.496 60.367 22.533 Z "
|
||||
fill-rule="evenodd" fill="rgb(255,255,255)" />
|
||||
<path
|
||||
d=" M 70.378 14.707 L 70.352 31.36 L 74.662 30.529 L 74.67 21.087 C 74.671 19.681 76.679 18.039 79.198 18.065 C 79.733 17.097 80.738 14.625 80.983 14.062 C 75.354 14.049 75.282 15.68 74.303 16.483 C 74.293 14.952 74.3 14.033 74.3 14.033 L 70.378 14.707 L 70.378 14.707 Z "
|
||||
fill-rule="evenodd" fill="rgb(255,255,255)" />
|
||||
<path
|
||||
d=" M 94.632 16.893 C 94.591 16.873 92.385 14.312 87.949 14.292 C 83.795 14.223 79.135 15.834 79.061 22.8 C 79.097 28.925 83.537 31.318 87.973 31.365 C 92.72 31.414 94.609 28.396 94.722 28.322 C 94.156 27.83 92.034 25.728 92.034 25.728 C 92.034 25.728 90.709 27.615 88.138 27.639 C 85.566 27.664 83.331 25.651 83.299 22.844 C 83.266 20.036 85.354 18.515 88.157 18.392 C 90.584 18.392 91.984 19.959 91.984 19.959 L 94.632 16.893 L 94.632 16.893 Z "
|
||||
fill-rule="evenodd" fill="rgb(255,255,255)" />
|
||||
<path
|
||||
d=" M 100.065 8.879 L 95.996 9.835 L 96.026 31.526 L 100.034 30.802 L 100.08 20.595 C 100.089 19.524 101.628 17.88 104.2 17.933 C 106.658 17.958 107.207 19.571 107.201 19.775 L 107.272 31.592 L 111.224 30.894 L 111.239 18.363 C 111.265 17.157 108.598 14.61 104.311 14.592 C 102.273 14.596 101.145 15.057 100.571 15.397 C 99.588 16.156 98.466 16.883 97.362 17.811 C 98.382 16.501 99.239 15.595 100.075 14.921 L 100.065 8.879 L 100.065 8.879 Z "
|
||||
fill-rule="evenodd" fill="rgb(255,255,255)" />
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
d=" M 114.673 9.441 L 116.508 8.982 L 116.595 30.85 L 114.73 31.168 L 114.673 9.441 L 114.673 9.441 Z "
|
||||
fill-rule="evenodd" fill="rgb(23,147,209)" />
|
||||
<path
|
||||
d=" M 119.663 15.968 L 121.271 15.252 L 121.285 30.932 L 119.731 31.253 L 119.663 15.968 L 119.663 15.968 Z M 119.28 10.314 L 120.577 9.255 L 121.655 10.454 L 120.357 11.54 L 119.28 10.314 L 119.28 10.314 Z "
|
||||
fill-rule="evenodd" fill="rgb(23,147,209)" />
|
||||
<path
|
||||
d=" M 124.296 15.682 L 126.131 15.308 L 126.14 18.586 C 126.14 18.727 127.148 14.924 132.008 15.009 C 136.727 15.035 137.499 18.688 137.473 19.507 L 137.531 31.034 L 135.922 31.384 L 135.913 19.998 C 135.932 19.665 135.178 16.853 131.843 16.843 C 128.509 16.833 126.199 19.265 126.203 20.818 L 126.229 30.848 L 124.365 31.335 L 124.296 15.682 L 124.296 15.682 Z "
|
||||
fill-rule="evenodd" fill="rgb(23,147,209)" />
|
||||
<path
|
||||
d=" M 153.547 31.117 L 151.711 31.492 L 151.703 28.214 C 151.703 28.073 150.694 31.876 145.835 31.791 C 141.116 31.765 140.344 28.112 140.37 27.293 L 140.311 15.765 L 142.261 15.372 L 142.292 26.758 C 142.292 27.069 142.665 29.947 145.999 29.957 C 149.334 29.967 151.669 27.949 151.686 24.911 L 151.662 15.928 L 153.477 15.464 L 153.547 31.117 L 153.547 31.117 Z "
|
||||
fill-rule="evenodd" fill="rgb(23,147,209)" />
|
||||
<path
|
||||
d=" M 157.144 15.553 L 155.857 16.56 L 160.792 23.018 L 155.529 30.478 L 156.894 31.492 L 161.841 24.563 L 166.948 31.656 L 168.211 30.649 L 162.738 23.065 L 167.104 16.933 L 165.762 15.797 L 161.785 21.472 L 157.144 15.553 L 157.144 15.553 Z "
|
||||
fill-rule="evenodd" fill="rgb(23,147,209)" />
|
||||
</g>
|
||||
<path
|
||||
d=" M 33.112 3.879 C 30.965 9.143 29.67 12.587 27.279 17.695 C 28.745 19.248 30.544 21.058 33.466 23.101 C 30.325 21.809 28.182 20.511 26.581 19.164 C 23.521 25.549 18.728 34.643 9 52.121 C 16.645 47.707 22.572 44.986 28.095 43.948 C 27.858 42.928 27.723 41.824 27.733 40.673 L 27.742 40.428 C 27.863 35.53 30.411 31.763 33.43 32.019 C 36.448 32.274 38.794 36.455 38.673 41.353 C 38.65 42.275 38.546 43.162 38.364 43.984 C 43.828 45.053 49.691 47.767 57.233 52.121 C 55.746 49.383 54.419 46.915 53.151 44.565 C 51.154 43.017 49.072 41.003 44.823 38.822 C 47.743 39.581 49.834 40.456 51.464 41.435 C 38.575 17.439 37.532 14.251 33.112 3.879 Z "
|
||||
fill-rule="evenodd" fill="rgb(23,147,209)" />
|
||||
<g>
|
||||
<path
|
||||
d=" M 170.614 30.156 L 170.614 28.802 L 170.109 28.802 L 170.109 28.621 L 171.325 28.621 L 171.325 28.802 L 170.817 28.802 L 170.817 30.156 L 170.614 30.156 Z "
|
||||
fill="rgb(23,147,209)" />
|
||||
<path
|
||||
d=" M 171.536 30.156 L 171.536 28.621 L 171.842 28.621 L 172.205 29.708 C 172.238 29.809 172.263 29.884 172.278 29.935 C 172.295 29.879 172.323 29.797 172.36 29.689 L 172.727 28.621 L 173 28.621 L 173 30.156 L 172.804 30.156 L 172.804 28.871 L 172.358 30.156 L 172.175 30.156 L 171.732 28.849 L 171.732 30.156 L 171.536 30.156 Z "
|
||||
fill="rgb(23,147,209)" />
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
d=" M 57.471 47.815 L 57.471 46.493 L 56.977 46.493 L 56.977 46.316 L 58.166 46.316 L 58.166 46.493 L 57.67 46.493 L 57.67 47.815 L 57.471 47.815 Z "
|
||||
fill="rgb(23,147,209)" />
|
||||
<path
|
||||
d=" M 58.372 47.815 L 58.372 46.316 L 58.671 46.316 L 59.026 47.377 C 59.059 47.476 59.083 47.55 59.098 47.599 C 59.115 47.545 59.141 47.465 59.177 47.359 L 59.536 46.316 L 59.803 46.316 L 59.803 47.815 L 59.612 47.815 L 59.612 46.56 L 59.176 47.815 L 58.997 47.815 L 58.564 46.539 L 58.564 47.815 L 58.372 47.815"
|
||||
fill="rgb(23,147,209)" />
|
||||
</g>
|
||||
</g>
|
||||
<g clip-path="url(#_clipPath_NvFIpNfWUS6M4fZAtfyVzggsKR3URDoi)"><text transform="matrix(1,0,0,1,87.023,43.899)"
|
||||
style="font-family:'Open Sans';font-weight:700;font-size:11px;font-style:normal;fill:#ffffff;stroke:none;">user
|
||||
repository</text></g>
|
||||
<defs>
|
||||
<clipPath id="_clipPath_NvFIpNfWUS6M4fZAtfyVzggsKR3URDoi">
|
||||
<rect x="0" y="0" width="86" height="14.98" transform="matrix(1,0,0,1,87,32.142)" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 7.5 KiB |
BIN
docs/gh-logo-2.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
docs/gh-logo.png
Normal file
After Width: | Height: | Size: 304 KiB |
BIN
docs/logo.png
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 172 KiB |
191
package.json
@@ -1,24 +1,93 @@
|
||||
{
|
||||
"name": "antares",
|
||||
"productName": "Antares",
|
||||
"version": "0.0.9",
|
||||
"description": "A cross-platform easy to use SQL client.",
|
||||
"version": "0.5.1",
|
||||
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/EStarium/antares.git",
|
||||
"repository": "https://github.com/Fabio286/antares.git",
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development electron-webpack dev",
|
||||
"compile": "electron-webpack",
|
||||
"build": "cross-env NODE_ENV=production npm run compile && electron-builder",
|
||||
"debug": "npm run rebuild:electron && npm run debug-runner",
|
||||
"debug-runner": "node scripts/devRunner.js --remote-debug",
|
||||
"compile": "npm run compile:main && npm run compile:workers && npm run compile:renderer",
|
||||
"compile:main": "webpack --mode=production --config webpack.main.config.js",
|
||||
"compile:workers": "webpack --mode=production --config webpack.workers.config.js",
|
||||
"compile:renderer": "webpack --mode=production --config webpack.renderer.config.js",
|
||||
"build": "cross-env NODE_ENV=production npm run compile",
|
||||
"build:local": "npm run build && electron-builder",
|
||||
"build:appx": "npm run build:local -- --win appx",
|
||||
"rebuild:electron": "rimraf ./dist && npm run postinstall",
|
||||
"release": "standard-version",
|
||||
"release:pre": "npm run release -- --prerelease alpha",
|
||||
"test": "npm run lint",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"test": "npm run compile && node tests/app.spec.js",
|
||||
"lint": "eslint . --ext .js,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
|
||||
"lint:fix": "eslint . --ext .js,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix"
|
||||
"lint:fix": "eslint . --ext .js,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
|
||||
"contributors:add": "all-contributors add",
|
||||
"contributors:generate": "all-contributors generate"
|
||||
},
|
||||
"author": "Fabio Di Stasio <fabio286@gmail.com>",
|
||||
"main": "./dist/main.js",
|
||||
"build": {
|
||||
"appId": "com.estarium.antares",
|
||||
"appId": "com.fabio286.antares",
|
||||
"artifactName": "${productName}-${version}-${os}_${arch}.${ext}",
|
||||
"asar": true,
|
||||
"buildDependenciesFromSource": true,
|
||||
"directories": {
|
||||
"output": "build",
|
||||
"buildResources": "assets"
|
||||
},
|
||||
"asarUnpack": "**\\*.{node,dll}",
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"node_modules",
|
||||
"package.json"
|
||||
],
|
||||
"win": {
|
||||
"target": [
|
||||
"nsis",
|
||||
"portable"
|
||||
]
|
||||
},
|
||||
"mac": {
|
||||
"target": {
|
||||
"target": "default",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
{
|
||||
"target": "deb",
|
||||
"arch": "x64"
|
||||
},
|
||||
{
|
||||
"target": "AppImage",
|
||||
"arch": [
|
||||
"x64",
|
||||
"armv7l",
|
||||
"arm64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": "Development"
|
||||
},
|
||||
"appImage": {
|
||||
"license": "./LICENSE",
|
||||
"category": "Development"
|
||||
},
|
||||
"portable": {
|
||||
"artifactName": "${productName}-${version}-portable.exe"
|
||||
},
|
||||
"appx": {
|
||||
"displayName": "Antares SQL",
|
||||
"backgroundColor": "transparent",
|
||||
"showNameOnTiles": true,
|
||||
"identityName": "62514FabioDiStasio.AntaresSQLClient",
|
||||
"publisher": "CN=1A2729ED-865C-41D2-9038-39AE2A63AA52",
|
||||
"applicationId": "FabioDiStasio.AntaresSQLClient"
|
||||
},
|
||||
"dmg": {
|
||||
"contents": [
|
||||
{
|
||||
@@ -32,62 +101,72 @@
|
||||
"path": "/Applications"
|
||||
}
|
||||
]
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
"deb",
|
||||
"AppImage"
|
||||
],
|
||||
"category": "Development"
|
||||
}
|
||||
},
|
||||
"electronWebpack": {
|
||||
"renderer": {
|
||||
"webpackConfig": "webpack.config.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/font": "^5.8.55",
|
||||
"electron-log": "^4.3.0",
|
||||
"electron-updater": "^4.3.5",
|
||||
"lodash": "^4.17.20",
|
||||
"@electron/remote": "^2.0.1",
|
||||
"@mdi/font": "^6.1.95",
|
||||
"@turf/helpers": "^6.5.0",
|
||||
"@vscode/vscode-languagedetection": "^1.0.21",
|
||||
"ace-builds": "^1.4.13",
|
||||
"better-sqlite3": "^7.5.0",
|
||||
"electron-log": "^4.4.1",
|
||||
"electron-store": "^8.0.1",
|
||||
"electron-updater": "^4.6.1",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"faker": "^5.5.3",
|
||||
"leaflet": "^1.7.1",
|
||||
"marked": "^4.0.0",
|
||||
"moment": "^2.29.1",
|
||||
"monaco-editor": "^0.20.0",
|
||||
"mssql": "^6.2.3",
|
||||
"mysql": "^2.18.1",
|
||||
"pg": "^8.5.1",
|
||||
"source-map-support": "^0.5.16",
|
||||
"mysql2": "^2.3.2",
|
||||
"pg": "^8.7.1",
|
||||
"pgsql-ast-parser": "^7.2.1",
|
||||
"source-map-support": "^0.5.20",
|
||||
"spectre.css": "^0.5.9",
|
||||
"vue-i18n": "^8.22.1",
|
||||
"vue-the-mask": "^0.11.1",
|
||||
"sql-formatter": "^4.0.2",
|
||||
"ssh2-promise": "^1.0.2",
|
||||
"v-mask": "^2.3.0",
|
||||
"vue-i18n": "^8.26.5",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuex": "^3.5.1",
|
||||
"vuex-persist": "^3.1.3"
|
||||
"vuex": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.1.0",
|
||||
"@babel/eslint-parser": "^7.15.7",
|
||||
"@babel/preset-env": "^7.15.8",
|
||||
"all-contributors-cli": "^6.20.0",
|
||||
"babel-loader": "^8.2.3",
|
||||
"chalk": "^4.1.2",
|
||||
"cross-env": "^7.0.2",
|
||||
"electron": "^10.1.5",
|
||||
"electron-builder": "^22.9.1",
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"electron-webpack": "^2.8.2",
|
||||
"electron-webpack-vue": "^2.4.0",
|
||||
"eslint": "^7.13.0",
|
||||
"eslint-config-standard": "^16.0.1",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"css-loader": "^6.5.0",
|
||||
"electron": "^17.0.1",
|
||||
"electron-builder": "^22.14.11",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.1.0",
|
||||
"eslint-plugin-vue": "^7.1.0",
|
||||
"monaco-editor-webpack-plugin": "^1.9.1",
|
||||
"node-sass": "^5.0.0",
|
||||
"sass-loader": "^10.1.0",
|
||||
"standard-version": "^9.0.0",
|
||||
"stylelint": "^13.7.2",
|
||||
"stylelint-config-standard": "^20.0.0",
|
||||
"stylelint-scss": "^3.18.0",
|
||||
"vue": "^2.6.12",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"webpack": "^4.44.2"
|
||||
"eslint-plugin-promise": "^5.2.0",
|
||||
"eslint-plugin-vue": "^8.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"mini-css-extract-plugin": "~2.4.5",
|
||||
"node-loader": "^2.0.0",
|
||||
"playwright": "^1.18.1",
|
||||
"progress-webpack-plugin": "^1.0.12",
|
||||
"rimraf": "^3.0.2",
|
||||
"sass": "^1.42.1",
|
||||
"sass-loader": "^12.3.0",
|
||||
"standard-version": "^9.3.1",
|
||||
"style-loader": "^3.3.1",
|
||||
"stylelint": "^13.13.1",
|
||||
"stylelint-config-standard": "^22.0.0",
|
||||
"stylelint-scss": "^3.21.0",
|
||||
"tree-kill": "^1.2.2",
|
||||
"vue": "^2.6.14",
|
||||
"vue-loader": "^15.9.8",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"webpack": "^5.60.0",
|
||||
"webpack-cli": "^4.9.1",
|
||||
"webpack-dev-server": "^4.4.0"
|
||||
}
|
||||
}
|
||||
|
131
scripts/devRunner.js
Normal file
@@ -0,0 +1,131 @@
|
||||
process.env.NODE_ENV = 'development';
|
||||
// process.env.ELECTRON_ENABLE_LOGGING = true
|
||||
|
||||
const chalk = require('chalk');
|
||||
const electron = require('electron');
|
||||
const webpack = require('webpack');
|
||||
const WebpackDevServer = require('webpack-dev-server');
|
||||
const kill = require('tree-kill');
|
||||
|
||||
const path = require('path');
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
const mainConfig = require('../webpack.main.config');
|
||||
const rendererConfig = require('../webpack.renderer.config');
|
||||
const workersConfig = require('../webpack.workers.config');
|
||||
|
||||
let electronProcess = null;
|
||||
let manualRestart = null;
|
||||
const remoteDebugging = process.argv.includes('--remote-debug');
|
||||
|
||||
if (remoteDebugging) {
|
||||
// disable dvtools open in electron
|
||||
process.env.RENDERER_REMOTE_DEBUGGING = true;
|
||||
}
|
||||
|
||||
async function killElectron (pid) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (pid) {
|
||||
kill(pid, 'SIGKILL', err => {
|
||||
if (err) reject(err);
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
else
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
async function restartElectron () {
|
||||
console.log(chalk.gray('\nStarting electron...'));
|
||||
|
||||
const { pid } = electronProcess || {};
|
||||
await killElectron(pid);
|
||||
|
||||
electronProcess = spawn(electron, [
|
||||
path.join(__dirname, '../dist/main.js'),
|
||||
// '--enable-logging', // Enable to show logs from all electron processes
|
||||
remoteDebugging ? '--inspect=9222' : '',
|
||||
remoteDebugging ? '--remote-debugging-port=9223' : ''
|
||||
]);
|
||||
|
||||
electronProcess.stdout.on('data', data => {
|
||||
console.log(chalk.white(data.toString()));
|
||||
});
|
||||
|
||||
electronProcess.stderr.on('data', data => {
|
||||
console.error(chalk.red(data.toString()));
|
||||
});
|
||||
|
||||
electronProcess.on('exit', (code, signal) => {
|
||||
if (!manualRestart) process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
function startMain () {
|
||||
const webpackSetup = webpack([mainConfig, workersConfig]);
|
||||
|
||||
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 => {
|
||||
if (err) console.error(chalk.red(err));
|
||||
});
|
||||
}
|
||||
|
||||
function startRenderer (callback) {
|
||||
const compiler = webpack(rendererConfig);
|
||||
const { name } = compiler;
|
||||
|
||||
compiler.hooks.afterEmit.tap('afterEmit', () => {
|
||||
console.log(chalk.gray(`\nCompiled ${name} script!`));
|
||||
console.log(chalk.gray(`\nWatching file changes for ${name} script...`));
|
||||
});
|
||||
|
||||
const server = new WebpackDevServer(compiler, {
|
||||
hot: true,
|
||||
port: 9080,
|
||||
client: {
|
||||
overlay: true,
|
||||
logging: 'warn'
|
||||
}
|
||||
});
|
||||
|
||||
server.startCallback(err => {
|
||||
if (err) console.error(chalk.red(err));
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
startRenderer(startMain);
|
161
snap/snapcraft.yaml
Normal file
@@ -0,0 +1,161 @@
|
||||
name: antares
|
||||
adopt-info: antares
|
||||
summary: Open source SQL client made to be simple and complete.
|
||||
description: |
|
||||
Antares is an SQL client that aims to become an useful and complete tool, especially for developers.
|
||||
The target is to support as many databases as possible, and all major operating systems, including the ARM versions.
|
||||
base: core20
|
||||
|
||||
grade: stable
|
||||
confinement: strict
|
||||
|
||||
architectures:
|
||||
- build-on: amd64
|
||||
compression: lzo
|
||||
layout:
|
||||
/etc/nsswitch.conf:
|
||||
bind-file: $SNAP/etc/nsswitch.conf
|
||||
|
||||
parts:
|
||||
antares:
|
||||
plugin: dump
|
||||
source: .
|
||||
override-build: |
|
||||
snapcraftctl build
|
||||
ARCHITECTURE=$(dpkg --print-architecture)
|
||||
if [ "${ARCHITECTURE}" = "amd64" ]; then
|
||||
FILTER="amd64.deb"
|
||||
else
|
||||
echo "ERROR! Antares only produces debs for amd64. Failing the build here."
|
||||
exit 1
|
||||
fi
|
||||
# Get the latest releases json
|
||||
echo "Get GitHub releases..."
|
||||
wget --quiet https://api.github.com/repos/fabio286/antares/releases/latest -O releases.json
|
||||
# Get the version from the tag_name and the download URL.
|
||||
VERSION=$(jq . releases.json | grep tag_name | cut -d'"' -f4 | sed s'/release-//')
|
||||
DEB_URL=$(cat releases.json | jq -r ".assets[] | select(.name | test(\"${FILTER}\")) | .browser_download_url")
|
||||
DEB=$(basename "${DEB_URL}")
|
||||
echo "Downloading ${DEB_URL}..."
|
||||
wget --quiet "${DEB_URL}" -O "${SNAPCRAFT_PART_INSTALL}/${DEB}"
|
||||
echo "Unpacking ${DEB}..."
|
||||
dpkg -x "${SNAPCRAFT_PART_INSTALL}/${DEB}" ${SNAPCRAFT_PART_INSTALL}
|
||||
rm -f releases.json 2>/dev/null
|
||||
rm -f "${SNAPCRAFT_PART_INSTALL}/${DEB}" 2>/dev/null
|
||||
echo $VERSION > $SNAPCRAFT_STAGE/version
|
||||
# Correct path to icon.
|
||||
sed -i 's|Icon=antares|Icon=/usr/share/icons/hicolor/256x256/apps/antares\.png|g' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/antares.desktop
|
||||
# Delete usr/bin/antares, it's a broken symlink pointing outside the snap.
|
||||
rm -f ${SNAPCRAFT_PART_INSTALL}/usr/bin/antares
|
||||
chmod -s ${SNAPCRAFT_PART_INSTALL}/opt/Antares/chrome-sandbox
|
||||
snapcraftctl set-version "$(echo $VERSION)"
|
||||
build-packages:
|
||||
- dpkg
|
||||
- jq
|
||||
- sed
|
||||
- wget
|
||||
stage-packages:
|
||||
- fcitx-frontend-gtk3
|
||||
- libappindicator3-1
|
||||
- libasound2
|
||||
- libcurl4
|
||||
- libgconf-2-4
|
||||
- libgtk-3-0
|
||||
- libnotify4
|
||||
- libnspr4
|
||||
- libnss3
|
||||
- libpcre3
|
||||
- libpulse0
|
||||
- libxss1
|
||||
- libsecret-1-0
|
||||
- libxtst6
|
||||
- libxkbfile1
|
||||
- gcc-10-base
|
||||
- libapparmor1
|
||||
- libblkid1
|
||||
- libbsd0
|
||||
- libcom-err2
|
||||
- libcrypt1
|
||||
- libdb5.3
|
||||
- libdbus-1-3
|
||||
- libexpat1
|
||||
- libffi7
|
||||
- libgcc-s1
|
||||
- libgcrypt20
|
||||
- libglib2.0-0
|
||||
- libgmp10
|
||||
- libgnutls30
|
||||
- libgpg-error0
|
||||
- libgssapi-krb5-2
|
||||
- libhogweed5
|
||||
- libidn2-0
|
||||
- libjson-c4
|
||||
- libk5crypto3
|
||||
- libkeyutils1
|
||||
- libkrb5-3
|
||||
- libkrb5support0
|
||||
- liblz4-1
|
||||
- liblzma5
|
||||
- libmount1
|
||||
- libnettle7
|
||||
- libp11-kit0
|
||||
- libpcre2-8-0
|
||||
- libselinux1
|
||||
- libsqlite3-0
|
||||
- libssl1.1
|
||||
- libstdc++6
|
||||
- libsystemd0
|
||||
- libtasn1-6
|
||||
- libudev1
|
||||
- libunistring2
|
||||
- libuuid1
|
||||
- libwrap0
|
||||
- libzstd1
|
||||
- zlib1g
|
||||
- libx11-xcb1
|
||||
- libdrm2
|
||||
- libgbm1
|
||||
- libxcb-dri3-0
|
||||
cleanup:
|
||||
after: [antares]
|
||||
plugin: nil
|
||||
build-snaps: [gnome-3-38-2004]
|
||||
override-prime: |
|
||||
set -eux
|
||||
cd /snap/gnome-3-38-2004/current
|
||||
find . -type f,l -exec rm -f $SNAPCRAFT_PRIME/{} \;
|
||||
|
||||
mdns-lookup:
|
||||
# Make resolution of ".local" host names (Zero-Conf/mDNS/DNS-SD)
|
||||
# working: Take the original nsswitch.conf file from the base
|
||||
# Snap and add "mdns4_minimal [NOTFOUND=return]" to its "hosts:" line
|
||||
# Also install corresponding mdns4_minimal plug-in
|
||||
# See: https://forum.snapcraft.io/t/no-mdns-support-in-snaps-should-core-have-a-modified-nsswitch-conf/
|
||||
plugin: nil
|
||||
stage-packages:
|
||||
- libnss-mdns
|
||||
override-prime: |
|
||||
set -eux
|
||||
sed -Ee 's/^\s*hosts:(\s+)files/hosts:\1files mdns4_minimal \[NOTFOUND=return\]/' /snap/core20/current/etc/nsswitch.conf > $SNAPCRAFT_STAGE/etc/nsswitch.conf
|
||||
snapcraftctl prime
|
||||
prime:
|
||||
- lib/$SNAPCRAFT_ARCH_TRIPLET/libnss_mdns4_minimal*
|
||||
- etc/nsswitch.conf
|
||||
|
||||
apps:
|
||||
antares:
|
||||
command: opt/Antares/antares --no-sandbox
|
||||
desktop: usr/share/applications/antares.desktop
|
||||
extensions: [gnome-3-38]
|
||||
environment:
|
||||
# Fallback to XWayland if running in a Wayland session.
|
||||
DISABLE_WAYLAND: 1
|
||||
plugs:
|
||||
- browser-support
|
||||
- cups-control
|
||||
- home
|
||||
- network
|
||||
- opengl
|
||||
- pulseaudio
|
||||
- removable-media
|
||||
- unity7
|
217
src/common/FakerMethods.js
Normal file
@@ -0,0 +1,217 @@
|
||||
export default class {
|
||||
static get _methods () {
|
||||
return [
|
||||
{ name: 'zipCode', group: 'address', types: ['string'] },
|
||||
{ name: 'zipCodeByState', group: 'address', types: ['string'] },
|
||||
{ name: 'city', group: 'address', types: ['string'] },
|
||||
{ name: 'cityPrefix', group: 'address', types: ['string'] },
|
||||
{ name: 'citySuffix', group: 'address', types: ['string'] },
|
||||
{ name: 'streetName', group: 'address', types: ['string'] },
|
||||
{ name: 'streetAddress', group: 'address', types: ['string'] },
|
||||
{ name: 'streetSuffix', group: 'address', types: ['string'] },
|
||||
{ name: 'streetPrefix', group: 'address', types: ['string'] },
|
||||
{ name: 'secondaryAddress', group: 'address', types: ['string'] },
|
||||
{ name: 'county', group: 'address', types: ['string'] },
|
||||
{ name: 'country', group: 'address', types: ['string'] },
|
||||
{ name: 'countryCode', group: 'address', types: ['string'] },
|
||||
{ name: 'state', group: 'address', types: ['string'] },
|
||||
{ name: 'stateAbbr', group: 'address', types: ['string'] },
|
||||
{ name: 'latitude', group: 'address', types: ['string'] },
|
||||
{ name: 'longitude', group: 'address', types: ['string'] },
|
||||
{ name: 'direction', group: 'address', types: ['string'] },
|
||||
{ name: 'cardinalDirection', group: 'address', types: ['string'] },
|
||||
{ name: 'ordinalDirection', group: 'address', types: ['string'] },
|
||||
// { name: 'nearbyGPSCoordinate', group: 'address', types: ['string'] },
|
||||
{ name: 'timeZone', group: 'address', types: ['string'] },
|
||||
|
||||
{ name: 'color', group: 'commerce', types: ['string'] },
|
||||
{ name: 'department', group: 'commerce', types: ['string'] },
|
||||
{ name: 'productName', group: 'commerce', types: ['string'] },
|
||||
{ name: 'price', group: 'commerce', types: ['string', 'float'] },
|
||||
{ name: 'productAdjective', group: 'commerce', types: ['string'] },
|
||||
{ name: 'productMaterial', group: 'commerce', types: ['string'] },
|
||||
{ name: 'product', group: 'commerce', types: ['string'] },
|
||||
{ name: 'productDescription', group: 'commerce', types: ['string'] },
|
||||
|
||||
{ name: 'suffixes', group: 'company', types: ['string'] },
|
||||
{ name: 'companyName', group: 'company', types: ['string'] },
|
||||
{ name: 'companySuffix', group: 'company', types: ['string'] },
|
||||
{ name: 'catchPhrase', group: 'company', types: ['string'] },
|
||||
{ name: 'bs', group: 'company', types: ['string'] },
|
||||
{ name: 'catchPhraseAdjective', group: 'company', types: ['string'] },
|
||||
{ name: 'catchPhraseDescriptor', group: 'company', types: ['string'] },
|
||||
{ name: 'catchPhraseNoun', group: 'company', types: ['string'] },
|
||||
{ name: 'bsAdjective', group: 'company', types: ['string'] },
|
||||
{ name: 'bsBuzz', group: 'company', types: ['string'] },
|
||||
{ name: 'bsNoun', group: 'company', types: ['string'] },
|
||||
|
||||
{ name: 'column', group: 'database', types: ['string'] },
|
||||
{ name: 'type', group: 'database', types: ['string'] },
|
||||
{ name: 'collation', group: 'database', types: ['string'] },
|
||||
{ name: 'engine', group: 'database', types: ['string'] },
|
||||
|
||||
{ name: 'past', group: 'date', types: ['string', 'datetime'] },
|
||||
{ name: 'future', group: 'date', types: ['string', 'datetime'] },
|
||||
// { name: 'between', group: 'date', types: ['string'] },
|
||||
{ name: 'recent', group: 'date', types: ['string', 'datetime'] },
|
||||
{ name: 'soon', group: 'date', types: ['string', 'datetime'] },
|
||||
{ name: 'month', group: 'date', types: ['string'] },
|
||||
{ name: 'weekday', group: 'date', types: ['string'] },
|
||||
|
||||
{ name: 'account', group: 'finance', types: ['string', 'number'] },
|
||||
{ name: 'accountName', group: 'finance', types: ['string'] },
|
||||
{ name: 'routingNumber', group: 'finance', types: ['string', 'number'] },
|
||||
{ name: 'mask', group: 'finance', types: ['string', 'number'] },
|
||||
{ name: 'amount', group: 'finance', types: ['string', 'float'] },
|
||||
{ name: 'transactionType', group: 'finance', types: ['string'] },
|
||||
{ name: 'currencyCode', group: 'finance', types: ['string'] },
|
||||
{ name: 'currencyName', group: 'finance', types: ['string'] },
|
||||
{ name: 'currencySymbol', group: 'finance', types: ['string'] },
|
||||
{ name: 'bitcoinAddress', group: 'finance', types: ['string'] },
|
||||
{ name: 'litecoinAddress', group: 'finance', types: ['string'] },
|
||||
{ name: 'creditCardNumber', group: 'finance', types: ['string'] },
|
||||
{ name: 'creditCardCVV', group: 'finance', types: ['string', 'number'] },
|
||||
{ name: 'ethereumAddress', group: 'finance', types: ['string'] },
|
||||
{ name: 'iban', group: 'finance', types: ['string'] },
|
||||
{ name: 'bic', group: 'finance', types: ['string'] },
|
||||
{ name: 'transactionDescription', group: 'finance', types: ['string'] },
|
||||
|
||||
{ name: 'branch', group: 'git', types: ['string'] },
|
||||
{ name: 'commitEntry', group: 'git', types: ['string'] },
|
||||
{ name: 'commitMessage', group: 'git', types: ['string'] },
|
||||
{ name: 'commitSha', group: 'git', types: ['string'] },
|
||||
{ name: 'shortSha', group: 'git', types: ['string'] },
|
||||
|
||||
{ name: 'abbreviation', group: 'hacker', types: ['string'] },
|
||||
{ name: 'adjective', group: 'hacker', types: ['string'] },
|
||||
{ name: 'noun', group: 'hacker', types: ['string'] },
|
||||
{ name: 'verb', group: 'hacker', types: ['string'] },
|
||||
{ name: 'ingverb', group: 'hacker', types: ['string'] },
|
||||
{ name: 'phrase', group: 'hacker', types: ['string'] },
|
||||
|
||||
// { name: 'avatar', group: 'internet', types: ['string'] },
|
||||
{ name: 'email', group: 'internet', types: ['string'] },
|
||||
{ name: 'exampleEmail', group: 'internet', types: ['string'] },
|
||||
{ name: 'userName', group: 'internet', types: ['string'] },
|
||||
{ name: 'protocol', group: 'internet', types: ['string'] },
|
||||
{ name: 'url', group: 'internet', types: ['string'] },
|
||||
{ name: 'domainName', group: 'internet', types: ['string'] },
|
||||
{ name: 'domainSuffix', group: 'internet', types: ['string'] },
|
||||
{ name: 'domainWord', group: 'internet', types: ['string'] },
|
||||
{ name: 'ip', group: 'internet', types: ['string'] },
|
||||
{ name: 'ipv6', group: 'internet', types: ['string'] },
|
||||
{ name: 'userAgent', group: 'internet', types: ['string'] },
|
||||
{ name: 'color', group: 'internet', types: ['string'] },
|
||||
{ name: 'mac', group: 'internet', types: ['string'] },
|
||||
{ name: 'password', group: 'internet', types: ['string'] },
|
||||
|
||||
{ name: 'word', group: 'lorem', types: ['string'] },
|
||||
{ name: 'words', group: 'lorem', types: ['string'] },
|
||||
{ name: 'sentence', group: 'lorem', types: ['string'] },
|
||||
{ name: 'slug', group: 'lorem', types: ['string'] },
|
||||
{ name: 'sentences', group: 'lorem', types: ['string'] },
|
||||
{ name: 'paragraph', group: 'lorem', types: ['string'] },
|
||||
{ name: 'paragraphs', group: 'lorem', types: ['string'] },
|
||||
{ name: 'text', group: 'lorem', types: ['string'] },
|
||||
{ name: 'lines', group: 'lorem', types: ['string'] },
|
||||
|
||||
{ name: 'genre', group: 'music', types: ['string'] },
|
||||
|
||||
{ name: 'firstName', group: 'name', types: ['string'] },
|
||||
{ name: 'lastName', group: 'name', types: ['string'] },
|
||||
{ name: 'middleName', group: 'name', types: ['string'] },
|
||||
{ name: 'findName', group: 'name', types: ['string'] },
|
||||
{ name: 'jobTitle', group: 'name', types: ['string'] },
|
||||
{ name: 'gender', group: 'name', types: ['string'] },
|
||||
{ name: 'prefix', group: 'name', types: ['string'] },
|
||||
{ name: 'suffix', group: 'name', types: ['string'] },
|
||||
{ name: 'title', group: 'name', types: ['string'] },
|
||||
{ name: 'jobDescriptor', group: 'name', types: ['string'] },
|
||||
{ name: 'jobArea', group: 'name', types: ['string'] },
|
||||
{ name: 'jobType', group: 'name', types: ['string'] },
|
||||
|
||||
{ name: 'phoneNumber', group: 'phone', types: ['string'] },
|
||||
{ name: 'phoneNumberFormat', group: 'phone', types: ['string'] },
|
||||
{ name: 'phoneFormats', group: 'phone', types: ['string'] },
|
||||
|
||||
{ name: 'number', group: 'datatype', types: ['string', 'number'], params: ['min', 'max'] },
|
||||
{ name: 'float', group: 'random', types: ['string', 'float'], params: ['min', 'max'] },
|
||||
{ name: 'arrayElement', group: 'random', types: ['string'] },
|
||||
{ name: 'arrayElements', group: 'random', types: ['string'] },
|
||||
{ name: 'objectElement', group: 'random', types: ['string'] },
|
||||
{ name: 'uuid', group: 'random', types: ['string'] },
|
||||
{ name: 'boolean', group: 'random', types: ['string'] },
|
||||
{ name: 'word', group: 'random', types: ['string'] },
|
||||
{ name: 'words', group: 'random', types: ['string'] },
|
||||
// { name: 'image', group: 'random', types: ['string'] },
|
||||
{ name: 'locale', group: 'random', types: ['string'] },
|
||||
{ name: 'alpha', group: 'random', types: ['string'] },
|
||||
{ name: 'alphaNumeric', group: 'random', types: ['string'] },
|
||||
{ name: 'hexaDecimal', group: 'random', types: ['string'] },
|
||||
|
||||
{ name: 'fileName', group: 'system', types: ['string'] },
|
||||
{ name: 'commonFileName', group: 'system', types: ['string'] },
|
||||
{ name: 'mimeType', group: 'system', types: ['string'] },
|
||||
{ name: 'commonFileType', group: 'system', types: ['string'] },
|
||||
{ name: 'commonFileExt', group: 'system', types: ['string'] },
|
||||
{ name: 'fileType', group: 'system', types: ['string'] },
|
||||
{ name: 'fileExt', group: 'system', types: ['string'] },
|
||||
{ name: 'directoryPath', group: 'system', types: ['string'] },
|
||||
{ name: 'filePath', group: 'system', types: ['string'] },
|
||||
{ name: 'semver', group: 'system', types: ['string'] },
|
||||
|
||||
{ name: 'recent', group: 'time', types: ['string', 'time'] },
|
||||
|
||||
{ name: 'vehicle', group: 'vehicle', types: ['string'] },
|
||||
{ name: 'manufacturer', group: 'vehicle', types: ['string'] },
|
||||
{ name: 'model', group: 'vehicle', types: ['string'] },
|
||||
{ name: 'type', group: 'vehicle', types: ['string'] },
|
||||
{ name: 'fuel', group: 'vehicle', types: ['string'] },
|
||||
{ name: 'vin', group: 'vehicle', types: ['string'] },
|
||||
{ name: 'color', group: 'vehicle', types: ['string'] }
|
||||
];
|
||||
}
|
||||
|
||||
static getGroups () {
|
||||
const groupsObj = this._methods.reduce((acc, curr) => {
|
||||
if (curr.group in acc)
|
||||
curr.types.forEach(type => acc[curr.group].add(type));
|
||||
else
|
||||
acc[curr.group] = new Set(curr.types);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const groupsArr = [];
|
||||
|
||||
for (const key in groupsObj)
|
||||
groupsArr.push({ name: key, types: [...groupsObj[key]] });
|
||||
|
||||
return groupsArr.sort((a, b) => {
|
||||
if (a.name < b.name)
|
||||
return -1;
|
||||
|
||||
if (b.name > a.name)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}); ;
|
||||
}
|
||||
|
||||
static getGroupsByType (type) {
|
||||
if (!type) return [];
|
||||
return this.getGroups().filter(group => group.types.includes(type));
|
||||
}
|
||||
|
||||
static getMethods ({ type, group }) {
|
||||
return this._methods.filter(method => method.group === group && method.types.includes(type)).sort((a, b) => {
|
||||
if (a.name < b.name)
|
||||
return -1;
|
||||
|
||||
if (b.name > a.name)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
90
src/common/customizations/defaults.js
Normal file
@@ -0,0 +1,90 @@
|
||||
module.exports = {
|
||||
// Defaults
|
||||
defaultPort: null,
|
||||
defaultUser: null,
|
||||
defaultDatabase: null,
|
||||
// Core
|
||||
database: false,
|
||||
collations: false,
|
||||
engines: false,
|
||||
connectionSchema: false,
|
||||
sslConnection: false,
|
||||
sshConnection: false,
|
||||
fileConnection: false,
|
||||
cancelQueries: false,
|
||||
// Tools
|
||||
processesList: false,
|
||||
usersManagement: false,
|
||||
variables: false,
|
||||
// Structure
|
||||
schemas: false,
|
||||
tables: false,
|
||||
views: false,
|
||||
triggers: false,
|
||||
triggerFunctions: false,
|
||||
routines: false,
|
||||
functions: false,
|
||||
schedulers: false,
|
||||
// Settings
|
||||
elementsWrapper: '',
|
||||
stringsWrapper: '"',
|
||||
tableAdd: false,
|
||||
viewAdd: false,
|
||||
triggerAdd: false,
|
||||
triggerFunctionAdd: false,
|
||||
routineAdd: false,
|
||||
functionAdd: false,
|
||||
schedulerAdd: false,
|
||||
databaseEdit: false,
|
||||
schemaEdit: false,
|
||||
schemaDrop: false,
|
||||
schemaExport: false,
|
||||
schemaImport: false,
|
||||
tableSettings: false,
|
||||
tableOptions: false,
|
||||
tableArray: false,
|
||||
tableRealCount: false,
|
||||
viewSettings: false,
|
||||
triggerSettings: false,
|
||||
triggerFunctionSettings: false,
|
||||
routineSettings: false,
|
||||
functionSettings: false,
|
||||
schedulerSettings: false,
|
||||
indexes: false,
|
||||
foreigns: false,
|
||||
sortableFields: false,
|
||||
unsigned: false,
|
||||
nullable: false,
|
||||
nullablePrimary: false,
|
||||
zerofill: false,
|
||||
autoIncrement: false,
|
||||
comment: false,
|
||||
collation: false,
|
||||
definer: false,
|
||||
onUpdate: false,
|
||||
viewAlgorithm: false,
|
||||
viewSqlSecurity: false,
|
||||
viewUpdateOption: false,
|
||||
procedureDeterministic: false,
|
||||
procedureDataAccess: false,
|
||||
procedureSql: false,
|
||||
procedureContext: false,
|
||||
procedureLanguage: false,
|
||||
functionDeterministic: false,
|
||||
functionDataAccess: false,
|
||||
functionSql: false,
|
||||
functionContext: false,
|
||||
functionLanguage: false,
|
||||
triggerSql: false,
|
||||
triggerStatementInCreation: false,
|
||||
triggerMultipleEvents: false,
|
||||
triggerTableInName: false,
|
||||
triggerUpdateColumns: false,
|
||||
triggerOnlyRename: false,
|
||||
triggerEnableDisable: false,
|
||||
triggerFunctionSql: false,
|
||||
triggerFunctionlanguages: false,
|
||||
parametersLength: false,
|
||||
languages: false,
|
||||
readOnlyMode: false
|
||||
};
|
6
src/common/customizations/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
maria: require('./mysql'),
|
||||
mysql: require('./mysql'),
|
||||
pg: require('./postgresql'),
|
||||
sqlite: require('./sqlite')
|
||||
};
|
70
src/common/customizations/mysql.js
Normal file
@@ -0,0 +1,70 @@
|
||||
const defaults = require('./defaults');
|
||||
|
||||
module.exports = {
|
||||
...defaults,
|
||||
// Defaults
|
||||
defaultPort: 3306,
|
||||
defaultUser: 'root',
|
||||
defaultDatabase: null,
|
||||
// Core
|
||||
connectionSchema: true,
|
||||
collations: true,
|
||||
engines: true,
|
||||
sslConnection: true,
|
||||
sshConnection: true,
|
||||
cancelQueries: true,
|
||||
// Tools
|
||||
processesList: true,
|
||||
// Structure
|
||||
schemas: true,
|
||||
tables: true,
|
||||
views: true,
|
||||
triggers: true,
|
||||
routines: true,
|
||||
functions: true,
|
||||
schedulers: true,
|
||||
// Settings
|
||||
elementsWrapper: '',
|
||||
stringsWrapper: '"',
|
||||
tableAdd: true,
|
||||
viewAdd: true,
|
||||
triggerAdd: true,
|
||||
routineAdd: true,
|
||||
functionAdd: true,
|
||||
schedulerAdd: true,
|
||||
schemaEdit: true,
|
||||
schemaDrop: true,
|
||||
schemaExport: true,
|
||||
schemaImport: true,
|
||||
tableSettings: true,
|
||||
viewSettings: true,
|
||||
triggerSettings: true,
|
||||
routineSettings: true,
|
||||
functionSettings: true,
|
||||
schedulerSettings: true,
|
||||
indexes: true,
|
||||
foreigns: true,
|
||||
sortableFields: true,
|
||||
unsigned: true,
|
||||
nullable: true,
|
||||
zerofill: true,
|
||||
tableOptions: true,
|
||||
autoIncrement: true,
|
||||
comment: true,
|
||||
collation: true,
|
||||
definer: true,
|
||||
onUpdate: true,
|
||||
viewAlgorithm: true,
|
||||
viewSqlSecurity: true,
|
||||
viewUpdateOption: true,
|
||||
procedureDeterministic: true,
|
||||
procedureDataAccess: true,
|
||||
procedureSql: 'BEGIN\r\n\r\nEND',
|
||||
procedureContext: true,
|
||||
triggerSql: 'BEGIN\r\n\r\nEND',
|
||||
functionDeterministic: true,
|
||||
functionDataAccess: true,
|
||||
functionSql: 'BEGIN\r\n\r\nEND',
|
||||
parametersLength: true,
|
||||
readOnlyMode: true
|
||||
};
|
63
src/common/customizations/postgresql.js
Normal file
@@ -0,0 +1,63 @@
|
||||
const defaults = require('./defaults');
|
||||
|
||||
module.exports = {
|
||||
...defaults,
|
||||
// Defaults
|
||||
defaultPort: 5432,
|
||||
defaultUser: 'postgres',
|
||||
defaultDatabase: 'postgres',
|
||||
// Core
|
||||
database: true,
|
||||
sslConnection: true,
|
||||
sshConnection: true,
|
||||
cancelQueries: true,
|
||||
// Tools
|
||||
processesList: true,
|
||||
// Structure
|
||||
schemas: true,
|
||||
tables: true,
|
||||
views: true,
|
||||
triggers: true,
|
||||
triggerFunctions: true,
|
||||
routines: true,
|
||||
functions: true,
|
||||
// Settings
|
||||
elementsWrapper: '"',
|
||||
stringsWrapper: '\'',
|
||||
tableAdd: true,
|
||||
viewAdd: true,
|
||||
triggerAdd: true,
|
||||
triggerFunctionAdd: true,
|
||||
routineAdd: true,
|
||||
functionAdd: true,
|
||||
schemaDrop: true,
|
||||
databaseEdit: false,
|
||||
schemaExport: false,
|
||||
schemaImport: false,
|
||||
tableSettings: true,
|
||||
viewSettings: true,
|
||||
triggerSettings: true,
|
||||
triggerFunctionSettings: true,
|
||||
routineSettings: true,
|
||||
functionSettings: true,
|
||||
indexes: true,
|
||||
foreigns: true,
|
||||
nullable: true,
|
||||
tableArray: true,
|
||||
procedureSql: '$procedure$\r\n\r\n$procedure$',
|
||||
procedureContext: true,
|
||||
procedureLanguage: true,
|
||||
functionSql: '$function$\r\n\r\n$function$',
|
||||
triggerFunctionSql: '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$',
|
||||
triggerFunctionlanguages: ['plpgsql'],
|
||||
functionContext: true,
|
||||
functionLanguage: true,
|
||||
triggerSql: 'EXECUTE PROCEDURE ',
|
||||
triggerStatementInCreation: true,
|
||||
triggerMultipleEvents: true,
|
||||
triggerTableInName: true,
|
||||
triggerOnlyRename: false,
|
||||
triggerEnableDisable: true,
|
||||
languages: ['sql', 'plpgsql', 'c', 'internal'],
|
||||
readOnlyMode: true
|
||||
};
|
27
src/common/customizations/sqlite.js
Normal file
@@ -0,0 +1,27 @@
|
||||
module.exports = {
|
||||
// Core
|
||||
fileConnection: true,
|
||||
// Structure
|
||||
schemas: false,
|
||||
tables: true,
|
||||
views: true,
|
||||
triggers: true,
|
||||
// Settings
|
||||
elementsWrapper: '"',
|
||||
stringsWrapper: '\'',
|
||||
tableAdd: true,
|
||||
viewAdd: true,
|
||||
triggerAdd: true,
|
||||
schemaEdit: false,
|
||||
tableSettings: true,
|
||||
tableRealCount: true,
|
||||
viewSettings: true,
|
||||
triggerSettings: true,
|
||||
indexes: true,
|
||||
foreigns: true,
|
||||
sortableFields: true,
|
||||
nullable: true,
|
||||
nullablePrimary: true,
|
||||
triggerSql: 'BEGIN\r\n\r\nEND',
|
||||
readOnlyMode: true
|
||||
};
|
@@ -4,7 +4,7 @@ module.exports = [
|
||||
types: [
|
||||
{
|
||||
name: 'TINYINT',
|
||||
length: true,
|
||||
length: 4,
|
||||
collation: false,
|
||||
unsigned: true,
|
||||
zerofill: true
|
||||
@@ -66,6 +66,7 @@ module.exports = [
|
||||
{
|
||||
name: 'DECIMAL',
|
||||
length: true,
|
||||
scale: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
@@ -91,7 +92,7 @@ module.exports = [
|
||||
},
|
||||
{
|
||||
name: 'TINYTEXT',
|
||||
length: true,
|
||||
length: false,
|
||||
collation: true,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
@@ -119,8 +120,8 @@ module.exports = [
|
||||
},
|
||||
{
|
||||
name: 'JSON',
|
||||
length: true,
|
||||
collation: true,
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
}
|
||||
@@ -206,7 +207,7 @@ module.exports = [
|
||||
},
|
||||
{
|
||||
name: 'TIMESTAMP',
|
||||
length: true,
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
@@ -218,56 +219,56 @@ module.exports = [
|
||||
types: [
|
||||
{
|
||||
name: 'POINT',
|
||||
length: true,
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'LINESTRING',
|
||||
length: true,
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'POLYGON',
|
||||
length: true,
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'GEOMETRY',
|
||||
length: true,
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'MULTIPOINT',
|
||||
length: true,
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'MULTILINESTRING',
|
||||
length: true,
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'MULTIPOLYGON',
|
||||
length: true,
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'GEOMETRYCOLLECTION',
|
||||
length: true,
|
||||
name: 'GEOMCOLLECTION',
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
@@ -277,13 +278,6 @@ module.exports = [
|
||||
{
|
||||
group: 'other',
|
||||
types: [
|
||||
{
|
||||
name: 'UNKNOWN',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'ENUM',
|
||||
length: true,
|
||||
@@ -299,5 +293,17 @@ module.exports = [
|
||||
zerofill: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'unknown',
|
||||
types: [
|
||||
{
|
||||
name: 'UNKNOWN',
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
293
src/common/data-types/postgresql.js
Normal file
@@ -0,0 +1,293 @@
|
||||
module.exports = [
|
||||
{
|
||||
group: 'integer',
|
||||
types: [
|
||||
{
|
||||
name: 'SMALLINT',
|
||||
length: false,
|
||||
unsigned: true
|
||||
},
|
||||
{
|
||||
name: 'INTEGER',
|
||||
length: false,
|
||||
unsigned: true
|
||||
},
|
||||
{
|
||||
name: 'BIGINT',
|
||||
length: false,
|
||||
unsigned: true
|
||||
},
|
||||
{
|
||||
name: 'DECIMAL',
|
||||
length: false,
|
||||
unsigned: true
|
||||
},
|
||||
{
|
||||
name: 'SMALLSERIAL',
|
||||
length: false,
|
||||
unsigned: true
|
||||
},
|
||||
{
|
||||
name: 'SERIAL',
|
||||
length: false,
|
||||
unsigned: true
|
||||
},
|
||||
{
|
||||
name: 'BIGSERIAL',
|
||||
length: false,
|
||||
unsigned: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'float',
|
||||
types: [
|
||||
{
|
||||
name: 'REAL',
|
||||
length: false,
|
||||
unsigned: true
|
||||
},
|
||||
{
|
||||
name: 'NUMERIC',
|
||||
length: true,
|
||||
unsigned: true,
|
||||
scale: true
|
||||
},
|
||||
{
|
||||
name: 'DOUBLE PRECISION',
|
||||
length: false,
|
||||
unsigned: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'monetary',
|
||||
types: [
|
||||
{
|
||||
name: 'money',
|
||||
length: false,
|
||||
unsigned: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'string',
|
||||
types: [
|
||||
{
|
||||
name: 'CHARACTER VARYING',
|
||||
length: true,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'CHARACTER',
|
||||
length: true,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'TEXT',
|
||||
length: false,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: '"CHAR"',
|
||||
length: false,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'NAME',
|
||||
length: false,
|
||||
unsigned: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'binary',
|
||||
types: [
|
||||
{
|
||||
name: 'BYTEA',
|
||||
length: false,
|
||||
unsigned: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'time',
|
||||
types: [
|
||||
{
|
||||
name: 'TIMESTAMP WITHOUT TIME ZONE',
|
||||
length: false,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'TIMESTAMP WITH TIME ZONE',
|
||||
length: false,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'DATE',
|
||||
length: false,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'TIME WITHOUT TIME ZONE',
|
||||
length: false,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'TIME WITH TIME ZONE',
|
||||
length: false,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'INTERVAL',
|
||||
length: false,
|
||||
unsigned: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'boolean',
|
||||
types: [
|
||||
{
|
||||
name: 'BOOLEAN',
|
||||
length: false,
|
||||
unsigned: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'geometric',
|
||||
types: [
|
||||
{
|
||||
name: 'POINT',
|
||||
length: false,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'LINE',
|
||||
length: false,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'LSEG',
|
||||
length: false,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'BOX',
|
||||
length: false,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'PATH',
|
||||
length: false,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'POLYGON',
|
||||
length: false,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'CIRCLE',
|
||||
length: false,
|
||||
unsigned: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'network',
|
||||
types: [
|
||||
{
|
||||
name: 'CIDR',
|
||||
length: false,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'INET',
|
||||
length: false,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'MACADDR',
|
||||
length: false,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'MACADDR8',
|
||||
length: false,
|
||||
unsigned: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'bit',
|
||||
types: [
|
||||
{
|
||||
name: 'BIT',
|
||||
length: true,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'BIT VARYING',
|
||||
length: true,
|
||||
unsigned: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'text search',
|
||||
types: [
|
||||
{
|
||||
name: 'TSVECTOR',
|
||||
length: false,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'TSQUERY',
|
||||
length: false,
|
||||
unsigned: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'uuid',
|
||||
types: [
|
||||
{
|
||||
name: 'UUID',
|
||||
length: false,
|
||||
unsigned: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'xml',
|
||||
types: [
|
||||
{
|
||||
name: 'XML',
|
||||
length: false,
|
||||
unsigned: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'json',
|
||||
types: [
|
||||
{
|
||||
name: 'JSON',
|
||||
length: false,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'JSONB',
|
||||
length: false,
|
||||
unsigned: false
|
||||
},
|
||||
{
|
||||
name: 'JSONPATH',
|
||||
length: false,
|
||||
unsigned: false
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
137
src/common/data-types/sqlite.js
Normal file
@@ -0,0 +1,137 @@
|
||||
module.exports = [
|
||||
{
|
||||
group: 'integer',
|
||||
types: [
|
||||
{
|
||||
name: 'INT',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: true,
|
||||
zerofill: true
|
||||
},
|
||||
{
|
||||
name: 'INTEGER',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: true,
|
||||
zerofill: true
|
||||
},
|
||||
{
|
||||
name: 'BIGINT',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: true,
|
||||
zerofill: true
|
||||
},
|
||||
{
|
||||
name: 'NUMERIC',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: true,
|
||||
zerofill: true
|
||||
},
|
||||
{
|
||||
name: 'BOOLEAN',
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: true,
|
||||
zerofill: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'float',
|
||||
types: [
|
||||
{
|
||||
name: 'FLOAT',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'REAL',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'string',
|
||||
types: [
|
||||
{
|
||||
name: 'CHAR',
|
||||
length: true,
|
||||
collation: true,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'VARCHAR',
|
||||
length: true,
|
||||
collation: true,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'TEXT',
|
||||
length: true,
|
||||
collation: true,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'binary',
|
||||
types: [
|
||||
{
|
||||
name: 'BLOB',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'time',
|
||||
types: [
|
||||
{
|
||||
name: 'DATE',
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'TIME',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'DATETIME',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'other',
|
||||
types: [
|
||||
{
|
||||
name: 'NONE',
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
@@ -1,12 +1,108 @@
|
||||
export const TEXT = ['char', 'varchar'];
|
||||
export const LONG_TEXT = ['text', 'mediumtext', 'longtext'];
|
||||
export const TEXT = [
|
||||
'CHAR',
|
||||
'VARCHAR',
|
||||
'CHARACTER',
|
||||
'CHARACTER VARYING'
|
||||
];
|
||||
|
||||
export const NUMBER = ['int', 'tinyint', 'smallint', 'mediumint', 'bigint', 'float', 'double', 'decimal', 'bool'];
|
||||
export const LONG_TEXT = [
|
||||
'TEXT',
|
||||
'MEDIUMTEXT',
|
||||
'LONGTEXT',
|
||||
'JSON',
|
||||
'VARBINARY'
|
||||
];
|
||||
|
||||
export const DATE = ['date'];
|
||||
export const TIME = ['time'];
|
||||
export const DATETIME = ['datetime', 'timestamp'];
|
||||
export const ARRAY = [
|
||||
'ARRAY',
|
||||
'ANYARRAY'
|
||||
];
|
||||
|
||||
export const BLOB = ['blob', 'mediumblob', 'longblob'];
|
||||
export const TEXT_SEARCH = [
|
||||
'TSVECTOR',
|
||||
'TSQUERY'
|
||||
];
|
||||
|
||||
export const BIT = ['bit'];
|
||||
export const NUMBER = [
|
||||
'INT',
|
||||
'TINYINT',
|
||||
'SMALLINT',
|
||||
'MEDIUMINT',
|
||||
'BIGINT',
|
||||
'DECIMAL',
|
||||
'NUMERIC',
|
||||
'INTEGER',
|
||||
'SMALLSERIAL',
|
||||
'SERIAL',
|
||||
'BIGSERIAL',
|
||||
'OID',
|
||||
'XID'
|
||||
];
|
||||
|
||||
export const FLOAT = [
|
||||
'FLOAT',
|
||||
'DECIMAL',
|
||||
'DOUBLE',
|
||||
'REAL',
|
||||
'DOUBLE PRECISION',
|
||||
'MONEY'
|
||||
];
|
||||
|
||||
export const BOOLEAN = [
|
||||
'BOOL',
|
||||
'BOOLEAN'
|
||||
];
|
||||
|
||||
export const DATE = ['DATE'];
|
||||
|
||||
export const TIME = [
|
||||
'TIME',
|
||||
'TIME WITH TIME ZONE'
|
||||
];
|
||||
|
||||
export const DATETIME = [
|
||||
'DATETIME',
|
||||
'TIMESTAMP',
|
||||
'TIMESTAMP WITHOUT TIME ZONE',
|
||||
'TIMESTAMP WITH TIME ZONE'
|
||||
];
|
||||
|
||||
// Used to check datetime fields only
|
||||
export const HAS_TIMEZONE = [
|
||||
'TIMESTAMP WITH TIME ZONE',
|
||||
'TIME WITH TIME ZONE'
|
||||
];
|
||||
|
||||
export const BLOB = [
|
||||
'BLOB',
|
||||
'TINYBLOB',
|
||||
'MEDIUMBLOB',
|
||||
'LONGBLOB',
|
||||
'BYTEA'
|
||||
];
|
||||
|
||||
export const BIT = [
|
||||
'BIT',
|
||||
'BIT VARYING'
|
||||
];
|
||||
|
||||
export const SPATIAL = [
|
||||
'POINT',
|
||||
'LINESTRING',
|
||||
'POLYGON',
|
||||
'GEOMETRY',
|
||||
'MULTIPOINT',
|
||||
'MULTILINESTRING',
|
||||
'MULTIPOLYGON',
|
||||
'GEOMCOLLECTION',
|
||||
'GEOMETRYCOLLECTION'
|
||||
];
|
||||
|
||||
// Used to check multi spatial fields only
|
||||
export const IS_MULTI_SPATIAL = [
|
||||
'MULTIPOINT',
|
||||
'MULTILINESTRING',
|
||||
'MULTIPOLYGON',
|
||||
'GEOMCOLLECTION',
|
||||
'GEOMETRYCOLLECTION'
|
||||
];
|
||||
|
6
src/common/index-types/mysql.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = [
|
||||
'PRIMARY',
|
||||
'INDEX',
|
||||
'UNIQUE',
|
||||
'FULLTEXT'
|
||||
];
|
5
src/common/index-types/postgresql.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = [
|
||||
'PRIMARY',
|
||||
'INDEX',
|
||||
'UNIQUE'
|
||||
];
|
5
src/common/index-types/sqlite.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = [
|
||||
'PRIMARY',
|
||||
'INDEX',
|
||||
'UNIQUE'
|
||||
];
|
10
src/common/libs/getArrayDepth.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
*
|
||||
* @param {any[]} array
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getArrayDepth (array) {
|
||||
return Array.isArray(array)
|
||||
? 1 + Math.max(0, ...array.map(getArrayDepth))
|
||||
: 0;
|
||||
}
|
@@ -33,7 +33,7 @@ const lookup = {
|
||||
*/
|
||||
export default function hexToBinary (hex) {
|
||||
let binary = '';
|
||||
for (let i = 0, len = hex.length; i < len; i++)
|
||||
for (let i = 0; i < hex.length; i++)
|
||||
binary += lookup[hex[i]];
|
||||
|
||||
return binary;
|
||||
|
@@ -23,7 +23,7 @@ export function mimeFromHex (hex) {
|
||||
case '425A68':
|
||||
return { ext: 'bz2', mime: 'application/x-bzip2' };
|
||||
default:
|
||||
switch (hex) { // 4 bites
|
||||
switch (hex) { // 4 bytes
|
||||
case '89504E47':
|
||||
return { ext: 'png', mime: 'image/png' };
|
||||
case '47494638':
|
||||
|
@@ -11,8 +11,8 @@ const regex = new RegExp(pattern);
|
||||
*/
|
||||
function sqlEscaper (string) {
|
||||
return string.replace(regex, char => {
|
||||
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '"', '\\', '\\\\', '%'];
|
||||
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\'\'', '""', '\\\\', '\\\\\\\\', '\\%'];
|
||||
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '\"', '\\', '\\\\', '%'];
|
||||
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\\\'', '\\\"', '\\\\', '\\\\\\\\', '\%'];
|
||||
return r[m.indexOf(char)] || char;
|
||||
});
|
||||
}
|
||||
|
93
src/common/libs/sqlParser.js
Normal file
@@ -0,0 +1,93 @@
|
||||
import { Transform } from 'stream';
|
||||
|
||||
export default class SqlParser extends Transform {
|
||||
constructor (opts) {
|
||||
opts = {
|
||||
delimiter: ';',
|
||||
encoding: 'utf8',
|
||||
writableObjectMode: true,
|
||||
readableObjectMode: true,
|
||||
...opts
|
||||
};
|
||||
super(opts);
|
||||
this._buffer = '';
|
||||
this._lastChar = '';
|
||||
this._last9Chars = '';
|
||||
this.encoding = opts.encoding;
|
||||
this.delimiter = opts.delimiter;
|
||||
|
||||
this.isEscape = false;
|
||||
this.currentQuote = null;
|
||||
this.isDelimiter = false;
|
||||
}
|
||||
|
||||
_transform (chunk, encoding, next) {
|
||||
for (const char of chunk.toString(this.encoding)) {
|
||||
this.checkEscape();
|
||||
this._buffer += char;
|
||||
this._lastChar = char;
|
||||
this._last9Chars += char.trim().toLocaleLowerCase();
|
||||
|
||||
if (this._last9Chars.length > 9)
|
||||
this._last9Chars = this._last9Chars.slice(-9);
|
||||
|
||||
this.checkNewDelimiter(char);
|
||||
this.checkQuote(char);
|
||||
const query = this.getQuery();
|
||||
|
||||
if (query)
|
||||
this.push(query);
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
checkEscape () {
|
||||
if (this._buffer.length > 0) {
|
||||
this.isEscape = this._lastChar === '\\'
|
||||
? !this.isEscape
|
||||
: false;
|
||||
}
|
||||
}
|
||||
|
||||
checkNewDelimiter (char) {
|
||||
if (this.currentQuote === null && this._last9Chars === 'delimiter') {
|
||||
this.isDelimiter = true;
|
||||
this._buffer = '';
|
||||
}
|
||||
else {
|
||||
const isNewLine = char === '\n' || char === '\r';
|
||||
if (isNewLine && this.isDelimiter) {
|
||||
this.isDelimiter = false;
|
||||
this.delimiter = this._buffer.trim();
|
||||
this._buffer = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkQuote (char) {
|
||||
const isQuote = !this.isEscape && (char === '\'' || char === '"');
|
||||
if (isQuote && this.currentQuote === char)
|
||||
this.currentQuote = null;
|
||||
|
||||
else if (isQuote && this.currentQuote === null)
|
||||
this.currentQuote = char;
|
||||
}
|
||||
|
||||
getQuery () {
|
||||
if (this.isDelimiter)
|
||||
return false;
|
||||
|
||||
let query = false;
|
||||
let demiliterFound = false;
|
||||
if (this.currentQuote === null && this._buffer.length >= this.delimiter.length)
|
||||
demiliterFound = this._last9Chars.slice(-this.delimiter.length) === this.delimiter;
|
||||
|
||||
if (demiliterFound) {
|
||||
const parsedStr = this._buffer.trim();
|
||||
query = parsedStr.slice(0, parsedStr.length - this.delimiter.length);
|
||||
this._buffer = '';
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
}
|
@@ -1,95 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
import { app, BrowserWindow, nativeImage } from 'electron';
|
||||
import * as path from 'path';
|
||||
import { format as formatUrl } from 'url';
|
||||
import ipcHandlers from './ipc-handlers';
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
||||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
|
||||
|
||||
// global reference to mainWindow (necessary to prevent window from being garbage collected)
|
||||
let mainWindow;
|
||||
|
||||
async function createMainWindow () {
|
||||
const icon = require('../renderer/images/logo-32.png');
|
||||
const window = new BrowserWindow({
|
||||
width: 1024,
|
||||
height: 800,
|
||||
minWidth: 900,
|
||||
minHeight: 550,
|
||||
title: 'Antares',
|
||||
autoHideMenuBar: true,
|
||||
icon: nativeImage.createFromDataURL(icon.default),
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
'web-security': false,
|
||||
enableRemoteModule: true,
|
||||
spellcheck: false
|
||||
},
|
||||
frame: false,
|
||||
backgroundColor: '#1d1d1d'
|
||||
});
|
||||
|
||||
if (isDevelopment) {
|
||||
await window.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`);
|
||||
|
||||
const { default: installExtension, VUEJS_DEVTOOLS } = require('electron-devtools-installer');
|
||||
window.webContents.openDevTools();
|
||||
|
||||
installExtension(VUEJS_DEVTOOLS)
|
||||
.then(name => {
|
||||
console.log(name, 'installed');
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
else {
|
||||
await window.loadURL(formatUrl({
|
||||
pathname: path.join(__dirname, 'index.html'),
|
||||
protocol: 'file',
|
||||
slashes: true
|
||||
}));
|
||||
}
|
||||
|
||||
window.on('closed', () => {
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
window.webContents.on('devtools-opened', () => {
|
||||
window.focus();
|
||||
setImmediate(() => {
|
||||
window.focus();
|
||||
});
|
||||
});
|
||||
|
||||
return window;
|
||||
};
|
||||
|
||||
if (!gotTheLock)
|
||||
app.quit();
|
||||
else {
|
||||
// Initialize ipcHandlers
|
||||
ipcHandlers();
|
||||
|
||||
// quit application when all windows are closed
|
||||
app.on('window-all-closed', () => {
|
||||
// on macOS it is common for applications to stay open until the user explicitly quits
|
||||
if (process.platform !== 'darwin')
|
||||
app.quit();
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
// on macOS it is common to re-create a window even after all windows have been closed
|
||||
if (mainWindow === null)
|
||||
mainWindow = createMainWindow();
|
||||
});
|
||||
|
||||
// create main BrowserWindow when electron is ready
|
||||
app.on('ready', () => {
|
||||
mainWindow = createMainWindow();
|
||||
});
|
||||
}
|
@@ -1,7 +1,20 @@
|
||||
import { app, ipcMain } from 'electron';
|
||||
import { app, ipcMain, dialog } from 'electron';
|
||||
|
||||
export default () => {
|
||||
ipcMain.on('close-app', () => {
|
||||
app.exit();
|
||||
});
|
||||
|
||||
ipcMain.on('get-key', async event => {
|
||||
const key = false;
|
||||
event.returnValue = key;
|
||||
});
|
||||
|
||||
ipcMain.handle('showOpenDialog', (event, options) => {
|
||||
return dialog.showOpenDialog(options);
|
||||
});
|
||||
|
||||
ipcMain.handle('get-download-dir-path', () => {
|
||||
return app.getPath('downloads');
|
||||
});
|
||||
};
|
||||
|
@@ -1,29 +1,58 @@
|
||||
|
||||
import fs from 'fs';
|
||||
import { ipcMain } from 'electron';
|
||||
import { ClientsFactory } from '../libs/ClientsFactory';
|
||||
|
||||
export default connections => {
|
||||
ipcMain.handle('test-connection', async (event, conn) => {
|
||||
const connection = ClientsFactory.getConnection({
|
||||
client: conn.client,
|
||||
params: {
|
||||
host: conn.host,
|
||||
port: +conn.port,
|
||||
user: conn.user,
|
||||
password: conn.password
|
||||
}
|
||||
});
|
||||
const params = {
|
||||
host: conn.host,
|
||||
port: +conn.port,
|
||||
user: conn.user,
|
||||
password: conn.password,
|
||||
application_name: 'Antares SQL',
|
||||
readonly: conn.readonly
|
||||
};
|
||||
|
||||
await connection.connect();
|
||||
if (conn.database)
|
||||
params.database = conn.database;
|
||||
|
||||
if (conn.databasePath)
|
||||
params.databasePath = conn.databasePath;
|
||||
|
||||
if (conn.ssl) {
|
||||
params.ssl = {
|
||||
key: conn.key ? fs.readFileSync(conn.key) : null,
|
||||
cert: conn.cert ? fs.readFileSync(conn.cert) : null,
|
||||
ca: conn.ca ? fs.readFileSync(conn.ca) : null,
|
||||
ciphers: conn.ciphers
|
||||
};
|
||||
}
|
||||
|
||||
if (conn.ssh) {
|
||||
params.ssh = {
|
||||
host: conn.sshHost,
|
||||
username: conn.sshUser,
|
||||
password: conn.sshPass,
|
||||
port: conn.sshPort ? conn.sshPort : 22,
|
||||
privateKey: conn.sshKey ? fs.readFileSync(conn.sshKey) : null,
|
||||
passphrase: conn.sshPassphrase
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const connection = await ClientsFactory.getClient({
|
||||
client: conn.client,
|
||||
params
|
||||
});
|
||||
await connection.connect();
|
||||
|
||||
await connection.select('1+1').run();
|
||||
connection.destroy();
|
||||
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err };
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -32,21 +61,54 @@ export default connections => {
|
||||
});
|
||||
|
||||
ipcMain.handle('connect', async (event, conn) => {
|
||||
const connection = ClientsFactory.getConnection({
|
||||
client: conn.client,
|
||||
params: {
|
||||
host: conn.host,
|
||||
port: +conn.port,
|
||||
user: conn.user,
|
||||
password: conn.password
|
||||
},
|
||||
poolSize: 1
|
||||
});
|
||||
const params = {
|
||||
host: conn.host,
|
||||
port: +conn.port,
|
||||
user: conn.user,
|
||||
password: conn.password,
|
||||
application_name: 'Antares SQL',
|
||||
readonly: conn.readonly
|
||||
};
|
||||
|
||||
if (conn.database)
|
||||
params.database = conn.database;
|
||||
|
||||
if (conn.databasePath)
|
||||
params.databasePath = conn.databasePath;
|
||||
|
||||
if (conn.schema)
|
||||
params.schema = conn.schema;
|
||||
|
||||
if (conn.ssl) {
|
||||
params.ssl = {
|
||||
key: conn.key ? fs.readFileSync(conn.key) : null,
|
||||
cert: conn.cert ? fs.readFileSync(conn.cert) : null,
|
||||
ca: conn.ca ? fs.readFileSync(conn.ca) : null,
|
||||
ciphers: conn.ciphers
|
||||
};
|
||||
}
|
||||
|
||||
if (conn.ssh) {
|
||||
params.ssh = {
|
||||
host: conn.sshHost,
|
||||
username: conn.sshUser,
|
||||
password: conn.sshPass,
|
||||
port: conn.sshPort ? conn.sshPort : 22,
|
||||
privateKey: conn.sshKey ? fs.readFileSync(conn.sshKey) : null,
|
||||
passphrase: conn.sshPassphrase
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const connection = ClientsFactory.getClient({
|
||||
client: conn.client,
|
||||
params,
|
||||
poolSize: 5
|
||||
});
|
||||
|
||||
await connection.connect();
|
||||
|
||||
const structure = await connection.getStructure();
|
||||
const structure = await connection.getStructure(new Set());
|
||||
|
||||
connections[conn.uid] = connection;
|
||||
|
||||
|
@@ -1,110 +0,0 @@
|
||||
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
export default connections => {
|
||||
ipcMain.handle('create-database', async (event, params) => {
|
||||
try {
|
||||
const query = `CREATE DATABASE \`${params.name}\` COLLATE ${params.collation}`;
|
||||
await connections[params.uid].raw(query);
|
||||
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('update-database', async (event, params) => {
|
||||
try {
|
||||
const query = `ALTER DATABASE \`${params.name}\` COLLATE ${params.collation}`;
|
||||
await connections[params.uid].raw(query);
|
||||
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('delete-database', async (event, params) => {
|
||||
try {
|
||||
const query = `DROP DATABASE \`${params.database}\``;
|
||||
await connections[params.uid].raw(query);
|
||||
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-database-collation', async (event, params) => { // TODO: move to mysql class
|
||||
try {
|
||||
const query = `SELECT \`DEFAULT_COLLATION_NAME\` FROM \`information_schema\`.\`SCHEMATA\` WHERE \`SCHEMA_NAME\`='${params.database}'`;
|
||||
const collation = await connections[params.uid].raw(query);
|
||||
|
||||
return { status: 'success', response: collation.rows.length ? collation.rows[0].DEFAULT_COLLATION_NAME : '' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-structure', async (event, uid) => {
|
||||
try {
|
||||
const structure = await connections[uid].getStructure();
|
||||
|
||||
return { status: 'success', response: structure };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-collations', async (event, uid) => {
|
||||
try {
|
||||
const result = await connections[uid].getCollations();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-variables', async (event, uid) => {
|
||||
try {
|
||||
const result = await connections[uid].getVariables();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('use-schema', async (event, { uid, schema }) => {
|
||||
if (!schema) return;
|
||||
|
||||
try {
|
||||
await connections[uid].use(schema);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('raw-query', async (event, { uid, query, schema }) => {
|
||||
if (!query) return;
|
||||
|
||||
try {
|
||||
const result = await connections[uid].raw(query, { nest: true, details: true });
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
63
src/main/ipc-handlers/functions.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
export default (connections) => {
|
||||
ipcMain.handle('get-function-informations', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid].getFunctionInformations(params);
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('drop-function', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].dropFunction(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('alter-function', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].alterFunction(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('alter-trigger-function', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].alterTriggerFunction(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('create-function', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].createFunction(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('create-trigger-function', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].createTriggerFunction(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
@@ -1,15 +1,27 @@
|
||||
import connection from './connection';
|
||||
import tables from './tables';
|
||||
import views from './views';
|
||||
import triggers from './triggers';
|
||||
import routines from './routines';
|
||||
import functions from './functions';
|
||||
import schedulers from './schedulers';
|
||||
import updates from './updates';
|
||||
import application from './application';
|
||||
import database from './database';
|
||||
import schema from './schema';
|
||||
import users from './users';
|
||||
|
||||
const connections = {};
|
||||
|
||||
export default () => {
|
||||
connection(connections);
|
||||
tables(connections);
|
||||
database(connections);
|
||||
views(connections);
|
||||
triggers(connections);
|
||||
routines(connections);
|
||||
functions(connections);
|
||||
schedulers(connections);
|
||||
schema(connections);
|
||||
users(connections);
|
||||
updates();
|
||||
application();
|
||||
};
|
||||
|
43
src/main/ipc-handlers/routines.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
export default (connections) => {
|
||||
ipcMain.handle('get-routine-informations', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid].getRoutineInformations(params);
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('drop-routine', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].dropRoutine(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('alter-routine', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].alterRoutine(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('create-routine', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].createRoutine(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
56
src/main/ipc-handlers/schedulers.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
export default (connections) => {
|
||||
ipcMain.handle('get-scheduler-informations', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid].getEventInformations(params);
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('drop-scheduler', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].dropEvent(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('alter-scheduler', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].alterEvent(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('create-scheduler', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].createEvent(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('toggle-scheduler', async (event, params) => {
|
||||
try {
|
||||
if (!params.enabled)
|
||||
await connections[params.uid].enableEvent({ ...params });
|
||||
else
|
||||
await connections[params.uid].disableEvent({ ...params });
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
385
src/main/ipc-handlers/schema.js
Normal file
@@ -0,0 +1,385 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fork } from 'child_process';
|
||||
import { ipcMain, dialog } from 'electron';
|
||||
|
||||
// @TODO: need some factories
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
|
||||
export default connections => {
|
||||
let exporter = null;
|
||||
let importer = null;
|
||||
|
||||
ipcMain.handle('create-schema', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].createSchema(params);
|
||||
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('update-schema', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].alterSchema(params);
|
||||
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('delete-schema', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].dropSchema(params);
|
||||
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-schema-collation', async (event, params) => {
|
||||
try {
|
||||
const collation = await connections[params.uid].getDatabaseCollation(
|
||||
params
|
||||
);
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
response: collation.rows.length
|
||||
? collation.rows[0].DEFAULT_COLLATION_NAME
|
||||
: ''
|
||||
};
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-structure', async (event, params) => {
|
||||
try {
|
||||
const structure = await connections[params.uid].getStructure(
|
||||
params.schemas
|
||||
);
|
||||
|
||||
return { status: 'success', response: structure };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-collations', async (event, uid) => {
|
||||
try {
|
||||
const result = await connections[uid].getCollations();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-variables', async (event, uid) => {
|
||||
try {
|
||||
const result = await connections[uid].getVariables();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-engines', async (event, uid) => {
|
||||
try {
|
||||
const result = await connections[uid].getEngines();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-version', async (event, uid) => {
|
||||
try {
|
||||
const result = await connections[uid].getVersion();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-processes', async (event, uid) => {
|
||||
try {
|
||||
const result = await connections[uid].getProcesses();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('kill-process', async (event, { uid, pid }) => {
|
||||
try {
|
||||
const result = await connections[uid].killProcess(pid);
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('use-schema', async (event, { uid, schema }) => {
|
||||
if (!schema) return;
|
||||
|
||||
try {
|
||||
await connections[uid].use(schema);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('raw-query', async (event, { uid, query, schema, tabUid, autocommit }) => {
|
||||
if (!query) return;
|
||||
|
||||
try {
|
||||
const result = await connections[uid].raw(query, {
|
||||
nest: true,
|
||||
details: true,
|
||||
schema,
|
||||
tabUid,
|
||||
autocommit,
|
||||
comments: false
|
||||
});
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('export', (event, { uid, type, tables, ...rest }) => {
|
||||
if (exporter !== null) return;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
(async () => {
|
||||
if (fs.existsSync(rest.outputFile)) { // If file exists ask for replace
|
||||
const result = await dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
message: `File ${rest.outputFile} already exists. Do you want to replace it?`,
|
||||
detail: 'A file with the same name already exists in the target folder. Replacing it will overwrite its current contents.',
|
||||
buttons: ['Cancel', 'Replace'],
|
||||
defaultId: 0,
|
||||
cancelId: 0
|
||||
});
|
||||
|
||||
if (result.response !== 1) {
|
||||
resolve({
|
||||
type: 'error',
|
||||
response: 'Operation aborted'
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Init exporter process
|
||||
exporter = fork(isDevelopment ? './dist/exporter.js' : path.resolve(__dirname, './exporter.js'), [], {
|
||||
execArgv: isDevelopment ? ['--inspect=9224'] : undefined
|
||||
});
|
||||
exporter.send({
|
||||
type: 'init',
|
||||
client: {
|
||||
name: type,
|
||||
config: await connections[uid].getDbConfig()
|
||||
},
|
||||
tables,
|
||||
options: rest
|
||||
});
|
||||
|
||||
// Exporter message listener
|
||||
exporter.on('message', ({ type, payload }) => {
|
||||
switch (type) {
|
||||
case 'export-progress':
|
||||
event.sender.send('export-progress', payload);
|
||||
break;
|
||||
case 'end':
|
||||
setTimeout(() => { // Ensures that writing process has finished
|
||||
exporter.kill();
|
||||
exporter = null;
|
||||
}, 2000);
|
||||
resolve({ status: 'success', response: payload });
|
||||
break;
|
||||
case 'cancel':
|
||||
exporter.kill();
|
||||
exporter = null;
|
||||
resolve({ status: 'error', response: 'Operation cancelled' });
|
||||
break;
|
||||
case 'error':
|
||||
exporter.kill();
|
||||
exporter = null;
|
||||
resolve({ status: 'error', response: payload });
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
exporter.on('exit', code => {
|
||||
exporter = null;
|
||||
resolve({ status: 'error', response: `Operation ended with code: ${code}` });
|
||||
});
|
||||
})();
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle('abort-export', async event => {
|
||||
let willAbort = false;
|
||||
|
||||
if (exporter) {
|
||||
const result = await dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
message: 'Are you sure you want to abort the export',
|
||||
buttons: ['Cancel', 'Abort'],
|
||||
defaultId: 0,
|
||||
cancelId: 0
|
||||
});
|
||||
|
||||
if (result.response === 1) {
|
||||
willAbort = true;
|
||||
exporter.send({ type: 'cancel' });
|
||||
}
|
||||
}
|
||||
|
||||
return { status: 'success', response: { willAbort } };
|
||||
});
|
||||
|
||||
ipcMain.handle('import-sql', async (event, options) => {
|
||||
if (importer !== null) return;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
(async () => {
|
||||
const dbConfig = await connections[options.uid].getDbConfig();
|
||||
|
||||
// Init importer process
|
||||
importer = fork(isDevelopment ? './dist/importer.js' : path.resolve(__dirname, './importer.js'), [], {
|
||||
execArgv: isDevelopment ? ['--inspect=9224'] : undefined
|
||||
});
|
||||
importer.send({
|
||||
type: 'init',
|
||||
dbConfig,
|
||||
options
|
||||
});
|
||||
|
||||
// Importer message listener
|
||||
importer.on('message', ({ type, payload }) => {
|
||||
switch (type) {
|
||||
case 'import-progress':
|
||||
event.sender.send('import-progress', payload);
|
||||
break;
|
||||
case 'query-error':
|
||||
event.sender.send('query-error', payload);
|
||||
break;
|
||||
case 'end':
|
||||
setTimeout(() => { // Ensures that writing process has finished
|
||||
importer?.kill();
|
||||
importer = null;
|
||||
}, 2000);
|
||||
resolve({ status: 'success', response: payload });
|
||||
break;
|
||||
case 'cancel':
|
||||
importer.kill();
|
||||
importer = null;
|
||||
resolve({ status: 'error', response: 'Operation cancelled' });
|
||||
break;
|
||||
case 'error':
|
||||
importer.kill();
|
||||
importer = null;
|
||||
resolve({ status: 'error', response: payload });
|
||||
break;
|
||||
}
|
||||
});
|
||||
})();
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle('abort-import-sql', async event => {
|
||||
let willAbort = false;
|
||||
|
||||
if (importer) {
|
||||
const result = await dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
message: 'Are you sure you want to abort the import',
|
||||
buttons: ['Cancel', 'Abort'],
|
||||
defaultId: 0,
|
||||
cancelId: 0
|
||||
});
|
||||
|
||||
if (result.response === 1) {
|
||||
willAbort = true;
|
||||
importer.send({ type: 'cancel' });
|
||||
}
|
||||
}
|
||||
|
||||
return { status: 'success', response: { willAbort } };
|
||||
});
|
||||
|
||||
ipcMain.handle('kill-tab-query', async (event, { uid, tabUid }) => {
|
||||
if (!tabUid) return;
|
||||
|
||||
try {
|
||||
await connections[uid].killTabQuery(tabUid);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('commit-tab', async (event, { uid, tabUid }) => {
|
||||
if (!tabUid) return;
|
||||
|
||||
try {
|
||||
await connections[uid].commitTab(tabUid);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('rollback-tab', async (event, { uid, tabUid }) => {
|
||||
if (!tabUid) return;
|
||||
|
||||
try {
|
||||
await connections[uid].rollbackTab(tabUid);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('destroy-connection-to-commit', async (event, { uid, tabUid }) => {
|
||||
if (!tabUid) return;
|
||||
|
||||
try {
|
||||
await connections[uid].destroyConnectionToCommit(tabUid);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
@@ -1,6 +1,9 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import faker from 'faker';
|
||||
import moment from 'moment';
|
||||
import { sqlEscaper } from 'common/libs/sqlEscaper';
|
||||
import { TEXT, LONG_TEXT, NUMBER, BLOB } from 'common/fieldTypes';
|
||||
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes';
|
||||
import * as customizations from 'common/customizations';
|
||||
import fs from 'fs';
|
||||
|
||||
export default (connections) => {
|
||||
@@ -14,14 +17,54 @@ export default (connections) => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-table-data', async (event, { uid, schema, table }) => {
|
||||
ipcMain.handle('get-table-data', async (event, { uid, schema, table, limit, page, sortParams, where }) => {
|
||||
try {
|
||||
const result = await connections[uid]
|
||||
const offset = (page - 1) * limit;
|
||||
const query = connections[uid]
|
||||
.select('*')
|
||||
.schema(schema)
|
||||
.from(table)
|
||||
.limit(1000)
|
||||
.run({ details: true });
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
|
||||
if (sortParams && sortParams.field && sortParams.dir)
|
||||
query.orderBy({ [sortParams.field]: sortParams.dir.toUpperCase() });
|
||||
|
||||
if (where)
|
||||
query.where(where);
|
||||
|
||||
const result = await query.run({ details: true, schema });
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-table-count', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid].getTableApproximateCount(params);
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-table-options', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid].getTableOptions(params);
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-table-indexes', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid].getTableIndexes(params);
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
@@ -42,33 +85,107 @@ export default (connections) => {
|
||||
});
|
||||
|
||||
ipcMain.handle('update-table-cell', async (event, params) => {
|
||||
try {
|
||||
delete params.row._antares_id;
|
||||
const { stringsWrapper: sw } = customizations[connections[params.uid]._client];
|
||||
|
||||
try { // TODO: move to client classes
|
||||
let escapedParam;
|
||||
let reload = false;
|
||||
const id = typeof params.id === 'number' ? params.id : `"${params.id}"`;
|
||||
const id = typeof params.id === 'number' ? params.id : `${sw}${params.id}${sw}`;
|
||||
|
||||
if (NUMBER.includes(params.type))
|
||||
if ([...NUMBER, ...FLOAT].includes(params.type))
|
||||
escapedParam = params.content;
|
||||
else if ([...TEXT, ...LONG_TEXT].includes(params.type))
|
||||
escapedParam = `"${sqlEscaper(params.content)}"`;
|
||||
else if ([...TEXT, ...LONG_TEXT].includes(params.type)) {
|
||||
switch (connections[params.uid]._client) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
escapedParam = `"${sqlEscaper(params.content)}"`;
|
||||
break;
|
||||
case 'pg':
|
||||
escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`;
|
||||
break;
|
||||
case 'sqlite':
|
||||
escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (ARRAY.includes(params.type))
|
||||
escapedParam = `'${params.content}'`;
|
||||
else if (TEXT_SEARCH.includes(params.type))
|
||||
escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`;
|
||||
else if (BLOB.includes(params.type)) {
|
||||
if (params.content) {
|
||||
const fileBlob = fs.readFileSync(params.content);
|
||||
escapedParam = `0x${fileBlob.toString('hex')}`;
|
||||
let fileBlob;
|
||||
|
||||
switch (connections[params.uid]._client) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
fileBlob = fs.readFileSync(params.content);
|
||||
escapedParam = `0x${fileBlob.toString('hex')}`;
|
||||
break;
|
||||
case 'pg':
|
||||
fileBlob = fs.readFileSync(params.content);
|
||||
escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`;
|
||||
break;
|
||||
case 'sqlite':
|
||||
fileBlob = fs.readFileSync(params.content);
|
||||
escapedParam = `X'${fileBlob.toString('hex')}'`;
|
||||
break;
|
||||
}
|
||||
reload = true;
|
||||
}
|
||||
else
|
||||
escapedParam = '""';
|
||||
else {
|
||||
switch (connections[params.uid]._client) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
escapedParam = '\'\'';
|
||||
break;
|
||||
case 'pg':
|
||||
escapedParam = 'decode(\'\', \'hex\')';
|
||||
break;
|
||||
case 'sqlite':
|
||||
escapedParam = 'X\'\'';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (BIT.includes(params.type)) {
|
||||
escapedParam = `b'${sqlEscaper(params.content)}'`;
|
||||
reload = true;
|
||||
}
|
||||
else if (params.content === null)
|
||||
escapedParam = 'NULL';
|
||||
else
|
||||
escapedParam = `"${sqlEscaper(params.content)}"`;
|
||||
escapedParam = `'${sqlEscaper(params.content)}'`;
|
||||
|
||||
await connections[params.uid]
|
||||
.update({ [params.field]: `= ${escapedParam}` })
|
||||
.schema(params.schema)
|
||||
.from(params.table)
|
||||
.where({ [params.primary]: `= ${id}` })
|
||||
.run();
|
||||
if (params.primary) { // TODO: handle multiple primary
|
||||
await connections[params.uid]
|
||||
.update({ [params.field]: `= ${escapedParam}` })
|
||||
.schema(params.schema)
|
||||
.from(params.table)
|
||||
.where({ [params.primary]: `= ${id}` })
|
||||
.limit(1)
|
||||
.run();
|
||||
}
|
||||
else {
|
||||
const { orgRow } = params;
|
||||
reload = true;
|
||||
|
||||
for (const key in orgRow) {
|
||||
if (typeof orgRow[key] === 'string')
|
||||
orgRow[key] = `'${orgRow[key]}'`;
|
||||
|
||||
orgRow[key] = `= ${orgRow[key]}`;
|
||||
}
|
||||
|
||||
await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.update({ [params.field]: `= ${escapedParam}` })
|
||||
.from(params.table)
|
||||
.where(orgRow)
|
||||
.limit(1)
|
||||
.run();
|
||||
}
|
||||
|
||||
return { status: 'success', response: { reload } };
|
||||
}
|
||||
@@ -78,22 +195,56 @@ export default (connections) => {
|
||||
});
|
||||
|
||||
ipcMain.handle('delete-table-rows', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.delete(params.table)
|
||||
.where({ [params.primary]: `IN (${params.rows.join(',')})` })
|
||||
.run();
|
||||
if (params.primary) {
|
||||
const idString = params.rows.map(row => {
|
||||
const fieldName = Object.keys(row)[0].includes('.') ? `${params.table}.${params.primary}` : params.primary;
|
||||
|
||||
return { status: 'success', response: result };
|
||||
return typeof row[fieldName] === 'string'
|
||||
? `'${row[fieldName]}'`
|
||||
: row[fieldName];
|
||||
}).join(',');
|
||||
|
||||
try {
|
||||
const result = await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.delete(params.table)
|
||||
.where({ [params.primary]: `IN (${idString})` })
|
||||
.run();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
else {
|
||||
try {
|
||||
for (const row of params.rows) {
|
||||
for (const key in row) {
|
||||
if (typeof row[key] === 'string')
|
||||
row[key] = `'${row[key]}'`;
|
||||
|
||||
row[key] = `= ${row[key]}`;
|
||||
}
|
||||
|
||||
await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.delete(params.table)
|
||||
.where(row)
|
||||
.limit(1)
|
||||
.run();
|
||||
}
|
||||
|
||||
return { status: 'success', response: [] };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('insert-table-rows', async (event, params) => {
|
||||
try {
|
||||
try { // TODO: move to client classes
|
||||
const insertObj = {};
|
||||
for (const key in params.row) {
|
||||
const type = params.fields[key];
|
||||
@@ -101,32 +252,166 @@ export default (connections) => {
|
||||
|
||||
if (params.row[key] === null)
|
||||
escapedParam = 'NULL';
|
||||
else if (NUMBER.includes(type))
|
||||
escapedParam = params.row[key];
|
||||
else if ([...TEXT, ...LONG_TEXT].includes(type))
|
||||
escapedParam = `"${sqlEscaper(params.row[key])}"`;
|
||||
else if (BLOB.includes(type)) {
|
||||
if (params.row[key]) {
|
||||
const fileBlob = fs.readFileSync(params.row[key]);
|
||||
escapedParam = `0x${fileBlob.toString('hex')}`;
|
||||
else if ([...NUMBER, ...FLOAT].includes(type))
|
||||
escapedParam = +params.row[key];
|
||||
else if ([...TEXT, ...LONG_TEXT].includes(type)) {
|
||||
switch (connections[params.uid]._client) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
escapedParam = `"${sqlEscaper(params.row[key].value)}"`;
|
||||
break;
|
||||
case 'pg':
|
||||
escapedParam = `'${params.row[key].value.replaceAll('\'', '\'\'')}'`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (BLOB.includes(type)) {
|
||||
if (params.row[key].value) {
|
||||
let fileBlob;
|
||||
|
||||
switch (connections[params.uid]._client) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
fileBlob = fs.readFileSync(params.row[key].value);
|
||||
escapedParam = `0x${fileBlob.toString('hex')}`;
|
||||
break;
|
||||
case 'pg':
|
||||
fileBlob = fs.readFileSync(params.row[key].value);
|
||||
escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (connections[params.uid]._client) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
escapedParam = '""';
|
||||
break;
|
||||
case 'pg':
|
||||
escapedParam = 'decode(\'\', \'hex\')';
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
escapedParam = '""';
|
||||
}
|
||||
else
|
||||
escapedParam = `"${sqlEscaper(params.row[key])}"`;
|
||||
|
||||
insertObj[key] = escapedParam;
|
||||
}
|
||||
|
||||
for (let i = 0; i < params.repeat; i++) {
|
||||
await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.into(params.table)
|
||||
.insert(insertObj)
|
||||
.run();
|
||||
const rows = new Array(+params.repeat).fill(insertObj);
|
||||
|
||||
await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.into(params.table)
|
||||
.insert(rows)
|
||||
.run();
|
||||
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('insert-table-fake-rows', async (event, params) => {
|
||||
try { // TODO: move to client classes
|
||||
const rows = [];
|
||||
|
||||
for (let i = 0; i < +params.repeat; i++) {
|
||||
const insertObj = {};
|
||||
|
||||
for (const key in params.row) {
|
||||
const type = params.fields[key];
|
||||
let escapedParam;
|
||||
|
||||
if (!('group' in params.row[key]) || params.row[key].group === 'manual') { // Manual value
|
||||
if (params.row[key].value === null || params.row[key].value === undefined)
|
||||
escapedParam = 'NULL';
|
||||
else if ([...NUMBER, ...FLOAT].includes(type))
|
||||
escapedParam = params.row[key].value;
|
||||
else if ([...TEXT, ...LONG_TEXT].includes(type)) {
|
||||
switch (connections[params.uid]._client) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
escapedParam = `"${sqlEscaper(params.row[key].value)}"`;
|
||||
break;
|
||||
case 'pg':
|
||||
escapedParam = `'${params.row[key].value.replaceAll('\'', '\'\'')}'`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (BLOB.includes(type)) {
|
||||
if (params.row[key].value) {
|
||||
let fileBlob;
|
||||
|
||||
switch (connections[params.uid]._client) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
fileBlob = fs.readFileSync(params.row[key].value);
|
||||
escapedParam = `0x${fileBlob.toString('hex')}`;
|
||||
break;
|
||||
case 'pg':
|
||||
fileBlob = fs.readFileSync(params.row[key].value);
|
||||
escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (connections[params.uid]._client) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
escapedParam = '""';
|
||||
break;
|
||||
case 'pg':
|
||||
escapedParam = 'decode(\'\', \'hex\')';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (BIT.includes(type))
|
||||
escapedParam = `b'${sqlEscaper(params.row[key].value)}'`;
|
||||
else
|
||||
escapedParam = `'${sqlEscaper(params.row[key].value)}'`;
|
||||
|
||||
insertObj[key] = escapedParam;
|
||||
}
|
||||
else { // Faker value
|
||||
const parsedParams = {};
|
||||
let fakeValue;
|
||||
|
||||
if (params.locale)
|
||||
faker.locale = params.locale;
|
||||
|
||||
if (Object.keys(params.row[key].params).length) {
|
||||
Object.keys(params.row[key].params).forEach(param => {
|
||||
if (!isNaN(params.row[key].params[param]))
|
||||
parsedParams[param] = +params.row[key].params[param];
|
||||
});
|
||||
fakeValue = faker[params.row[key].group][params.row[key].method](parsedParams);
|
||||
}
|
||||
else
|
||||
fakeValue = faker[params.row[key].group][params.row[key].method]();
|
||||
|
||||
if (typeof fakeValue === 'string') {
|
||||
if (params.row[key].length)
|
||||
fakeValue = fakeValue.substr(0, params.row[key].length);
|
||||
fakeValue = `'${sqlEscaper(fakeValue)}'`;
|
||||
}
|
||||
else if ([...DATE, ...DATETIME].includes(type))
|
||||
fakeValue = `'${moment(fakeValue).format('YYYY-MM-DD HH:mm:ss.SSSSSS')}'`;
|
||||
|
||||
insertObj[key] = fakeValue;
|
||||
}
|
||||
}
|
||||
|
||||
rows.push(insertObj);
|
||||
}
|
||||
|
||||
await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.into(params.table)
|
||||
.insert(rows)
|
||||
.run();
|
||||
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
@@ -137,13 +422,13 @@ export default (connections) => {
|
||||
ipcMain.handle('get-foreign-list', async (event, { uid, schema, table, column, description }) => {
|
||||
try {
|
||||
const query = connections[uid]
|
||||
.select(`${column} AS foreignColumn`)
|
||||
.select(`${column} AS foreign_column`)
|
||||
.schema(schema)
|
||||
.from(table)
|
||||
.orderBy('foreignColumn ASC');
|
||||
.orderBy('foreign_column ASC');
|
||||
|
||||
if (description)
|
||||
query.select(`LEFT(${description}, 20) AS foreignDescription`);
|
||||
query.select(`LEFT(${description}, 20) AS foreign_description`);
|
||||
|
||||
const results = await query.run();
|
||||
|
||||
@@ -154,6 +439,16 @@ export default (connections) => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('create-table', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].createTable(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('alter-table', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].alterTable(params);
|
||||
@@ -163,4 +458,34 @@ export default (connections) => {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('duplicate-table', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].duplicateTable(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('truncate-table', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].truncateTable(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('drop-table', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].dropTable(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
56
src/main/ipc-handlers/triggers.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
export default (connections) => {
|
||||
ipcMain.handle('get-trigger-informations', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid].getTriggerInformations(params);
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('drop-trigger', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].dropTrigger(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('alter-trigger', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].alterTrigger(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('create-trigger', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].createTrigger(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('toggle-trigger', async (event, params) => {
|
||||
try {
|
||||
if (!params.enabled)
|
||||
await connections[params.uid].enableTrigger(params);
|
||||
else
|
||||
await connections[params.uid].disableTrigger(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
@@ -1,16 +1,25 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import Store from 'electron-store';
|
||||
const persistentStore = new Store({ name: 'settings' });
|
||||
const isMacOS = process.platform === 'darwin';
|
||||
|
||||
let mainWindow;
|
||||
autoUpdater.allowPrerelease = true;
|
||||
autoUpdater.allowPrerelease = persistentStore.get('allow_prerelease', true);
|
||||
|
||||
export default () => {
|
||||
ipcMain.on('check-for-updates', event => {
|
||||
mainWindow = event;
|
||||
|
||||
autoUpdater.checkForUpdatesAndNotify().catch(() => {
|
||||
mainWindow.reply('check-failed');
|
||||
});
|
||||
if (process.windowsStore || (process.platform === 'linux' && !process.env.APPIMAGE))
|
||||
mainWindow.reply('no-auto-update');
|
||||
else if (isMacOS) { // Temporary solution on MacOS for unsigned app updates
|
||||
autoUpdater.autoDownload = false;
|
||||
}
|
||||
else {
|
||||
autoUpdater.checkForUpdatesAndNotify().catch(() => {
|
||||
mainWindow.reply('check-failed');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('restart-to-update', () => {
|
||||
@@ -23,7 +32,10 @@ export default () => {
|
||||
});
|
||||
|
||||
autoUpdater.on('update-available', () => {
|
||||
mainWindow.reply('update-available');
|
||||
if (isMacOS)
|
||||
mainWindow.reply('link-to-download');
|
||||
else
|
||||
mainWindow.reply('update-available');
|
||||
});
|
||||
|
||||
autoUpdater.on('update-not-available', () => {
|
||||
|
15
src/main/ipc-handlers/users.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
export default (connections) => {
|
||||
ipcMain.handle('get-users', async (event, uid) => {
|
||||
try {
|
||||
const result = await connections[uid].getUsers();
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
if (err.code === 'ER_TABLEACCESS_DENIED_ERROR')
|
||||
return { status: 'success', response: [] };
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
43
src/main/ipc-handlers/views.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
export default (connections) => {
|
||||
ipcMain.handle('get-view-informations', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid].getViewInformations(params);
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('drop-view', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].dropView(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('alter-view', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].alterView(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('create-view', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].createView(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
@@ -1,4 +1,10 @@
|
||||
'use strict';
|
||||
const queryLogger = sql => {
|
||||
// Remove comments, newlines and multiple spaces
|
||||
const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' ');
|
||||
console.log(escapedSql);
|
||||
};
|
||||
|
||||
/**
|
||||
* As Simple As Possible Query Builder Core
|
||||
*
|
||||
@@ -16,7 +22,8 @@ export class AntaresCore {
|
||||
this._params = args.params;
|
||||
this._poolSize = args.poolSize || false;
|
||||
this._connection = null;
|
||||
this._logger = args.logger || console.log;
|
||||
this._ssh = null;
|
||||
this._logger = args.logger || queryLogger;
|
||||
|
||||
this._queryDefaults = {
|
||||
schema: '',
|
||||
@@ -26,9 +33,10 @@ export class AntaresCore {
|
||||
groupBy: [],
|
||||
orderBy: [],
|
||||
limit: [],
|
||||
offset: [],
|
||||
join: [],
|
||||
update: [],
|
||||
insert: {},
|
||||
insert: [],
|
||||
delete: false
|
||||
};
|
||||
this._query = Object.assign({}, this._queryDefaults);
|
||||
@@ -109,6 +117,11 @@ export class AntaresCore {
|
||||
return this;
|
||||
}
|
||||
|
||||
offset (...args) {
|
||||
this._query.offset = args;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String | Array} args field = value
|
||||
* @returns
|
||||
@@ -120,12 +133,12 @@ export class AntaresCore {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} obj field: value
|
||||
* @param {Array} arr Array of row objects
|
||||
* @returns
|
||||
* @memberof AntaresCore
|
||||
*/
|
||||
insert (obj) {
|
||||
this._query.insert = { ...this._query.insert, ...obj };
|
||||
insert (arr) {
|
||||
this._query.insert = [...this._query.insert, ...arr];
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -134,7 +147,7 @@ export class AntaresCore {
|
||||
* @returns {Promise}
|
||||
* @memberof AntaresCore
|
||||
*/
|
||||
async run (args) {
|
||||
run (args) {
|
||||
const rawQuery = this.getSQL();
|
||||
this._resetQuery();
|
||||
return this.raw(rawQuery, args);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
import { MySQLClient } from './clients/MySQLClient';
|
||||
|
||||
import { PostgreSQLClient } from './clients/PostgreSQLClient';
|
||||
import { SQLiteClient } from './clients/SQLiteClient';
|
||||
export class ClientsFactory {
|
||||
/**
|
||||
* Returns a database connection based on received args.
|
||||
@@ -11,17 +12,27 @@ export class ClientsFactory {
|
||||
* @param {String} args.params.host
|
||||
* @param {Number} args.params.port
|
||||
* @param {String} args.params.password
|
||||
* @param {String=} args.params.database
|
||||
* @param {String=} args.params.schema
|
||||
* @param {String} args.params.ssh.host
|
||||
* @param {String} args.params.ssh.username
|
||||
* @param {String} args.params.ssh.password
|
||||
* @param {Number} args.params.ssh.port
|
||||
* @param {Number=} args.poolSize
|
||||
* @returns Database Connection
|
||||
* @memberof ClientsFactory
|
||||
*/
|
||||
static getConnection (args) {
|
||||
static getClient (args) {
|
||||
switch (args.client) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
return new MySQLClient(args);
|
||||
case 'pg':
|
||||
return new PostgreSQLClient(args);
|
||||
case 'sqlite':
|
||||
return new SQLiteClient(args);
|
||||
default:
|
||||
return new Error(`Unknown database client: ${args.client}`);
|
||||
throw new Error(`Unknown database client: ${args.client}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1675
src/main/libs/clients/PostgreSQLClient.js
Normal file
859
src/main/libs/clients/SQLiteClient.js
Normal file
@@ -0,0 +1,859 @@
|
||||
'use strict';
|
||||
import sqlite from 'better-sqlite3';
|
||||
import { AntaresCore } from '../AntaresCore';
|
||||
import dataTypes from 'common/data-types/sqlite';
|
||||
import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes';
|
||||
|
||||
export class SQLiteClient extends AntaresCore {
|
||||
constructor (args) {
|
||||
super(args);
|
||||
|
||||
this._schema = null;
|
||||
this._connectionsToCommit = new Map();
|
||||
}
|
||||
|
||||
_getTypeInfo (type) {
|
||||
return dataTypes
|
||||
.reduce((acc, group) => [...acc, ...group.types], [])
|
||||
.filter(_type => _type.name === type.toUpperCase())[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async connect () {
|
||||
this._connection = this.getConnection();
|
||||
}
|
||||
|
||||
getConnection () {
|
||||
return sqlite(this._params.databasePath, {
|
||||
fileMustExist: true,
|
||||
readonly: this._params.readonly
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
destroy () {}
|
||||
|
||||
/**
|
||||
* Executes an USE query
|
||||
*
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
use () {}
|
||||
|
||||
/**
|
||||
* @param {Array} schemas list
|
||||
* @returns {Array.<Object>} databases scructure
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async getStructure (schemas) {
|
||||
const { rows: databases } = await this.raw('SELECT * FROM pragma_database_list');
|
||||
|
||||
const filteredDatabases = databases;
|
||||
|
||||
const tablesArr = [];
|
||||
const triggersArr = [];
|
||||
let schemaSize = 0;
|
||||
|
||||
for (const db of filteredDatabases) {
|
||||
if (!schemas.has(db.name)) continue;
|
||||
|
||||
let { rows: tables } = await this.raw(`
|
||||
SELECT *
|
||||
FROM "${db.name}".sqlite_master
|
||||
WHERE type IN ('table', 'view')
|
||||
AND name NOT LIKE 'sqlite_%'
|
||||
ORDER BY name
|
||||
`);
|
||||
if (tables.length) {
|
||||
tables = tables.map(table => {
|
||||
table.Db = db.name;
|
||||
return table;
|
||||
});
|
||||
tablesArr.push(...tables);
|
||||
}
|
||||
|
||||
let { rows: triggers } = await this.raw(`SELECT * FROM "${db.name}".sqlite_master WHERE type='trigger'`);
|
||||
if (triggers.length) {
|
||||
triggers = triggers.map(trigger => {
|
||||
trigger.Db = db.name;
|
||||
return trigger;
|
||||
});
|
||||
triggersArr.push(...triggers);
|
||||
}
|
||||
}
|
||||
|
||||
return filteredDatabases.map(db => {
|
||||
if (schemas.has(db.name)) {
|
||||
// TABLES
|
||||
const remappedTables = tablesArr.filter(table => table.Db === db.name).map(table => {
|
||||
const tableSize = 0;
|
||||
schemaSize += tableSize;
|
||||
|
||||
return {
|
||||
name: table.name,
|
||||
type: table.type,
|
||||
rows: false,
|
||||
size: false
|
||||
};
|
||||
});
|
||||
|
||||
// TRIGGERS
|
||||
const remappedTriggers = triggersArr.filter(trigger => trigger.Db === db.name).map(trigger => {
|
||||
return {
|
||||
name: trigger.name,
|
||||
table: trigger.tbl_name
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
name: db.name,
|
||||
size: schemaSize,
|
||||
tables: remappedTables,
|
||||
functions: [],
|
||||
procedures: [],
|
||||
triggers: remappedTriggers,
|
||||
schedulers: []
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
name: db.name,
|
||||
size: 0,
|
||||
tables: [],
|
||||
functions: [],
|
||||
procedures: [],
|
||||
triggers: [],
|
||||
schedulers: []
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} params
|
||||
* @param {String} params.schema
|
||||
* @param {String} params.table
|
||||
* @returns {Object} table scructure
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async getTableColumns ({ schema, table }) {
|
||||
const { rows: fields } = await this.raw(`SELECT * FROM "${schema}".pragma_table_info('${table}')`);
|
||||
|
||||
return fields.map(field => {
|
||||
const [type, length] = field.type.includes('(')
|
||||
? field.type.replace(')', '').split('(').map(el => {
|
||||
if (!isNaN(el)) el = +el;
|
||||
return el;
|
||||
})
|
||||
: [field.type, null];
|
||||
|
||||
return {
|
||||
name: field.name,
|
||||
key: null,
|
||||
type: type.trim(),
|
||||
schema: schema,
|
||||
table: table,
|
||||
numPrecision: [...NUMBER, ...FLOAT].includes(type) ? length : null,
|
||||
datePrecision: null,
|
||||
charLength: ![...NUMBER, ...FLOAT].includes(type) ? length : null,
|
||||
nullable: !field.notnull,
|
||||
unsigned: null,
|
||||
zerofill: null,
|
||||
order: typeof field.cid === 'string' ? +field.cid + 1 : field.cid + 1,
|
||||
default: field.dflt_value,
|
||||
charset: null,
|
||||
collation: null,
|
||||
autoIncrement: false,
|
||||
onUpdate: null,
|
||||
comment: ''
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} params
|
||||
* @param {String} params.schema
|
||||
* @param {String} params.table
|
||||
* @returns {Object} table row count
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async getTableApproximateCount ({ schema, table }) {
|
||||
const { rows } = await this.raw(`SELECT COUNT(*) AS count FROM "${schema}"."${table}"`);
|
||||
|
||||
return rows.length ? rows[0].count : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} params
|
||||
* @param {String} params.schema
|
||||
* @param {String} params.table
|
||||
* @returns {Object} table options
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async getTableOptions ({ schema, table }) {
|
||||
return { name: table };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} params
|
||||
* @param {String} params.schema
|
||||
* @param {String} params.table
|
||||
* @returns {Object} table indexes
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async getTableIndexes ({ schema, table }) {
|
||||
const remappedIndexes = [];
|
||||
const { rows: primaryKeys } = await this.raw(`SELECT * FROM "${schema}".pragma_table_info('${table}') WHERE pk != 0`);
|
||||
|
||||
for (const key of primaryKeys) {
|
||||
remappedIndexes.push({
|
||||
name: 'PRIMARY',
|
||||
column: key.name,
|
||||
indexType: null,
|
||||
type: 'PRIMARY',
|
||||
cardinality: null,
|
||||
comment: '',
|
||||
indexComment: ''
|
||||
});
|
||||
}
|
||||
|
||||
const { rows: indexes } = await this.raw(`SELECT * FROM "${schema}".pragma_index_list('${table}');`);
|
||||
|
||||
for (const index of indexes) {
|
||||
const { rows: details } = await this.raw(`SELECT * FROM "${schema}".pragma_index_info('${index.name}');`);
|
||||
|
||||
for (const detail of details) {
|
||||
remappedIndexes.push({
|
||||
name: index.name,
|
||||
column: detail.name,
|
||||
indexType: null,
|
||||
type: index.unique === 1 ? 'UNIQUE' : 'INDEX',
|
||||
cardinality: null,
|
||||
comment: '',
|
||||
indexComment: ''
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return remappedIndexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} params
|
||||
* @param {String} params.schema
|
||||
* @param {String} params.table
|
||||
* @returns {Object} table key usage
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async getKeyUsage ({ schema, table }) {
|
||||
const { rows } = await this.raw(`SELECT * FROM "${schema}".pragma_foreign_key_list('${table}');`);
|
||||
|
||||
return rows.map(field => {
|
||||
return {
|
||||
schema: schema,
|
||||
table: table,
|
||||
field: field.from,
|
||||
position: field.id + 1,
|
||||
constraintPosition: null,
|
||||
constraintName: field.id,
|
||||
refSchema: schema,
|
||||
refTable: field.table,
|
||||
refField: field.to,
|
||||
onUpdate: field.on_update,
|
||||
onDelete: field.on_delete
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async getUsers () {}
|
||||
|
||||
/**
|
||||
* SHOW CREATE VIEW
|
||||
*
|
||||
* @returns {Array.<Object>} view informations
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async getViewInformations ({ schema, view }) {
|
||||
const sql = `SELECT "sql" FROM "${schema}".sqlite_master WHERE "type"='view' AND name='${view}'`;
|
||||
const results = await this.raw(sql);
|
||||
|
||||
return results.rows.map(row => {
|
||||
return {
|
||||
sql: row.sql.match(/(?<=AS ).*?$/gs)[0],
|
||||
name: view
|
||||
};
|
||||
})[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* DROP VIEW
|
||||
*
|
||||
* @returns {Array.<Object>} parameters
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async dropView (params) {
|
||||
const sql = `DROP VIEW "${params.schema}"."${params.view}"`;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* ALTER VIEW
|
||||
*
|
||||
* @returns {Array.<Object>} parameters
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async alterView (params) {
|
||||
const { view } = params;
|
||||
try {
|
||||
await this.dropView({ schema: view.schema, view: view.oldName });
|
||||
await this.createView(view);
|
||||
}
|
||||
catch (err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CREATE VIEW
|
||||
*
|
||||
* @returns {Array.<Object>} parameters
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async createView (params) {
|
||||
const sql = `CREATE VIEW "${params.schema}"."${params.name}" AS ${params.sql}`;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* SHOW CREATE TRIGGER
|
||||
*
|
||||
* @returns {Array.<Object>} view informations
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async getTriggerInformations ({ schema, trigger }) {
|
||||
const sql = `SELECT "sql" FROM "${schema}".sqlite_master WHERE "type"='trigger' AND name='${trigger}'`;
|
||||
const results = await this.raw(sql);
|
||||
|
||||
return results.rows.map(row => {
|
||||
return {
|
||||
sql: row.sql.match(/(BEGIN|begin)(.*)(END|end)/gs)[0],
|
||||
name: trigger,
|
||||
table: row.sql.match(/(?<=ON `).*?(?=`)/gs)[0],
|
||||
activation: row.sql.match(/(BEFORE|AFTER)/gs)[0],
|
||||
event: row.sql.match(/(INSERT|UPDATE|DELETE)/gs)[0]
|
||||
};
|
||||
})[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* DROP TRIGGER
|
||||
*
|
||||
* @returns {Array.<Object>} parameters
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async dropTrigger (params) {
|
||||
const sql = `DROP TRIGGER \`${params.schema}\`.\`${params.trigger}\``;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* ALTER TRIGGER
|
||||
*
|
||||
* @returns {Array.<Object>} parameters
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async alterTrigger (params) {
|
||||
const { trigger } = params;
|
||||
const tempTrigger = Object.assign({}, trigger);
|
||||
tempTrigger.name = `Antares_${tempTrigger.name}_tmp`;
|
||||
|
||||
try {
|
||||
await this.createTrigger(tempTrigger);
|
||||
await this.dropTrigger({ schema: trigger.schema, trigger: tempTrigger.name });
|
||||
await this.dropTrigger({ schema: trigger.schema, trigger: trigger.oldName });
|
||||
await this.createTrigger(trigger);
|
||||
}
|
||||
catch (err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CREATE TRIGGER
|
||||
*
|
||||
* @returns {Array.<Object>} parameters
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async createTrigger (params) {
|
||||
const sql = `CREATE ${params.definer ? `DEFINER=${params.definer} ` : ''}TRIGGER \`${params.schema}\`.\`${params.name}\` ${params.activation} ${params.event} ON \`${params.table}\` FOR EACH ROW ${params.sql}`;
|
||||
return await this.raw(sql, { split: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* SHOW COLLATION
|
||||
*
|
||||
* @returns {Array.<Object>} collations list
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async getCollations () {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* SHOW VARIABLES
|
||||
*
|
||||
* @returns {Array.<Object>} variables list
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async getVariables () {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* SHOW ENGINES
|
||||
*
|
||||
* @returns {Array.<Object>} engines list
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async getEngines () {
|
||||
return {
|
||||
name: 'SQLite',
|
||||
support: 'YES',
|
||||
comment: '',
|
||||
isDefault: true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* SHOW VARIABLES LIKE '%vers%'
|
||||
*
|
||||
* @returns {Array.<Object>} version parameters
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async getVersion () {
|
||||
const os = require('os');
|
||||
const sql = 'SELECT sqlite_version() AS version';
|
||||
const { rows } = await this.raw(sql);
|
||||
|
||||
return {
|
||||
number: rows[0].version,
|
||||
name: 'SQLite',
|
||||
arch: process.arch,
|
||||
os: `${os.type()} ${os.release()}`
|
||||
};
|
||||
}
|
||||
|
||||
async getProcesses () {}
|
||||
|
||||
async killProcess () {}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} tabUid
|
||||
* @returns {Promise<null>}
|
||||
*/
|
||||
async commitTab (tabUid) {
|
||||
const connection = this._connectionsToCommit.get(tabUid);
|
||||
if (connection) {
|
||||
connection.prepare('COMMIT').run();
|
||||
return this.destroyConnectionToCommit(tabUid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} tabUid
|
||||
* @returns {Promise<null>}
|
||||
*/
|
||||
async rollbackTab (tabUid) {
|
||||
const connection = this._connectionsToCommit.get(tabUid);
|
||||
if (connection) {
|
||||
connection.prepare('ROLLBACK').run();
|
||||
return this.destroyConnectionToCommit(tabUid);
|
||||
}
|
||||
}
|
||||
|
||||
destroyConnectionToCommit (tabUid) {
|
||||
const connection = this._connectionsToCommit.get(tabUid);
|
||||
if (connection) {
|
||||
connection.close();
|
||||
this._connectionsToCommit.delete(tabUid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CREATE TABLE
|
||||
*
|
||||
* @returns {Promise<null>}
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async createTable (params) {
|
||||
const {
|
||||
schema,
|
||||
fields,
|
||||
foreigns,
|
||||
indexes,
|
||||
options
|
||||
} = params;
|
||||
const newColumns = [];
|
||||
const newIndexes = [];
|
||||
const manageIndexes = [];
|
||||
const newForeigns = [];
|
||||
|
||||
let sql = `CREATE TABLE "${schema}"."${options.name}"`;
|
||||
|
||||
// ADD FIELDS
|
||||
fields.forEach(field => {
|
||||
const typeInfo = this._getTypeInfo(field.type);
|
||||
const length = typeInfo?.length ? field.enumValues || field.numLength || field.charLength || field.datePrecision : false;
|
||||
|
||||
newColumns.push(`"${field.name}"
|
||||
${field.type.toUpperCase()}${length && length !== true ? `(${length})` : ''}
|
||||
${field.unsigned ? 'UNSIGNED' : ''}
|
||||
${field.nullable ? 'NULL' : 'NOT NULL'}
|
||||
${field.autoIncrement ? 'AUTO_INCREMENT' : ''}
|
||||
${field.default ? `DEFAULT ${field.default}` : ''}
|
||||
${field.onUpdate ? `ON UPDATE ${field.onUpdate}` : ''}`);
|
||||
});
|
||||
|
||||
// ADD INDEX
|
||||
indexes.forEach(index => {
|
||||
const fields = index.fields.map(field => `"${field}"`).join(',');
|
||||
const type = index.type;
|
||||
|
||||
if (type === 'PRIMARY')
|
||||
newIndexes.push(`PRIMARY KEY (${fields})`);
|
||||
else
|
||||
manageIndexes.push(`CREATE ${type === 'UNIQUE' ? type : ''} INDEX "${index.name}" ON "${options.name}" (${fields})`);
|
||||
});
|
||||
|
||||
// ADD FOREIGN KEYS
|
||||
foreigns.forEach(foreign => {
|
||||
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(', ')})`;
|
||||
if (manageIndexes.length) sql = `${sql}; ${manageIndexes.join(';')}`;
|
||||
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* ALTER TABLE
|
||||
*
|
||||
* @returns {Promise<null>}
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async alterTable (params) {
|
||||
try {
|
||||
await this.raw('BEGIN TRANSACTION');
|
||||
await this.raw('PRAGMA foreign_keys = 0');
|
||||
|
||||
const tmpName = `Antares_${params.table}_tmp`;
|
||||
await this.raw(`CREATE TABLE "${tmpName}" AS SELECT * FROM "${params.table}"`);
|
||||
await this.dropTable(params);
|
||||
|
||||
const createTableParams = {
|
||||
schema: params.schema,
|
||||
fields: params.tableStructure.fields,
|
||||
foreigns: params.tableStructure.foreigns,
|
||||
indexes: params.tableStructure.indexes.filter(index => !index.name.includes('sqlite_autoindex')),
|
||||
options: { name: params.tableStructure.name }
|
||||
};
|
||||
await this.createTable(createTableParams);
|
||||
const insertFields = createTableParams.fields
|
||||
.filter(field => {
|
||||
return (
|
||||
params.additions.every(add => add.name !== field.name) &&
|
||||
params.deletions.every(del => del.name !== field.name)
|
||||
);
|
||||
})
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(`"${curr.name}"`);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const selectFields = insertFields.map(field => {
|
||||
const renamedField = params.changes.find(change => `"${change.name}"` === field);
|
||||
if (renamedField)
|
||||
return `"${renamedField.orgName}"`;
|
||||
return field;
|
||||
});
|
||||
|
||||
await this.raw(`INSERT INTO "${createTableParams.options.name}" (${insertFields.join(',')}) SELECT ${selectFields.join(',')} FROM "${tmpName}"`);
|
||||
|
||||
await this.dropTable({ schema: params.schema, table: tmpName });
|
||||
await this.raw('PRAGMA foreign_keys = 1');
|
||||
await this.raw('COMMIT');
|
||||
}
|
||||
catch (err) {
|
||||
await this.raw('ROLLBACK');
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DUPLICATE TABLE
|
||||
*
|
||||
* @returns {Promise<null>}
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async duplicateTable (params) { // TODO: retrive table informations and create a copy
|
||||
const sql = `CREATE TABLE "${params.schema}"."${params.table}_copy" AS SELECT * FROM "${params.schema}"."${params.table}"`;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* TRUNCATE TABLE
|
||||
*
|
||||
* @returns {Promise<null>}
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async truncateTable (params) {
|
||||
const sql = `DELETE FROM "${params.schema}"."${params.table}"`;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* DROP TABLE
|
||||
*
|
||||
* @returns {Promise<null>}
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async dropTable (params) {
|
||||
const sql = `DROP TABLE "${params.schema}"."${params.table}"`;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {String} SQL string
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
getSQL () {
|
||||
// SELECT
|
||||
const selectArray = this._query.select.reduce(this._reducer, []);
|
||||
let selectRaw = '';
|
||||
|
||||
if (selectArray.length)
|
||||
selectRaw = selectArray.length ? `SELECT ${selectArray.join(', ')} ` : 'SELECT * ';
|
||||
|
||||
// FROM
|
||||
let fromRaw = '';
|
||||
|
||||
if (!this._query.update.length && !Object.keys(this._query.insert).length && !!this._query.from)
|
||||
fromRaw = 'FROM';
|
||||
else if (Object.keys(this._query.insert).length)
|
||||
fromRaw = 'INTO';
|
||||
|
||||
fromRaw += this._query.from ? ` ${this._query.schema ? `"${this._query.schema}".` : ''}"${this._query.from}" ` : '';
|
||||
|
||||
// WHERE
|
||||
const whereArray = this._query.where
|
||||
.reduce(this._reducer, [])
|
||||
?.map(clausole => clausole.replace('= null', 'IS NULL'));
|
||||
const whereRaw = whereArray.length ? `WHERE ${whereArray.join(' AND ')} ` : '';
|
||||
|
||||
// UPDATE
|
||||
const updateArray = this._query.update.reduce(this._reducer, []);
|
||||
const updateRaw = updateArray.length ? `SET ${updateArray.join(', ')} ` : '';
|
||||
|
||||
// INSERT
|
||||
let insertRaw = '';
|
||||
|
||||
if (this._query.insert.length) {
|
||||
const fieldsList = Object.keys(this._query.insert[0]);
|
||||
const rowsList = this._query.insert.map(el => `(${Object.values(el).join(', ')})`);
|
||||
|
||||
insertRaw = `(${fieldsList.join(', ')}) VALUES ${rowsList.join(', ')} `;
|
||||
}
|
||||
|
||||
// GROUP BY
|
||||
const groupByArray = this._query.groupBy.reduce(this._reducer, []);
|
||||
const groupByRaw = groupByArray.length ? `GROUP BY ${groupByArray.join(', ')} ` : '';
|
||||
|
||||
// ORDER BY
|
||||
const orderByArray = this._query.orderBy.reduce(this._reducer, []);
|
||||
const orderByRaw = orderByArray.length ? `ORDER BY ${orderByArray.join(', ')} ` : '';
|
||||
|
||||
// LIMIT
|
||||
const limitRaw = this._query.limit.length ? `LIMIT ${this._query.limit.join(', ')} ` : '';
|
||||
|
||||
// OFFSET
|
||||
const offsetRaw = this._query.offset.length ? `OFFSET ${this._query.offset.join(', ')} ` : '';
|
||||
|
||||
return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${offsetRaw}${insertRaw}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} sql raw SQL query
|
||||
* @param {object} args
|
||||
* @param {boolean} args.nest
|
||||
* @param {boolean} args.details
|
||||
* @param {boolean} args.split
|
||||
* @returns {Promise}
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async raw (sql, args) {
|
||||
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
|
||||
|
||||
args = {
|
||||
nest: false,
|
||||
details: false,
|
||||
split: true,
|
||||
comments: true,
|
||||
autocommit: true,
|
||||
...args
|
||||
};
|
||||
|
||||
if (!args.comments)
|
||||
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments
|
||||
|
||||
const resultsArr = [];
|
||||
let paramsArr = [];
|
||||
const queries = args.split
|
||||
? sql.split(/((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm)
|
||||
.filter(Boolean)
|
||||
.map(q => q.trim())
|
||||
: [sql];
|
||||
|
||||
let connection;
|
||||
|
||||
if (!args.autocommit && args.tabUid) { // autocommit OFF
|
||||
if (this._connectionsToCommit.has(args.tabUid))
|
||||
connection = this._connectionsToCommit.get(args.tabUid);
|
||||
else {
|
||||
connection = this.getConnection();
|
||||
connection.prepare('BEGIN TRANSACTION').run();
|
||||
this._connectionsToCommit.set(args.tabUid, connection);
|
||||
}
|
||||
}
|
||||
else// autocommit ON
|
||||
connection = this._connection;
|
||||
|
||||
for (const query of queries) {
|
||||
if (!query) continue;
|
||||
const timeStart = new Date();
|
||||
let timeStop;
|
||||
const keysArr = [];
|
||||
|
||||
const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => {
|
||||
(async () => {
|
||||
let queryResult;
|
||||
let affectedRows;
|
||||
let fields;
|
||||
const detectedTypes = {};
|
||||
|
||||
try {
|
||||
const stmt = connection.prepare(query);
|
||||
|
||||
if (stmt.reader) {
|
||||
queryResult = stmt.all();
|
||||
fields = stmt.columns();
|
||||
|
||||
if (queryResult.length) {
|
||||
fields.forEach(field => {
|
||||
detectedTypes[field.name] = typeof queryResult[0][field.name];
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
const info = queryResult = stmt.run();
|
||||
affectedRows = info.changes;
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
timeStop = new Date();
|
||||
|
||||
let remappedFields = fields
|
||||
? fields.map(field => {
|
||||
let [parsedType, length] = field.type?.includes('(')
|
||||
? field.type.replace(')', '').split('(').map(el => {
|
||||
if (!isNaN(el))
|
||||
el = +el;
|
||||
else
|
||||
el = el.trim();
|
||||
return el;
|
||||
})
|
||||
: [field.type, null];
|
||||
|
||||
if ([...TIME, ...DATETIME].includes(parsedType)) {
|
||||
const firstNotNull = queryResult.find(res => res[field.name] !== null);
|
||||
if (firstNotNull && firstNotNull[field.name].includes('.'))
|
||||
length = firstNotNull[field.name].split('.').pop().length;
|
||||
}
|
||||
|
||||
return {
|
||||
name: field.name,
|
||||
alias: field.name,
|
||||
orgName: field.column,
|
||||
schema: field.database,
|
||||
table: field.table,
|
||||
tableAlias: field.table,
|
||||
orgTable: field.table,
|
||||
type: field.type !== null ? parsedType : detectedTypes[field.name],
|
||||
length
|
||||
};
|
||||
}).filter(Boolean)
|
||||
: [];
|
||||
|
||||
if (args.details) {
|
||||
paramsArr = remappedFields.map(field => {
|
||||
return {
|
||||
table: field.table,
|
||||
schema: field.schema
|
||||
};
|
||||
}).filter((val, i, arr) => arr.findIndex(el => el.schema === val.schema && el.table === val.table) === i);
|
||||
|
||||
for (const paramObj of paramsArr) {
|
||||
if (!paramObj.table || !paramObj.schema) continue;
|
||||
|
||||
try {
|
||||
const indexes = await this.getTableIndexes(paramObj);
|
||||
|
||||
remappedFields = remappedFields.map(field => {
|
||||
// const detailedField = columns.find(f => f.name === field.name);
|
||||
const fieldIndex = indexes.find(i => i.column === field.name);
|
||||
if (field.table === paramObj.table && field.schema === paramObj.schema) {
|
||||
// if (detailedField) {
|
||||
// const length = detailedField.numPrecision || detailedField.charLength || detailedField.datePrecision || null;
|
||||
// field = { ...field, ...detailedField, length };
|
||||
// }
|
||||
|
||||
if (fieldIndex) {
|
||||
const key = fieldIndex.type === 'PRIMARY' ? 'pri' : fieldIndex.type === 'UNIQUE' ? 'uni' : 'mul';
|
||||
field = { ...field, key };
|
||||
};
|
||||
}
|
||||
|
||||
return field;
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolve({
|
||||
duration: timeStop - timeStart,
|
||||
rows: Array.isArray(queryResult) ? queryResult.some(el => Array.isArray(el)) ? [] : queryResult : false,
|
||||
report: affectedRows !== undefined ? { affectedRows } : null,
|
||||
fields: remappedFields,
|
||||
keys: keysArr
|
||||
});
|
||||
})();
|
||||
});
|
||||
|
||||
resultsArr.push({ rows, report, fields, keys, duration });
|
||||
}
|
||||
|
||||
return resultsArr.length === 1 ? resultsArr[0] : resultsArr;
|
||||
}
|
||||
}
|
85
src/main/libs/exporters/BaseExporter.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import fs from 'fs';
|
||||
import { createGzip } from 'zlib';
|
||||
import path from 'path';
|
||||
import EventEmitter from 'events';
|
||||
|
||||
export class BaseExporter extends EventEmitter {
|
||||
constructor (tables, options) {
|
||||
super();
|
||||
this._tables = tables;
|
||||
this._options = options;
|
||||
this._isCancelled = false;
|
||||
this._outputFileStream = fs.createWriteStream(this._options.outputFile, { flags: 'w' });
|
||||
this._processedStream = null;
|
||||
this._state = {};
|
||||
|
||||
if (this._options.outputFormat === 'sql.zip') {
|
||||
const outputZipStream = createGzip();
|
||||
outputZipStream.pipe(this._outputFileStream);
|
||||
this._processedStream = outputZipStream;
|
||||
}
|
||||
else
|
||||
this._processedStream = this._outputFileStream;
|
||||
|
||||
this._processedStream.once('error', err => {
|
||||
this._isCancelled = true;
|
||||
this.emit('error', err);
|
||||
});
|
||||
}
|
||||
|
||||
async run () {
|
||||
try {
|
||||
this.emit('start', this);
|
||||
await this.dump();
|
||||
}
|
||||
catch (err) {
|
||||
this.emit('error', err);
|
||||
throw err;
|
||||
}
|
||||
finally {
|
||||
this._processedStream.end();
|
||||
this.emit('end');
|
||||
}
|
||||
}
|
||||
|
||||
get isCancelled () {
|
||||
return this._isCancelled;
|
||||
}
|
||||
|
||||
get outputFile () {
|
||||
return this._options.outputFile;
|
||||
}
|
||||
|
||||
outputFileExists () {
|
||||
return fs.existsSync(this._options.outputFile);
|
||||
}
|
||||
|
||||
cancel () {
|
||||
this._isCancelled = true;
|
||||
this.emit('cancel');
|
||||
this.emitUpdate({ op: 'cancelling' });
|
||||
}
|
||||
|
||||
emitUpdate (state) {
|
||||
this.emit('progress', { ...this._state, ...state });
|
||||
}
|
||||
|
||||
writeString (data) {
|
||||
if (this._isCancelled) return;
|
||||
|
||||
try {
|
||||
fs.accessSync(this._options.outputFile);
|
||||
}
|
||||
catch (err) {
|
||||
this._isCancelled = true;
|
||||
|
||||
const fileName = path.basename(this._options.outputFile);
|
||||
this.emit('error', `The file ${fileName} is not accessible`);
|
||||
}
|
||||
this._processedStream.write(data);
|
||||
}
|
||||
|
||||
dump () {
|
||||
throw new Error('Exporter must implement the "dump" method');
|
||||
}
|
||||
}
|
412
src/main/libs/exporters/sql/MysqlExporter.js
Normal file
@@ -0,0 +1,412 @@
|
||||
import { SqlExporter } from './SqlExporter';
|
||||
import { BLOB, BIT, DATE, DATETIME, FLOAT, SPATIAL, IS_MULTI_SPATIAL, NUMBER } from 'common/fieldTypes';
|
||||
import hexToBinary from 'common/libs/hexToBinary';
|
||||
import { getArrayDepth } from 'common/libs/getArrayDepth';
|
||||
import moment from 'moment';
|
||||
import { lineString, point, polygon } from '@turf/helpers';
|
||||
|
||||
export default class MysqlExporter extends SqlExporter {
|
||||
async getSqlHeader () {
|
||||
let dump = await super.getSqlHeader();
|
||||
dump += `
|
||||
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
SET NAMES utf8mb4;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;`;
|
||||
|
||||
return dump;
|
||||
}
|
||||
|
||||
async getFooter () {
|
||||
const footer = await super.getFooter();
|
||||
|
||||
return `/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
|
||||
${footer}
|
||||
`;
|
||||
}
|
||||
|
||||
async getCreateTable (tableName) {
|
||||
const { rows } = await this._client.raw(
|
||||
`SHOW CREATE TABLE \`${this.schemaName}\`.\`${tableName}\``
|
||||
);
|
||||
|
||||
if (rows.length !== 1) return '';
|
||||
|
||||
const col = 'Create View' in rows[0] ? 'Create View' : 'Create Table';
|
||||
|
||||
return rows[0][col] + ';';
|
||||
}
|
||||
|
||||
getDropTable (tableName) {
|
||||
return `DROP TABLE IF EXISTS \`${tableName}\`;`;
|
||||
}
|
||||
|
||||
async * getTableInsert (tableName) {
|
||||
let rowCount = 0;
|
||||
let sqlStr = '';
|
||||
|
||||
const countResults = await this._client.raw(`SELECT COUNT(1) as count FROM \`${this.schemaName}\`.\`${tableName}\``);
|
||||
if (countResults.rows.length === 1) rowCount = countResults.rows[0].count;
|
||||
|
||||
if (rowCount > 0) {
|
||||
let queryLength = 0;
|
||||
let rowsWritten = 0;
|
||||
let rowIndex = 0;
|
||||
const { sqlInsertDivider, sqlInsertAfter } = this._options;
|
||||
const columns = await this._client.getTableColumns({
|
||||
table: tableName,
|
||||
schema: this.schemaName
|
||||
});
|
||||
|
||||
const notGeneratedColumns = columns.filter(col => !col.generated);
|
||||
const columnNames = notGeneratedColumns.map(col => '`' + col.name + '`');
|
||||
const insertStmt = `INSERT INTO \`${tableName}\` (${columnNames.join(
|
||||
', '
|
||||
)}) VALUES`;
|
||||
|
||||
sqlStr += `LOCK TABLES \`${tableName}\` WRITE;\n`;
|
||||
sqlStr += `/*!40000 ALTER TABLE \`${tableName}\` DISABLE KEYS */;`;
|
||||
sqlStr += '\n\n';
|
||||
yield sqlStr;
|
||||
|
||||
yield insertStmt;
|
||||
|
||||
const stream = await this._queryStream(
|
||||
`SELECT ${columnNames.join(', ')} FROM \`${this.schemaName}\`.\`${tableName}\``
|
||||
);
|
||||
|
||||
for await (const row of stream) {
|
||||
if (this.isCancelled) {
|
||||
stream.destroy();
|
||||
yield null;
|
||||
return;
|
||||
}
|
||||
|
||||
let sqlInsertString = '';
|
||||
|
||||
if (
|
||||
(sqlInsertDivider === 'bytes' && queryLength >= sqlInsertAfter * 1024) ||
|
||||
(sqlInsertDivider === 'rows' && rowsWritten === sqlInsertAfter)
|
||||
) {
|
||||
sqlInsertString += `;\n${insertStmt}\n\t(`;
|
||||
queryLength = 0;
|
||||
rowsWritten = 0;
|
||||
}
|
||||
else if (parseInt(rowIndex) === 0) sqlInsertString += '\n\t(';
|
||||
else sqlInsertString += ',\n\t(';
|
||||
|
||||
for (const i in notGeneratedColumns) {
|
||||
const column = notGeneratedColumns[i];
|
||||
const val = row[column.name];
|
||||
|
||||
if (val === null) sqlInsertString += 'NULL';
|
||||
else if (DATE.includes(column.type)) {
|
||||
sqlInsertString += moment(val).isValid()
|
||||
? this.escapeAndQuote(moment(val).format('YYYY-MM-DD'))
|
||||
: val;
|
||||
}
|
||||
else if (DATETIME.includes(column.type)) {
|
||||
let datePrecision = '';
|
||||
for (let i = 0; i < column.precision; i++)
|
||||
datePrecision += i === 0 ? '.S' : 'S';
|
||||
|
||||
sqlInsertString += moment(val).isValid()
|
||||
? this.escapeAndQuote(moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`))
|
||||
: this.escapeAndQuote(val);
|
||||
}
|
||||
else if (BIT.includes(column.type))
|
||||
sqlInsertString += `b'${hexToBinary(Buffer.from(val).toString('hex'))}'`;
|
||||
else if (BLOB.includes(column.type))
|
||||
sqlInsertString += `X'${val.toString('hex').toUpperCase()}'`;
|
||||
else if (NUMBER.includes(column.type))
|
||||
sqlInsertString += val;
|
||||
else if (FLOAT.includes(column.type))
|
||||
sqlInsertString += parseFloat(val);
|
||||
else if (SPATIAL.includes(column.type)) {
|
||||
let geoJson;
|
||||
if (IS_MULTI_SPATIAL.includes(column.type)) {
|
||||
const features = [];
|
||||
for (const element of val)
|
||||
features.push(this.getMarkers(element));
|
||||
|
||||
geoJson = {
|
||||
type: 'FeatureCollection',
|
||||
features
|
||||
};
|
||||
}
|
||||
else
|
||||
geoJson = this._getGeoJSON(val);
|
||||
|
||||
sqlInsertString += `ST_GeomFromGeoJSON('${JSON.stringify(geoJson)}')`;
|
||||
}
|
||||
else if (val === '') sqlInsertString += '\'\'';
|
||||
else {
|
||||
sqlInsertString += typeof val === 'string'
|
||||
? this.escapeAndQuote(val)
|
||||
: typeof val === 'object'
|
||||
? this.escapeAndQuote(JSON.stringify(val))
|
||||
: val;
|
||||
}
|
||||
|
||||
if (parseInt(i) !== notGeneratedColumns.length - 1)
|
||||
sqlInsertString += ', ';
|
||||
}
|
||||
|
||||
sqlInsertString += ')';
|
||||
|
||||
queryLength += sqlInsertString.length;
|
||||
rowsWritten++;
|
||||
rowIndex++;
|
||||
yield sqlInsertString;
|
||||
}
|
||||
|
||||
sqlStr = ';\n\n';
|
||||
sqlStr += `/*!40000 ALTER TABLE \`${tableName}\` ENABLE KEYS */;\n`;
|
||||
sqlStr += 'UNLOCK TABLES;';
|
||||
|
||||
yield sqlStr;
|
||||
}
|
||||
}
|
||||
|
||||
async getViews () {
|
||||
const { rows: views } = await this._client.raw(
|
||||
`SHOW TABLE STATUS FROM \`${this.schemaName}\` WHERE Comment = 'VIEW'`
|
||||
);
|
||||
let sqlString = '';
|
||||
|
||||
for (const view of views) {
|
||||
sqlString += `DROP VIEW IF EXISTS \`${view.Name}\`;\n`;
|
||||
const viewSyntax = await this.getCreateTable(view.Name);
|
||||
sqlString += viewSyntax.replaceAll('`' + this.schemaName + '`.', '');
|
||||
sqlString += '\n';
|
||||
}
|
||||
|
||||
return sqlString;
|
||||
}
|
||||
|
||||
async getTriggers () {
|
||||
const { rows: triggers } = await this._client.raw(
|
||||
`SHOW TRIGGERS FROM \`${this.schemaName}\``
|
||||
);
|
||||
const generatedTables = this._tables
|
||||
.filter(t => t.includeStructure)
|
||||
.map(t => t.table);
|
||||
|
||||
let sqlString = '';
|
||||
|
||||
for (const trigger of triggers) {
|
||||
const {
|
||||
Trigger: name,
|
||||
Timing: timing,
|
||||
Event: event,
|
||||
Table: table,
|
||||
Statement: statement,
|
||||
sql_mode: sqlMode
|
||||
} = trigger;
|
||||
|
||||
if (!generatedTables.includes(table)) continue;
|
||||
|
||||
const definer = this.getEscapedDefiner(trigger.Definer);
|
||||
sqlString += '/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;;\n';
|
||||
sqlString += `/*!50003 SET SQL_MODE="${sqlMode}" */;\n`;
|
||||
sqlString += 'DELIMITER ;;\n';
|
||||
sqlString += '/*!50003 CREATE*/ ';
|
||||
sqlString += `/*!50017 DEFINER=${definer}*/ `;
|
||||
sqlString += `/*!50003 TRIGGER \`${name}\` ${timing} ${event} ON \`${table}\` FOR EACH ROW ${statement}*/;;\n`;
|
||||
sqlString += 'DELIMITER ;\n';
|
||||
sqlString += '/*!50003 SET SQL_MODE=@OLD_SQL_MODE */;\n\n';
|
||||
}
|
||||
|
||||
return sqlString;
|
||||
}
|
||||
|
||||
async getSchedulers () {
|
||||
const { rows: schedulers } = await this._client.raw(
|
||||
`SELECT *, EVENT_SCHEMA AS \`Db\`, EVENT_NAME AS \`Name\` FROM information_schema.\`EVENTS\` WHERE EVENT_SCHEMA = '${this.schemaName}'`
|
||||
);
|
||||
let sqlString = '';
|
||||
|
||||
for (const scheduler of schedulers) {
|
||||
const {
|
||||
EVENT_NAME: name,
|
||||
SQL_MODE: sqlMode,
|
||||
EVENT_TYPE: type,
|
||||
INTERVAL_VALUE: intervalValue,
|
||||
INTERVAL_FIELD: intervalField,
|
||||
STARTS: starts,
|
||||
ENDS: ends,
|
||||
EXECUTE_AT: at,
|
||||
ON_COMPLETION: onCompletion,
|
||||
STATUS: status,
|
||||
EVENT_DEFINITION: definition
|
||||
} = scheduler;
|
||||
|
||||
const definer = this.getEscapedDefiner(scheduler.DEFINER);
|
||||
const comment = this.escapeAndQuote(scheduler.EVENT_COMMENT);
|
||||
|
||||
sqlString += `/*!50106 DROP EVENT IF EXISTS \`${name}\` */;\n`;
|
||||
sqlString += '/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;;\n';
|
||||
sqlString += `/*!50003 SET SQL_MODE='${sqlMode}' */;\n`;
|
||||
sqlString += 'DELIMITER ;;\n';
|
||||
sqlString += '/*!50106 CREATE*/ ';
|
||||
sqlString += `/*!50117 DEFINER=${definer}*/ `;
|
||||
sqlString += `/*!50106 EVENT \`${name}\` ON SCHEDULE `;
|
||||
if (type === 'RECURRING') {
|
||||
sqlString += `EVERY ${intervalValue} ${intervalField} STARTS '${starts}' `;
|
||||
|
||||
if (ends) sqlString += `ENDS '${ends}' `;
|
||||
}
|
||||
else sqlString += `AT '${at}' `;
|
||||
sqlString += `ON COMPLETION ${onCompletion} ${
|
||||
status === 'disabled' ? 'DISABLE' : 'ENABLE'
|
||||
} COMMENT ${comment || '\'\''} DO ${definition}*/;;\n`;
|
||||
sqlString += 'DELIMITER ;\n';
|
||||
sqlString += '/*!50003 SET SQL_MODE=@OLD_SQL_MODE*/;;\n';
|
||||
}
|
||||
|
||||
return sqlString;
|
||||
}
|
||||
|
||||
async getFunctions () {
|
||||
const { rows: functions } = await this._client.raw(
|
||||
`SHOW FUNCTION STATUS WHERE \`Db\` = '${this.schemaName}';`
|
||||
);
|
||||
|
||||
let sqlString = '';
|
||||
|
||||
for (const func of functions) {
|
||||
const definer = this.getEscapedDefiner(func.Definer);
|
||||
sqlString += await this.getRoutineSyntax(
|
||||
func.Name,
|
||||
func.Type,
|
||||
definer
|
||||
);
|
||||
}
|
||||
|
||||
return sqlString;
|
||||
}
|
||||
|
||||
async getRoutines () {
|
||||
const { rows: routines } = await this._client.raw(
|
||||
`SHOW PROCEDURE STATUS WHERE \`Db\` = '${this.schemaName}';`
|
||||
);
|
||||
|
||||
let sqlString = '';
|
||||
|
||||
for (const routine of routines) {
|
||||
const definer = this.getEscapedDefiner(routine.Definer);
|
||||
|
||||
sqlString += await this.getRoutineSyntax(
|
||||
routine.Name,
|
||||
routine.Type,
|
||||
definer
|
||||
);
|
||||
}
|
||||
|
||||
return sqlString;
|
||||
}
|
||||
|
||||
async getRoutineSyntax (name, type, definer) {
|
||||
const { rows: routines } = await this._client.raw(
|
||||
`SHOW CREATE ${type} \`${this.schemaName}\`.\`${name}\``
|
||||
);
|
||||
|
||||
if (routines.length === 0) return '';
|
||||
|
||||
const routine = routines[0];
|
||||
|
||||
const fieldName = `Create ${type === 'PROCEDURE' ? 'Procedure' : 'Function'}`;
|
||||
const sqlMode = routine.sql_mode;
|
||||
const createProcedure = routine[fieldName];
|
||||
let sqlString = '';
|
||||
|
||||
if (createProcedure) { // If procedure body not empty
|
||||
const startOffset = createProcedure.indexOf(type);
|
||||
const procedureBody = createProcedure.substring(startOffset);
|
||||
|
||||
sqlString += `/*!50003 DROP ${type} IF EXISTS ${name}*/;;\n`;
|
||||
sqlString += '/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;;\n';
|
||||
sqlString += `/*!50003 SET SQL_MODE="${sqlMode}"*/;;\n`;
|
||||
sqlString += 'DELIMITER ;;\n';
|
||||
sqlString += `/*!50003 CREATE*/ /*!50020 DEFINER=${definer}*/ /*!50003 ${procedureBody}*/;;\n`;
|
||||
sqlString += 'DELIMITER ;\n';
|
||||
sqlString += '/*!50003 SET SQL_MODE=@OLD_SQL_MODE*/;\n';
|
||||
}
|
||||
|
||||
return sqlString;
|
||||
}
|
||||
|
||||
async _queryStream (sql) {
|
||||
if (process.env.NODE_ENV === 'development') console.log('EXPORTER:', sql);
|
||||
const isPool = typeof this._client._connection.getConnection === 'function';
|
||||
const connection = isPool ? await this._client._connection.getConnection() : this._client._connection;
|
||||
const stream = connection.connection.query(sql).stream();
|
||||
const dispose = () => connection.destroy();
|
||||
|
||||
stream.on('end', dispose);
|
||||
stream.on('error', dispose);
|
||||
stream.on('close', dispose);
|
||||
return stream;
|
||||
}
|
||||
|
||||
getEscapedDefiner (definer) {
|
||||
return definer
|
||||
.split('@')
|
||||
.map(part => '`' + part + '`')
|
||||
.join('@');
|
||||
}
|
||||
|
||||
escapeAndQuote (val) {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g;
|
||||
const CHARS_ESCAPE_MAP = {
|
||||
'\0': '\\0',
|
||||
'\b': '\\b',
|
||||
'\t': '\\t',
|
||||
'\n': '\\n',
|
||||
'\r': '\\r',
|
||||
'\x1a': '\\Z',
|
||||
'"': '\\"',
|
||||
'\'': '\\\'',
|
||||
'\\': '\\\\'
|
||||
};
|
||||
let chunkIndex = CHARS_TO_ESCAPE.lastIndex = 0;
|
||||
let escapedVal = '';
|
||||
let match;
|
||||
|
||||
while ((match = CHARS_TO_ESCAPE.exec(val))) {
|
||||
escapedVal += val.slice(chunkIndex, match.index) + CHARS_ESCAPE_MAP[match[0]];
|
||||
chunkIndex = CHARS_TO_ESCAPE.lastIndex;
|
||||
}
|
||||
|
||||
if (chunkIndex === 0)
|
||||
return `'${val}'`;
|
||||
|
||||
if (chunkIndex < val.length)
|
||||
return `'${escapedVal + val.slice(chunkIndex)}'`;
|
||||
|
||||
return `'${escapedVal}'`;
|
||||
}
|
||||
|
||||
_getGeoJSON (val) {
|
||||
if (Array.isArray(val)) {
|
||||
if (getArrayDepth(val) === 1)
|
||||
return lineString(val.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []));
|
||||
else
|
||||
return polygon(val.map(arr => arr.reduce((acc, curr) => [...acc, [curr.x, curr.y]], [])));
|
||||
}
|
||||
else
|
||||
return point([val.x, val.y]);
|
||||
}
|
||||
}
|
162
src/main/libs/exporters/sql/SqlExporter.js
Normal file
@@ -0,0 +1,162 @@
|
||||
import moment from 'moment';
|
||||
import { BaseExporter } from '../BaseExporter';
|
||||
|
||||
export class SqlExporter extends BaseExporter {
|
||||
constructor (client, tables, options) {
|
||||
super(tables, options);
|
||||
this._client = client;
|
||||
this._commentChar = '#';
|
||||
}
|
||||
|
||||
get schemaName () {
|
||||
return this._options.schema;
|
||||
}
|
||||
|
||||
get host () {
|
||||
return this._client._params.host;
|
||||
}
|
||||
|
||||
async getServerVersion () {
|
||||
const version = await this._client.getVersion();
|
||||
return `${version.name} ${version.number}`;
|
||||
}
|
||||
|
||||
async dump () {
|
||||
const { includes } = this._options;
|
||||
const extraItems = Object.keys(includes).filter(key => includes[key]);
|
||||
const totalTableToProcess = this._tables.filter(
|
||||
t => t.includeStructure || t.includeContent || t.includeDropStatement
|
||||
).length;
|
||||
const processingItemCount = totalTableToProcess + extraItems.length;
|
||||
|
||||
const exportState = {
|
||||
totalItems: processingItemCount,
|
||||
currentItemIndex: 0,
|
||||
currentItem: '',
|
||||
op: ''
|
||||
};
|
||||
|
||||
const header = await this.getSqlHeader();
|
||||
this.writeString(header);
|
||||
this.writeString('\n\n\n');
|
||||
|
||||
for (const item of this._tables) {
|
||||
// user abort operation
|
||||
if (this.isCancelled) return;
|
||||
|
||||
// skip item if not set to output any detail for them
|
||||
if (
|
||||
!item.includeStructure &&
|
||||
!item.includeContent &&
|
||||
!item.includeDropStatement
|
||||
)
|
||||
continue;
|
||||
|
||||
exportState.currentItemIndex++;
|
||||
exportState.currentItem = item.table;
|
||||
exportState.op = 'FETCH';
|
||||
|
||||
this.emitUpdate(exportState);
|
||||
|
||||
const tableHeader = this.buildComment(
|
||||
`Dump of table ${item.table}\n------------------------------------------------------------`
|
||||
);
|
||||
this.writeString(tableHeader);
|
||||
this.writeString('\n\n');
|
||||
|
||||
if (item.includeDropStatement) {
|
||||
const dropTableSyntax = this.getDropTable(item.table);
|
||||
this.writeString(dropTableSyntax);
|
||||
this.writeString('\n\n');
|
||||
}
|
||||
|
||||
if (item.includeStructure) {
|
||||
const createTableSyntax = await this.getCreateTable(item.table);
|
||||
this.writeString(createTableSyntax);
|
||||
this.writeString('\n\n');
|
||||
}
|
||||
|
||||
if (item.includeContent) {
|
||||
exportState.op = 'WRITE';
|
||||
this.emitUpdate(exportState);
|
||||
for await (const sqlStr of this.getTableInsert(item.table)) {
|
||||
if (this.isCancelled) return;
|
||||
this.writeString(sqlStr);
|
||||
}
|
||||
|
||||
this.writeString('\n\n');
|
||||
}
|
||||
|
||||
this.writeString('\n\n');
|
||||
}
|
||||
|
||||
for (const item of extraItems) {
|
||||
const processingMethod = `get${item.charAt(0).toUpperCase() + item.slice(1)}`;
|
||||
exportState.currentItemIndex++;
|
||||
exportState.currentItem = item;
|
||||
exportState.op = 'PROCESSING';
|
||||
this.emitUpdate(exportState);
|
||||
|
||||
if (this[processingMethod]) {
|
||||
const data = await this[processingMethod]();
|
||||
if (data !== '') {
|
||||
const header =
|
||||
this.buildComment(
|
||||
`Dump of ${item}\n------------------------------------------------------------`
|
||||
) + '\n\n';
|
||||
|
||||
this.writeString(header);
|
||||
this.writeString(data);
|
||||
this.writeString('\n\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const footer = await this.getFooter();
|
||||
this.writeString(footer);
|
||||
}
|
||||
|
||||
buildComment (text) {
|
||||
return text
|
||||
.split('\n')
|
||||
.map(txt => `${this._commentChar} ${txt}`)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
async getSqlHeader () {
|
||||
const serverVersion = await this.getServerVersion();
|
||||
const header = `************************************************************
|
||||
Antares - SQL Client
|
||||
Version ${process.env.PACKAGE_VERSION}
|
||||
|
||||
https://antares-sql.app/
|
||||
https://github.com/Fabio286/antares
|
||||
|
||||
Host: ${this.host} (${serverVersion})
|
||||
Database: ${this.schemaName}
|
||||
Generation time: ${moment().format()}
|
||||
************************************************************`;
|
||||
|
||||
return this.buildComment(header);
|
||||
}
|
||||
|
||||
async getFooter () {
|
||||
return this.buildComment(`Dump completed on ${moment().format()}`);
|
||||
}
|
||||
|
||||
getCreateTable (tableName) {
|
||||
throw new Error(
|
||||
'Sql Exporter must implement the "getCreateTable" method'
|
||||
);
|
||||
}
|
||||
|
||||
getDropTable (tableName) {
|
||||
throw new Error('Sql Exporter must implement the "getDropTable" method');
|
||||
}
|
||||
|
||||
getTableInsert (tableName) {
|
||||
throw new Error(
|
||||
'Sql Exporter must implement the "getTableInsert" method'
|
||||
);
|
||||
}
|
||||
}
|
53
src/main/libs/importers/BaseImporter.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import fs from 'fs';
|
||||
import EventEmitter from 'events';
|
||||
|
||||
export class BaseImporter extends EventEmitter {
|
||||
constructor (options) {
|
||||
super();
|
||||
this._options = options;
|
||||
this._isCancelled = false;
|
||||
this._fileHandler = fs.createReadStream(this._options.file, {
|
||||
flags: 'r',
|
||||
highWaterMark: 4 * 1024
|
||||
});
|
||||
this._state = {};
|
||||
|
||||
this._fileHandler.once('error', err => {
|
||||
this._isCancelled = true;
|
||||
this.emit('error', err);
|
||||
});
|
||||
}
|
||||
|
||||
async run () {
|
||||
try {
|
||||
this.emit('start', this);
|
||||
await this.import();
|
||||
}
|
||||
catch (err) {
|
||||
this.emit('error', err);
|
||||
throw err;
|
||||
}
|
||||
finally {
|
||||
this._fileHandler.close();
|
||||
this.emit('end');
|
||||
}
|
||||
}
|
||||
|
||||
get isCancelled () {
|
||||
return this._isCancelled;
|
||||
}
|
||||
|
||||
cancel () {
|
||||
this._isCancelled = true;
|
||||
this.emit('cancel');
|
||||
this.emitUpdate({ op: 'cancelling' });
|
||||
}
|
||||
|
||||
emitUpdate (state) {
|
||||
this.emit('progress', { ...this._state, ...state });
|
||||
}
|
||||
|
||||
import () {
|
||||
throw new Error('Exporter must implement the "import" method');
|
||||
}
|
||||
}
|
85
src/main/libs/importers/sql/MysqlImporter.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import fs from 'fs/promises';
|
||||
import SqlParser from '../../../../common/libs/sqlParser';
|
||||
import { BaseImporter } from '../BaseImporter';
|
||||
|
||||
export default class MysqlImporter extends BaseImporter {
|
||||
constructor (client, options) {
|
||||
super(options);
|
||||
this._client = client;
|
||||
}
|
||||
|
||||
async import () {
|
||||
try {
|
||||
const { size: totalFileSize } = await fs.stat(this._options.file);
|
||||
const parser = new SqlParser();
|
||||
let readPosition = 0;
|
||||
let queryCount = 0;
|
||||
|
||||
this.emitUpdate({
|
||||
fileSize: totalFileSize,
|
||||
readPosition: 0,
|
||||
percentage: 0,
|
||||
queryCount: 0
|
||||
});
|
||||
|
||||
// 1. detect file encoding
|
||||
// 2. set fh encoding
|
||||
// 3. detect sql mode
|
||||
// 4. restore sql mode in case of exception
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this._fileHandler.pipe(parser);
|
||||
|
||||
parser.on('error', reject);
|
||||
|
||||
parser.on('close', async () => {
|
||||
console.log('TOTAL QUERIES', queryCount);
|
||||
console.log('import end');
|
||||
resolve();
|
||||
});
|
||||
|
||||
parser.on('data', async (query) => {
|
||||
queryCount++;
|
||||
parser.pause();
|
||||
|
||||
try {
|
||||
await this._client.query(query);
|
||||
}
|
||||
catch (error) {
|
||||
this.emit('query-error', {
|
||||
sql: query,
|
||||
message: error.sqlMessage,
|
||||
sqlSnippet: error.sql,
|
||||
time: new Date().getTime()
|
||||
});
|
||||
}
|
||||
|
||||
this.emitUpdate({
|
||||
queryCount,
|
||||
readPosition,
|
||||
percentage: readPosition / totalFileSize * 100
|
||||
});
|
||||
this._fileHandler.pipe(parser);
|
||||
parser.resume();
|
||||
});
|
||||
|
||||
parser.on('pause', () => {
|
||||
this._fileHandler.unpipe(parser);
|
||||
this._fileHandler.readableFlowing = false;
|
||||
});
|
||||
|
||||
this._fileHandler.on('data', (chunk) => {
|
||||
readPosition += chunk.length;
|
||||
});
|
||||
|
||||
this._fileHandler.on('error', (err) => {
|
||||
console.log(err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
}
|
172
src/main/main.js
Normal file
@@ -0,0 +1,172 @@
|
||||
'use strict';
|
||||
|
||||
import { app, BrowserWindow, /* session, */ nativeImage, Menu } from 'electron';
|
||||
import * as path from 'path';
|
||||
import Store from 'electron-store';
|
||||
import * as windowStateKeeper from 'electron-window-state';
|
||||
import * as remoteMain from '@electron/remote/main';
|
||||
|
||||
import ipcHandlers from './ipc-handlers';
|
||||
|
||||
// remoteMain.initialize();
|
||||
Store.initRenderer();
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
const isMacOS = process.platform === 'darwin';
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
||||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
|
||||
|
||||
// global reference to mainWindow (necessary to prevent window from being garbage collected)
|
||||
let mainWindow;
|
||||
let mainWindowState;
|
||||
|
||||
async function createMainWindow () {
|
||||
const icon = require('../renderer/images/logo-32.png');
|
||||
const window = new BrowserWindow({
|
||||
width: mainWindowState.width,
|
||||
height: mainWindowState.height,
|
||||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
minWidth: 900,
|
||||
minHeight: 550,
|
||||
title: 'Antares SQL',
|
||||
autoHideMenuBar: true,
|
||||
icon: nativeImage.createFromDataURL(icon.default),
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
'web-security': false,
|
||||
spellcheck: false
|
||||
},
|
||||
frame: false,
|
||||
titleBarStyle: isMacOS ? 'hidden' : 'default',
|
||||
trafficLightPosition: isMacOS ? { x: 10, y: 8 } : undefined,
|
||||
backgroundColor: '#1d1d1d'
|
||||
});
|
||||
|
||||
mainWindowState.manage(window);
|
||||
window.on('moved', saveWindowState);
|
||||
|
||||
remoteMain.enable(window.webContents);
|
||||
|
||||
try {
|
||||
if (isDevelopment) {
|
||||
const { default: installExtension, VUEJS3_DEVTOOLS } = require('electron-devtools-installer');
|
||||
const options = {
|
||||
loadExtensionOptions: { allowFileAccess: true }
|
||||
};
|
||||
|
||||
try {
|
||||
const name = await installExtension(VUEJS3_DEVTOOLS, options);
|
||||
console.log(`Added Extension: ${name}`);
|
||||
}
|
||||
catch (err) {
|
||||
console.log('An error occurred: ', err);
|
||||
}
|
||||
|
||||
await window.loadURL('http://localhost:9080');
|
||||
}
|
||||
else {
|
||||
const indexPath = path.resolve(__dirname, 'index.html');
|
||||
await window.loadFile(indexPath);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
window.on('closed', () => {
|
||||
window.removeListener('moved', saveWindowState);
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
if (!gotTheLock) app.quit();
|
||||
else {
|
||||
require('@electron/remote/main').initialize();
|
||||
|
||||
// Initialize ipcHandlers
|
||||
ipcHandlers();
|
||||
|
||||
// quit application when all windows are closed
|
||||
app.on('window-all-closed', () => {
|
||||
// on macOS it is common for applications to stay open until the user explicitly quits
|
||||
if (isMacOS) app.quit();
|
||||
});
|
||||
|
||||
app.on('activate', async () => {
|
||||
// on macOS it is common to re-create a window even after all windows have been closed
|
||||
if (mainWindow === null)
|
||||
mainWindow = await createMainWindow();
|
||||
});
|
||||
|
||||
// create main BrowserWindow when electron is ready
|
||||
app.on('ready', async () => {
|
||||
mainWindowState = windowStateKeeper({
|
||||
defaultWidth: 1024,
|
||||
defaultHeight: 800
|
||||
});
|
||||
|
||||
mainWindow = await createMainWindow();
|
||||
createAppMenu();
|
||||
|
||||
// if (isDevelopment)
|
||||
// mainWindow.webContents.openDevTools();
|
||||
|
||||
process.on('uncaughtException', error => {
|
||||
mainWindow.webContents.send('unhandled-exception', error);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', error => {
|
||||
mainWindow.webContents.send('unhandled-exception', error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createAppMenu () {
|
||||
let menu = null;
|
||||
|
||||
if (isMacOS) {
|
||||
menu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: app.name,
|
||||
submenu: [
|
||||
{ role: 'about' },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Check for Updates...',
|
||||
click: (_menuItem, win) => win.webContents.send('open-updates-preferences')
|
||||
},
|
||||
{
|
||||
label: 'Preferences',
|
||||
click: (_menuItem, win) => win.webContents.send('toggle-preferences'),
|
||||
accelerator: 'CmdOrCtrl+,'
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ role: 'hide' },
|
||||
{ role: 'hideOthers' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'quit' }
|
||||
]
|
||||
},
|
||||
{
|
||||
role: 'editMenu'
|
||||
},
|
||||
{
|
||||
role: 'viewMenu'
|
||||
},
|
||||
{
|
||||
role: 'windowMenu'
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
Menu.setApplicationMenu(menu);
|
||||
}
|
||||
|
||||
function saveWindowState () {
|
||||
mainWindowState.saveState(mainWindow);
|
||||
}
|
60
src/main/workers/exporter.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import { ClientsFactory } from '../libs/ClientsFactory';
|
||||
import MysqlExporter from '../libs/exporters/sql/MysqlExporter.js';
|
||||
import fs from 'fs';
|
||||
let exporter;
|
||||
|
||||
process.on('message', async ({ type, client, tables, options }) => {
|
||||
if (type === 'init') {
|
||||
const connection = await ClientsFactory.getClient({
|
||||
client: client.name,
|
||||
params: client.config,
|
||||
poolSize: 5
|
||||
});
|
||||
await connection.connect();
|
||||
|
||||
switch (client.name) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
exporter = new MysqlExporter(connection, tables, options);
|
||||
break;
|
||||
default:
|
||||
process.send({
|
||||
type: 'error',
|
||||
payload: `"${client.name}" exporter not aviable`
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
exporter.once('error', err => {
|
||||
console.error(err);
|
||||
process.send({
|
||||
type: 'error',
|
||||
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')
|
||||
exporter.cancel();
|
||||
});
|
68
src/main/workers/importer.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import { ClientsFactory } from '../libs/ClientsFactory';
|
||||
import MysqlImporter from '../libs/importers/sql/MysqlImporter';
|
||||
let importer;
|
||||
|
||||
process.on('message', async ({ type, dbConfig, options }) => {
|
||||
if (type === 'init') {
|
||||
const connection = await ClientsFactory.getClient({
|
||||
client: options.type,
|
||||
params: {
|
||||
...dbConfig,
|
||||
schema: options.schema
|
||||
},
|
||||
poolSize: 1
|
||||
});
|
||||
|
||||
const pool = await connection.getConnectionPool();
|
||||
|
||||
switch (options.type) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
importer = new MysqlImporter(pool, options);
|
||||
break;
|
||||
default:
|
||||
process.send({
|
||||
type: 'error',
|
||||
payload: `"${options.type}" importer not aviable`
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
importer.once('error', err => {
|
||||
console.error(err);
|
||||
process.send({
|
||||
type: 'error',
|
||||
payload: err.toString()
|
||||
});
|
||||
});
|
||||
|
||||
importer.once('end', () => {
|
||||
process.send({
|
||||
type: 'end',
|
||||
payload: { cancelled: importer.isCancelled }
|
||||
});
|
||||
});
|
||||
|
||||
importer.once('cancel', () => {
|
||||
process.send({ type: 'cancel' });
|
||||
});
|
||||
|
||||
importer.on('progress', state => {
|
||||
process.send({
|
||||
type: 'import-progress',
|
||||
payload: state
|
||||
});
|
||||
});
|
||||
|
||||
importer.on('query-error', state => {
|
||||
process.send({
|
||||
type: 'query-error',
|
||||
payload: state
|
||||
});
|
||||
});
|
||||
|
||||
importer.run();
|
||||
}
|
||||
else if (type === 'cancel')
|
||||
importer.cancel();
|
||||
});
|
@@ -1,22 +1,25 @@
|
||||
<template>
|
||||
<div id="wrapper">
|
||||
<div id="wrapper" :class="[`theme-${applicationTheme}`, !disableBlur || 'no-blur']">
|
||||
<TheTitleBar />
|
||||
<div id="window-content">
|
||||
<TheSettingBar />
|
||||
<div id="main-content" class="container">
|
||||
<TheAppWelcome v-if="!connections.length" @new-conn="showNewConnModal" />
|
||||
<div v-else class="columns col-gapless">
|
||||
<div class="columns col-gapless">
|
||||
<Workspace
|
||||
v-for="connection in connections"
|
||||
:key="connection.uid"
|
||||
:connection="connection"
|
||||
/>
|
||||
<div class="connection-panel-wrapper">
|
||||
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
|
||||
</div>
|
||||
</div>
|
||||
<TheFooter />
|
||||
<TheNotificationsBoard />
|
||||
<TheScratchpad v-if="isScratchpad" />
|
||||
<ModalSettings v-if="isSettingModal" />
|
||||
<BaseTextEditor class="d-none" value="" />
|
||||
</div>
|
||||
<TheFooter />
|
||||
<TheNotificationsBoard />
|
||||
<ModalNewConnection v-if="isNewConnModal" />
|
||||
<ModalSettings v-if="isSettingModal" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -24,6 +27,7 @@
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { Menu, getCurrentWindow } from '@electron/remote';
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
@@ -32,29 +36,72 @@ export default {
|
||||
TheSettingBar: () => import(/* webpackChunkName: "TheSettingBar" */'@/components/TheSettingBar'),
|
||||
TheFooter: () => import(/* webpackChunkName: "TheFooter" */'@/components/TheFooter'),
|
||||
TheNotificationsBoard: () => import(/* webpackChunkName: "TheNotificationsBoard" */'@/components/TheNotificationsBoard'),
|
||||
TheAppWelcome: () => import(/* webpackChunkName: "TheAppWelcome" */'@/components/TheAppWelcome'),
|
||||
Workspace: () => import(/* webpackChunkName: "Workspace" */'@/components/Workspace'),
|
||||
ModalNewConnection: () => import(/* webpackChunkName: "ModalNewConnection" */'@/components/ModalNewConnection'),
|
||||
ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings')
|
||||
WorkspaceAddConnectionPanel: () => import(/* webpackChunkName: "WorkspaceAddConnectionPanel" */'@/components/WorkspaceAddConnectionPanel'),
|
||||
ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings'),
|
||||
TheScratchpad: () => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad'),
|
||||
BaseTextEditor: () => import(/* webpackChunkName: "BaseTextEditor" */'@/components/BaseTextEditor')
|
||||
},
|
||||
data () {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
isLoading: 'application/isLoading',
|
||||
isNewConnModal: 'application/isNewModal',
|
||||
isEditModal: 'application/isEditModal',
|
||||
isSettingModal: 'application/isSettingModal',
|
||||
connections: 'connections/getConnections'
|
||||
isScratchpad: 'application/isScratchpad',
|
||||
connections: 'connections/getConnections',
|
||||
applicationTheme: 'settings/getApplicationTheme',
|
||||
disableBlur: 'settings/getDisableBlur',
|
||||
isUnsavedDiscardModal: 'workspaces/isUnsavedDiscardModal'
|
||||
})
|
||||
},
|
||||
mounted () {
|
||||
ipcRenderer.send('check-for-updates');
|
||||
this.checkVersionUpdate();
|
||||
|
||||
const InputMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: this.$t('word.cut'),
|
||||
role: 'cut'
|
||||
},
|
||||
{
|
||||
label: this.$t('word.copy'),
|
||||
role: 'copy'
|
||||
},
|
||||
{
|
||||
label: this.$t('word.paste'),
|
||||
role: 'paste'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: this.$t('message.selectAll'),
|
||||
role: 'selectall'
|
||||
}
|
||||
]);
|
||||
|
||||
document.body.addEventListener('contextmenu', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
let node = e.target;
|
||||
|
||||
while (node) {
|
||||
if (node.nodeName.match(/^(input|textarea)$/i) || node.isContentEditable) {
|
||||
InputMenu.popup(getCurrentWindow());
|
||||
break;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
showNewConnModal: 'application/showNewConnModal'
|
||||
showNewConnModal: 'application/showNewConnModal',
|
||||
checkVersionUpdate: 'application/checkVersionUpdate'
|
||||
})
|
||||
}
|
||||
};
|
||||
@@ -86,5 +133,15 @@ export default {
|
||||
> .columns {
|
||||
height: calc(100vh - #{$footer-height});
|
||||
}
|
||||
|
||||
.connection-panel-wrapper{
|
||||
height: calc(100vh - #{$excluding-size});
|
||||
width: 100%;
|
||||
padding-top: 15vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -24,7 +24,7 @@
|
||||
<slot name="body" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div v-if="!hideFooter" class="modal-footer">
|
||||
<button
|
||||
class="btn btn-primary mr-2"
|
||||
@click.stop="confirmModal"
|
||||
@@ -51,6 +51,10 @@ export default {
|
||||
validator: prop => ['small', 'medium', '400', 'large'].includes(prop),
|
||||
default: 'small'
|
||||
},
|
||||
hideFooter: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
confirmText: String,
|
||||
cancelText: String
|
||||
},
|
||||
@@ -74,6 +78,12 @@ export default {
|
||||
else return '';
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
confirmModal () {
|
||||
this.$emit('confirm');
|
||||
@@ -82,6 +92,11 @@ export default {
|
||||
|
||||
hideModal () {
|
||||
this.$emit('hide');
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.hideModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -95,5 +110,4 @@ export default {
|
||||
.modal.modal-sm .modal-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="context">
|
||||
<div class="context" :class="{'bottom': isBottom}">
|
||||
<a
|
||||
class="context-overlay"
|
||||
@click="close"
|
||||
@@ -23,21 +23,29 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
contextSize: null
|
||||
contextSize: null,
|
||||
isBottom: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
position () {
|
||||
const { clientY, clientX } = this.contextEvent;
|
||||
let topCord = `${clientY + 5}px`;
|
||||
let leftCord = `${clientX + 5}px`;
|
||||
let topCord = 0;
|
||||
let leftCord = 0;
|
||||
|
||||
if (this.contextSize) {
|
||||
if (clientY + this.contextSize.height + 5 >= window.innerHeight)
|
||||
topCord = `${clientY - this.contextSize.height}px`;
|
||||
if (this.contextEvent) {
|
||||
const { clientY, clientX } = this.contextEvent;
|
||||
topCord = `${clientY + 2}px`;
|
||||
leftCord = `${clientX + 5}px`;
|
||||
|
||||
if (clientX + this.contextSize.width + 5 >= window.innerWidth)
|
||||
leftCord = `${clientX - this.contextSize.width}px`;
|
||||
if (this.contextSize) {
|
||||
if (clientY + (this.contextSize.height < 200 ? 200 : this.contextSize.height) + 5 >= window.innerHeight) {
|
||||
topCord = `${clientY + 3 - this.contextSize.height}px`;
|
||||
this.isBottom = true;
|
||||
}
|
||||
|
||||
if (clientX + this.contextSize.width + 5 >= window.innerWidth)
|
||||
leftCord = `${clientX - this.contextSize.width}px`;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -50,7 +58,8 @@ export default {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
mounted () {
|
||||
this.contextSize = this.$refs.contextContent.getBoundingClientRect();
|
||||
if (this.$refs.contextContent)
|
||||
this.contextSize = this.$refs.contextContent.getBoundingClientRect();
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
@@ -69,57 +78,89 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.context {
|
||||
.context {
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
z-index: 400;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
height: 100vh;
|
||||
right: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
|
||||
&:not(.bottom) .context-submenu {
|
||||
top: -0.2rem;
|
||||
}
|
||||
|
||||
&.bottom .context-submenu {
|
||||
bottom: -0.2rem;
|
||||
}
|
||||
|
||||
.context-container {
|
||||
min-width: 100px;
|
||||
z-index: 10;
|
||||
padding: 0;
|
||||
background: #1d1d1d;
|
||||
border-radius: $border-radius;
|
||||
border: 1px solid $bg-color-light-dark;
|
||||
display: flex;
|
||||
color: $body-font-color;
|
||||
font-size: 16px;
|
||||
z-index: 400;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
height: 100vh;
|
||||
right: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
pointer-events: initial;
|
||||
|
||||
.context-container {
|
||||
min-width: 100px;
|
||||
max-width: 150px;
|
||||
z-index: 10;
|
||||
box-shadow: 0 0 1px 0 #000;
|
||||
padding: 0;
|
||||
background: #1d1d1d;
|
||||
border-radius: 0.1rem;
|
||||
.context-element {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
pointer-events: initial;
|
||||
align-items: center;
|
||||
margin: 0.2rem;
|
||||
padding: 0.1rem 0.3rem;
|
||||
border-radius: $border-radius;
|
||||
cursor: pointer;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
|
||||
.context-element {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.1rem 0.3rem;
|
||||
cursor: pointer;
|
||||
justify-content: space-between;
|
||||
.context-submenu {
|
||||
border-radius: $border-radius;
|
||||
border: 1px solid $bg-color-light-dark;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.2s;
|
||||
position: absolute;
|
||||
left: 100%;
|
||||
min-width: 100px;
|
||||
background: #1d1d1d;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $primary-color;
|
||||
&:hover {
|
||||
.context-submenu {
|
||||
display: block;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.context-overlay {
|
||||
background: transparent;
|
||||
bottom: 0;
|
||||
cursor: default;
|
||||
display: block;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.context-overlay {
|
||||
background: transparent;
|
||||
bottom: 0;
|
||||
cursor: default;
|
||||
display: block;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
pointer-events: none;
|
||||
filter: grayscale(100%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
22
src/renderer/components/BaseLoader.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div class="empty">
|
||||
<div class="loading loading-lg" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BaseLoader'
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.empty {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
left: 0;
|
||||
justify-content: center;
|
||||
right: 0;
|
||||
}
|
||||
</style>
|
108
src/renderer/components/BaseMap.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<div id="map" class="map" />
|
||||
</template>
|
||||
<script>
|
||||
import L from 'leaflet';
|
||||
import {
|
||||
point,
|
||||
lineString,
|
||||
polygon
|
||||
} from '@turf/helpers';
|
||||
import { getArrayDepth } from 'common/libs/getArrayDepth';
|
||||
|
||||
export default {
|
||||
name: 'BaseMap',
|
||||
props: {
|
||||
points: [Object, Array],
|
||||
isMultiSpatial: Boolean
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
map: null,
|
||||
markers: [],
|
||||
center: null
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
if (this.isMultiSpatial) {
|
||||
for (const element of this.points)
|
||||
this.markers.push(this.getMarkers(element));
|
||||
}
|
||||
else {
|
||||
this.markers = this.getMarkers(this.points);
|
||||
|
||||
if (!Array.isArray(this.points))
|
||||
this.center = [this.points.y, this.points.x];
|
||||
}
|
||||
|
||||
this.map = L.map('map', {
|
||||
center: this.center || [0, 0],
|
||||
zoom: 15,
|
||||
minZoom: 1,
|
||||
attributionControl: false
|
||||
});
|
||||
|
||||
L.control.attribution({ prefix: '<b>Leaflet</b>' }).addTo(this.map);
|
||||
|
||||
const geoJsonObj = L.geoJSON(this.markers, {
|
||||
style: function () {
|
||||
return {
|
||||
weight: 2,
|
||||
fillColor: '#ff7800',
|
||||
color: '#ff7800',
|
||||
opacity: 0.8,
|
||||
fillOpacity: 0.4
|
||||
};
|
||||
},
|
||||
pointToLayer: function (feature, latlng) {
|
||||
return L.circleMarker(latlng, {
|
||||
radius: 7,
|
||||
weight: 2,
|
||||
fillColor: '#ff7800',
|
||||
color: '#ff7800',
|
||||
opacity: 0.8,
|
||||
fillOpacity: 0.4
|
||||
});
|
||||
}
|
||||
}).addTo(this.map);
|
||||
|
||||
const southWest = L.latLng(-90, -180);
|
||||
const northEast = L.latLng(90, 180);
|
||||
const bounds = L.latLngBounds(southWest, northEast);
|
||||
this.map.setMaxBounds(bounds);
|
||||
|
||||
if (!this.center) this.map.fitBounds(geoJsonObj.getBounds());
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <b>OpenStreetMap</b>'
|
||||
}).addTo(this.map);
|
||||
},
|
||||
methods: {
|
||||
getMarkers (points) {
|
||||
if (Array.isArray(points)) {
|
||||
if (getArrayDepth(points) === 1)
|
||||
return lineString(points.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []));
|
||||
else
|
||||
return polygon(points.map(arr => arr.reduce((acc, curr) => [...acc, [curr.x, curr.y]], [])));
|
||||
}
|
||||
else
|
||||
return point([points.x, points.y]);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.map {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.marker-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: $primary-color;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 5px 1px darken($body-font-color-dark, 40%);
|
||||
}
|
||||
</style>
|
131
src/renderer/components/BaseTextEditor.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div class="editor-wrapper">
|
||||
<div
|
||||
:id="`editor-${id}`"
|
||||
class="editor"
|
||||
:class="editorClass"
|
||||
:style="{height: `${height}px`}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as ace from 'ace-builds';
|
||||
import 'ace-builds/webpack-resolver';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'BaseTextEditor',
|
||||
props: {
|
||||
value: String,
|
||||
mode: { type: String, default: 'text' },
|
||||
editorClass: { type: String, default: '' },
|
||||
autoFocus: { type: Boolean, default: false },
|
||||
readOnly: { type: Boolean, default: false },
|
||||
showLineNumbers: { type: Boolean, default: true },
|
||||
height: { type: Number, default: 200 }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
editor: null,
|
||||
id: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
editorTheme: 'settings/getEditorTheme',
|
||||
editorFontSize: 'settings/getEditorFontSize',
|
||||
autoComplete: 'settings/getAutoComplete',
|
||||
lineWrap: 'settings/getLineWrap'
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
mode () {
|
||||
if (this.editor)
|
||||
this.editor.session.setMode(`ace/mode/${this.mode}`);
|
||||
},
|
||||
editorTheme () {
|
||||
if (this.editor)
|
||||
this.editor.setTheme(`ace/theme/${this.editorTheme}`);
|
||||
},
|
||||
editorFontSize () {
|
||||
const sizes = {
|
||||
small: '12px',
|
||||
medium: '14px',
|
||||
large: '16px'
|
||||
};
|
||||
|
||||
if (this.editor) {
|
||||
this.editor.setOptions({
|
||||
fontSize: sizes[this.editorFontSize]
|
||||
});
|
||||
}
|
||||
},
|
||||
autoComplete () {
|
||||
if (this.editor) {
|
||||
this.editor.setOptions({
|
||||
enableLiveAutocompletion: this.autoComplete
|
||||
});
|
||||
}
|
||||
},
|
||||
lineWrap () {
|
||||
if (this.editor) {
|
||||
this.editor.setOptions({
|
||||
wrap: this.lineWrap
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.id = this._uid;
|
||||
},
|
||||
mounted () {
|
||||
this.editor = ace.edit(`editor-${this.id}`, {
|
||||
mode: `ace/mode/${this.mode}`,
|
||||
theme: `ace/theme/${this.editorTheme}`,
|
||||
value: this.value || '',
|
||||
fontSize: '14px',
|
||||
printMargin: false,
|
||||
readOnly: this.readOnly,
|
||||
showLineNumbers: this.showLineNumbers,
|
||||
showGutter: this.showLineNumbers
|
||||
});
|
||||
|
||||
this.editor.setOptions({
|
||||
enableBasicAutocompletion: false,
|
||||
wrap: this.lineWrap,
|
||||
enableSnippets: false,
|
||||
enableLiveAutocompletion: false
|
||||
});
|
||||
|
||||
this.editor.session.on('change', () => {
|
||||
const content = this.editor.getValue();
|
||||
this.$emit('update:value', content);
|
||||
});
|
||||
|
||||
if (this.autoFocus) {
|
||||
setTimeout(() => {
|
||||
this.editor.focus();
|
||||
this.editor.resize();
|
||||
}, 20);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.editor.resize();
|
||||
}, 20);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.editor-wrapper {
|
||||
.editor {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.ace_.mdi {
|
||||
display: inline-block;
|
||||
width: 17px;
|
||||
}
|
||||
</style>
|
@@ -4,7 +4,7 @@
|
||||
class="toast mt-2"
|
||||
:class="toastStatus.className"
|
||||
>
|
||||
<span class="p-vcentered text-left" v-html="`${toastStatus.iconTag} ${message}`" />
|
||||
<span class="p-vcentered text-left"><i class="mdi mdi-24px mr-1" :class="toastStatus.iconName" /> {{ message }}</span>
|
||||
<button class="btn btn-clear" @click="hideToast" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -30,27 +30,27 @@ export default {
|
||||
computed: {
|
||||
toastStatus () {
|
||||
let className = '';
|
||||
let iconTag = '';
|
||||
let iconName = '';
|
||||
switch (this.status) {
|
||||
case 'success':
|
||||
className = 'toast-success';
|
||||
iconTag = '<i class="mdi mdi-24px mdi-check mr-1"></i>';
|
||||
iconName = 'mdi-check';
|
||||
break;
|
||||
case 'error':
|
||||
className = 'toast-error';
|
||||
iconTag = '<i class="mdi mdi-24px mdi-alert-rhombus mr-1"></i>';
|
||||
iconName = 'mdi-alert-rhombus';
|
||||
break;
|
||||
case 'warning':
|
||||
className = 'toast-warning';
|
||||
iconTag = '<i class="mdi mdi-24px mdi-alert mr-1"></i>';
|
||||
iconName = 'mdi-alert';
|
||||
break;
|
||||
case 'primary':
|
||||
className = 'toast-primary';
|
||||
iconTag = '<i class="mdi mdi-24px mdi-information-outline mr-1"></i>';
|
||||
iconName = 'mdi-information-outline';
|
||||
break;
|
||||
}
|
||||
|
||||
return { className, iconTag };
|
||||
return { className, iconName };
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
109
src/renderer/components/BaseUploadInput.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<label :for="`id_${id}`" class="file-uploader">
|
||||
<span class="file-uploader-message">
|
||||
<i class="mdi mdi-folder-open mr-1" />{{ message }}
|
||||
</span>
|
||||
<span class="text-ellipsis file-uploader-value">
|
||||
{{ value | lastPart }}
|
||||
</span>
|
||||
<i
|
||||
v-if="value.length"
|
||||
class="file-uploader-reset mdi mdi-close"
|
||||
@click.prevent="clear"
|
||||
/>
|
||||
<form :ref="`form_${id}`">
|
||||
<input
|
||||
:id="`id_${id}`"
|
||||
class="file-uploader-input"
|
||||
type="file"
|
||||
@change="$emit('change', $event)"
|
||||
>
|
||||
</form>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BaseUploadInput',
|
||||
filters: {
|
||||
lastPart (string) {
|
||||
if (!string) return '';
|
||||
|
||||
string = string.split(/[/\\]+/).pop();
|
||||
if (string.length >= 19)
|
||||
string = `...${string.slice(-19)}`;
|
||||
return string;
|
||||
}
|
||||
},
|
||||
props: {
|
||||
message: {
|
||||
default: 'Browse',
|
||||
type: String
|
||||
},
|
||||
value: {
|
||||
default: '',
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
id: null
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.id = this._uid;
|
||||
},
|
||||
methods: {
|
||||
clear () {
|
||||
this.$emit('clear');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.file-uploader {
|
||||
border-radius: $border-radius;
|
||||
height: 1.8rem;
|
||||
line-height: 1.2rem;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s, border 0.2s, box-shadow 0.2s, color 0.2s;
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
|
||||
> span {
|
||||
padding: 0.25rem 0.4rem;
|
||||
}
|
||||
|
||||
.file-uploader-message {
|
||||
display: flex;
|
||||
word-break: keep-all;
|
||||
border-radius: $border-radius 0 0 $border-radius;
|
||||
}
|
||||
|
||||
.file-uploader-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.file-uploader-value {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.file-uploader-reset {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: calc(50% - 8px);
|
||||
}
|
||||
}
|
||||
|
||||
:disabled {
|
||||
.file-uploader {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -41,14 +41,16 @@ export default {
|
||||
localScrollElement: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
scrollElement () {
|
||||
this.setScrollElement();
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this._checkScrollPosition = this.checkScrollPosition.bind(this);
|
||||
this.localScrollElement = this.scrollElement ? this.scrollElement : this.$el;
|
||||
this.updateWindow();
|
||||
this.localScrollElement.addEventListener('scroll', this._checkScrollPosition);
|
||||
this.setScrollElement();
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.localScrollElement.removeEventListener('scroll', this._checkScrollPosition);
|
||||
this.localScrollElement.removeEventListener('scroll', this.checkScrollPosition);
|
||||
},
|
||||
methods: {
|
||||
checkScrollPosition (e) {
|
||||
@@ -58,7 +60,7 @@ export default {
|
||||
this.updateWindow(e);
|
||||
}, 200);
|
||||
},
|
||||
updateWindow (e) {
|
||||
updateWindow () {
|
||||
const visibleItemsCount = Math.ceil(this.visibleHeight / this.itemHeight);
|
||||
const totalScrollHeight = this.items.length * this.itemHeight;
|
||||
const offset = 50;
|
||||
@@ -74,6 +76,14 @@ export default {
|
||||
|
||||
this.topHeight = firstCutIndex * this.itemHeight;
|
||||
this.bottomHeight = totalScrollHeight - this.visibleItems.length * this.itemHeight - this.topHeight;
|
||||
},
|
||||
setScrollElement () {
|
||||
if (this.localScrollElement)
|
||||
this.localScrollElement.removeEventListener('scroll', this.checkScrollPosition);
|
||||
|
||||
this.localScrollElement = this.scrollElement ? this.scrollElement : this.$el;
|
||||
this.updateWindow();
|
||||
this.localScrollElement.addEventListener('scroll', this.checkScrollPosition);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
257
src/renderer/components/FakerSelect.vue
Normal file
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<fieldset class="input-group mb-0">
|
||||
<select
|
||||
v-model="selectedGroup"
|
||||
class="form-select"
|
||||
:disabled="!isChecked"
|
||||
style="flex-grow: 0;"
|
||||
@change="onChange"
|
||||
>
|
||||
<option value="manual">
|
||||
{{ $t('message.manualValue') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="group in fakerGroups"
|
||||
:key="group.name"
|
||||
:value="group.name"
|
||||
>
|
||||
{{ $t(`faker.${group.name}`) }}
|
||||
</option>
|
||||
</select>
|
||||
<select
|
||||
v-if="selectedGroup !== 'manual'"
|
||||
v-model="selectedMethod"
|
||||
class="form-select"
|
||||
:disabled="!isChecked"
|
||||
@change="onChange"
|
||||
>
|
||||
<option
|
||||
v-for="method in fakerMethods"
|
||||
:key="method.name"
|
||||
:value="method.name"
|
||||
>
|
||||
{{ $t(`faker.${method.name}`) }}
|
||||
</option>
|
||||
</select>
|
||||
<ForeignKeySelect
|
||||
v-else-if="foreignKeys.includes(field.name)"
|
||||
ref="formInput"
|
||||
class="form-select"
|
||||
:value.sync="selectedValue"
|
||||
:key-usage="getKeyUsage(field.name)"
|
||||
:disabled="!isChecked"
|
||||
/>
|
||||
<input
|
||||
v-else-if="inputProps().mask"
|
||||
ref="formInput"
|
||||
v-model="selectedValue"
|
||||
v-mask="inputProps().mask"
|
||||
class="form-input"
|
||||
:type="inputProps().type"
|
||||
:disabled="!isChecked"
|
||||
>
|
||||
<BaseUploadInput
|
||||
v-else-if="inputProps().type === 'file'"
|
||||
:value="selectedValue"
|
||||
:message="$t('word.browse')"
|
||||
@clear="clearValue"
|
||||
@change="filesChange($event)"
|
||||
/>
|
||||
<input
|
||||
v-else-if="inputProps().type === 'number'"
|
||||
ref="formInput"
|
||||
v-model="selectedValue"
|
||||
class="form-input"
|
||||
step="any"
|
||||
:type="inputProps().type"
|
||||
:disabled="!isChecked"
|
||||
>
|
||||
<select
|
||||
v-else-if="enumArray"
|
||||
v-model="selectedValue"
|
||||
class="form-select"
|
||||
:disabled="!isChecked"
|
||||
@change="onChange"
|
||||
>
|
||||
<option
|
||||
v-for="val in enumArray"
|
||||
:key="val"
|
||||
:value="val"
|
||||
>
|
||||
{{ val }}
|
||||
</option>
|
||||
</select>
|
||||
<input
|
||||
v-else
|
||||
ref="formInput"
|
||||
v-model="selectedValue"
|
||||
class="form-input"
|
||||
:type="inputProps().type"
|
||||
:disabled="!isChecked"
|
||||
>
|
||||
<template v-if="methodData && 'params' in methodData">
|
||||
<input
|
||||
v-for="(option, key) in methodData.params"
|
||||
:key="key"
|
||||
v-model="methodParams[option]"
|
||||
class="form-input column"
|
||||
:type="inputProps().type"
|
||||
:disabled="!isChecked"
|
||||
:placeholder="option"
|
||||
>
|
||||
</template>
|
||||
<slot />
|
||||
</fieldset>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { VueMaskDirective } from 'v-mask';
|
||||
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
|
||||
import BaseUploadInput from '@/components/BaseUploadInput';
|
||||
import ForeignKeySelect from '@/components/ForeignKeySelect';
|
||||
import FakerMethods from 'common/FakerMethods';
|
||||
|
||||
export default {
|
||||
name: 'FakerSelect',
|
||||
components: {
|
||||
ForeignKeySelect,
|
||||
BaseUploadInput
|
||||
},
|
||||
directives: {
|
||||
mask: VueMaskDirective
|
||||
},
|
||||
props: {
|
||||
type: String,
|
||||
field: Object,
|
||||
isChecked: Boolean,
|
||||
foreignKeys: Array,
|
||||
keyUsage: Array,
|
||||
fieldLength: Number,
|
||||
fieldObj: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localType: null,
|
||||
selectedGroup: 'manual',
|
||||
selectedMethod: '',
|
||||
selectedValue: '',
|
||||
debounceTimeout: null,
|
||||
methodParams: {},
|
||||
enumArray: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
fakerGroups () {
|
||||
if ([...TEXT, ...LONG_TEXT].includes(this.type))
|
||||
this.localType = 'string';
|
||||
else if (NUMBER.includes(this.type))
|
||||
this.localType = 'number';
|
||||
else if (FLOAT.includes(this.type))
|
||||
this.localType = 'float';
|
||||
else if ([...DATE, ...DATETIME].includes(this.type))
|
||||
this.localType = 'datetime';
|
||||
else if (TIME.includes(this.type))
|
||||
this.localType = 'time';
|
||||
else
|
||||
this.localType = 'none';
|
||||
|
||||
return FakerMethods.getGroupsByType(this.localType);
|
||||
},
|
||||
fakerMethods () {
|
||||
return FakerMethods.getMethods({ type: this.localType, group: this.selectedGroup });
|
||||
},
|
||||
methodData () {
|
||||
return this.fakerMethods.find(method => method.name === this.selectedMethod);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
fieldObj () {
|
||||
if (this.fieldObj) {
|
||||
if (Array.isArray(this.fieldObj.value)) {
|
||||
this.enumArray = this.fieldObj.value;
|
||||
this.selectedValue = this.fieldObj.value[0];
|
||||
}
|
||||
else
|
||||
this.selectedValue = this.fieldObj.value;
|
||||
}
|
||||
},
|
||||
selectedGroup () {
|
||||
if (this.fakerMethods.length)
|
||||
this.selectedMethod = this.fakerMethods[0].name;
|
||||
else
|
||||
this.selectedMethod = '';
|
||||
},
|
||||
selectedMethod () {
|
||||
this.onChange();
|
||||
},
|
||||
selectedValue () {
|
||||
clearTimeout(this.debounceTimeout);
|
||||
this.debounceTimeout = null;
|
||||
this.debounceTimeout = setTimeout(() => {
|
||||
this.onChange();
|
||||
}, 200);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
inputProps () {
|
||||
if ([...TEXT, ...LONG_TEXT].includes(this.type))
|
||||
return { type: 'text', mask: false };
|
||||
|
||||
if ([...NUMBER, ...FLOAT].includes(this.type))
|
||||
return { type: 'number', mask: false };
|
||||
|
||||
if (TIME.includes(this.type)) {
|
||||
let timeMask = '##:##:##';
|
||||
const precision = this.fieldLength;
|
||||
|
||||
for (let i = 0; i < precision; i++)
|
||||
timeMask += i === 0 ? '.#' : '#';
|
||||
|
||||
return { type: 'text', mask: timeMask };
|
||||
}
|
||||
|
||||
if (DATE.includes(this.type))
|
||||
return { type: 'text', mask: '####-##-##' };
|
||||
|
||||
if (DATETIME.includes(this.type)) {
|
||||
let datetimeMask = '####-##-## ##:##:##';
|
||||
const precision = this.fieldLength;
|
||||
|
||||
for (let i = 0; i < precision; i++)
|
||||
datetimeMask += i === 0 ? '.#' : '#';
|
||||
|
||||
return { type: 'text', mask: datetimeMask };
|
||||
}
|
||||
|
||||
if (BLOB.includes(this.type))
|
||||
return { type: 'file', mask: false };
|
||||
|
||||
if (BIT.includes(this.type))
|
||||
return { type: 'text', mask: false };
|
||||
|
||||
return { type: 'text', mask: false };
|
||||
},
|
||||
getKeyUsage (keyName) {
|
||||
return this.keyUsage.find(key => key.field === keyName);
|
||||
},
|
||||
filesChange (event) {
|
||||
const { files } = event.target;
|
||||
if (!files.length) return;
|
||||
|
||||
this.selectedValue = files[0].path;
|
||||
},
|
||||
clearValue () {
|
||||
this.selectedValue = '';
|
||||
},
|
||||
onChange () {
|
||||
this.$emit('update:value', {
|
||||
group: this.selectedGroup,
|
||||
method: this.selectedMethod,
|
||||
params: this.methodParams,
|
||||
value: this.selectedValue,
|
||||
length: this.fieldLength
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -1,17 +1,21 @@
|
||||
<template>
|
||||
<select
|
||||
ref="editField"
|
||||
class="px-1"
|
||||
class="form-select pl-1 pr-4"
|
||||
:class="{'small-select': size === 'small'}"
|
||||
@change="onChange"
|
||||
@blur="$emit('blur')"
|
||||
>
|
||||
<option v-if="!isValidDefault" :value="value">
|
||||
{{ value === null ? 'NULL' : value }}
|
||||
</option>
|
||||
<option
|
||||
v-for="row in foreignList"
|
||||
:key="row.foreignColumn"
|
||||
:value="row.foreignColumn"
|
||||
:selected="row.foreignColumn === value"
|
||||
:key="row.foreign_column"
|
||||
:value="row.foreign_column"
|
||||
:selected="row.foreign_column === value"
|
||||
>
|
||||
{{ row.foreignColumn }} {{ 'foreignDescription' in row ? ` - ${row.foreignDescription}` : '' | cutText }}
|
||||
{{ row.foreign_column }} {{ 'foreign_description' in row ? ` - ${row.foreign_description}` : '' | cutText }}
|
||||
</option>
|
||||
</select>
|
||||
</template>
|
||||
@@ -30,7 +34,11 @@ export default {
|
||||
},
|
||||
props: {
|
||||
value: [String, Number],
|
||||
keyUsage: Object
|
||||
keyUsage: Object,
|
||||
size: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -40,10 +48,15 @@ export default {
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected'
|
||||
})
|
||||
}),
|
||||
isValidDefault () {
|
||||
if (!this.foreignList.length) return true;
|
||||
if (this.value === null) return false;
|
||||
return this.foreignList.some(foreign => foreign.foreign_column.toString() === this.value.toString());
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
let firstTextField;
|
||||
let foreignDesc;
|
||||
const params = {
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.keyUsage.refSchema,
|
||||
@@ -52,8 +65,10 @@ export default {
|
||||
|
||||
try { // Field data
|
||||
const { status, response } = await Tables.getTableColumns(params);
|
||||
if (status === 'success')
|
||||
firstTextField = response.find(field => [...TEXT, ...LONG_TEXT].includes(field.type)).name || false;
|
||||
if (status === 'success') {
|
||||
const textField = response.find(field => [...TEXT, ...LONG_TEXT].includes(field.type) && field.name !== this.keyUsage.refField);
|
||||
foreignDesc = textField ? textField.name : false;
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
@@ -64,8 +79,8 @@ export default {
|
||||
try { // Foregn list
|
||||
const { status, response } = await Tables.getForeignList({
|
||||
...params,
|
||||
column: this.keyUsage.refColumn,
|
||||
description: firstTextField
|
||||
column: this.keyUsage.refField,
|
||||
description: foreignDesc
|
||||
});
|
||||
|
||||
if (status === 'success')
|
||||
|
@@ -15,10 +15,11 @@
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.user') }}:</label>
|
||||
<label class="form-label">{{ $t('word.user') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="credentials.user"
|
||||
class="form-input"
|
||||
type="text"
|
||||
@@ -27,7 +28,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.password') }}:</label>
|
||||
<label class="form-label">{{ $t('word.password') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<input
|
||||
@@ -40,7 +41,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer text-light">
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary mr-2" @click.stop="sendCredentials">
|
||||
{{ $t('word.send') }}
|
||||
</button>
|
||||
@@ -63,6 +64,11 @@ export default {
|
||||
}
|
||||
};
|
||||
},
|
||||
created () {
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
closeModal () {
|
||||
this.$emit('close-asking');
|
||||
|
138
src/renderer/components/ModalAskParameters.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.run')"
|
||||
:cancel-text="$t('word.cancel')"
|
||||
size="400"
|
||||
@confirm="runRoutine"
|
||||
@hide="closeModal"
|
||||
>
|
||||
<template #header>
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-play mr-1" />
|
||||
<span class="cut-text">{{ $t('word.parameters') }}: {{ localRoutine.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="content">
|
||||
<form class="form-horizontal">
|
||||
<div
|
||||
v-for="(parameter, i) in inParameters"
|
||||
:key="parameter._antares_id"
|
||||
class="form-group"
|
||||
>
|
||||
<div class="col-4">
|
||||
<label class="form-label">{{ parameter.name }}</label>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<div class="input-group">
|
||||
<input
|
||||
:ref="i === 0 ? 'firstInput' : ''"
|
||||
v-model="values[`${i}-${parameter.name}`]"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
<span
|
||||
:title="`${parameter.type} ${parameter.length}`"
|
||||
class="input-group-addon field-type cut-text"
|
||||
:class="typeClass(parameter.type)"
|
||||
>
|
||||
{{ parameter.type }} {{ parameter.length | wrapNumber }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { NUMBER, FLOAT } from 'common/fieldTypes';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'ModalAskParameters',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
filters: {
|
||||
wrapNumber (num) {
|
||||
if (!num) return '';
|
||||
return `(${num})`;
|
||||
}
|
||||
},
|
||||
props: {
|
||||
localRoutine: Object,
|
||||
client: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
values: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
inParameters () {
|
||||
return this.localRoutine.parameters.filter(param => param.context === 'IN');
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput[0].focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
typeClass (type) {
|
||||
if (type)
|
||||
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
|
||||
return '';
|
||||
},
|
||||
runRoutine () {
|
||||
const valArr = Object.keys(this.values).reduce((acc, curr, i) => {
|
||||
let qc;
|
||||
switch (this.client) {
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
qc = '"';
|
||||
break;
|
||||
case 'pg':
|
||||
qc = '\'';
|
||||
break;
|
||||
default:
|
||||
qc = '"';
|
||||
}
|
||||
|
||||
const param = this.localRoutine.parameters.find(param => `${i}-${param.name}` === curr);
|
||||
|
||||
const value = [...NUMBER, ...FLOAT].includes(param.type) ? this.values[curr] : `${qc}${this.values[curr]}${qc}`;
|
||||
acc.push(value);
|
||||
return acc;
|
||||
}, []);
|
||||
this.$emit('confirm', valArr);
|
||||
},
|
||||
closeModal () {
|
||||
this.$emit('close');
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.field-type {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
|
||||
.input-group-addon {
|
||||
max-width: 100px;
|
||||
}
|
||||
</style>
|
49
src/renderer/components/ModalDiscardChanges.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.discard')"
|
||||
:cancel-text="$t('word.stay')"
|
||||
@confirm="$emit('confirm')"
|
||||
@hide="$emit('close')"
|
||||
>
|
||||
<template #header>
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-content-save-alert mr-1" /> {{ $t('message.unsavedChanges') }}
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div>
|
||||
{{ $t('message.discardUnsavedChanges') }}
|
||||
</div>
|
||||
</template>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'ModalDiscardChanges',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-container {
|
||||
max-width: 360px;
|
||||
}
|
||||
</style>
|
@@ -1,247 +0,0 @@
|
||||
<template>
|
||||
<div class="modal active">
|
||||
<a class="modal-overlay c-hand" @click="closeModal" />
|
||||
<div class="modal-container">
|
||||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-server mr-1" /> {{ $t('message.editConnection') }}
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body pb-0">
|
||||
<div class="content">
|
||||
<form class="form-horizontal">
|
||||
<fieldset class="m-0" :disabled="isTesting">
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.connectionName') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.client') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<select v-model="localConnection.client" class="form-select">
|
||||
<option value="mysql">
|
||||
MySQL
|
||||
</option>
|
||||
<option value="maria">
|
||||
MariaDB
|
||||
</option>
|
||||
<!-- <option value="mssql">
|
||||
Microsoft SQL
|
||||
</option>
|
||||
<option value="pg">
|
||||
PostgreSQL
|
||||
</option>
|
||||
<option value="oracledb">
|
||||
Oracle DB
|
||||
</option> -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.hostName') }}/IP:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.host"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.port') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.port"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.user') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.user"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:disabled="localConnection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.password') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.password"
|
||||
class="form-input"
|
||||
type="password"
|
||||
:disabled="localConnection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12" />
|
||||
<div class="col-8 col-sm-12">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="localConnection.ask" type="checkbox"><i class="form-icon" /> {{ $t('message.askCredentials') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<BaseToast
|
||||
class="mb-2"
|
||||
:message="toast.message"
|
||||
:status="toast.status"
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-footer text-light">
|
||||
<button
|
||||
class="btn btn-gray mr-2"
|
||||
:class="{'loading': isTesting}"
|
||||
@click="startTest"
|
||||
>
|
||||
{{ $t('message.testConnection') }}
|
||||
</button>
|
||||
<button class="btn btn-primary mr-2" @click="saveEditConnection">
|
||||
{{ $t('word.save') }}
|
||||
</button>
|
||||
<button class="btn btn-link" @click="closeModal">
|
||||
{{ $t('word.close') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ModalAskCredentials
|
||||
v-if="isAsking"
|
||||
@close-asking="closeAsking"
|
||||
@credentials="continueTest"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import Connection from '@/ipc-api/Connection';
|
||||
import ModalAskCredentials from '@/components/ModalAskCredentials';
|
||||
import BaseToast from '@/components/BaseToast';
|
||||
|
||||
export default {
|
||||
name: 'ModalEditConnection',
|
||||
components: {
|
||||
ModalAskCredentials,
|
||||
BaseToast
|
||||
},
|
||||
props: {
|
||||
connection: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
toast: {
|
||||
status: '',
|
||||
message: ''
|
||||
},
|
||||
isTesting: false,
|
||||
isAsking: false,
|
||||
localConnection: null
|
||||
};
|
||||
},
|
||||
created () {
|
||||
this.localConnection = Object.assign({}, this.connection);
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
editConnection: 'connections/editConnection'
|
||||
}),
|
||||
async startTest () {
|
||||
this.isTesting = true;
|
||||
this.toast = {
|
||||
status: '',
|
||||
message: ''
|
||||
};
|
||||
|
||||
if (this.localConnection.ask)
|
||||
this.isAsking = true;
|
||||
else {
|
||||
try {
|
||||
const res = await Connection.makeTest(this.localConnection);
|
||||
if (res.status === 'error')
|
||||
this.toast = { status: 'error', message: res.response.message };
|
||||
else
|
||||
this.toast = { status: 'success', message: this.$t('message.connectionSuccessfullyMade') };
|
||||
}
|
||||
catch (err) {
|
||||
this.toast = { status: 'error', message: err.stack };
|
||||
}
|
||||
|
||||
this.isTesting = false;
|
||||
}
|
||||
},
|
||||
async continueTest (credentials) { // if "Ask for credentials" is true
|
||||
this.isAsking = false;
|
||||
const params = Object.assign({}, this.localConnection, credentials);
|
||||
try {
|
||||
const res = await Connection.makeTest(params);
|
||||
if (res.status === 'error')
|
||||
this.toast = { status: 'error', message: res.response.message };
|
||||
else
|
||||
this.toast = { status: 'success', message: this.$t('message.connectionSuccessfullyMade') };
|
||||
}
|
||||
catch (err) {
|
||||
this.toast = { status: 'error', message: err.stack };
|
||||
}
|
||||
|
||||
this.isTesting = false;
|
||||
},
|
||||
saveEditConnection () {
|
||||
this.editConnection(this.localConnection);
|
||||
this.closeModal();
|
||||
},
|
||||
closeAsking () {
|
||||
this.isTesting = false;
|
||||
this.isAsking = false;
|
||||
},
|
||||
closeModal () {
|
||||
this.$emit('close');
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-container {
|
||||
max-width: 450px;
|
||||
}
|
||||
</style>
|
@@ -5,7 +5,8 @@
|
||||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-database-edit mr-1" /> {{ $t('message.editDatabase') }}
|
||||
<i class="mdi mdi-24px mdi-database-edit mr-1" />
|
||||
<span class="cut-text">{{ $t('message.editSchema') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||
@@ -15,7 +16,7 @@
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.name') }}:</label>
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<input
|
||||
@@ -23,17 +24,21 @@
|
||||
class="form-input"
|
||||
type="text"
|
||||
required
|
||||
:placeholder="$t('message.databaseName')"
|
||||
:placeholder="$t('message.schemaName')"
|
||||
readonly
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.collation') }}:</label>
|
||||
<label class="form-label">{{ $t('word.collation') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<select v-model="database.collation" class="form-select">
|
||||
<select
|
||||
ref="firstInput"
|
||||
v-model="database.collation"
|
||||
class="form-select"
|
||||
>
|
||||
<option
|
||||
v-for="collation in collations"
|
||||
:key="collation.id"
|
||||
@@ -48,8 +53,8 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer text-light">
|
||||
<button class="btn btn-primary mr-2" @click.stop="updateDatabase">
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary mr-2" @click.stop="updateSchema">
|
||||
{{ $t('word.update') }}
|
||||
</button>
|
||||
<button class="btn btn-link" @click.stop="closeModal">
|
||||
@@ -62,12 +67,12 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import Database from '@/ipc-api/Database';
|
||||
import Schema from '@/ipc-api/Schema';
|
||||
|
||||
export default {
|
||||
name: 'ModalEditDatabase',
|
||||
name: 'ModalEditSchema',
|
||||
props: {
|
||||
selectedDatabase: String
|
||||
selectedSchema: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -94,7 +99,7 @@ export default {
|
||||
async created () {
|
||||
let actualCollation;
|
||||
try {
|
||||
const { status, response } = await Database.getDatabaseCollation({ uid: this.selectedWorkspace, database: this.selectedDatabase });
|
||||
const { status, response } = await Schema.getDatabaseCollation({ uid: this.selectedWorkspace, database: this.selectedSchema });
|
||||
|
||||
if (status === 'success')
|
||||
actualCollation = response;
|
||||
@@ -107,13 +112,17 @@ export default {
|
||||
}
|
||||
|
||||
this.database = {
|
||||
name: this.selectedDatabase,
|
||||
prevName: this.selectedDatabase,
|
||||
name: this.selectedSchema,
|
||||
prevName: this.selectedSchema,
|
||||
collation: actualCollation || this.defaultCollation,
|
||||
prevCollation: actualCollation || this.defaultCollation
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
@@ -122,10 +131,10 @@ export default {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification'
|
||||
}),
|
||||
async updateDatabase () {
|
||||
async updateSchema () {
|
||||
if (this.database.collation !== this.database.prevCollation) {
|
||||
try {
|
||||
const { status, response } = await Database.updateDatabase({
|
||||
const { status, response } = await Schema.updateSchema({
|
||||
uid: this.selectedWorkspace,
|
||||
...this.database
|
||||
});
|
507
src/renderer/components/ModalExportSchema.vue
Normal file
@@ -0,0 +1,507 @@
|
||||
<template>
|
||||
<div class="modal active">
|
||||
<a class="modal-overlay" @click.stop="closeModal" />
|
||||
<div class="modal-container p-0">
|
||||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-database-arrow-down mr-1" />
|
||||
<span class="cut-text">{{ $t('message.exportSchema') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body pb-0">
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('message.directoryPath') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<fieldset class="input-group">
|
||||
<input
|
||||
v-model="basePath"
|
||||
class="form-input"
|
||||
type="text"
|
||||
required
|
||||
readonly
|
||||
:placeholder="$t('message.schemaName')"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary input-group-btn"
|
||||
@click.prevent="openPathDialog"
|
||||
>
|
||||
{{ $t('word.change') }}
|
||||
</button>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="columns export-options">
|
||||
<div class="column col-8 left">
|
||||
<div class="columns mb-2">
|
||||
<div class="column col-auto d-flex text-italic ">
|
||||
<i class="mdi mdi-file-document-outline mr-2" />
|
||||
{{ filename }}
|
||||
</div>
|
||||
|
||||
<div class="column col-auto col-ml-auto ">
|
||||
<button
|
||||
class="btn btn-dark btn-sm"
|
||||
:title="$t('word.refresh')"
|
||||
@click="refresh"
|
||||
>
|
||||
<i class="mdi mdi-database-refresh" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm"
|
||||
:title="$t('message.uncheckAllTables')"
|
||||
:disabled="isRefreshing"
|
||||
@click="uncheckAllTables"
|
||||
>
|
||||
<i class="mdi mdi-file-tree-outline" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm"
|
||||
:title="$t('message.checkAllTables')"
|
||||
:disabled="isRefreshing"
|
||||
@click="checkAllTables"
|
||||
>
|
||||
<i class="mdi mdi-file-tree" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-query-results">
|
||||
<div ref="table" class="table table-hover">
|
||||
<div class="thead">
|
||||
<div class="tr text-center">
|
||||
<div class="th no-border" style="width: 50%;" />
|
||||
<div class="th no-border">
|
||||
<label
|
||||
class="form-checkbox m-0 px-2 form-inline"
|
||||
@click.prevent="toggleAllTablesOption('includeStructure')"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:indeterminate.prop="includeStructureStatus === 2"
|
||||
:checked.prop="!!includeStructureStatus"
|
||||
>
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="th no-border">
|
||||
<label
|
||||
class="form-checkbox m-0 px-2 form-inline"
|
||||
@click.prevent="toggleAllTablesOption('includeContent')"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:indeterminate.prop="includeContentStatus === 2"
|
||||
:checked.prop="!!includeContentStatus"
|
||||
>
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="th no-border">
|
||||
<label
|
||||
class="form-checkbox m-0 px-2 form-inline"
|
||||
@click.prevent="toggleAllTablesOption('includeDropStatement')"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:indeterminate.prop="includeDropStatementStatus === 2"
|
||||
:checked.prop="!!includeDropStatementStatus"
|
||||
>
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tr">
|
||||
<div class="th" style="width: 50%;">
|
||||
<div class="table-column-title">
|
||||
<span>{{ $t('word.table') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="th text-center">
|
||||
<div class="table-column-title">
|
||||
<span>{{ $t('word.structure') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="th text-center">
|
||||
<div class="table-column-title">
|
||||
<span>{{ $t('word.content') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="th text-center">
|
||||
<div class="table-column-title">
|
||||
<span>{{ $t('word.drop') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tbody">
|
||||
<div
|
||||
v-for="item in tables"
|
||||
:key="item.name"
|
||||
class="tr"
|
||||
>
|
||||
<div class="td">
|
||||
{{ item.table }}
|
||||
</div>
|
||||
<div class="td text-center">
|
||||
<label class="form-checkbox m-0 px-2 form-inline">
|
||||
<input
|
||||
v-model="item.includeStructure"
|
||||
type="checkbox"
|
||||
><i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="td text-center">
|
||||
<label class="form-checkbox m-0 px-2 form-inline">
|
||||
<input
|
||||
v-model="item.includeContent"
|
||||
type="checkbox"
|
||||
><i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="td text-center">
|
||||
<label class="form-checkbox m-0 px-2 form-inline">
|
||||
<input
|
||||
v-model="item.includeDropStatement"
|
||||
type="checkbox"
|
||||
><i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-4">
|
||||
<h5 class="h5">
|
||||
{{ $t('word.options') }}
|
||||
</h5>
|
||||
<span class="h6">{{ $t('word.includes') }}:</span>
|
||||
<label
|
||||
v-for="(_, key) in options.includes"
|
||||
:key="key"
|
||||
class="form-checkbox"
|
||||
>
|
||||
<input v-model="options.includes[key]" type="checkbox"><i class="form-icon" /> {{ $t(`word.${key}`) }}
|
||||
</label>
|
||||
|
||||
<div class="h6 mt-4 mb-2">
|
||||
{{ $t('message.newInserStmtEvery') }}:
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column col-6">
|
||||
<input
|
||||
v-model.number="options.sqlInsertAfter"
|
||||
type="number"
|
||||
class="form-input"
|
||||
value="250"
|
||||
>
|
||||
</div>
|
||||
<div class="column col-6">
|
||||
<select v-model="options.sqlInsertDivider" class="form-select">
|
||||
<option value="bytes">
|
||||
KiB
|
||||
</option>
|
||||
<option value="rows">
|
||||
{{ $tc('word.row', 2) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h6 mb-2 mt-4">
|
||||
{{ $t('message.ourputFormat') }}:
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column h5 mb-4">
|
||||
<select v-model="options.outputFormat" class="form-select">
|
||||
<option value="sql">
|
||||
{{ $t('message.singleFile', {ext: '.sql'}) }}
|
||||
</option>
|
||||
<option value="sql.zip">
|
||||
{{ $t('message.zipCompressedFile', {ext: '.sql'}) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer columns">
|
||||
<div class="column col modal-progress-wrapper text-left">
|
||||
<div v-if="progressPercentage > 0" class="export-progress">
|
||||
<span class="progress-status">
|
||||
{{ progressPercentage }}% - {{ progressStatus }}
|
||||
</span>
|
||||
<progress
|
||||
class="progress d-block"
|
||||
:value="progressPercentage"
|
||||
max="100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-auto px-0">
|
||||
<button class="btn btn-link" @click.stop="closeModal">
|
||||
{{ $t('word.close') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary mr-2"
|
||||
:class="{'loading': isExporting}"
|
||||
:disabled="isExporting || isRefreshing"
|
||||
autofocus
|
||||
@click.prevent="startExport"
|
||||
>
|
||||
{{ $t('word.export') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import moment from 'moment';
|
||||
import customizations from 'common/customizations';
|
||||
import Application from '@/ipc-api/Application';
|
||||
import Schema from '@/ipc-api/Schema';
|
||||
|
||||
export default {
|
||||
name: 'ModalExportSchema',
|
||||
|
||||
props: {
|
||||
selectedSchema: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isExporting: false,
|
||||
isRefreshing: false,
|
||||
progressPercentage: 0,
|
||||
progressStatus: '',
|
||||
tables: [],
|
||||
options: {
|
||||
includes: {},
|
||||
outputFormat: 'sql',
|
||||
sqlInsertAfter: 250,
|
||||
sqlInsertDivider: 'bytes'
|
||||
},
|
||||
basePath: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace',
|
||||
getDatabaseVariable: 'workspaces/getDatabaseVariable'
|
||||
}),
|
||||
currentWorkspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
},
|
||||
schemaItems () {
|
||||
const db = this.currentWorkspace.structure.find(db => db.name === this.selectedSchema);
|
||||
if (db)
|
||||
return db.tables.filter(table => table.type === 'table');
|
||||
|
||||
return [];
|
||||
},
|
||||
filename () {
|
||||
const date = moment().format('YYYY-MM-DD');
|
||||
return `${this.selectedSchema}_${date}.${this.options.outputFormat}`;
|
||||
},
|
||||
dumpFilePath () {
|
||||
return `${this.basePath}/${this.filename}`;
|
||||
},
|
||||
includeStructureStatus () {
|
||||
if (this.tables.every(item => item.includeStructure)) return 1;
|
||||
else if (this.tables.some(item => item.includeStructure)) return 2;
|
||||
else return 0;
|
||||
},
|
||||
includeContentStatus () {
|
||||
if (this.tables.every(item => item.includeContent)) return 1;
|
||||
else if (this.tables.some(item => item.includeContent)) return 2;
|
||||
else return 0;
|
||||
},
|
||||
includeDropStatementStatus () {
|
||||
if (this.tables.every(item => item.includeDropStatement)) return 1;
|
||||
else if (this.tables.some(item => item.includeDropStatement)) return 2;
|
||||
else return 0;
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
if (!this.schemaItems.length) await this.refresh();
|
||||
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
|
||||
this.basePath = await Application.getDownloadPathDirectory();
|
||||
this.tables = this.schemaItems.map(item => ({
|
||||
table: item.name,
|
||||
includeStructure: true,
|
||||
includeContent: true,
|
||||
includeDropStatement: true
|
||||
}));
|
||||
|
||||
const structure = ['views', 'triggers', 'routines', 'functions', 'schedulers', 'triggerFunctions'];
|
||||
|
||||
structure.forEach(feat => {
|
||||
const val = customizations[this.currentWorkspace.client][feat];
|
||||
if (val)
|
||||
this.$set(this.options.includes, feat, true);
|
||||
});
|
||||
|
||||
ipcRenderer.on('export-progress', this.updateProgress);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
ipcRenderer.off('export-progress', this.updateProgress);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
refreshSchema: 'workspaces/refreshSchema'
|
||||
}),
|
||||
async startExport () {
|
||||
this.isExporting = true;
|
||||
const { uid, client } = this.currentWorkspace;
|
||||
const params = {
|
||||
uid,
|
||||
type: client,
|
||||
schema: this.selectedSchema,
|
||||
outputFile: this.dumpFilePath,
|
||||
tables: [...this.tables],
|
||||
...this.options
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Schema.export(params);
|
||||
if (status === 'success')
|
||||
this.progressStatus = response.cancelled ? this.$t('word.aborted') : this.$t('word.completed');
|
||||
else {
|
||||
this.progressStatus = response;
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isExporting = false;
|
||||
},
|
||||
updateProgress (event, state) {
|
||||
this.progressPercentage = Number((state.currentItemIndex / state.totalItems * 100).toFixed(1));
|
||||
switch (state.op) {
|
||||
case 'PROCESSING':
|
||||
this.progressStatus = this.$t('message.processingTableExport', { table: state.currentItem });
|
||||
break;
|
||||
case 'FETCH':
|
||||
this.progressStatus = this.$t('message.fechingTableExport', { table: state.currentItem });
|
||||
break;
|
||||
case 'WRITE':
|
||||
this.progressStatus = this.$t('message.writingTableExport', { table: state.currentItem });
|
||||
break;
|
||||
}
|
||||
},
|
||||
async closeModal () {
|
||||
let willClose = true;
|
||||
if (this.isExporting) {
|
||||
willClose = false;
|
||||
const { response } = await Schema.abortExport();
|
||||
willClose = response.willAbort;
|
||||
}
|
||||
|
||||
if (willClose)
|
||||
this.$emit('close');
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
},
|
||||
checkAllTables () {
|
||||
this.tables = this.tables.map(item => ({ ...item, includeStructure: true, includeContent: true, includeDropStatement: true }));
|
||||
},
|
||||
uncheckAllTables () {
|
||||
this.tables = this.tables.map(item => ({ ...item, includeStructure: false, includeContent: false, includeDropStatement: false }));
|
||||
},
|
||||
toggleAllTablesOption (option) {
|
||||
const options = ['includeStructure', 'includeContent', 'includeDropStatement'];
|
||||
if (!options.includes(option)) return;
|
||||
|
||||
if (this[`${option}Status`] !== 1)
|
||||
this.tables = this.tables.map(item => ({ ...item, [option]: true }));
|
||||
else
|
||||
this.tables = this.tables.map(item => ({ ...item, [option]: false }));
|
||||
},
|
||||
async refresh () {
|
||||
this.isRefreshing = true;
|
||||
await this.refreshSchema({ uid: this.currentWorkspace.uid, schema: this.selectedSchema });
|
||||
this.isRefreshing = false;
|
||||
},
|
||||
async openPathDialog () {
|
||||
const result = await Application.showOpenDialog({ properties: ['openDirectory'] });
|
||||
if (result && !result.canceled)
|
||||
this.basePath = result.filePaths[0];
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.export-options {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.workspace-query-results {
|
||||
flex: 1 0 1px;
|
||||
.table {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.form-checkbox {
|
||||
min-height: 0.8rem;
|
||||
padding: 0;
|
||||
|
||||
.form-icon {
|
||||
top: 0.1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
|
||||
.modal-container {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
max-height: 60vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-status {
|
||||
font-style: italic;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
</style>
|
400
src/renderer/components/ModalFakerRows.vue
Normal file
@@ -0,0 +1,400 @@
|
||||
<template>
|
||||
<div class="modal active">
|
||||
<a class="modal-overlay" @click.stop="closeModal" />
|
||||
<div class="modal-container p-0">
|
||||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
|
||||
<span class="cut-text">{{ $tc('message.insertRow', 2) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body pb-0">
|
||||
<div class="content">
|
||||
<form class="form-horizontal">
|
||||
<fieldset :disabled="isInserting">
|
||||
<div
|
||||
v-for="field in fields"
|
||||
:key="field.name"
|
||||
class="form-group"
|
||||
>
|
||||
<div class="col-3 col-sm-12">
|
||||
<label class="form-label" :title="field.name">{{ field.name }}</label>
|
||||
</div>
|
||||
<div class="column columns col-sm-12">
|
||||
<FakerSelect
|
||||
:type="field.type"
|
||||
class="column columns pr-0"
|
||||
:is-checked="!fieldsToExclude.includes(field.name)"
|
||||
:foreign-keys="foreignKeys"
|
||||
:key-usage="keyUsage"
|
||||
:field="field"
|
||||
:field-length="fieldLength(field)"
|
||||
:field-obj="localRow[field.name]"
|
||||
:value.sync="localRow[field.name]"
|
||||
>
|
||||
<span class="input-group-addon field-type" :class="typeClass(field.type)">
|
||||
{{ field.type }} {{ fieldLength(field) | wrapNumber }}
|
||||
</span>
|
||||
<label class="form-checkbox ml-3" :title="$t('word.insert')">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="!fieldsToExclude.includes(field.name)"
|
||||
@change.prevent="toggleFields($event, field)"
|
||||
><i class="form-icon" />
|
||||
</label>
|
||||
</FakerSelect>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer columns">
|
||||
<div class="column d-flex" :class="hasFakes ? 'col-4' : 'col-2'">
|
||||
<div class="input-group tooltip tooltip-right" :data-tooltip="$t('message.numberOfInserts')">
|
||||
<input
|
||||
v-model="nInserts"
|
||||
type="number"
|
||||
class="form-input"
|
||||
min="1"
|
||||
:disabled="isInserting"
|
||||
>
|
||||
<span class="input-group-addon">
|
||||
<i class="mdi mdi-24px mdi-repeat" />
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="hasFakes"
|
||||
class="tooltip tooltip-right ml-2"
|
||||
:data-tooltip="$t('message.fakeDataLanguage')"
|
||||
>
|
||||
<select v-model="fakerLocale" class="form-select">
|
||||
<option value="ar">
|
||||
Arabic
|
||||
</option><option value="az">
|
||||
Azerbaijani
|
||||
</option><option value="zh_CN">
|
||||
Chinese
|
||||
</option><option value="zh_TW">
|
||||
Chinese (Taiwan)
|
||||
</option><option value="cz">
|
||||
Czech
|
||||
</option><option value="nl">
|
||||
Dutch
|
||||
</option><option value="nl_BE">
|
||||
Dutch (Belgium)
|
||||
</option><option value="en">
|
||||
English
|
||||
</option><option value="en_AU_ocker">
|
||||
English (Australia Ocker)
|
||||
</option><option value="en_AU">
|
||||
English (Australia)
|
||||
</option><option value="en_BORK">
|
||||
English (Bork)
|
||||
</option><option value="en_CA">
|
||||
English (Canada)
|
||||
</option><option value="en_GB">
|
||||
English (Great Britain)
|
||||
</option><option value="en_IND">
|
||||
English (India)
|
||||
</option><option value="en_IE">
|
||||
English (Ireland)
|
||||
</option><option value="en_ZA">
|
||||
English (South Africa)
|
||||
</option><option value="en_US">
|
||||
English (United States)
|
||||
</option><option value="fa">
|
||||
Farsi
|
||||
</option><option value="fi">
|
||||
Finnish
|
||||
</option><option value="fr">
|
||||
French
|
||||
</option><option value="fr_CA">
|
||||
French (Canada)
|
||||
</option><option value="fr_CH">
|
||||
French (Switzerland)
|
||||
</option><option value="ge">
|
||||
Georgian
|
||||
</option><option value="de">
|
||||
German
|
||||
</option><option value="de_AT">
|
||||
German (Austria)
|
||||
</option><option value="de_CH">
|
||||
German (Switzerland)
|
||||
</option><option value="hr">
|
||||
Hrvatski
|
||||
</option><option value="id_ID">
|
||||
Indonesia
|
||||
</option><option value="it">
|
||||
Italian
|
||||
</option><option value="ja">
|
||||
Japanese
|
||||
</option><option value="ko">
|
||||
Korean
|
||||
</option><option value="nep">
|
||||
Nepalese
|
||||
</option><option value="nb_NO">
|
||||
Norwegian
|
||||
</option><option value="pl">
|
||||
Polish
|
||||
</option><option value="pt_BR">
|
||||
Portuguese (Brazil)
|
||||
</option><option value="pt_PT">
|
||||
Portuguese (Portugal)
|
||||
</option><option value="ro">
|
||||
Romanian
|
||||
</option><option value="ru">
|
||||
Russian
|
||||
</option><option value="sk">
|
||||
Slovakian
|
||||
</option><option value="es">
|
||||
Spanish
|
||||
</option><option value="es_MX">
|
||||
Spanish (Mexico)
|
||||
</option><option value="sv">
|
||||
Swedish
|
||||
</option><option value="tr">
|
||||
Turkish
|
||||
</option><option value="uk">
|
||||
Ukrainian
|
||||
</option><option value="vi">
|
||||
Vietnamese
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-auto">
|
||||
<button
|
||||
class="btn btn-primary mr-2"
|
||||
:class="{'loading': isInserting}"
|
||||
@click.stop="insertRows"
|
||||
>
|
||||
{{ $t('word.insert') }}
|
||||
</button>
|
||||
<button class="btn btn-link" @click.stop="closeModal">
|
||||
{{ $t('word.close') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import FakerSelect from '@/components/FakerSelect';
|
||||
|
||||
export default {
|
||||
name: 'ModalFakerRows',
|
||||
components: {
|
||||
FakerSelect
|
||||
},
|
||||
filters: {
|
||||
wrapNumber (num) {
|
||||
if (!num) return '';
|
||||
return `(${num})`;
|
||||
}
|
||||
},
|
||||
props: {
|
||||
tabUid: [String, Number],
|
||||
fields: Array,
|
||||
keyUsage: Array
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localRow: {},
|
||||
fieldsToExclude: [],
|
||||
nInserts: 1,
|
||||
isInserting: false,
|
||||
fakerLocale: 'en'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace',
|
||||
getWorkspaceTab: 'workspaces/getWorkspaceTab'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
},
|
||||
foreignKeys () {
|
||||
return this.keyUsage.map(key => key.field);
|
||||
},
|
||||
hasFakes () {
|
||||
return Object.keys(this.localRow).some(field => 'group' in this.localRow[field] && this.localRow[field].group !== 'manual');
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
nInserts (val) {
|
||||
if (!val || val < 1)
|
||||
this.nInserts = 1;
|
||||
else if (val > 1000)
|
||||
this.nInserts = 1000;
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
mounted () {
|
||||
const rowObj = {};
|
||||
|
||||
for (const field of this.fields) {
|
||||
let fieldDefault;
|
||||
|
||||
if (field.default === 'NULL') fieldDefault = null;
|
||||
else {
|
||||
if ([...NUMBER, ...FLOAT].includes(field.type))
|
||||
fieldDefault = !field.default || Number.isNaN(+field.default.replaceAll('\'', '')) ? null : +field.default.replaceAll('\'', '');
|
||||
else if ([...TEXT, ...LONG_TEXT].includes(field.type)) {
|
||||
fieldDefault = field.default
|
||||
? field.default.includes('\'')
|
||||
? field.default.split('\'')[1]
|
||||
: field.default
|
||||
: '';
|
||||
}
|
||||
else if ([...TIME, ...DATE].includes(field.type))
|
||||
fieldDefault = field.default;
|
||||
else if (BIT.includes(field.type))
|
||||
fieldDefault = field.default.replaceAll('\'', '').replaceAll('b', '');
|
||||
else if (DATETIME.includes(field.type)) {
|
||||
if (field.default && ['current_timestamp', 'now()'].some(term => field.default.toLowerCase().includes(term))) {
|
||||
let datePrecision = '';
|
||||
for (let i = 0; i < field.datePrecision; i++)
|
||||
datePrecision += i === 0 ? '.S' : 'S';
|
||||
fieldDefault = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
|
||||
}
|
||||
else
|
||||
fieldDefault = field.default;
|
||||
}
|
||||
else if (field.enumValues)
|
||||
fieldDefault = field.enumValues.replaceAll('\'', '').split(',');
|
||||
else
|
||||
fieldDefault = field.default;
|
||||
}
|
||||
|
||||
rowObj[field.name] = { value: fieldDefault };
|
||||
|
||||
if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields
|
||||
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
|
||||
}
|
||||
|
||||
this.localRow = { ...rowObj };
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification'
|
||||
}),
|
||||
typeClass (type) {
|
||||
if (type)
|
||||
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
|
||||
return '';
|
||||
},
|
||||
async insertRows () {
|
||||
this.isInserting = true;
|
||||
const rowToInsert = this.localRow;
|
||||
|
||||
Object.keys(rowToInsert).forEach(key => {
|
||||
if (this.fieldsToExclude.includes(key))
|
||||
delete rowToInsert[key];
|
||||
|
||||
if (typeof rowToInsert[key] === 'undefined')
|
||||
delete rowToInsert[key];
|
||||
});
|
||||
|
||||
const fieldTypes = {};
|
||||
this.fields.forEach(field => {
|
||||
fieldTypes[field.name] = field.type;
|
||||
});
|
||||
|
||||
try {
|
||||
const { status, response } = await Tables.insertTableFakeRows({
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.workspace.breadcrumbs.schema,
|
||||
table: this.workspace.breadcrumbs.table,
|
||||
row: rowToInsert,
|
||||
repeat: this.nInserts,
|
||||
fields: fieldTypes,
|
||||
locale: this.fakerLocale
|
||||
});
|
||||
|
||||
if (status === 'success') {
|
||||
this.closeModal();
|
||||
this.$emit('reload');
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isInserting = false;
|
||||
},
|
||||
closeModal () {
|
||||
this.$emit('hide');
|
||||
},
|
||||
fieldLength (field) {
|
||||
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
|
||||
else if (TEXT.includes(field.type)) return field.charLength;
|
||||
return field.length;
|
||||
},
|
||||
toggleFields (event, field) {
|
||||
if (event.target.checked)
|
||||
this.fieldsToExclude = this.fieldsToExclude.filter(f => f !== field.name);
|
||||
else
|
||||
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
|
||||
},
|
||||
filesChange (event, field) {
|
||||
const { files } = event.target;
|
||||
if (!files.length) return;
|
||||
|
||||
this.localRow[field] = files[0].path;
|
||||
},
|
||||
getKeyUsage (keyName) {
|
||||
return this.keyUsage.find(key => key.field === keyName);
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-container {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
overflow: hidden;
|
||||
white-space: normal;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.input-group-addon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.field-type {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
</style>
|
283
src/renderer/components/ModalHistory.vue
Normal file
@@ -0,0 +1,283 @@
|
||||
<template>
|
||||
<div class="modal active">
|
||||
<a class="modal-overlay" @click.stop="closeModal" />
|
||||
<div class="modal-container p-0 pb-4">
|
||||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-history mr-1" />
|
||||
<span class="cut-text">{{ $t('word.history') }}: {{ connectionName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body p-0 workspace-query-results">
|
||||
<div
|
||||
v-if="history.length"
|
||||
ref="searchForm"
|
||||
class="form-group has-icon-right p-2 m-0"
|
||||
>
|
||||
<input
|
||||
v-model="searchTerm"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:placeholder="$t('message.searchForQueries')"
|
||||
>
|
||||
<i v-if="!searchTerm" class="form-icon mdi mdi-magnify mdi-18px pr-4" />
|
||||
<i
|
||||
v-else
|
||||
class="form-icon c-hand mdi mdi-backspace mdi-18px pr-4"
|
||||
@click="searchTerm = ''"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="history.length"
|
||||
ref="tableWrapper"
|
||||
class="vscroll px-1 "
|
||||
:style="{'height': resultsSize+'px'}"
|
||||
>
|
||||
<div ref="table">
|
||||
<BaseVirtualScroll
|
||||
ref="resultTable"
|
||||
:items="filteredHistory"
|
||||
:item-height="66"
|
||||
:visible-height="resultsSize"
|
||||
:scroll-element="scrollElement"
|
||||
>
|
||||
<template slot-scope="{ items }">
|
||||
<div
|
||||
v-for="query in items"
|
||||
:key="query.uid"
|
||||
class="tile my-2"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="tile-icon">
|
||||
<i class="mdi mdi-code-tags pr-1" />
|
||||
</div>
|
||||
<div class="tile-content">
|
||||
<div class="tile-title">
|
||||
<code
|
||||
class="cut-text"
|
||||
:title="query.sql"
|
||||
v-html="highlightWord(query.sql)"
|
||||
/>
|
||||
</div>
|
||||
<div class="tile-bottom-content">
|
||||
<small class="tile-subtitle">{{ query.schema }} · {{ formatDate(query.date) }}</small>
|
||||
<div class="tile-history-buttons">
|
||||
<button class="btn btn-link pl-1" @click.stop="$emit('select-query', query.sql)">
|
||||
<i class="mdi mdi-open-in-app pr-1" /> {{ $t('word.select') }}
|
||||
</button>
|
||||
<button class="btn btn-link pl-1" @click="copyQuery(query.sql)">
|
||||
<i class="mdi mdi-content-copy pr-1" /> {{ $t('word.copy') }}
|
||||
</button>
|
||||
<button class="btn btn-link pl-1" @click="deleteQuery(query)">
|
||||
<i class="mdi mdi-delete-forever pr-1" /> {{ $t('word.delete') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</BaseVirtualScroll>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="empty">
|
||||
<div class="empty-icon">
|
||||
<i class="mdi mdi-history mdi-48px" />
|
||||
</div>
|
||||
<p class="empty-title h5">
|
||||
{{ $t('message.thereIsNoQueriesYet') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
|
||||
|
||||
export default {
|
||||
name: 'ModalHistory',
|
||||
components: {
|
||||
BaseVirtualScroll
|
||||
},
|
||||
props: {
|
||||
connection: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
resultsSize: 1000,
|
||||
isQuering: false,
|
||||
scrollElement: null,
|
||||
searchTermInterval: null,
|
||||
searchTerm: '',
|
||||
localSearchTerm: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getConnectionName: 'connections/getConnectionName',
|
||||
getHistoryByWorkspace: 'history/getHistoryByWorkspace'
|
||||
}),
|
||||
connectionName () {
|
||||
return this.getConnectionName(this.connection.uid);
|
||||
},
|
||||
history () {
|
||||
return this.getHistoryByWorkspace(this.connection.uid) || [];
|
||||
},
|
||||
filteredHistory () {
|
||||
return this.history.filter(q => q.sql.toLowerCase().search(this.searchTerm.toLowerCase()) >= 0);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
searchTerm () {
|
||||
clearTimeout(this.searchTermInterval);
|
||||
|
||||
this.searchTermInterval = setTimeout(() => {
|
||||
this.localSearchTerm = this.searchTerm;
|
||||
}, 200);
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey, { capture: true });
|
||||
},
|
||||
updated () {
|
||||
if (this.$refs.table)
|
||||
this.refreshScroller();
|
||||
|
||||
if (this.$refs.tableWrapper)
|
||||
this.scrollElement = this.$refs.tableWrapper;
|
||||
},
|
||||
mounted () {
|
||||
this.resizeResults();
|
||||
window.addEventListener('resize', this.resizeResults);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey, { capture: true });
|
||||
window.removeEventListener('resize', this.resizeResults);
|
||||
clearInterval(this.refreshInterval);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
deleteQueryFromHistory: 'history/deleteQueryFromHistory'
|
||||
}),
|
||||
copyQuery (sql) {
|
||||
navigator.clipboard.writeText(sql);
|
||||
},
|
||||
deleteQuery (query) {
|
||||
this.deleteQueryFromHistory({
|
||||
workspace: this.connection.uid,
|
||||
...query
|
||||
});
|
||||
},
|
||||
resizeResults () {
|
||||
if (this.$refs.resultTable) {
|
||||
const el = this.$refs.tableWrapper.parentElement;
|
||||
|
||||
if (el)
|
||||
this.resultsSize = el.offsetHeight - this.$refs.searchForm.offsetHeight;
|
||||
|
||||
this.$refs.resultTable.updateWindow();
|
||||
}
|
||||
},
|
||||
formatDate (date) {
|
||||
return moment(date).isValid() ? moment(date).format('HH:mm:ss - YYYY/MM/DD') : date;
|
||||
},
|
||||
refreshScroller () {
|
||||
this.resizeResults();
|
||||
},
|
||||
closeModal () {
|
||||
this.$emit('close');
|
||||
},
|
||||
highlightWord (string) {
|
||||
string = string.replaceAll('<', '<').replaceAll('>', '>');
|
||||
|
||||
if (this.searchTerm) {
|
||||
const regexp = new RegExp(`(${this.searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
||||
return string.replace(regexp, '<span class="text-primary text-bold">$1</span>');
|
||||
}
|
||||
else
|
||||
return string;
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vscroll {
|
||||
height: 1000px;
|
||||
overflow: auto;
|
||||
overflow-anchor: none;
|
||||
}
|
||||
|
||||
.tile {
|
||||
border-radius: $border-radius;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
.tile-content {
|
||||
.tile-bottom-content {
|
||||
.tile-history-buttons {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tile-icon {
|
||||
font-size: 1.2rem;
|
||||
margin-left: 0.3rem;
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
.tile-content {
|
||||
padding: 0.3rem;
|
||||
padding-left: 0.1rem;
|
||||
max-width: calc(100% - 30px);
|
||||
|
||||
code {
|
||||
max-width: 100%;
|
||||
display: inline-block;
|
||||
font-size: 100%;
|
||||
// color: $primary-color;
|
||||
opacity: 0.8;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|