mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 10:04:33 +02:00
Compare commits
1927 commits
v0.9.3-alp
...
main
Author | SHA1 | Date | |
---|---|---|---|
0e81bddedd | |||
e09b257936 | |||
fbd625cf5b | |||
eccd0e12a7 | |||
c2ff18b111 | |||
83d06259a7 | |||
bea6c086af | |||
b11073521b | |||
329ce9dc20 | |||
b0e46fb998 | |||
33f0d2a160 | |||
7811aae324 | |||
f5d0b90eda | |||
5f1597c27f | |||
bdd8888f1b | |||
e24a64ba25 | |||
ef4ea345fd | |||
ea27044d91 | |||
cb70ed8445 | |||
9fe59ffaa1 | |||
6fb092dd58 | |||
3cb329b379 | |||
8c224ab7dc | |||
577da699a8 | |||
b28ee0aa85 | |||
cc6b3eb958 | |||
ff6ef4b130 | |||
b0c0940e5d | |||
e57d3cf665 | |||
6ed283a5eb | |||
f308134276 | |||
bb8da6f5d8 | |||
aaa2084bfd | |||
9da3fb80f0 | |||
5ed11d771f | |||
adb0c82332 | |||
8d74566b6c | |||
363b90da07 | |||
34747356c9 | |||
03cc026965 | |||
6178454250 | |||
40f7337b6c | |||
0beb4aea23 | |||
6562a44175 | |||
d2b3f3a36e | |||
0f3611fb15 | |||
4d3a0832d5 | |||
24f41edb63 | |||
7714c7aeb6 | |||
6b4bdb4d7d | |||
cbc928f117 | |||
216799fe2b | |||
01b1977630 | |||
41d9642eb1 | |||
787cb2d862 | |||
fbf4e68d9a | |||
791e9ab9c6 | |||
98dada2af5 | |||
4f4c678eac | |||
f5de99a25b | |||
c8fa826e60 | |||
7bed6041f3 | |||
b58749a8a5 | |||
dd703b4bbd | |||
6b7146e02c | |||
2b32fb740c | |||
e4744a113f | |||
9eb3e3f889 | |||
6dc09c4d1b | |||
919b9c6eeb | |||
71c62a2f2c | |||
6d8f81ea89 | |||
de25c95a05 | |||
3a095a00ae | |||
69672482b3 | |||
c4ab8dfe4a | |||
09a87ab9d5 | |||
248bc596cc | |||
e5215a838e | |||
d48f457fd5 | |||
d5eea3c99c | |||
ec6b16b672 | |||
4de818f3a6 | |||
dc67b5632d | |||
6b7327df3d | |||
0bd6f6c9f9 | |||
20b1fe7e2d | |||
1599eb1515 | |||
61e861ee06 | |||
0e07697a98 | |||
11e51afe07 | |||
f63c597e80 | |||
454c4758d0 | |||
184caf57ab | |||
e65e3a984c | |||
e9ee0e600d | |||
dd964474ee | |||
6b693c6759 | |||
d2ee01479a | |||
9520bb4689 | |||
90790bd8e0 | |||
de82f649b1 | |||
d078394af4 | |||
a258f4b16f | |||
eeb72faf83 | |||
0240c6cd1d | |||
7e1d6f6100 | |||
01f3cefe55 | |||
659b1c0c56 | |||
1c6109a25a | |||
f6170604a6 | |||
ffb3db775f | |||
d90ac7cec0 | |||
34dcdd5d8a | |||
bab8aad126 | |||
d4275642d7 | |||
7ff95571c3 | |||
297be826dd | |||
e529bbb395 | |||
1b927ad183 | |||
8081a44e92 | |||
470503eb5c | |||
e221637394 | |||
14cbd46a88 | |||
2ee4776693 | |||
b2657bcbbd | |||
515f895ed3 | |||
6c9439ac70 | |||
2f21dc6abc | |||
2ea6a4fe13 | |||
84504f734f | |||
100c871cd9 | |||
e1b5872e26 | |||
744ed425c3 | |||
2b21aab26b | |||
c369037deb | |||
c8e6e11d84 | |||
05a274edeb | |||
bdaed0e873 | |||
6566412d64 | |||
25de300970 | |||
2f07441fc6 | |||
fd9fb81fa2 | |||
9706260933 | |||
4a34386c26 | |||
419f7732df | |||
59778165bf | |||
0c322bacf8 | |||
e7ce198fa1 | |||
dd4ca1903b | |||
956cede593 | |||
374f400f30 | |||
36890fe131 | |||
d3d09d4019 | |||
f307ed779f | |||
7ecccabcc0 | |||
33ad50fbe2 | |||
3574f76a79 | |||
0100104afb | |||
89f61c15fd | |||
7eb9fa1dc5 | |||
451ec404e9 | |||
e64d71b135 | |||
22f0226600 | |||
836f4b99d8 | |||
c64ea890d1 | |||
6c147106df | |||
6f64b61ea5 | |||
e284a58db9 | |||
8ccff438d0 | |||
cf06bbffea | |||
a6eec88337 | |||
f33222794b | |||
9db78d1031 | |||
0349cf1b7c | |||
fbcc49019f | |||
8b65b6240b | |||
98d8812571 | |||
c42ac793b9 | |||
2156a8fa8f | |||
ba5fbd3a71 | |||
5c232a2165 | |||
31a93b3183 | |||
fea294b26f | |||
f76dc5da14 | |||
f1971cdf4f | |||
1337a4a0c8 | |||
347a916a34 | |||
b1a1a40e97 | |||
4405293a90 | |||
d95bea09a8 | |||
b7563040ef | |||
30de19f729 | |||
2c24545e68 | |||
35276b01ef | |||
10be8db7d0 | |||
1cfae172bb | |||
4a3a0e4893 | |||
fde3de12d6 | |||
b002e7136e | |||
25bd486f8e | |||
9969b214d9 | |||
084c1143ee | |||
67a3ab31e7 | |||
91941eec7b | |||
6db6c486a3 | |||
d87579f9ac | |||
ee6ca66602 | |||
0213e538fa | |||
796d1c0566 | |||
47242fb33e | |||
70228054fd | |||
f8bff53eae | |||
2bfc8c345a | |||
7d33c0aad9 | |||
dbc959d06e | |||
3a84ec0bbb | |||
7a09f94767 | |||
25dedfde6c | |||
e90d1f7716 | |||
273914d714 | |||
44d24123b1 | |||
d7bd9c86a8 | |||
303ac0d6ef | |||
b52d55285c | |||
5ef97720b0 | |||
c329070606 | |||
7f86ab2ece | |||
5bee82a17c | |||
657a6a8967 | |||
404585ebe0 | |||
c26f9cb9ba | |||
7eaac1cc29 | |||
a07249213e | |||
b69e485f10 | |||
fc055671cb | |||
916c2a4a63 | |||
8d47d4890e | |||
b569cbc315 | |||
4f50b802ac | |||
0e1986b2da | |||
390817caa7 | |||
11a27bcbd7 | |||
877b517b72 | |||
9a8ebfef77 | |||
b017f39133 | |||
6dd78bf8fc | |||
fffc59b0a6 | |||
12d1010da9 | |||
3b505d24f2 | |||
fc9e848f1f | |||
f437b2a01b | |||
0738fe1454 | |||
321dda3fd9 | |||
37bc01e426 | |||
f11c61c8a6 | |||
acb2bb4e9b | |||
910690178e | |||
950eab71fe | |||
38c54b575a | |||
c15e489b1c | |||
923ab36668 | |||
21ab92f873 | |||
75c5227536 | |||
fa9d684853 | |||
a0525ef01b | |||
bb668a1ac9 | |||
8332690c67 | |||
139bdc7699 | |||
2ccabc28bc | |||
8525a7a321 | |||
045cb38a63 | |||
9734ce9a1c | |||
64e5e35b6f | |||
c3d558a07a | |||
a955d45518 | |||
7099d2e379 | |||
5093abc1dc | |||
704186c3c2 | |||
f239a1a3be | |||
fd63cc7fa7 | |||
74d79204ec | |||
ec34a690e9 | |||
17bce4b0db | |||
d38b3bc672 | |||
c89a809e6d | |||
d906d9ab5e | |||
a6c671e113 | |||
2f977e748b | |||
ba8a8bc7b9 | |||
3b52312150 | |||
f4b4f6b398 | |||
34101b8a20 | |||
3c9c115931 | |||
13e5f98b06 | |||
e58cc7a073 | |||
c650b3c94b | |||
6b0e06481c | |||
beb8317f94 | |||
4cf8097c83 | |||
96a300ada5 | |||
4b225ce6f4 | |||
3773ccb657 | |||
526911b54f | |||
dfdd639073 | |||
e658de3811 | |||
88c13c8129 | |||
ff624a4c9a | |||
c8ae7487f7 | |||
c33d23f326 | |||
8be087e4fc | |||
0e111e628b | |||
33465a1169 | |||
9e617b2da5 | |||
1371cadc02 | |||
8fefc11380 | |||
8ef8744ba1 | |||
0a9ce0d202 | |||
127f4db1a1 | |||
634cb35048 | |||
0502506b90 | |||
6670e0a6d1 | |||
1449869c0f | |||
cb8d17d006 | |||
d21100a14b | |||
6cce06eb2e | |||
6a58807227 | |||
01e4385c40 | |||
f526a9adb3 | |||
09a0bf0c0d | |||
1f38267626 | |||
8d62550775 | |||
8d7b961f4e | |||
463a1766f1 | |||
b5bb7acab6 | |||
e445d23968 | |||
77d95baf00 | |||
cf37622365 | |||
4f3b4b2762 | |||
243dc771fd | |||
a99bece107 | |||
d81305d019 | |||
34e4578633 | |||
4b1b351711 | |||
a25419275b | |||
8865897260 | |||
5ec2cd4dc6 | |||
127e5a4120 | |||
6c8e03c297 | |||
fb8e4b5170 | |||
eaffe29512 | |||
eae7edddb4 | |||
dbd0081cf6 | |||
b2f6464645 | |||
39b401f848 | |||
74f6482bb0 | |||
b7e0e20829 | |||
0f5736cdcc | |||
f003855e8a | |||
cb095ebae5 | |||
b389ca4e7f | |||
22f8ea1ce8 | |||
4ebcd1c7f6 | |||
f13f06c795 | |||
4a6e87efd7 | |||
ba6f07407c | |||
08c2b7809a | |||
d42b25d6eb | |||
c6eb7b71b2 | |||
d50b3b4db7 | |||
b619e8506a | |||
cf35520526 | |||
b7567ac33e | |||
7dc113948d | |||
81e9559dba | |||
cd4ccd5cf7 | |||
b70a209899 | |||
6a352e63b7 | |||
93eea0bd8b | |||
21ccd4dde1 | |||
b78d96bd66 | |||
87d7e73615 | |||
12b1f9e436 | |||
92039f63df | |||
79471dc151 | |||
a979b84339 | |||
1bba117db2 | |||
718ffd5ee7 | |||
d06cdd5649 | |||
9d44a47317 | |||
058eacd07a | |||
f4e11f190c | |||
d86d36a16e | |||
f8c3423018 | |||
03adceca23 | |||
e279f45229 | |||
8f5b4d67fe | |||
f5a3f96520 | |||
89faf52e57 | |||
4ccc509992 | |||
dde0a6e812 | |||
4aaf91d187 | |||
31363c9de2 | |||
1bbfa6f22b | |||
69a7705043 | |||
a66e875be2 | |||
b994a2ae4f | |||
041b477a4a | |||
20dd7cda2c | |||
9c33b28fdb | |||
70b73b0f7d | |||
eea52c55b6 | |||
b4bb4ef241 | |||
eec3f42ce0 | |||
ce87464d23 | |||
5f61991ab8 | |||
c067967d21 | |||
0fb6f40ee6 | |||
39d28291bd | |||
fdcaf7815a | |||
2e9344bba7 | |||
30bb0ba828 | |||
6104799d78 | |||
b1d25bdc44 | |||
5d4544f436 | |||
2685c7d5d5 | |||
beec229360 | |||
bc0e0fceb5 | |||
2a98731b5d | |||
eb3a74e146 | |||
01714c7691 | |||
5a9e7efd7b | |||
a847648b51 | |||
6928ab14cc | |||
7a9ee28bf7 | |||
67001ac842 | |||
5128a8c9f4 | |||
a37498d84e | |||
aa3034411d | |||
1f28074566 | |||
b62d3e3dc9 | |||
ac77e1edbd | |||
53fcc6cc56 | |||
90b0ff06e1 | |||
f4de64d3be | |||
53a34eaaa2 | |||
9d2332477f | |||
965dd5e29d | |||
83ecc2bca1 | |||
5402f4d9b1 | |||
e7cbb7b514 | |||
f83751cc80 | |||
857916285d | |||
26478ef0a4 | |||
47363b2521 | |||
a31fce3301 | |||
8d81e89d09 | |||
cd1a37ddbe | |||
552a5a9ac8 | |||
98f48295d0 | |||
f0a8b92d1b | |||
fadb14ad2f | |||
2e2444eb47 | |||
23d260a351 | |||
334bfa5281 | |||
9d9f54a006 | |||
49dc83553e | |||
c5b0378a80 | |||
236fb17376 | |||
10ab050346 | |||
a4aac80fac | |||
2fa50ccc8a | |||
9d86bb69bf | |||
789ae8ea79 | |||
ef79fc620a | |||
cc7dd28447 | |||
9020f78af9 | |||
a306914e8b | |||
03d1fb5dcb | |||
85dccb2962 | |||
c2387e0bf2 | |||
c64e68bc38 | |||
04c9dd31fa | |||
0de2b4ca44 | |||
2f0f7da8f4 | |||
c5aa759af8 | |||
6b61dbc3c4 | |||
e6f08d25cc | |||
fe4312007c | |||
4562f173d3 | |||
6bc3564a5e | |||
ef9ec3903a | |||
60453f5348 | |||
4778ff9a81 | |||
5117104049 | |||
abd06ac114 | |||
66e23be799 | |||
6f39c720dc | |||
7a95885009 | |||
0a84f35fdf | |||
b6aea495cb | |||
2d8416e34e | |||
05ca724374 | |||
8ae107393e | |||
ebca695503 | |||
e40007c755 | |||
9b407d5e96 | |||
318a3498bb | |||
1bf1b5a1c3 | |||
cefa80b647 | |||
9ce6f5726e | |||
64e115d4f3 | |||
49c34773d6 | |||
76ab882d0f | |||
518a739f21 | |||
467f015cf1 | |||
022d7ef258 | |||
b941eda41a | |||
33e2d63de1 | |||
eb3fb5e222 | |||
0c7fc3ebf2 | |||
d5567220f7 | |||
da9301c211 | |||
8b0ce23beb | |||
c2f9edae30 | |||
3a82e51a4f | |||
e47b644bf9 | |||
8778c17d88 | |||
de42481e24 | |||
cce39a161f | |||
3c353f51e3 | |||
fe01b064c3 | |||
7d79a3b74b | |||
8fb5f51ae0 | |||
f67f62f3ad | |||
64b5c2aa1a | |||
1c4402094a | |||
1a0e1fde1e | |||
95cf9c2056 | |||
6773fee6eb | |||
78f5c9d460 | |||
3713018f25 | |||
3eb09c5ac0 | |||
0ca8ee854a | |||
e75992869d | |||
e7431249e1 | |||
ef449d3ec3 | |||
d071075c55 | |||
640959ffa5 | |||
bcddc20f33 | |||
8736613277 | |||
f9c92fcbe9 | |||
c5214bc083 | |||
7b126afd35 | |||
f4326e196c | |||
667b0f7f15 | |||
6f39d434a7 | |||
ff34ef2d0d | |||
db07de5b2d | |||
fc38f79674 | |||
5ae2ec9d28 | |||
a1164eea60 | |||
87002745a5 | |||
f85e032e2f | |||
4a1b2849fa | |||
e63775f433 | |||
baae3a5bb4 | |||
543cbbdff8 | |||
9ad9e9b8c8 | |||
e23c2e5cdd | |||
cbfc141c89 | |||
a4dd5a25cf | |||
a1c92a0a03 | |||
eff0383202 | |||
80b21e6bef | |||
7159b0e86b | |||
22382a182b | |||
ad7bfb2e41 | |||
db64b34e09 | |||
0be22c33dd | |||
b5b1db99a1 | |||
da881d8bd2 | |||
8e78111b67 | |||
7cc5841d83 | |||
3e54a57e24 | |||
368afc0501 | |||
1a927f66c7 | |||
8fa3fbea56 | |||
20b9988bb9 | |||
1637b7bd59 | |||
15d6ba4fca | |||
a246a11d53 | |||
bd406b0f67 | |||
15b186f404 | |||
7c22d2f44b | |||
e3f158079c | |||
20c9c49b9c | |||
a26d152d92 | |||
1cfd8ab911 | |||
9f41476a85 | |||
a3821a14ad | |||
a94bad4a67 | |||
67211949ca | |||
dfd76eb1cd | |||
1694472d60 | |||
19e0848211 | |||
7285145071 | |||
c081261cfb | |||
750d6676d3 | |||
072450ecc4 | |||
366fad678d | |||
678c4ae09e | |||
c5f7238f5e | |||
ad40c595b6 | |||
366a871f86 | |||
2481bd4deb | |||
6877c61482 | |||
9be1685746 | |||
accde1fda3 | |||
5bc53c39f0 | |||
387a72f6f3 | |||
f4b5be5b49 | |||
dba2e5a3b2 | |||
d20e9e7494 | |||
57d69f88a1 | |||
3092fc5202 | |||
bdce742dcf | |||
d11e1fe0e1 | |||
cc92fcc214 | |||
b579cdb8c3 | |||
210ec62ba3 | |||
d673d20a01 | |||
06f8bc64ac | |||
ee1cff9588 | |||
bc286b36a2 | |||
3d9a9c90b2 | |||
7ce160da69 | |||
f56790dbc3 | |||
2ccf41f674 | |||
013fa6ab2e | |||
7ee4f6b61f | |||
05e44d7983 | |||
a2c53b5496 | |||
7f81de7ea2 | |||
966821cef8 | |||
a1b2258bd2 | |||
5a42033a89 | |||
e2eed4d428 | |||
be592e49ae | |||
01123e9998 | |||
60c1b14116 | |||
54ddb69083 | |||
02e88f70fe | |||
ef8b309fa6 | |||
e22736ae02 | |||
ff777b212c | |||
79130edbe9 | |||
27fc465a3a | |||
dc832b8edd | |||
ca789cfcdc | |||
996e2b4200 | |||
6495336400 | |||
9dacf64601 | |||
85b81bb0dd | |||
8967d2ac91 | |||
ebf7e821fe | |||
c5aaeb61e1 | |||
f4f4f33d91 | |||
86d818fccd | |||
4aeba647f2 | |||
7eb8242964 | |||
46f125ea17 | |||
98765ab2a4 | |||
471255ede8 | |||
566f74b88a | |||
b7633ff7d9 | |||
908f111128 | |||
e5c5e87449 | |||
64fc26ae8e | |||
3e85268f39 | |||
88bf8dbc7a | |||
f33b7f7bae | |||
0801d0ac4d | |||
ec13228efb | |||
0272fa3ee3 | |||
2680d5d365 | |||
bb1965481d | |||
a016d07466 | |||
e40ff05190 | |||
424f4738c5 | |||
cec7403491 | |||
884927cacd | |||
f1f9620c7a | |||
7549e34d45 | |||
ca46949653 | |||
90ba20411a | |||
8a164a6b89 | |||
06ec029ee7 | |||
ee0e3e4ad3 | |||
5ac7e22b0b | |||
9b0cbf7eea | |||
13064cea1a | |||
fae47151cb | |||
fbc319a09a | |||
ca7e7f6558 | |||
d91b7763e8 | |||
43fdd83178 | |||
98f1f3c5ca | |||
7cd1be6588 | |||
1d5a0970c6 | |||
cc60391d26 | |||
2db37adf21 | |||
137ac189fc | |||
23c60e80e8 | |||
a2ee95e53a | |||
0b43cc8e48 | |||
e91cf49f21 | |||
bfc0c88154 | |||
d30f071c99 | |||
67483c6dc4 | |||
53955a8107 | |||
2faf398e4b | |||
c81698a698 | |||
60a899c2ba | |||
38ce7a9843 | |||
fa658ae52e | |||
c9436ddf19 | |||
4e404c0de6 | |||
7b712b9369 | |||
5b39540ab5 | |||
ac5e87bdcf | |||
085fd0c668 | |||
9392d8f2e1 | |||
bc4f7e7ef1 | |||
3c3c6a9ae9 | |||
6ab29ad687 | |||
a9cdd75f95 | |||
55fed0a954 | |||
d5e7f92333 | |||
0fffe86f05 | |||
674dd2e383 | |||
6dd1c396fc | |||
d9f9c724ea | |||
00888c4b59 | |||
b73756541b | |||
25aa026f08 | |||
6830d9dcb2 | |||
1f38a59bb8 | |||
1027ea1b98 | |||
5d8554df08 | |||
2143a2a5b9 | |||
ecfc3ef643 | |||
534291671e | |||
026c929fc0 | |||
073966122f | |||
0a59c4f616 | |||
a3b48d2bd9 | |||
726385361e | |||
6a596f1087 | |||
169990ed58 | |||
e3d9e7e24c | |||
1db19df71b | |||
fda687308b | |||
8d704ff7f7 | |||
d919696d24 | |||
5558173181 | |||
377048b90a | |||
6f2c5ac5f3 | |||
91a0f90a1e | |||
9474220e45 | |||
64f09b1b41 | |||
f87d19ca4a | |||
d60e555193 | |||
28bf27b2f0 | |||
b6f5528b29 | |||
88606131a7 | |||
8815fff6a7 | |||
0da364828b | |||
8bc8cdffcc | |||
5786f1f1dd | |||
ff91eb888c | |||
a849c83e1e | |||
b07e96302a | |||
8a731e7604 | |||
8d70378fc9 | |||
7b09894a4b | |||
6f28f4ef32 | |||
5789c7a65f | |||
9b6647c29e | |||
02deab90b7 | |||
6042263523 | |||
ec99882497 | |||
4dfb36f572 | |||
de4eb9355b | |||
b2a83568e1 | |||
6b7df9d0a6 | |||
8b41139b48 | |||
e26d512951 | |||
19eb6ee6e9 | |||
436420f854 | |||
85689a4d37 | |||
32317b373f | |||
2f2f9d956a | |||
31c833135e | |||
0457297200 | |||
2de4d32cbb | |||
0d3ec7aa4b | |||
a7b02a400f | |||
00e46c02ef | |||
6f7166201f | |||
6d80d15859 | |||
681bc5703f | |||
7394e8b31b | |||
b3519eef56 | |||
ad6f913aab | |||
ada8072ee9 | |||
30996c03b6 | |||
4036eb8e9b | |||
1640f6c66e | |||
2f197225a9 | |||
d600d999d2 | |||
8b7f7c47aa | |||
4d59311f66 | |||
a970f96165 | |||
b5268ec4f0 | |||
22e98a816f | |||
c4dbe0caef | |||
16f37677d5 | |||
b03a882cfa | |||
b42c40dd78 | |||
a3d05f3521 | |||
fd33ccbe1e | |||
0014462c63 | |||
3bb9dd3991 | |||
62ea8fa9b2 | |||
02c9710b92 | |||
cefd1b3efc | |||
4b87d95670 | |||
864589b647 | |||
e357c6a1dc | |||
463c8c3829 | |||
55d8b554aa | |||
9bbdaa055a | |||
672ec7a4a1 | |||
c98b37db96 | |||
bef2e80acc | |||
ba846013e8 | |||
ed469ee035 | |||
48260f2c09 | |||
4eec0b969a | |||
3060940808 | |||
d17878b123 | |||
227bc40a00 | |||
ec06389507 | |||
4c3b3d79af | |||
94f23d47b7 | |||
e839453806 | |||
a0759139ec | |||
8f1894da50 | |||
18534a87aa | |||
d35860cc54 | |||
2189f99918 | |||
0368c57d87 | |||
ac790c118c | |||
214d9b1efc | |||
69c5050060 | |||
a30d42f11b | |||
2e02186ea6 | |||
90a5dba302 | |||
7823321559 | |||
cefaea2369 | |||
00585db9b8 | |||
aefca6e175 | |||
ee41286ff7 | |||
6e16a1bc07 | |||
ae1dc1ccb8 | |||
e1d04fe3ee | |||
7ed5e617f1 | |||
0ca35be688 | |||
2abe3da834 | |||
4ea01f39b4 | |||
edfe2f3064 | |||
5b89ab5e53 | |||
6980aa4e2f | |||
17504e194c | |||
ac9c46680d | |||
4f98929446 | |||
63cdd7759e | |||
474d1d0bac | |||
bb5ee9a374 | |||
00991eb93c | |||
41e13770b8 | |||
d90fd7c243 | |||
46eef7a3b5 | |||
e3bca5bdc7 | |||
73f6bba630 | |||
f28e47f441 | |||
d8df718645 | |||
9b820f6562 | |||
30710fbb7e | |||
bdc21d1c53 | |||
305aaf1626 | |||
929f470988 | |||
b4170f5329 | |||
c80917187e | |||
2b722c184a | |||
cfb26f88e4 | |||
740c319a36 | |||
031749218c | |||
6380fb7110 | |||
acf47f8f96 | |||
9612106717 | |||
bd4e9bc233 | |||
ed7dc955c8 | |||
ae6d69ac2e | |||
0527cc65f5 | |||
b4bbab67fc | |||
f60725ce98 | |||
49e25d6cc3 | |||
2206ed2e44 | |||
a8a6a183c6 | |||
5842519e2a | |||
71ad71d0bf | |||
14a6dab29e | |||
1b47882a1d | |||
a290c854d5 | |||
bb0fc79b4f | |||
c689b93ee1 | |||
9074e8e5eb | |||
0b35d393f5 | |||
a55e9ba9f8 | |||
7a9fa80a1f | |||
26d815067a | |||
c2d09902d7 | |||
d752314a67 | |||
462e4162c4 | |||
bf89828c75 | |||
1fab9f8e53 | |||
000702740f | |||
ad11332d16 | |||
54cf0bef3f | |||
83fc9c50fe | |||
ed1070be79 | |||
e7586ac967 | |||
8c69b74fd8 | |||
5e90c2e823 | |||
4c08d5d04e | |||
22912c9c1e | |||
a2edf4967a | |||
5a58ef97bb | |||
6877e950ad | |||
1917ec077d | |||
0aa3b7c249 | |||
3fd2c402af | |||
645b159d73 | |||
ce12679f2d | |||
e6782b6aac | |||
db579504f0 | |||
c06bb4abfa | |||
e569e352ad | |||
5c6f486efd | |||
fbc4e807cf | |||
1881079f4a | |||
7a5a2c1f63 | |||
c1852a4d5f | |||
aafa3cd5ba | |||
90730a01df | |||
04a9f72de1 | |||
a6630234d8 | |||
1e787d8c82 | |||
efb1a8e61b | |||
b29c539d5f | |||
e7f8dc73e0 | |||
dc17d4c034 | |||
f01cc38e64 | |||
7a88875309 | |||
6829a9df85 | |||
97bc309bb6 | |||
75dfa4d28a | |||
72658837c9 | |||
0290c1a28c | |||
2f4bb6378b | |||
fe70516788 | |||
038e6acc4b | |||
8eb81b8a36 | |||
53355ef4a0 | |||
539e838b2a | |||
d91d9cc90d | |||
0599ba1593 | |||
01801766b6 | |||
d126117bd3 | |||
ea409bd888 | |||
7619c5de8c | |||
2578b2eaf0 | |||
b960dea9c2 | |||
b7b51ad469 | |||
ab43314619 | |||
02062e3e18 | |||
ca1257bab7 | |||
4d0bef497b | |||
16786d4a7e | |||
32e1aa7edb | |||
63273b7f02 | |||
f4b00ce68a | |||
b901b8efaa | |||
bf4855aa63 | |||
06e79e627e | |||
6020b69527 | |||
2dbf6883a0 | |||
b5b5170a67 | |||
c27c6db199 | |||
d5a1a74f1a | |||
d24a67d813 | |||
f044b64ace | |||
c78d30f9ef | |||
bf31ca5891 | |||
99e6c86220 | |||
27e05d2d8e | |||
618bf3ae4b | |||
3e3d96a4bb | |||
85a13bd77e | |||
7d7dbf1ba0 | |||
f5415ce7cd | |||
e9c07731b7 | |||
705b8fa292 | |||
e57856d328 | |||
ead5169dfa | |||
c22122cdb4 | |||
00afc9d611 | |||
5202a93676 | |||
e398eaa413 | |||
018add39d2 | |||
8f2f464846 | |||
ff0e4c25a2 | |||
7c5cdc1290 | |||
47e5cf6958 | |||
5c582173f5 | |||
c97f3c158d | |||
eb0cf4a4a1 | |||
02efa9bc21 | |||
2e8fa11b37 | |||
d2eb4ffd09 | |||
12e5d2ff7a | |||
16af549274 | |||
1db9d1fca6 | |||
0f10d73a61 | |||
851cef2b9b | |||
c164faae21 | |||
766342a866 | |||
e3e486b5fc | |||
e0bd7c5e09 | |||
9872b52741 | |||
8883616e67 | |||
b5ee39b2d2 | |||
6dc0a739c4 | |||
a5c9014a15 | |||
750d01bc92 | |||
596fe53bb8 | |||
8670646ca7 | |||
dea132203b | |||
cf50e69e3a | |||
f96c566223 | |||
bfaa284837 | |||
e8a2a5d12a | |||
2d6af962b5 | |||
006dec976a | |||
395e505b84 | |||
dbd1cf1d98 | |||
5bb25c3d45 | |||
5c34ef40da | |||
1cf6d5914a | |||
2422f1dab5 | |||
01ae2748c8 | |||
22e2f2f08a | |||
28e7694545 | |||
a5ebf2d0fc | |||
8dfae3b35a | |||
06dd1637cd | |||
8c2c6a8ce0 | |||
eda5580f83 | |||
dd3695d07a | |||
6b79f826eb | |||
bed858117b | |||
019f5f7226 | |||
9d96e1ee37 | |||
53c9f2a668 | |||
e2c615b4ed | |||
e05ec46b66 | |||
d6c182964b | |||
73704ea206 | |||
0f08b6525f | |||
8847ffdd40 | |||
083a98263b | |||
5e6d343eda | |||
670a1703de | |||
5426cb3a1b | |||
81f6ce4636 | |||
b1d8810bcf | |||
d94399e07c | |||
0be61e75c4 | |||
d8ce1d456f | |||
dab78fe190 | |||
442a041952 | |||
225af9e8b5 | |||
129b7f6029 | |||
6670f36ad8 | |||
e1bb03f4d9 | |||
a804ff94af | |||
96efa8fb25 | |||
4210c8c035 | |||
a73904549e | |||
8792d3b0f2 | |||
36e71f44f7 | |||
ade05024cc | |||
50b92d813e | |||
7c73e131a5 | |||
2f1132cec5 | |||
fec578b1d7 | |||
4a650e66c1 | |||
477c93570c | |||
1104d6b2f1 | |||
ab7f03a9e6 | |||
3b33ce9a62 | |||
45c221b9fa | |||
daa8604c7b | |||
05a7bcfa42 | |||
2582972c34 | |||
a2130fab5e | |||
860c70649e | |||
a6d2a34b58 | |||
befdb15f41 | |||
c7d94b3b63 | |||
4f7972b546 | |||
03be0db844 | |||
60b68612ab | |||
c6871dbf52 | |||
312df72f8b | |||
851f1f6072 | |||
e861dde9c8 | |||
381c0d7813 | |||
51360b8458 | |||
b837c0f854 | |||
dc843276c2 | |||
07e6c33e89 | |||
108851fb78 | |||
5121cb5a14 | |||
145ac1533a | |||
0489db4292 | |||
2e4b06732e | |||
d84dc12fb0 | |||
f2befd29f2 | |||
5cc05d26e7 | |||
cbe05caea2 | |||
f912841f5a | |||
c684cd58e0 | |||
6682bf360a | |||
27209ff843 | |||
d7755b147a | |||
4e14eeecb7 | |||
ee709b32ef | |||
4318902f5a | |||
0e53b34ed6 | |||
c19586369e | |||
cf522bd41e | |||
2416a13812 | |||
fa0557da46 | |||
6e78b4d37c | |||
0916ef4ce7 | |||
924871859f | |||
be10ffd564 | |||
230aaf041d | |||
839787134c | |||
7a2d1be134 | |||
af80565acd | |||
1ed281e7c4 | |||
e8a126f299 | |||
5e7ceebe32 | |||
1ca3703420 | |||
c32b435cee | |||
6c8720d636 | |||
03738bcb8f | |||
d74dda0bc5 | |||
131ec6fa63 | |||
9d8c736917 | |||
d92c4a4a90 | |||
781a086c8f | |||
4af574cd43 | |||
d257f9fe90 | |||
c1f516babb | |||
237318b268 | |||
937c7b1f4b | |||
87f531a575 | |||
a5259bd463 | |||
ac34904ea9 | |||
c47ef5fa47 | |||
fa73706af5 | |||
5cb1e3f661 | |||
b74218abab | |||
ee0d559d94 | |||
cf0e9de0ea | |||
349ded60d1 | |||
1c7feccc3a | |||
52e646683c | |||
e04b5e4a89 | |||
da38532635 | |||
1dc06d0aa4 | |||
30a25b76fb | |||
5f8192d5cf | |||
1852458cf5 | |||
dba125fc87 | |||
7092a5d447 | |||
5e2f2b6958 | |||
9c0cc0a427 | |||
9627f269fb | |||
5301cd3b05 | |||
3aaf28cb3b | |||
a272b6fc2c | |||
2579d167e7 | |||
9c745167ac | |||
6487776bcd | |||
d5c05d90e4 | |||
1f7f6c4714 | |||
05a31be2e5 | |||
a3aa3f8bb2 | |||
9fdeeff921 | |||
f34f7d317e | |||
1a27458d14 | |||
8f2637c39f | |||
26be304699 | |||
e0c1ec1121 | |||
f1e84c2728 | |||
9130d97cae | |||
3cb9724d93 | |||
5962450be7 | |||
3c8e77a237 | |||
3349d09654 | |||
b76726c4a5 | |||
11ae37f3b9 | |||
aef44b1536 | |||
5a10a07e21 | |||
abb4d0000d | |||
87f2cbff2a | |||
78b545dff0 | |||
498a5c2e3c | |||
476597e792 | |||
4fe9f82b40 | |||
05f631e4b4 | |||
8b777a3ad2 | |||
ced26da1a1 | |||
a009f6dd3c | |||
bd2e173620 | |||
92c4831761 | |||
e95c9f1ff7 | |||
95cecd2834 | |||
9f7bdfbc2b | |||
42b206536d | |||
e82592bab7 | |||
722f1ef785 | |||
a7ef928d0f | |||
b73aedd079 | |||
7ba7a435ef | |||
4cf915c399 | |||
fbf6853c96 | |||
55a17b793b | |||
e7c976f5f4 | |||
e5779c84ae | |||
9e802db6d3 | |||
49430f65d2 | |||
8b49fc4f8e | |||
d8b7889401 | |||
3b9db2980c | |||
304b35da1f | |||
3ae0fc5b42 | |||
3616e131f7 | |||
7ee6707047 | |||
165fb9588b | |||
1dc766e70e | |||
c6104a9dbc | |||
24ad55081c | |||
119eb4ed88 | |||
a6aabd1bc4 | |||
bb73d897f9 | |||
7705723fe0 | |||
ec6a83ebd1 | |||
2eb47cf57f | |||
c170a938d1 | |||
368fe13201 | |||
282dc84364 | |||
c3d5494f4c | |||
2f7c3bc73c | |||
bf866a73e6 | |||
08f35903af | |||
0a8178508d | |||
a4f4b7b052 | |||
697cb7610a | |||
67a2bc8f98 | |||
0e21c33376 | |||
8bbfa57304 | |||
c38175f83f | |||
045fcb1e4b | |||
61636fe473 | |||
760585f9a6 | |||
3b5131d63e | |||
24cfc8f691 | |||
13ad59ae6e | |||
25a1e3b495 | |||
2a954830ce | |||
cc182059bd | |||
0102220271 | |||
8d99b0bfa2 | |||
28fcd69846 | |||
dcfff32fcc | |||
9a39d2246e | |||
d88d89373f | |||
22e92af2ac | |||
1c90711411 | |||
96e5e875b8 | |||
d018627e9e | |||
85044465aa | |||
3cbd8532b1 | |||
7e3e315f06 | |||
331031c0b9 | |||
7eb65dd91e | |||
39b28884bf | |||
8015ce88c6 | |||
d7fa0a6039 | |||
026f78eb82 | |||
b6da01969f | |||
a1a0486507 | |||
3afdcbe6c7 | |||
7b0d909c9a | |||
9a19c56af0 | |||
99043037d9 | |||
fd6e364e40 | |||
c9884b76ce | |||
6cc5a0f6c5 | |||
4e22335181 | |||
b6e8cb2df5 | |||
d0a2eca8be | |||
5f76a0d283 | |||
146abb3994 | |||
e95f9a1d6a | |||
0c23ca0561 | |||
e5766e1324 | |||
ae03bb6012 | |||
59188d71eb | |||
bc292a66e6 | |||
3582beb6a0 | |||
97dd0e96fb | |||
362e2e6829 | |||
0f6ee30389 | |||
5d7508dbed | |||
b0d2c195ae | |||
6268a4f5eb | |||
5632310b8c | |||
34ed96aec8 | |||
dfa3ad74fa | |||
28590809d3 | |||
cac628817d | |||
3f49b63930 | |||
e445fbb3a3 | |||
5d111cac5b | |||
d9ea2e941f | |||
4a47c3f58c | |||
b2a068dbfe | |||
a7b32369b6 | |||
49c56bd8b5 | |||
75581144bb | |||
648bfabbdf | |||
9cafc7b786 | |||
9e1720eb4f | |||
f59ccd5ae5 | |||
dff5b8e3cd | |||
be6035e159 | |||
1bb18e1e98 | |||
01c42e8d77 | |||
68e3d79260 | |||
24cab2285e | |||
270e0d06d9 | |||
6f28598e6f | |||
cd7b0f6045 | |||
4d3873a08f | |||
5a942492ef | |||
0ffdded569 | |||
f575488d82 | |||
6a97a354dd | |||
c68c952f13 | |||
44a49a84a3 | |||
66137f1f68 | |||
33094fa7a4 | |||
3e19428beb | |||
a48c7f08f1 | |||
b81bcb2c9f | |||
e30d71a85e | |||
36d6f061b2 | |||
5d09de0b62 | |||
70142708e2 | |||
0b50d7aa12 | |||
eada33df2f | |||
c32051054a | |||
e041772bb3 | |||
efae8fe06e | |||
4851d3113e | |||
3cf6374a96 | |||
dc2d7f454e | |||
36b53fe963 | |||
e2b8d3d846 | |||
48bddd4557 | |||
0863ce7829 | |||
5e1bd05146 | |||
d0591d2b86 | |||
1c82677063 | |||
df3f6f37cf | |||
d1754c01c0 | |||
c299c3e82b | |||
fc76844362 | |||
f9c281eb54 | |||
5b8a6201f5 | |||
12ef947272 | |||
e551091c74 | |||
bdc44aec9b | |||
31e2fbb421 | |||
6f04901aa2 | |||
55b03e8161 | |||
f78802fe7f | |||
38a5495b18 | |||
d4cf192266 | |||
d2c5c9b166 | |||
b457bb0d64 | |||
8c46578d52 | |||
c98999190e | |||
719e8f760d | |||
18faafc968 | |||
0f0efc1d40 | |||
96506de85a | |||
25193756e1 | |||
528f8431f7 | |||
7a8d1e9dd1 | |||
287826831b | |||
147b11e6fe | |||
706511ae7b | |||
9aa0b7c7a0 | |||
005519c6c1 | |||
5c33091257 | |||
cd8df0c57a | |||
204c34ba0c | |||
809c2ffa77 | |||
a6a685f6c1 | |||
f46366de2a | |||
00bdcff28d | |||
a9468a9fd5 | |||
86a38e94ef | |||
72702294c7 | |||
84613e6eb2 | |||
0a8cc3ade7 | |||
c417886b8b | |||
342213d2f2 | |||
3bcb21326c | |||
03e5fe5001 | |||
f7da708519 | |||
e41d6d6ea2 | |||
ea4d0f21f9 | |||
bd35c2c8bc | |||
cd0b2132fd | |||
8de7303543 | |||
ba95ef2d64 | |||
4d40b59303 | |||
5b093256fb | |||
31cbdbeaf0 | |||
e2da509898 | |||
ee3dc8f853 | |||
b7fed0c095 | |||
38caea01f5 | |||
9cb5134737 | |||
8f1d7d2918 | |||
a473199954 | |||
5fbb256969 | |||
ef3fa71982 | |||
053215a6bb | |||
4dc5a482e0 | |||
1efd6d5439 | |||
ef98046313 | |||
920e46754b | |||
6be61b8706 | |||
5d6f677a5e | |||
f153b1dbf1 | |||
56de66e68e | |||
c6e3d9169e | |||
37702af344 | |||
4f2671c359 | |||
f869bc9db2 | |||
890bceeb0b | |||
377b3bc211 | |||
351283b776 | |||
01d242a53c | |||
260a6850de | |||
dd4e158586 | |||
ec83edeb1f | |||
7f411d6365 | |||
462db658b6 | |||
3baaf127b0 | |||
27f7cef2b3 | |||
a22f6fb4a4 | |||
33b7b395e5 | |||
85fa22fd78 | |||
95f50e84aa | |||
f6c37ff6f5 | |||
f1c2fad8c4 | |||
af5b1a2d8e | |||
739f8c4e13 | |||
fee05653c1 | |||
783c47f9ef | |||
bdf7f546a8 | |||
fabec55605 | |||
bc62355f10 | |||
ec67ec6f72 | |||
bb9d29d3b4 | |||
d373660502 | |||
685575be77 | |||
381da4c98a | |||
fe575eb31b | |||
cf36f72660 | |||
67849c8069 | |||
1463224318 | |||
a06bc031d4 | |||
c07d7c106f | |||
290a026932 | |||
36a50fa02b | |||
09786e8b28 | |||
45e6430bff | |||
009087f27a | |||
dfb5dd2903 | |||
ff82b0e0f4 | |||
2684d3be39 | |||
777e9d0182 | |||
3620fe93aa | |||
115a59d275 | |||
90401e7df5 | |||
c39af3f830 | |||
8e46485d8a | |||
9e1b4b6c25 | |||
c43524d2d8 | |||
0d659afdf2 | |||
4f780f3c2b | |||
e90f819cb2 | |||
22becd5485 | |||
ca726288af | |||
47af62d006 | |||
8bf17da934 | |||
cfb4719fec | |||
872c37d681 | |||
929211c7e2 | |||
72255f7959 | |||
99414af31d | |||
c07e0e05db | |||
fc35d5265a | |||
080b63191e | |||
e4642fc49d | |||
df3be3134d | |||
df95f4b82c | |||
3df0265ffc | |||
fd172c1962 | |||
487d89f9f3 | |||
1adb4f1890 | |||
bb512c123e | |||
badb521b8b | |||
a7eada0649 | |||
6c07eb596f | |||
5a867d7508 | |||
c16bc45c53 | |||
b0ed3d2474 | |||
5d0d008cdc | |||
b11e820ebc | |||
0cb16805bd | |||
7856b4df10 | |||
678c69732c | |||
aa8abf021e | |||
c55595484b | |||
ba695aae7d | |||
f6c7226dac | |||
eae42e9b5a | |||
1b878089d4 | |||
34a4b30f10 | |||
1a6c41a479 | |||
053eec4340 | |||
f87c6e61b4 | |||
854b2a464a | |||
f6b97e3f31 | |||
5f48dff549 | |||
7304246f69 | |||
77fc7bdd2f | |||
704f136aff | |||
29ddfa2f84 | |||
ec414b91c0 | |||
447bc4dd3b | |||
d8f70c2c6e | |||
b93a6a57f2 | |||
9184565d3f | |||
59fb66dc0b | |||
8cba7b6486 | |||
6aa58d5837 | |||
2ef289221c | |||
0b087daed0 | |||
6d59b79876 | |||
c453b599c0 | |||
e1df2ab141 | |||
8e79ba4c33 | |||
bdc9ddf50b | |||
cae51b2ad4 | |||
219f53208d | |||
7a381bf46d | |||
4761140ba9 | |||
4e667d37b7 | |||
cdcaa044cd | |||
a0886e1937 | |||
d96c6a818e | |||
fd12e37622 | |||
c62ded9a79 | |||
d0d51a6823 | |||
7ff2e55d11 | |||
4cd857902c | |||
a321f75f80 | |||
73910afd2d | |||
c36933d622 | |||
53dd156a3d | |||
17d012db9d | |||
69dba4487f | |||
30d4d70b75 | |||
b35f8811ae | |||
9fc3eca63b | |||
1f8d0800ba | |||
6ef8fbefb4 | |||
3a4de02569 | |||
df59c814f2 | |||
eb7122dc0f | |||
a09cfd89e9 | |||
c55585919c | |||
b248bd6e88 | |||
dc40c6d5a2 | |||
21c4613c93 | |||
67d7588688 | |||
80c61eab04 | |||
4f080d2640 | |||
a75c6d72f0 | |||
37c176ff41 | |||
b49c223dd2 | |||
2cd8eb7584 | |||
14e4ba136a | |||
96a868aad2 | |||
bc60f61eaa | |||
4b23e40aae | |||
e3a4989223 | |||
8470f412df | |||
05fe23564f | |||
5909f6912c | |||
43162ff476 | |||
593c15f48f | |||
43cd15ecc6 | |||
87388268e3 | |||
0deb074df7 | |||
0de71f4e6c | |||
5d75e62998 | |||
35689284d5 | |||
5c551812e6 | |||
627a090910 | |||
3064075efa | |||
4349c5f013 | |||
9e836c765b | |||
edde2fbc91 | |||
89abba8150 | |||
b3e1315db1 | |||
14798a4be8 | |||
8fa79c2bf8 | |||
6924c31b04 | |||
06b7a79b6f | |||
80a06026ee | |||
ab7a9598ac | |||
be702cc92a | |||
e26963770e | |||
3d2a13f1aa | |||
90a28484c0 | |||
d8e392ddba | |||
3d5cd58e9b | |||
e68a2aab2a | |||
4e796909c7 | |||
a3fb40bc82 | |||
8f41752c1d | |||
a8e6cb889b | |||
8b2c407725 | |||
0f2b48419d | |||
f1b3e2937d | |||
7bc7e155e9 | |||
b4eafeae3c | |||
85fccc9ca7 | |||
c7b07411d7 | |||
d579dcf60a | |||
278d0750ff | |||
0a808ea69a | |||
5de47ce6b8 | |||
0ce6a7b2d4 | |||
48b0acbc60 | |||
56d2cd7ca4 | |||
8493ff18ea | |||
38cbd8d503 | |||
af513864a2 | |||
f5a9528534 | |||
2b7cb3c428 | |||
d41a9acbad | |||
591d29cc20 | |||
5eee5633d5 | |||
98bcfacdbc | |||
3283717a59 | |||
4f984c1364 | |||
03778ba002 | |||
6c661d9863 | |||
c5262bb165 | |||
8652643f5b | |||
9b502fa50c | |||
16b445afc2 | |||
00a4803f00 | |||
e9d8b786b1 | |||
56f2fdeb89 | |||
020e4cb033 | |||
bbede074e9 | |||
08756650c2 | |||
6567b34fb2 | |||
40461517e9 | |||
a5387c0e4d | |||
6d019d78c3 | |||
9a1fca513d | |||
5311585db6 | |||
e1c66c09e7 | |||
c39af02e97 | |||
0a0e89a1bc | |||
8cee9a5833 | |||
8585ed7d88 | |||
37e38e9be9 | |||
67774765f5 | |||
67e4e4c142 | |||
ea4dfba4c6 | |||
819bda5963 | |||
ba6989159b | |||
2f3912c1bf | |||
ca07e2393d | |||
4d9415213b | |||
6b67c93420 | |||
e3ab15fe50 | |||
e97d3724a5 | |||
d74ff49b55 | |||
67584af06b | |||
8919e45967 | |||
c4ce7d4e82 | |||
e225e14c84 | |||
f3e9a54216 | |||
279c1bbf14 | |||
ae10f27a05 | |||
eb76b1aa2e | |||
19697da88b | |||
a637a7f8ee | |||
48e5252a7c | |||
5da63c197f | |||
d10cfe43ec | |||
a1e32cca1b | |||
8b09b7afaf | |||
48c41bb3dc | |||
4e185ea612 | |||
9a9f636562 | |||
b382d37f20 | |||
d40742bc08 | |||
d2da32a8dc | |||
8c9be9e9f3 | |||
cff449cfad | |||
a0754b3f94 | |||
4d605106c8 | |||
6efde55a3b | |||
45a8b19d60 | |||
3aa4123596 | |||
2bd8a30812 | |||
0107ce569f | |||
54fe725876 | |||
943cfafe61 | |||
93d2714298 | |||
c33522870e | |||
a42bb325a1 | |||
cf5e359c3e | |||
1cf29d7141 | |||
389e1e7676 | |||
299ae1e44d | |||
c03b4b0cd9 | |||
f29efac943 | |||
ccce4cfe63 | |||
3b7914eed7 | |||
cb813ba402 | |||
1bdf070dfe | |||
467c6af913 | |||
ed4901830d | |||
62620f4f9c | |||
41554904e5 | |||
3a47375855 | |||
52b4478f22 | |||
70c56487f4 | |||
338dbc1d92 | |||
25148be751 | |||
9ef9f1b556 | |||
4b1c19a1f6 | |||
18f4f699f4 | |||
8b5a0d9c02 | |||
484dc5fa59 | |||
4b016e0303 | |||
5f3050c3db | |||
321e241ddd | |||
d293d424dc | |||
7d61873bf2 | |||
129a6bd612 | |||
144f03f997 | |||
d6ae766cf4 | |||
7332b6d1b3 | |||
0f1ab97444 | |||
36f6e21679 | |||
ac631bda1d | |||
1377e93e69 | |||
8ddf3d259b | |||
0e0620deee | |||
5055c65838 | |||
47cb81cb93 | |||
91f6031d1d | |||
9913b07de9 | |||
ecf061571c | |||
9f3a3633e4 | |||
ee396ed58e | |||
68a7187be6 | |||
1b4720b293 | |||
af21b1292f | |||
bb787bfab5 | |||
e3a3a252e5 | |||
9304b9fb34 | |||
afed031baf | |||
6ec7397eef | |||
6c1d8a1415 | |||
9aa819abf3 | |||
48bd2ca460 | |||
a1421cffbb | |||
02793575d0 | |||
09222cfc45 | |||
1d8ad819da | |||
43b46cb99b | |||
104db8830e | |||
59fbee8893 | |||
a72531a41b | |||
febf4f5d86 | |||
9205d23390 | |||
92908b0a63 | |||
31be9a66bc | |||
b4c658165d | |||
692d1c7b84 | |||
8268dfd419 | |||
17926354e8 | |||
f1410e49af | |||
a3ed906051 | |||
fa0e7e15ac | |||
08173b6650 | |||
4ba5688447 | |||
3d1b995b5a | |||
6414d01cc3 | |||
6f8c5bf7c6 | |||
2a1b10a214 | |||
b553bef026 | |||
92b728f082 | |||
2b23abaec8 | |||
2c355f744b | |||
50a41046cd | |||
2918b9cc33 | |||
81bd1f94f1 | |||
dff6d387c3 | |||
2e3b2c8787 | |||
5fb1df2c71 | |||
90a77a55d1 | |||
9728f847ec | |||
023221408f | |||
ebee998e25 | |||
139ea334fc | |||
3317b07c41 | |||
23ed5be2d8 | |||
95be2f339c | |||
f0d7685a96 | |||
e471ac081c | |||
43856ef115 | |||
78753b4f92 | |||
fabc86962c | |||
a9a2b63650 | |||
3736f12712 | |||
4c556e61fd | |||
f3d244e90c | |||
35576bb760 | |||
415ff0c777 | |||
6244070e3c | |||
82c6e6462e | |||
85d9036ed3 | |||
579d1b0c47 | |||
692ce3592e | |||
9428d37319 | |||
e288136175 | |||
a77f8f5c03 | |||
bdfb1658ba | |||
5a5d7ac55d | |||
1343d6e7ea | |||
f160775ec0 | |||
ece6873ac7 | |||
9d01fd75a0 | |||
06d38c2585 | |||
be4d158c37 | |||
e75c92c27c | |||
204cedd7e2 | |||
f4896837ac | |||
b2ee1a6a98 | |||
f836f9197c | |||
ecc6312bc3 | |||
8a85dfd150 | |||
783ff06cb0 | |||
ff322c2ea4 | |||
36c03fa308 | |||
dc22051fef | |||
8216aa08f2 | |||
0a62046441 | |||
f8c0cf9884 | |||
24392a62dd | |||
c174a543fd |
15
.github/dependabot.yml
vendored
15
.github/dependabot.yml
vendored
|
@ -6,16 +6,19 @@ updates:
|
|||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
interval: "monthly"
|
||||
target-branch: "develop"
|
||||
|
||||
# Maintain dependencies for npm
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
directory: "/gui"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
interval: "monthly"
|
||||
target-branch: "develop"
|
||||
|
||||
# Maintain dependencies for pip
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: "monthly"
|
||||
target-branch: "develop"
|
37
.github/workflows/build_gui.yml
vendored
Normal file
37
.github/workflows/build_gui.yml
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
name: build_gui
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build_i686_x64_release:
|
||||
name: Build FreeDATA GUI
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-latest, windows-latest]
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
electron_parameters: "-p always"
|
||||
|
||||
- os: macos-latest
|
||||
electron_parameters: "-p always"
|
||||
|
||||
- os: windows-latest
|
||||
electron_parameters: "-p always --x64 --ia32"
|
||||
|
||||
steps:
|
||||
- name: Checkout code for ${{ matrix.platform.name }}
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: DJ2LS/FreeDATA
|
||||
- name: Electron Builder
|
||||
env: # Setting environment variables for the entire job
|
||||
GH_TOKEN: ${{ secrets.github_token }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
working-directory: gui
|
||||
run: |
|
||||
npm i
|
||||
npm run release
|
413
.github/workflows/build_multiplatform.yml
vendored
413
.github/workflows/build_multiplatform.yml
vendored
|
@ -1,413 +0,0 @@
|
|||
name: Build_Multiplatform
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
BUILD_AMD64:
|
||||
name: Build codec2 for x86/x64 devices
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, ubuntu-22.04, macos-11, macos-12]
|
||||
platform: [{name: "native"}, {name: "Windows", file: "dll"}]
|
||||
architecture: [i686-w64-mingw32, x86_64-w64-mingw32]
|
||||
include:
|
||||
|
||||
- os: ubuntu-20.04
|
||||
libcodec2_name: libcodec2.so.1.1
|
||||
libcodec2_os_name: libcodec2_ubuntu-2004
|
||||
libcodec2_filetype: so
|
||||
generator: Unix Makefiles
|
||||
shell: bash
|
||||
|
||||
- os: ubuntu-22.04
|
||||
libcodec2_name: libcodec2.so.1.1
|
||||
libcodec2_os_name: libcodec2_ubuntu-2204
|
||||
libcodec2_filetype: so
|
||||
generator: Unix Makefiles
|
||||
shell: bash
|
||||
|
||||
- os: macos-11
|
||||
libcodec2_name: libcodec2.1.1.dylib
|
||||
libcodec2_os_name: libcodec2_macos-11
|
||||
libcodec2_filetype: dylib
|
||||
generator: Unix Makefiles
|
||||
shell: bash
|
||||
|
||||
- os: macos-12
|
||||
libcodec2_name: libcodec2.1.1.dylib
|
||||
libcodec2_os_name: libcodec2_macos-12
|
||||
libcodec2_filetype: dylib
|
||||
generator: Unix Makefiles
|
||||
shell: bash
|
||||
|
||||
|
||||
steps:
|
||||
- name: Build codec2 on ${{ matrix.os }} for ${{ matrix.platform.name }}
|
||||
if: ${{startsWith(matrix.platform.name, 'native') }}
|
||||
run: |
|
||||
git clone https://github.com/drowe67/codec2.git
|
||||
cd codec2
|
||||
mkdir build
|
||||
mkdir tempfiles
|
||||
cd build
|
||||
cmake ../
|
||||
make
|
||||
mv src/${{ matrix.libcodec2_name }} ../tempfiles/libcodec2_${{ matrix.os }}_${{ matrix.platform.name }}.${{ matrix.libcodec2_filetype }}
|
||||
|
||||
- name: LIST ALL FILES ${{ github.workspace }}
|
||||
run: ls -R ${{ github.workspace }}
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{startsWith(matrix.platform.name, 'native') }}
|
||||
with:
|
||||
name: libcodec2_${{ matrix.os }}_${{ matrix.platform.name }}.${{ matrix.libcodec2_filetype }}
|
||||
# path: ${{ github.workspace }}/codec2/tempfiles/libcodec2_${{ matrix.os }}_${{ matrix.platform.name }}.${{ matrix.libcodec2_filetype }}
|
||||
path: ${{ github.workspace }}/codec2/tempfiles/
|
||||
|
||||
|
||||
- name: Build codec2 ${{ matrix.platform.name }} ${{ matrix.architecture }}
|
||||
if: ${{startsWith(matrix.os, 'ubuntu-20') && !startsWith(matrix.platform.name, 'native') }}
|
||||
run: |
|
||||
sudo apt install build-essential mingw-w64 g++-mingw-w64 make cmake
|
||||
git clone https://github.com/drowe67/codec2.git
|
||||
cd codec2
|
||||
mkdir tempfiles
|
||||
mkdir build_w32
|
||||
cd build_w32
|
||||
echo 'set(CMAKE_SYSTEM_NAME ${{ matrix.platform.name }})' > toolchain-ubuntu-mingw32.cmake
|
||||
echo 'set(CMAKE_C_COMPILER ${{ matrix.architecture }}-gcc)' >> toolchain-ubuntu-mingw32.cmake
|
||||
echo 'set(CMAKE_CXX_COMPILER ${{ matrix.architecture }}-g++)' >> toolchain-ubuntu-mingw32.cmake
|
||||
echo 'set(CMAKE_RC_COMPILER ${{ matrix.architecture }}-windres)' >> toolchain-ubuntu-mingw32.cmake
|
||||
echo 'set(CMAKE_FIND_ROOT_PATH /usr/${{ matrix.architecture }})' >> toolchain-ubuntu-mingw32.cmake
|
||||
echo 'set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)' >> toolchain-ubuntu-mingw32.cmake
|
||||
echo 'set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)' >> toolchain-ubuntu-mingw32.cmake
|
||||
echo 'set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)' >> toolchain-ubuntu-mingw32.cmake
|
||||
echo 'set(CMAKE_SHARED_LINKER_FLAGS "-static-libgcc -static-libstdc++ -static")' >> toolchain-ubuntu-mingw32.cmake
|
||||
cmake -G "Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=toolchain-ubuntu-mingw32.cmake ..
|
||||
make
|
||||
mv src/libcodec2.${{ matrix.platform.file }} ../tempfiles/libcodec2_${{ matrix.platform.name }}_${{ matrix.architecture }}.${{ matrix.platform.file }}
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{startsWith(matrix.os, 'ubuntu-20') && !startsWith(matrix.platform.name, 'native') }}
|
||||
with:
|
||||
name: libcodec2_${{ matrix.os }}_${{ matrix.platform.name }}_${{ matrix.architecture }}.${{ matrix.platform.file }}
|
||||
path: codec2/tempfiles/*
|
||||
|
||||
BUILD_ARM:
|
||||
# The host should always be linux
|
||||
runs-on: ubuntu-latest
|
||||
name: Build codec2 for ARM devices
|
||||
|
||||
# Run steps on a matrix of 2 arch/distro combinations
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- arch: armv7
|
||||
distro: bullseye
|
||||
libcodec2_os_name: libcodec2_bullseye_armv7.so
|
||||
- arch: armv7
|
||||
distro: ubuntu_latest
|
||||
libcodec2_os_name: libcodec2_ubuntu_latest_armv7.so
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
- uses: uraimo/run-on-arch-action@v2
|
||||
name: Build artifact
|
||||
id: build
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
distro: ${{ matrix.distro }}
|
||||
|
||||
# Not required, but speeds up builds
|
||||
githubToken: ${{ github.token }}
|
||||
|
||||
# Create an artifacts directory
|
||||
setup: |
|
||||
mkdir -p "${PWD}/artifacts"
|
||||
|
||||
# Mount the artifacts directory as /artifacts in the container
|
||||
dockerRunArgs: |
|
||||
--volume "${PWD}/artifacts:/artifacts"
|
||||
|
||||
# Pass some environment variables to the container
|
||||
env: | # YAML, but pipe character is necessary
|
||||
artifact_name: ${{ matrix.libcodec2_os_name }}
|
||||
|
||||
# The shell to run commands with in the container
|
||||
shell: /bin/sh
|
||||
|
||||
# Install some dependencies in the container. This speeds up builds if
|
||||
# you are also using githubToken. Any dependencies installed here will
|
||||
# be part of the container image that gets cached, so subsequent
|
||||
# builds don't have to re-install them. The image layer is cached
|
||||
# publicly in your project's package repository, so it is vital that
|
||||
# no secrets are present in the container state or logs.
|
||||
install: |
|
||||
case "${{ matrix.distro }}" in
|
||||
ubuntu*|jessie|stretch|buster|bullseye)
|
||||
apt-get update -q -y
|
||||
apt-get install -q -y git build-essential cmake gcc g++
|
||||
cmake --version
|
||||
;;
|
||||
fedora*)
|
||||
dnf -y update
|
||||
dnf -y install git which make cmake gcc-c++ gcc
|
||||
cmake --version
|
||||
;;
|
||||
alpine*)
|
||||
apk update
|
||||
apk add git cmake gcc g++
|
||||
cmake --version
|
||||
;;
|
||||
esac
|
||||
|
||||
# Produce a binary artifact and place it in the mounted volume
|
||||
run: |
|
||||
|
||||
git clone https://github.com/drowe67/codec2.git
|
||||
cd codec2
|
||||
git checkout master
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ../
|
||||
make
|
||||
mv ./src/libcodec2.so.1.1 /artifacts/${artifact_name}
|
||||
|
||||
- name: Show recursive PWD/artifacts
|
||||
# Items placed in /artifacts in the container will be in
|
||||
# ${PWD}/artifacts on the host.
|
||||
run: ls -al "${PWD}/artifacts"
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.libcodec2_os_name }}
|
||||
#path: $GITHUB_WORKSPACE/codec2/artifacts/*
|
||||
path: artifacts/*
|
||||
|
||||
build_i686_x64_release:
|
||||
needs: [BUILD_AMD64]
|
||||
name: Build FreeDATA packages
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-11, windows-latest]
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
zip_name: ubuntu_tnc
|
||||
generator: Unix Makefiles
|
||||
daemon_binary_name: freedata-daemon
|
||||
tnc_binary_name: freedata-tnc
|
||||
electron_parameters: "-p always"
|
||||
|
||||
- os: macos-11
|
||||
zip_name: macos_tnc
|
||||
generator: Unix Makefiles
|
||||
daemon_binary_name: freedata-daemon
|
||||
tnc_binary_name: freedata-tnc
|
||||
electron_parameters: "-p always"
|
||||
|
||||
- os: windows-latest
|
||||
zip_name: windows_tnc
|
||||
generator: Visual Studio 16 2019
|
||||
daemon_binary_name: freedata-daemon.exe
|
||||
tnc_binary_name: freedata-tnc.exe
|
||||
electron_parameters: "-p always --x64 --ia32"
|
||||
steps:
|
||||
- name: Checkout code for ${{ matrix.platform.name }}
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: DJ2LS/FreeDATA
|
||||
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Create tnc/dist
|
||||
working-directory: tnc
|
||||
run: |
|
||||
mkdir -p dist
|
||||
|
||||
- name: Create tnc/dist/tnc
|
||||
working-directory: tnc
|
||||
run: |
|
||||
mkdir -p dist/tnc
|
||||
|
||||
##- name: Download libcodec2 artifact TNC DIST
|
||||
## uses: actions/download-artifact@v3
|
||||
## with:
|
||||
## path: tnc/dist/codec2
|
||||
|
||||
- name: create tnc/lib/codec2
|
||||
working-directory: tnc/lib/
|
||||
run: |
|
||||
mkdir codec2
|
||||
|
||||
- name: Download libcodec2 artifact TNC LIB
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: tnc/lib/codec2
|
||||
|
||||
|
||||
- name: Install Linux dependencies
|
||||
# if: matrix.os == 'ubuntu-20.04'
|
||||
if: ${{startsWith(matrix.os, 'ubuntu')}}
|
||||
run: |
|
||||
sudo apt install -y portaudio19-dev libhamlib-dev libhamlib-utils build-essential cmake python3-libhamlib2 patchelf
|
||||
|
||||
- name: Install MacOS pyAudio
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
run: |
|
||||
brew install portaudio
|
||||
python -m pip install --upgrade pip
|
||||
pip3 install pyaudio
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
|
||||
- name: Add MacOS certs
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
run: chmod +x add-osx-cert.sh && ./add-osx-cert.sh
|
||||
env:
|
||||
CERTIFICATE_OSX_APPLICATION: ${{ secrets.CERTIFICATE_OSX_APPLICATION }}
|
||||
CERTIFICATE_PASSWORD: ${{ secrets.CERTIFICATE_PASSWORD }}
|
||||
|
||||
- name: Build binaries macOS
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
working-directory: tnc
|
||||
run: |
|
||||
# now build tnc binaries
|
||||
pyinstaller -y freedata.spec
|
||||
# and to some final cleanup
|
||||
# cp -r -f dist/tnc/* dist/
|
||||
# rm -r dist/tnc
|
||||
|
||||
- name: Build binaries Linux and Windows
|
||||
if: ${{!startsWith(matrix.os, 'macos')}}
|
||||
working-directory: tnc
|
||||
run: |
|
||||
# pyinstaller freedata.spec
|
||||
# python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --onefile daemon.py -o ${{ matrix.daemon_binary_name }}
|
||||
# python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --onefile main.py -o ${{ matrix.tnc_binary_name }}
|
||||
python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --standalone daemon.py
|
||||
python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --standalone main.py
|
||||
|
||||
- name: Copy binaries - Linux
|
||||
if: ${{startsWith(matrix.os, 'ubuntu')}}
|
||||
working-directory: tnc
|
||||
run: |
|
||||
cp -r -f daemon.dist/* dist/tnc
|
||||
cp -r -f main.dist/* dist/tnc
|
||||
|
||||
- name: Copy binaries - Windows
|
||||
if: ${{startsWith(matrix.os, 'windows')}}
|
||||
working-directory: tnc
|
||||
# These are powershell aliases, not UNIX commands.
|
||||
run: |
|
||||
cp -r -Force daemon.dist/* dist/tnc
|
||||
cp -r -Force main.dist/* dist/tnc
|
||||
|
||||
- name: Rename tnc binaries
|
||||
# we don't need renaming for pyinstaller builds as output name is defined
|
||||
if: ${{!startsWith(matrix.os, 'macos')}}
|
||||
working-directory: tnc
|
||||
run: |
|
||||
mv dist/tnc/daemon* dist/tnc/${{ matrix.daemon_binary_name }}
|
||||
mv dist/tnc/main* dist/tnc/${{ matrix.tnc_binary_name }}
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: tnc/dist/tnc
|
||||
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
- name: Download Portaudio binaries Linux macOS
|
||||
if: ${{!startsWith(matrix.os, 'windows')}}
|
||||
working-directory: tnc
|
||||
run: |
|
||||
if ! test -d "dist/tnc/_sounddevice_data"; then
|
||||
git clone https://github.com/spatialaudio/portaudio-binaries dist/tnc/_sounddevice_data/portaudio-binaries
|
||||
fi
|
||||
|
||||
- name: Download Portaudio binaries Windows
|
||||
if: ${{startsWith(matrix.os, 'windows')}}
|
||||
working-directory: tnc
|
||||
run: |
|
||||
if(Test-Path -Path "dist/tnc/_sounddevice_data"){
|
||||
echo "sounddevice folder already exists"
|
||||
} else {
|
||||
git clone https://github.com/spatialaudio/portaudio-binaries dist/tnc/_sounddevice_data/portaudio-binaries
|
||||
}
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
- name: cleanup on macos before code signing
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
run: |
|
||||
ls -l
|
||||
# find . -type d -name .git -exec rm -r {} \;
|
||||
find . -type d -o -name ".git" -delete
|
||||
|
||||
- name: Build/release Electron app
|
||||
uses: samuelmeuli/action-electron-builder@v1
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
with:
|
||||
package_root: "./gui/"
|
||||
github_token: ${{ secrets.github_token }}
|
||||
# If the commit is tagged with a version (e.g. "v1.0.0"),
|
||||
# release the app after building
|
||||
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
args: ${{ matrix.electron_parameters }}
|
||||
max_attempts: 3
|
||||
|
||||
- name: Compress TNC
|
||||
uses: thedoctor0/zip-release@master
|
||||
with:
|
||||
type: 'zip'
|
||||
filename: '${{ matrix.zip_name }}.zip'
|
||||
# directory: ./tnc/dist/tnc
|
||||
directory: ./tnc/dist/tnc
|
||||
path: .
|
||||
# exclusions: '*.git* /*node_modules/* .editorconfig'
|
||||
|
||||
- name: Release TNC
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
with:
|
||||
files: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip
|
||||
#files: ./tnc/dist/${{ matrix.zip_name }}.zip
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
#- name: Upload TNC artifacts
|
||||
# uses: actions/upload-artifact@v3
|
||||
# if: ${{!startsWith(github.ref, 'refs/tags/v')}}
|
||||
# with:
|
||||
# name: ${{ matrix.zip_name }}.zip
|
||||
# # path: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip
|
||||
# path: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip#
|
||||
|
||||
#- name: Upload App bundle artifacts
|
||||
# uses: actions/upload-artifact@v3
|
||||
# if: ${{!startsWith(github.ref, 'refs/tags/v')}}
|
||||
# with:
|
||||
# name: app_bundle_${{ matrix.os }}.zip
|
||||
# # path: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip
|
||||
# path: ./gui/dist/*
|
69
.github/workflows/build_nsis_bundle.yml
vendored
Normal file
69
.github/workflows/build_nsis_bundle.yml
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
name: Build and Release NSIS Installer
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build-and-release:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Electron Builder
|
||||
working-directory: gui
|
||||
run: |
|
||||
npm i
|
||||
npm run build
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
|
||||
- uses: robinraju/release-downloader@v1.9
|
||||
with:
|
||||
repository: "Hamlib/Hamlib"
|
||||
fileName: " hamlib-w64-*.zip"
|
||||
latest: true
|
||||
extract: true
|
||||
out-file-path: "modem/lib/hamlib/"
|
||||
|
||||
- name: Build binaries
|
||||
working-directory: modem
|
||||
run: |
|
||||
python3 -m nuitka --remove-output --assume-yes-for-downloads --follow-imports --include-data-dir=lib=lib --include-data-files=lib/codec2/*=lib/codec2/ --include-data-files=config.ini.example=config.ini --standalone server.py --output-filename=freedata-server
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
- name: Create installer
|
||||
uses: joncloud/makensis-action@v4.1
|
||||
with:
|
||||
script-file: "freedata-nsis-config.nsi"
|
||||
arguments: '/V3'
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: 'FreeDATA-Installer'
|
||||
path: ./FreeDATA-Installer.exe
|
||||
|
||||
- name: Upload Installer to Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
with:
|
||||
draft: true
|
||||
files: ./FreeDATA-Installer.exe
|
||||
tag_name: ${{ github.ref_name }}
|
||||
name: 'FreeDATA-Installer-${{ github.ref_name }}'
|
124
.github/workflows/build_server.yml
vendored
Normal file
124
.github/workflows/build_server.yml
vendored
Normal file
|
@ -0,0 +1,124 @@
|
|||
name: build_server
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build_i686_x64_release:
|
||||
name: Build FreeDATA packages
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-latest, windows-latest]
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
zip_name: freedata-server_ubuntu
|
||||
generator: Unix Makefiles
|
||||
modem_binary_name: freedata-server
|
||||
|
||||
- os: macos-latest
|
||||
zip_name: freedata-server_macos
|
||||
generator: Unix Makefiles
|
||||
modem_binary_name: freedata-server
|
||||
|
||||
- os: windows-latest
|
||||
zip_name: freedata-server_windows
|
||||
generator: Visual Studio 16 2019
|
||||
modem_binary_name: freedata-server.exe
|
||||
|
||||
steps:
|
||||
- name: Checkout code for ${{ matrix.platform.name }}
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: DJ2LS/FreeDATA
|
||||
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install Linux dependencies
|
||||
# if: matrix.os == 'ubuntu-20.04'
|
||||
if: ${{startsWith(matrix.os, 'ubuntu')}}
|
||||
run: |
|
||||
sudo apt install -y portaudio19-dev libhamlib-dev libhamlib-utils build-essential cmake python3-libhamlib2 patchelf
|
||||
|
||||
- name: Install MacOS pyAudio
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
run: |
|
||||
brew install portaudio
|
||||
python -m pip install --upgrade pip
|
||||
pip3 install pyaudio
|
||||
export PYTHONPATH=/Library/Frameworks/Python.framework/Versions/3.11/lib/:$PYTHONPATH
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
|
||||
- name: Add MacOS certs
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
run: chmod +x add-osx-cert.sh && ./add-osx-cert.sh
|
||||
env:
|
||||
CERTIFICATE_OSX_APPLICATION: ${{ secrets.CERTIFICATE_OSX_APPLICATION }}
|
||||
CERTIFICATE_PASSWORD: ${{ secrets.CERTIFICATE_PASSWORD }}
|
||||
|
||||
- name: Build binaries
|
||||
working-directory: modem
|
||||
run: |
|
||||
python3 -m nuitka --remove-output --assume-yes-for-downloads --follow-imports --include-data-dir=lib=lib --include-data-files=lib/codec2/*=lib/codec2/ --include-data-files=config.ini.example=config.ini --standalone server.py --output-filename=freedata-server
|
||||
|
||||
#- name: Download Portaudio binaries Linux macOS
|
||||
# if: ${{!startsWith(matrix.os, 'windows')}}
|
||||
# working-directory: modem
|
||||
# run: |
|
||||
# if ! test -d "server.dist/modem/_sounddevice_data"; then
|
||||
# git clone https://github.com/spatialaudio/portaudio-binaries dist/modem/_sounddevice_data/portaudio-binaries
|
||||
# fi
|
||||
|
||||
#- name: Download Portaudio binaries Windows
|
||||
# if: ${{startsWith(matrix.os, 'windows')}}
|
||||
# working-directory: modem
|
||||
# run: |
|
||||
# if(Test-Path -Path "server.dist/modem/_sounddevice_data"){
|
||||
# echo "sounddevice folder already exists"
|
||||
# } else {
|
||||
# git clone https://github.com/spatialaudio/portaudio-binaries dist/modem/_sounddevice_data/portaudio-binaries
|
||||
# }
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
- name: cleanup on macos before code signing
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
run: |
|
||||
ls -l
|
||||
# find . -type d -name .git -exec rm -r {} \;
|
||||
find . -type d -o -name ".git" -delete
|
||||
|
||||
- name: Compress Modem
|
||||
uses: thedoctor0/zip-release@master
|
||||
with:
|
||||
type: 'zip'
|
||||
filename: '${{ matrix.zip_name }}.zip'
|
||||
directory: ./modem/server.dist
|
||||
path: .
|
||||
# exclusions: '*.git* /*node_modules/* .editorconfig'
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: '${{ matrix.zip_name }}'
|
||||
path: ./modem/server.dist/${{ matrix.zip_name }}.zip
|
||||
|
||||
- name: Release Modem
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
with:
|
||||
draft: true
|
||||
files: ./modem/server.dist/${{ matrix.zip_name }}.zip
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
|
@ -39,13 +39,13 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
|
@ -56,7 +56,7 @@ jobs:
|
|||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
@ -70,4 +70,4 @@ jobs:
|
|||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
|
38
.github/workflows/gui_tests.yml
vendored
Normal file
38
.github/workflows/gui_tests.yml
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
name: GUI tests
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# The CMake configure and build commands are platform-agnostic and should work equally
|
||||
# well on Windows or Mac. You can convert this to a matrix build if you need
|
||||
# cross-platform coverage.
|
||||
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
# By default, GitHub will maximize the number of jobs run in parallel
|
||||
# depending on the available runners on GitHub-hosted virtual machines.
|
||||
# max-parallel: 8
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- node-version: "16"
|
||||
- node-version: "18"
|
||||
- node-version: "20"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: gui
|
||||
run: |
|
||||
npm i
|
||||
|
||||
- name: GUI Test
|
||||
working-directory: gui
|
||||
run: |
|
||||
npm run test
|
|
@ -1,4 +1,4 @@
|
|||
name: CTest
|
||||
name: Modem tests
|
||||
|
||||
on: [push]
|
||||
|
||||
|
@ -16,41 +16,34 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- python-version: "3.7"
|
||||
#- python-version: "3.7" EOL
|
||||
- python-version: "3.8"
|
||||
- python-version: "3.9"
|
||||
- python-version: "3.10"
|
||||
- python-version: "3.11"
|
||||
#- python-version: "3.12-dev"
|
||||
#- python-version: "3.12" NOT YET SUPPORTED BY NUITKA!
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
|
||||
- name: Install packages
|
||||
- name: Install system packages
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install octave octave-common octave-signal sox portaudio19-dev python3-pyaudio
|
||||
pip3 install psutil crcengine ujson pyserial numpy structlog sounddevice pyaudio requests websocket-client
|
||||
pip3 install pytest pytest-rerunfailures
|
||||
sudo apt-get update || true
|
||||
sudo apt-get install octave octave-common octave-signal sox portaudio19-dev
|
||||
|
||||
- name: Build codec2
|
||||
- name: Install python packages
|
||||
shell: bash
|
||||
run: |
|
||||
git clone https://github.com/drowe67/codec2.git
|
||||
cd codec2
|
||||
mkdir -p build_linux && cd build_linux && cmake .. && make
|
||||
pip3 install -r requirements.txt
|
||||
|
||||
- name: run ctests
|
||||
- name: run config tests
|
||||
shell: bash
|
||||
working-directory: ${{github.workspace}}
|
||||
run: |
|
||||
mkdir build && cd build
|
||||
cmake -DCODEC2_BUILD_DIR=$GITHUB_WORKSPACE/codec2/build_linux ..
|
||||
ctest --output-on-failure
|
||||
python -m unittest discover tests
|
4
.github/workflows/prettier.yaml
vendored
4
.github/workflows/prettier.yaml
vendored
|
@ -4,7 +4,7 @@ name: Prettier
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
|
||||
jobs:
|
||||
prettier:
|
||||
|
@ -12,7 +12,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Make sure the actual branch is checked out when running on pull requests
|
||||
ref: ${{ github.head_ref }}
|
||||
|
|
15
.gitignore
vendored
15
.gitignore
vendored
|
@ -1,5 +1,5 @@
|
|||
# possible installation of codec2 within tnc
|
||||
tnc/codec2
|
||||
# possible installation of codec2 within modem
|
||||
modem/codec2
|
||||
|
||||
# temporary test artifacts
|
||||
**/build
|
||||
|
@ -22,3 +22,14 @@ package-lock.json
|
|||
*.raw
|
||||
coverage.sh
|
||||
coverage.xml
|
||||
|
||||
#ignore node_modules
|
||||
/gui/node_modules/
|
||||
|
||||
#Ignore gui build items
|
||||
/gui/dist
|
||||
/gui/release
|
||||
/gui/dist-electron
|
||||
|
||||
#Ignore GUI config
|
||||
/gui/config/config.json
|
218
CMakeLists.txt
218
CMakeLists.txt
|
@ -1,218 +0,0 @@
|
|||
cmake_minimum_required(VERSION 3.0)
|
||||
project (FreeDATA)
|
||||
include(CTest)
|
||||
enable_testing()
|
||||
|
||||
# Find codec2
|
||||
if(CODEC2_BUILD_DIR)
|
||||
find_package(codec2 REQUIRED
|
||||
PATHS ${CODEC2_BUILD_DIR}
|
||||
NO_DEFAULT_PATH
|
||||
CONFIGS codec2.cmake
|
||||
)
|
||||
if(codec2_FOUND)
|
||||
message(STATUS "Codec2 library found in build tree.")
|
||||
endif()
|
||||
else()
|
||||
find_package(codec2 REQUIRED)
|
||||
endif()
|
||||
|
||||
# test variables
|
||||
set(FRAMESPERBURST 3)
|
||||
set(BURSTS 1)
|
||||
set(TESTFRAMES 3)
|
||||
|
||||
add_test(NAME audio_buffer
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_audiobuffer.py")
|
||||
set_tests_properties(audio_buffer PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
add_test(NAME resampler
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_resample_48_8.py")
|
||||
set_tests_properties(resampler PROPERTIES PASS_REGULAR_EXPRESSION "PASS")
|
||||
|
||||
add_test(NAME tnc_state_machine
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_tnc_states.py")
|
||||
set_tests_properties(tnc_state_machine PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
add_test(NAME tnc_irs_iss
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_tnc.py")
|
||||
set_tests_properties(tnc_irs_iss PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
# disabled this test as its actually broken since we introduced session IDs
|
||||
#add_test(NAME chat_text
|
||||
# COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
# export PYTHONPATH=../tnc;
|
||||
# cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
# python3 test_chat_text.py")
|
||||
# set_tests_properties(chat_text PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
add_test(NAME datac13_frames
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_datac13.py")
|
||||
set_tests_properties(datac13_frames PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
# disabled this test as its actually broken since we introduced dataclasses
|
||||
#add_test(NAME datac13_frames_negative
|
||||
# COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
# export PYTHONPATH=../tnc;
|
||||
# cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
# python3 test_datac13_negative.py")
|
||||
# set_tests_properties(datac13_frames_negative PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
add_test(NAME helper_routines
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_helpers.py")
|
||||
set_tests_properties(helper_routines PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
add_test(NAME py_highsnr_stdio_P_P_multi
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
export BURSTS=${BURSTS};
|
||||
export FRAMESPERBURST=${FRAMESPERBURST};
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_highsnr_stdio_P_P_multi.py")
|
||||
set_tests_properties(py_highsnr_stdio_P_P_multi PROPERTIES PASS_REGULAR_EXPRESSION "DATAC13: ${BURSTS}/${FRAMESPERBURST} DATAC1: ${BURSTS}/${FRAMESPERBURST} DATAC3: ${BURSTS}/${FRAMESPERBURST}")
|
||||
|
||||
add_test(NAME py_highsnr_stdio_P_P_datacx
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
export BURSTS=${BURSTS};
|
||||
export FRAMESPERBURST=${FRAMESPERBURST};
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_highsnr_stdio_P_P_datacx.py")
|
||||
set_tests_properties(py_highsnr_stdio_P_P_datacx PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}")
|
||||
|
||||
add_test(NAME py_highsnr_stdio_P_C_datacx
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
export BURSTS=${BURSTS};
|
||||
export FRAMESPERBURST=${FRAMESPERBURST};
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_highsnr_stdio_P_C_datacx.py")
|
||||
set_tests_properties(py_highsnr_stdio_P_C_datacx PROPERTIES PASS_REGULAR_EXPRESSION "HELLO WORLD")
|
||||
|
||||
add_test(NAME py_highsnr_stdio_C_P_datacx
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
export BURSTS=${BURSTS};
|
||||
export FRAMESPERBURST=${FRAMESPERBURST};
|
||||
export TESTFRAMES=${TESTFRAMES};
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_highsnr_stdio_C_P_datacx.py")
|
||||
set_tests_properties(py_highsnr_stdio_C_P_datacx PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}")
|
||||
|
||||
add_test(NAME highsnr_stdio_P_C_single
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 util_tx.py --mode datac13 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} |
|
||||
sox -t .s16 -r 48000 -c 1 - -t .s16 -r 8000 -c 1 - |
|
||||
freedv_data_raw_rx datac13 - - --framesperburst ${FRAMESPERBURST} | hexdump -C")
|
||||
set_tests_properties(highsnr_stdio_P_C_single PROPERTIES PASS_REGULAR_EXPRESSION "HELLO WORLD")
|
||||
|
||||
add_test(NAME highsnr_stdio_C_P_single
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
freedv_data_raw_tx datac13 --testframes ${TESTFRAMES} --bursts ${BURSTS} --framesperburst ${FRAMESPERBURST} /dev/zero - |
|
||||
sox -t .s16 -r 8000 -c 1 - -t .s16 -r 48000 -c 1 - |
|
||||
python3 util_rx.py --mode datac13 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}")
|
||||
set_tests_properties(highsnr_stdio_C_P_single PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}")
|
||||
|
||||
add_test(NAME highsnr_stdio_P_P_single
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 util_tx.py --mode datac13 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} |
|
||||
python3 util_rx.py --debug --mode datac13 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}")
|
||||
set_tests_properties(highsnr_stdio_P_P_single PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}")
|
||||
|
||||
add_test(NAME highsnr_stdio_P_P_multi
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 util_multimode_tx.py --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} |
|
||||
python3 util_multimode_rx.py --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} --timeout 60")
|
||||
set_tests_properties(highsnr_stdio_P_P_multi PROPERTIES PASS_REGULAR_EXPRESSION "DATAC13: ${BURSTS}/${FRAMESPERBURST} DATAC1: ${BURSTS}/${FRAMESPERBURST} DATAC3: ${BURSTS}/${FRAMESPERBURST}")
|
||||
|
||||
# These tests can't run on GitHub actions as we don't have a virtual sound card
|
||||
if(NOT DEFINED ENV{GITHUB_RUN_ID})
|
||||
|
||||
# uses aplay/arecord then pipe to Python
|
||||
add_test(NAME highsnr_virtual1_P_P_single_alsa
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
./test_virtual1.sh")
|
||||
set_tests_properties(highsnr_virtual1_P_P_single_alsa PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 5 RECEIVED FRAMES: 10 RX_ERRORS: 0")
|
||||
|
||||
# let Python do audio I/O
|
||||
add_test(NAME highsnr_virtual2_P_P_single
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
./test_virtual2.sh")
|
||||
set_tests_properties(highsnr_virtual2_P_P_single PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 3 RECEIVED FRAMES: 6 RX_ERRORS: 0")
|
||||
|
||||
# Multimode test with Python I/O
|
||||
add_test(NAME highsnr_virtual3_P_P_multi
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
./test_virtual_mm.sh")
|
||||
set_tests_properties(highsnr_virtual3_P_P_multi PROPERTIES PASS_REGULAR_EXPRESSION "DATAC13: 2/4 DATAC1: 2/4 DATAC3: 2/4")
|
||||
|
||||
# let Python do audio I/O via pyaudio callback mode
|
||||
add_test(NAME highsnr_virtual4_P_P_single_callback
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
./test_virtual3a.sh")
|
||||
set_tests_properties(highsnr_virtual4_P_P_single_callback PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 3 RECEIVED FRAMES: 6 RX_ERRORS: 0")
|
||||
|
||||
# let Python do audio I/O via pyaudio callback mode with code outside of callback
|
||||
add_test(NAME highsnr_virtual4_P_P_single_callback_outside
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
./test_virtual3b.sh")
|
||||
set_tests_properties(highsnr_virtual4_P_P_single_callback_outside PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 3 RECEIVED FRAMES: 6 RX_ERRORS: 0")
|
||||
|
||||
# let Python do audio I/O via pyaudio callback mode with code outside of callback
|
||||
add_test(NAME highsnr_virtual5_P_P_multi_callback
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
./test_virtual4a.sh")
|
||||
set_tests_properties(highsnr_virtual5_P_P_multi_callback PROPERTIES PASS_REGULAR_EXPRESSION "DATAC13: 2/4 DATAC1: 2/4 DATAC3: 2/4")
|
||||
|
||||
# let Python do audio I/O via pyaudio callback mode with code outside of callback
|
||||
add_test(NAME highsnr_virtual5_P_P_multi_callback_outside
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
./test_virtual4b.sh")
|
||||
set_tests_properties(highsnr_virtual5_P_P_multi_callback_outside PROPERTIES PASS_REGULAR_EXPRESSION "DATAC13: 2/4 DATAC1: 2/4 DATAC3: 2/4")
|
||||
|
||||
endif()
|
||||
|
135
documentation/FreeDATA-protocols.md
Normal file
135
documentation/FreeDATA-protocols.md
Normal file
|
@ -0,0 +1,135 @@
|
|||
# FreeDATA - Protocols
|
||||
|
||||
## ARQ Sessions
|
||||
|
||||
An ARQ Session represents a reliable data transmission session from a sending station (A) to a receiving station (B). It uses automatic repeat request on top of different codec2 modes according to the transmission channel conditions.
|
||||
|
||||
So lets say A wants to send some data to B. A typical scenario would be like this:
|
||||
|
||||
```
|
||||
ISS->(1)IRS:<datac13> OPEN_REQ(session id, origin, dest)
|
||||
IRS->(1)ISS:<datac13> OPEN_ACK (session id, proto version, speed level, frames, snr)
|
||||
|
||||
ISS->(1)IRS:<datac13> INFO(id, total_bytes, total_crc)
|
||||
IRS->(1)ISS:<datac13> INFO_ACK(id, total_crc)
|
||||
|
||||
ISS->(1)IRS:BURST (ID, offset, payload),(ID, offset, payload),(ID, offset, payload)
|
||||
IRS->(1)ISS:BURST_ACK (ID, next_offset, speed level, frames, snr)
|
||||
|
||||
ISS-->(1)IRS:Lost BURST (total or part)
|
||||
IRS->(1)ISS:BURST_NACK (ID, next_offset, speed level, frames, snr)
|
||||
|
||||
ISS->(1)IRS:BURST (ID, offset, payload),(ID, offset, payload),(ID, offset, payload)
|
||||
IRS->(1)ISS:DATA ACK NACK (ID, next_offset, speed level, frames, snr)
|
||||
```
|
||||
|
||||
### Frame details
|
||||
|
||||
#### SESSION_OPEN_REQ
|
||||
|
||||
ISS sends this first
|
||||
|
||||
DATAC13 Mode (12 bytes)
|
||||
|
||||
| field | bytes |
|
||||
| --------------- | ----- |
|
||||
| session id | 1 |
|
||||
| origin | 6 |
|
||||
| destination_crc | 3 |
|
||||
|
||||
#### SESSION_OPEN_ACK
|
||||
|
||||
Sent by the IRS in response to a SESSION_OPEN_REQ
|
||||
|
||||
DATAC13 Mode (12 bytes)
|
||||
|
||||
| field | bytes |
|
||||
| ---------------- | ----- |
|
||||
| session id | 1 |
|
||||
| origin | 6 |
|
||||
| destination_crc | 3 |
|
||||
| protocol version | 1 |
|
||||
| snr | 1 |
|
||||
|
||||
#### SESSION_INFO
|
||||
|
||||
ISS sends this in response to a SESSION_OPEN_ACK
|
||||
|
||||
DATAC13 Mode (12 bytes)
|
||||
|
||||
| field | bytes |
|
||||
| ----------- | ----- |
|
||||
| session id | 1 |
|
||||
| total bytes | 4 |
|
||||
| total crc | 4 |
|
||||
| snr | 1 |
|
||||
|
||||
#### SESSION_INFO_ACK
|
||||
|
||||
IRS sends this in response to a SESSION_INFO
|
||||
|
||||
DATAC13 Mode (12 bytes)
|
||||
|
||||
| field | bytes |
|
||||
| ---------------- | ----- |
|
||||
| session id | 1 |
|
||||
| total crc | 4 |
|
||||
| snr | 1 |
|
||||
| speed level | 1 |
|
||||
| frames per burst | 1 |
|
||||
|
||||
#### Data Burst
|
||||
|
||||
ISS sends this to send data to IRS
|
||||
|
||||
Mode according to handshake speed level
|
||||
|
||||
Frames per burst according to handshake
|
||||
|
||||
##### Modulation
|
||||
|
||||
Each burst is composed of frames_per_burst frames:
|
||||
|
||||
|preamble|f1|f2|f3|...|postamble|
|
||||
|
||||
##### Each data frame
|
||||
|
||||
| field | bytes |
|
||||
| ---------- | ------------------------------ |
|
||||
| session id | 1 |
|
||||
| offset | 4 |
|
||||
| payload | (the remaining payload length) |
|
||||
|
||||
#### DATA_BURST_ACK
|
||||
|
||||
Sent by the IRS following successful decoding of burst.
|
||||
|
||||
| field | bytes |
|
||||
| --------------------- | ----- |
|
||||
| session id | 1 |
|
||||
| next offset | 4 |
|
||||
| next speed level | 1 |
|
||||
| next frames per burst | 1 |
|
||||
| snr | 1 |
|
||||
|
||||
#### DATA_BURST_NACK
|
||||
|
||||
Sent by the IRS following unsuccessful decoding of burst or timeout.
|
||||
|
||||
| field | bytes |
|
||||
| --------------------- | ----- |
|
||||
| session id | 1 |
|
||||
| next offset | 4 |
|
||||
| next speed level | 1 |
|
||||
| next frames per burst | 1 |
|
||||
| snr | 1 |
|
||||
|
||||
#### DATA ACK NACK
|
||||
|
||||
Sent by the IRS after receiving data with a state information.
|
||||
|
||||
| field | bytes |
|
||||
| ---------- | ----- |
|
||||
| session id | 1 |
|
||||
| state | 1 |
|
||||
| snr | 1 |
|
BIN
documentation/icon.ico
Normal file
BIN
documentation/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 183 KiB |
132
freedata-nsis-config.nsi
Normal file
132
freedata-nsis-config.nsi
Normal file
|
@ -0,0 +1,132 @@
|
|||
!include "MUI2.nsh"
|
||||
|
||||
; Request administrative rights
|
||||
RequestExecutionLevel admin
|
||||
|
||||
; The name and file name of the installer
|
||||
Name "FreeDATA Installer"
|
||||
OutFile "FreeDATA-Installer.exe"
|
||||
|
||||
; Default installation directory for the server
|
||||
InstallDir "$LOCALAPPDATA\FreeDATA"
|
||||
|
||||
; Registry key to store the installation directory
|
||||
InstallDirRegKey HKCU "Software\FreeDATA" "Install_Dir"
|
||||
|
||||
; Modern UI settings
|
||||
!define MUI_ABORTWARNING
|
||||
|
||||
; Installer interface settings
|
||||
!define MUI_ICON "documentation\icon.ico"
|
||||
!define MUI_UNICON "documentation\icon.ico" ; Icon for the uninstaller
|
||||
|
||||
; Define the welcome page text
|
||||
!define MUI_WELCOMEPAGE_TEXT "Welcome to the FreeDATA Setup Wizard. This wizard will guide you through the installation process."
|
||||
!define MUI_FINISHPAGE_TEXT "Folder: $INSTDIR"
|
||||
!define MUI_DIRECTORYPAGE_TEXT_TOP "Please select the installation folder. It's recommended to use the suggested one to avoid permission problems."
|
||||
|
||||
; Pages
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
!insertmacro MUI_PAGE_LICENSE "LICENSE"
|
||||
!insertmacro MUI_PAGE_COMPONENTS
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
|
||||
; Uninstaller
|
||||
!insertmacro MUI_UNPAGE_WELCOME
|
||||
!insertmacro MUI_UNPAGE_CONFIRM
|
||||
!insertmacro MUI_UNPAGE_INSTFILES
|
||||
!insertmacro MUI_UNPAGE_FINISH
|
||||
|
||||
; Language (you can choose and configure the language(s) you want)
|
||||
!insertmacro MUI_LANGUAGE "English"
|
||||
|
||||
|
||||
; Installer Sections
|
||||
Section "FreeData Server" SEC01
|
||||
; Set output path to the installation directory
|
||||
SetOutPath $INSTDIR\freedata-server
|
||||
|
||||
; Check if "config.ini" exists and back it up
|
||||
IfFileExists $INSTDIR\freedata-server\config.ini backupConfig
|
||||
|
||||
doneBackup:
|
||||
; Add your application files here
|
||||
File /r "modem\server.dist\*"
|
||||
|
||||
; Restore the original "config.ini" if it was backed up
|
||||
IfFileExists $INSTDIR\freedata-server\config.ini.bak restoreConfig
|
||||
|
||||
; Create a shortcut in the user's desktop
|
||||
CreateShortCut "$DESKTOP\FreeDATA Server.lnk" "$INSTDIR\freedata-server\freedata-server.exe"
|
||||
|
||||
; Create Uninstaller
|
||||
WriteUninstaller "$INSTDIR\Uninstall.exe"
|
||||
|
||||
; Create a Start Menu directory
|
||||
CreateDirectory "$SMPROGRAMS\FreeDATA"
|
||||
|
||||
; Create shortcut in the Start Menu directory
|
||||
CreateShortCut "$SMPROGRAMS\FreeDATA\FreeDATA Server.lnk" "$INSTDIR\freedata-server\freedata-server.exe"
|
||||
|
||||
; Create an Uninstall shortcut
|
||||
CreateShortCut "$SMPROGRAMS\FreeDATA\Uninstall FreeDATA.lnk" "$INSTDIR\Uninstall.exe"
|
||||
|
||||
|
||||
; Backup "config.ini" before overwriting files
|
||||
backupConfig:
|
||||
Rename $INSTDIR\freedata-server\config.ini $INSTDIR\freedata-server\config.ini.bak
|
||||
Goto doneBackup
|
||||
|
||||
; Restore the original "config.ini"
|
||||
restoreConfig:
|
||||
Delete $INSTDIR\freedata-server\config.ini
|
||||
Rename $INSTDIR\freedata-server\config.ini.bak $INSTDIR\freedata-server\config.ini
|
||||
|
||||
|
||||
|
||||
SectionEnd
|
||||
|
||||
Section "FreeData x64 GUI" SEC02
|
||||
; Set output path to the GUI installation directory
|
||||
SetOutPath $INSTDIR\freedata-gui
|
||||
|
||||
; Add GUI files here
|
||||
File /r "gui\release\win-unpacked\*"
|
||||
|
||||
; Create a shortcut on the desktop for the GUI
|
||||
CreateShortCut "$DESKTOP\FreeDATA GUI.lnk" "$INSTDIR\freedata-gui\freedata.exe"
|
||||
|
||||
; Create a start menu shortcut
|
||||
CreateShortCut "$SMPROGRAMS\FreeDATA\FreeDATA GUI.lnk" "$INSTDIR\freedata-gui\freedata.exe"
|
||||
|
||||
; Create an Uninstall shortcut
|
||||
CreateShortCut "$SMPROGRAMS\FreeDATA\Uninstall FreeDATA.lnk" "$INSTDIR\Uninstall.exe"
|
||||
|
||||
SectionEnd
|
||||
|
||||
; Uninstaller Section
|
||||
Section "Uninstall"
|
||||
; Delete files and directories for the server
|
||||
Delete $INSTDIR\freedata-server\*.*
|
||||
RMDir /r $INSTDIR\freedata-server
|
||||
|
||||
; Delete files and directories for the GUI
|
||||
Delete $INSTDIR\freedata-gui\*.*
|
||||
RMDir /r $INSTDIR\freedata-gui
|
||||
|
||||
; Remove the desktop shortcuts
|
||||
Delete "$DESKTOP\FreeDATA Server.lnk"
|
||||
Delete "$DESKTOP\FreeDATA GUI.lnk"
|
||||
|
||||
; Remove Start Menu shortcuts
|
||||
Delete "$SMPROGRAMS\FreeDATA\*.*"
|
||||
RMDir "$SMPROGRAMS\FreeDATA"
|
||||
|
||||
; Attempt to delete the uninstaller itself
|
||||
Delete $EXEPATH
|
||||
|
||||
; Now remove the installation directory if it's empty
|
||||
RMDir /r $INSTDIR
|
||||
SectionEnd
|
23
gui/.eslintrc.json
Normal file
23
gui/.eslintrc.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/vue3-essential",
|
||||
"plugin:prettier/recommended",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module",
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"plugins": [
|
||||
"vue"
|
||||
],
|
||||
"ignorePatterns": ["**/src/assets/*", "**/src/js/deprecated*", "**/node_modules"],
|
||||
"rules": {
|
||||
}
|
||||
}
|
104
gui/.gitignore
vendored
104
gui/.gitignore
vendored
|
@ -1,104 +0,0 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
18
gui/README.md
Normal file
18
gui/README.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Vue 3 + TypeScript + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||
|
||||
## Type Support For `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
|
||||
|
||||
1. Disable the built-in TypeScript Extension
|
||||
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
|
||||
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
|
||||
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
|
24
gui/build/notarize_macos.js
Normal file
24
gui/build/notarize_macos.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
const { notarize } = require('@electron/notarize');
|
||||
|
||||
async function notarizing(context) {
|
||||
const { electronPlatformName, appOutDir } = context;
|
||||
console.log("Notarization...")
|
||||
if (electronPlatformName !== 'darwin') {
|
||||
console.log("--> Platform:" + electronPlatformName + " detected: not a APPLE system. Skipping")
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("--> Platform:" + electronPlatformName + " detected: Trying to notarize app.")
|
||||
const appName = context.packager.appInfo.productFilename;
|
||||
|
||||
return await notarize({
|
||||
tool: 'notarytool',
|
||||
appBundleId: 'app.freedata',
|
||||
appPath: `${appOutDir}/${appName}.app`,
|
||||
appleId: process.env.APPLE_ID,
|
||||
appleIdPassword: process.env.APPLE_ID_PASSWORD,
|
||||
teamId: process.env.APPLE_TEAM_ID
|
||||
});
|
||||
}
|
||||
|
||||
exports.default = notarizing;
|
10
gui/config/example.json
Normal file
10
gui/config/example.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"local": {
|
||||
"host": "127.0.0.1",
|
||||
"port": "5000",
|
||||
"spectrum": "waterfall",
|
||||
"wf_theme": 2,
|
||||
"update_channel": "alpha",
|
||||
"enable_sys_notification": false
|
||||
}
|
||||
}
|
349
gui/daemon.js
349
gui/daemon.js
|
@ -1,349 +0,0 @@
|
|||
var net = require("net");
|
||||
const path = require("path");
|
||||
const { ipcRenderer } = require("electron");
|
||||
const log = require("electron-log");
|
||||
const daemonLog = log.scope("daemon");
|
||||
|
||||
// https://stackoverflow.com/a/26227660
|
||||
var appDataFolder =
|
||||
process.env.APPDATA ||
|
||||
(process.platform == "darwin"
|
||||
? process.env.HOME + "/Library/Application Support"
|
||||
: process.env.HOME + "/.config");
|
||||
var configFolder = path.join(appDataFolder, "FreeDATA");
|
||||
var configPath = path.join(configFolder, "config.json");
|
||||
const config = require(configPath);
|
||||
|
||||
var daemon = new net.Socket();
|
||||
var socketchunk = ""; // Current message, per connection.
|
||||
|
||||
// global to keep track of daemon connection error emissions
|
||||
var daemonShowConnectStateError = 1;
|
||||
|
||||
// global for storing ip information
|
||||
var daemon_port = config.daemon_port;
|
||||
var daemon_host = config.daemon_host;
|
||||
|
||||
setTimeout(connectDAEMON, 500);
|
||||
|
||||
function connectDAEMON() {
|
||||
if (daemonShowConnectStateError == 1) {
|
||||
daemonLog.info("connecting to daemon");
|
||||
}
|
||||
|
||||
//clear message buffer after reconnecting or initial connection
|
||||
socketchunk = "";
|
||||
|
||||
if (config.tnclocation == "localhost") {
|
||||
daemon.connect(3001, "127.0.0.1");
|
||||
} else {
|
||||
daemon.connect(daemon_port, daemon_host);
|
||||
}
|
||||
|
||||
//client.setTimeout(5000);
|
||||
}
|
||||
|
||||
daemon.on("connect", function (err) {
|
||||
daemonLog.info("daemon connection established");
|
||||
let Data = {
|
||||
daemon_connection: daemon.readyState,
|
||||
};
|
||||
ipcRenderer.send("request-update-daemon-connection", Data);
|
||||
|
||||
daemonShowConnectStateError = 1;
|
||||
});
|
||||
|
||||
daemon.on("error", function (err) {
|
||||
if (daemonShowConnectStateError == 1) {
|
||||
daemonLog.error("daemon connection error");
|
||||
daemonLog.info("Make sure the daemon is started.");
|
||||
daemonLog.info('Run "python daemon.py" in the tnc directory.');
|
||||
|
||||
daemonShowConnectStateError = 0;
|
||||
}
|
||||
setTimeout(connectDAEMON, 500);
|
||||
daemon.destroy();
|
||||
let Data = {
|
||||
daemon_connection: daemon.readyState,
|
||||
};
|
||||
ipcRenderer.send("request-update-daemon-connection", Data);
|
||||
});
|
||||
|
||||
/*
|
||||
client.on('close', function(data) {
|
||||
console.log(' TNC connection closed');
|
||||
setTimeout(connectTNC, 2000)
|
||||
let Data = {
|
||||
daemon_connection: daemon.readyState,
|
||||
};
|
||||
ipcRenderer.send('request-update-daemon-connection', Data);
|
||||
});
|
||||
*/
|
||||
|
||||
daemon.on("end", function (data) {
|
||||
daemonLog.warn("daemon connection ended");
|
||||
daemon.destroy();
|
||||
setTimeout(connectDAEMON, 500);
|
||||
let Data = {
|
||||
daemon_connection: daemon.readyState,
|
||||
};
|
||||
ipcRenderer.send("request-update-daemon-connection", Data);
|
||||
});
|
||||
|
||||
//exports.writeCommand = function(command){
|
||||
writeDaemonCommand = function (command) {
|
||||
// we use the writingCommand function to update our TCPIP state because we are calling this function a lot
|
||||
// if socket opened, we are able to run commands
|
||||
if (daemon.readyState == "open") {
|
||||
//uiMain.setDAEMONconnection('open')
|
||||
daemon.write(command + "\n");
|
||||
}
|
||||
|
||||
if (daemon.readyState == "closed") {
|
||||
//uiMain.setDAEMONconnection('closed')
|
||||
}
|
||||
|
||||
if (daemon.readyState == "opening") {
|
||||
//uiMain.setDAEMONconnection('opening')
|
||||
}
|
||||
|
||||
let Data = {
|
||||
daemon_connection: daemon.readyState,
|
||||
};
|
||||
ipcRenderer.send("request-update-daemon-connection", Data);
|
||||
};
|
||||
|
||||
// "https://stackoverflow.com/questions/9070700/nodejs-net-createserver-large-amount-of-data-coming-in"
|
||||
|
||||
daemon.on("data", function (socketdata) {
|
||||
/*
|
||||
inspired by:
|
||||
stackoverflow.com questions 9070700 nodejs-net-createserver-large-amount-of-data-coming-in
|
||||
*/
|
||||
|
||||
socketdata = socketdata.toString("utf8"); // convert data to string
|
||||
socketchunk += socketdata; // append data to buffer so we can stick long data together
|
||||
|
||||
// check if we received begin and end of json data
|
||||
if (socketchunk.startsWith('{"') && socketchunk.endsWith('"}\n')) {
|
||||
var data = "";
|
||||
|
||||
// split data into chunks if we received multiple commands
|
||||
socketchunk = socketchunk.split("\n");
|
||||
data = JSON.parse(socketchunk[0]);
|
||||
|
||||
// search for empty entries in socketchunk and remove them
|
||||
for (i = 0; i < socketchunk.length; i++) {
|
||||
if (socketchunk[i] === "") {
|
||||
socketchunk.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
//iterate through socketchunks array to execute multiple commands in row
|
||||
for (i = 0; i < socketchunk.length; i++) {
|
||||
//check if data is not empty
|
||||
if (socketchunk[i].length > 0) {
|
||||
//try to parse JSON
|
||||
try {
|
||||
data = JSON.parse(socketchunk[i]);
|
||||
} catch (e) {
|
||||
console.log(e); // "SyntaxError
|
||||
daemonLog.error(e);
|
||||
daemonLog.debug(socketchunk[i]);
|
||||
socketchunk = "";
|
||||
}
|
||||
}
|
||||
|
||||
if (data["command"] == "daemon_state") {
|
||||
let Data = {
|
||||
input_devices: data["input_devices"],
|
||||
output_devices: data["output_devices"],
|
||||
python_version: data["python_version"],
|
||||
hamlib_version: data["hamlib_version"],
|
||||
serial_devices: data["serial_devices"],
|
||||
tnc_running_state: data["daemon_state"][0]["status"],
|
||||
ram_usage: data["ram"],
|
||||
cpu_usage: data["cpu"],
|
||||
version: data["version"],
|
||||
};
|
||||
ipcRenderer.send("request-update-daemon-state", Data);
|
||||
}
|
||||
|
||||
if (data["command"] == "test_hamlib") {
|
||||
let Data = {
|
||||
hamlib_result: data["result"],
|
||||
};
|
||||
ipcRenderer.send("request-update-hamlib-test", Data);
|
||||
}
|
||||
}
|
||||
|
||||
//finally delete message buffer
|
||||
socketchunk = "";
|
||||
}
|
||||
});
|
||||
|
||||
function hexToBytes(hex) {
|
||||
for (var bytes = [], c = 0; c < hex.length; c += 2)
|
||||
bytes.push(parseInt(hex.substr(c, 2), 16));
|
||||
return bytes;
|
||||
}
|
||||
|
||||
exports.getDaemonState = function () {
|
||||
//function getDaemonState(){
|
||||
command = '{"type" : "get", "command" : "daemon_state"}';
|
||||
writeDaemonCommand(command);
|
||||
};
|
||||
|
||||
// START TNC
|
||||
// ` `== multi line string
|
||||
|
||||
exports.startTNC = function (
|
||||
mycall,
|
||||
mygrid,
|
||||
rx_audio,
|
||||
tx_audio,
|
||||
radiocontrol,
|
||||
devicename,
|
||||
deviceport,
|
||||
pttprotocol,
|
||||
pttport,
|
||||
serialspeed,
|
||||
data_bits,
|
||||
stop_bits,
|
||||
handshake,
|
||||
rigctld_ip,
|
||||
rigctld_port,
|
||||
enable_fft,
|
||||
enable_scatter,
|
||||
low_bandwidth_mode,
|
||||
tuning_range_fmin,
|
||||
tuning_range_fmax,
|
||||
enable_fsk,
|
||||
tx_audio_level,
|
||||
respond_to_cq,
|
||||
rx_buffer_size,
|
||||
enable_explorer,
|
||||
explorer_stats,
|
||||
auto_tune,
|
||||
tx_delay,
|
||||
tci_ip,
|
||||
tci_port
|
||||
) {
|
||||
var json_command = JSON.stringify({
|
||||
type: "set",
|
||||
command: "start_tnc",
|
||||
parameter: [
|
||||
{
|
||||
mycall: mycall,
|
||||
mygrid: mygrid,
|
||||
rx_audio: rx_audio,
|
||||
tx_audio: tx_audio,
|
||||
radiocontrol: radiocontrol,
|
||||
devicename: devicename,
|
||||
deviceport: deviceport,
|
||||
pttprotocol: pttprotocol,
|
||||
pttport: pttport,
|
||||
serialspeed: serialspeed,
|
||||
data_bits: data_bits,
|
||||
stop_bits: stop_bits,
|
||||
handshake: handshake,
|
||||
rigctld_port: rigctld_port,
|
||||
rigctld_ip: rigctld_ip,
|
||||
enable_scatter: enable_scatter,
|
||||
enable_fft: enable_fft,
|
||||
enable_fsk: enable_fsk,
|
||||
low_bandwidth_mode: low_bandwidth_mode,
|
||||
tuning_range_fmin: tuning_range_fmin,
|
||||
tuning_range_fmax: tuning_range_fmax,
|
||||
tx_audio_level: tx_audio_level,
|
||||
respond_to_cq: respond_to_cq,
|
||||
rx_buffer_size: rx_buffer_size,
|
||||
enable_explorer: enable_explorer,
|
||||
enable_stats: explorer_stats,
|
||||
enable_auto_tune: auto_tune,
|
||||
tx_delay: tx_delay,
|
||||
tci_ip: tci_ip,
|
||||
tci_port: tci_port,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
daemonLog.debug(json_command);
|
||||
writeDaemonCommand(json_command);
|
||||
};
|
||||
|
||||
// STOP TNC
|
||||
exports.stopTNC = function () {
|
||||
command = '{"type" : "set", "command": "stop_tnc" , "parameter": "---" }';
|
||||
writeDaemonCommand(command);
|
||||
};
|
||||
|
||||
// TEST HAMLIB
|
||||
exports.testHamlib = function (
|
||||
radiocontrol,
|
||||
devicename,
|
||||
deviceport,
|
||||
serialspeed,
|
||||
pttprotocol,
|
||||
pttport,
|
||||
data_bits,
|
||||
stop_bits,
|
||||
handshake,
|
||||
rigctld_ip,
|
||||
rigctld_port
|
||||
) {
|
||||
var json_command = JSON.stringify({
|
||||
type: "get",
|
||||
command: "test_hamlib",
|
||||
parameter: [
|
||||
{
|
||||
radiocontrol: radiocontrol,
|
||||
devicename: devicename,
|
||||
deviceport: deviceport,
|
||||
pttprotocol: pttprotocol,
|
||||
pttport: pttport,
|
||||
serialspeed: serialspeed,
|
||||
data_bits: data_bits,
|
||||
stop_bits: stop_bits,
|
||||
handshake: handshake,
|
||||
rigctld_port: rigctld_port,
|
||||
rigctld_ip: rigctld_ip,
|
||||
},
|
||||
],
|
||||
});
|
||||
daemonLog.debug(json_command);
|
||||
writeDaemonCommand(json_command);
|
||||
};
|
||||
|
||||
//Save myCall
|
||||
exports.saveMyCall = function (callsign) {
|
||||
command =
|
||||
'{"type" : "set", "command": "mycallsign" , "parameter": "' +
|
||||
callsign +
|
||||
'"}';
|
||||
writeDaemonCommand(command);
|
||||
};
|
||||
|
||||
// Save myGrid
|
||||
exports.saveMyGrid = function (grid) {
|
||||
command =
|
||||
'{"type" : "set", "command": "mygrid" , "parameter": "' + grid + '"}';
|
||||
writeDaemonCommand(command);
|
||||
};
|
||||
|
||||
ipcRenderer.on("action-update-daemon-ip", (event, arg) => {
|
||||
daemon.destroy();
|
||||
let Data = {
|
||||
busy_state: "-",
|
||||
arq_state: "-",
|
||||
//channel_state: "-",
|
||||
frequency: "-",
|
||||
mode: "-",
|
||||
bandwidth: "-",
|
||||
dbfs_level: 0,
|
||||
};
|
||||
ipcRenderer.send("request-update-tnc-state", Data);
|
||||
daemon_port = arg.port;
|
||||
daemon_host = arg.adress;
|
||||
connectDAEMON();
|
||||
});
|
61
gui/electron-builder.json5
Normal file
61
gui/electron-builder.json5
Normal file
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* @see https://www.electron.build/configuration/configuration
|
||||
*/
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json",
|
||||
"appId": "app.freedata",
|
||||
"asar": true,
|
||||
"afterSign": "build/notarize_macos.js",
|
||||
"productName": "FreeDATA",
|
||||
"directories": {
|
||||
"output": "release"
|
||||
},
|
||||
|
||||
"asarUnpack": [
|
||||
"**/*.wav"
|
||||
],
|
||||
|
||||
|
||||
"files": [
|
||||
"dist",
|
||||
"dist-electron",
|
||||
],
|
||||
|
||||
|
||||
"mac": {
|
||||
"target": [
|
||||
{
|
||||
"target": "default",
|
||||
//"arch": ["universal"],
|
||||
"arch": ["arm64", "x64"],
|
||||
|
||||
}
|
||||
],
|
||||
"notarize": "false",
|
||||
"icon": "build/icon.png",
|
||||
"hardenedRuntime": true,
|
||||
"entitlements": "build/entitlements.plist",
|
||||
"entitlementsInherit": "build/entitlements.plist",
|
||||
"gatekeeperAssess": false,
|
||||
"mergeASARs": true,
|
||||
"x64ArchFiles": "**/*",
|
||||
"artifactName": "${productName}-GUI-Mac-${version}.${ext}"
|
||||
},
|
||||
"win": {
|
||||
"icon": "build/icon.png",
|
||||
"target": [
|
||||
{
|
||||
"target": "portable",
|
||||
"arch": ["arm64", "x64"]
|
||||
}
|
||||
],
|
||||
"artifactName": "${productName}-GUI-Windows-${version}.${ext}"
|
||||
},
|
||||
"linux": {
|
||||
"category": "Development",
|
||||
"target": [
|
||||
"AppImage"
|
||||
],
|
||||
"artifactName": "${productName}-GUI-Linux-${version}.${ext}"
|
||||
}
|
||||
}
|
11
gui/electron/electron-env.d.ts
vendored
Normal file
11
gui/electron/electron-env.d.ts
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
/// <reference types="vite-plugin-electron/electron-env" />
|
||||
|
||||
declare namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
VSCODE_DEBUG?: 'true'
|
||||
DIST_ELECTRON: string
|
||||
DIST: string
|
||||
/** /dist/ or /public/ */
|
||||
VITE_PUBLIC: string
|
||||
}
|
||||
}
|
223
gui/electron/main/index.ts
Normal file
223
gui/electron/main/index.ts
Normal file
|
@ -0,0 +1,223 @@
|
|||
import { app, BrowserWindow, shell, ipcMain } from "electron";
|
||||
import { release, platform } from "os";
|
||||
import { join, dirname } from "path";
|
||||
import { existsSync } from "fs";
|
||||
import { spawn } from "child_process";
|
||||
|
||||
// The built directory structure
|
||||
//
|
||||
// ├─┬ dist-electron
|
||||
// │ ├─┬ main
|
||||
// │ │ └── index.js > Electron-Main
|
||||
// │ └─┬ preload
|
||||
// │ └── index.js > Preload-Scripts
|
||||
// ├─┬ dist
|
||||
// │ └── index.html > Electron-Renderer
|
||||
//
|
||||
process.env.DIST_ELECTRON = join(__dirname, "..");
|
||||
process.env.DIST = join(process.env.DIST_ELECTRON, "../dist");
|
||||
process.env.VITE_PUBLIC = process.env.VITE_DEV_SERVER_URL
|
||||
? join(process.env.DIST_ELECTRON, "../public")
|
||||
: process.env.DIST;
|
||||
|
||||
// Disable GPU Acceleration for Windows 7
|
||||
if (release().startsWith("6.1")) app.disableHardwareAcceleration();
|
||||
|
||||
// Set application name for Windows 10+ notifications
|
||||
if (process.platform === "win32") app.setAppUserModelId(app.getName());
|
||||
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
close_sub_processes();
|
||||
app.quit();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Remove electron security warnings
|
||||
// This warning only shows in development mode
|
||||
// Read more on https://www.electronjs.org/docs/latest/tutorial/security
|
||||
// process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
|
||||
|
||||
// set daemon process var
|
||||
var serverProcess = null;
|
||||
let win: BrowserWindow | null = null;
|
||||
// Here, you can also use other preload
|
||||
const preload = join(__dirname, "../preload/index.js");
|
||||
const url = process.env.VITE_DEV_SERVER_URL;
|
||||
const indexHtml = join(process.env.DIST, "index.html");
|
||||
|
||||
async function createWindow() {
|
||||
win = new BrowserWindow({
|
||||
title: "FreeDATA",
|
||||
width: 1200,
|
||||
height: 670,
|
||||
icon: join(process.env.VITE_PUBLIC, "icon_cube_border.png"),
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
preload,
|
||||
backgroundThrottling: false,
|
||||
// Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
|
||||
// Consider using contextBridge.exposeInMainWorld
|
||||
// Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
},
|
||||
});
|
||||
|
||||
if (process.env.VITE_DEV_SERVER_URL) {
|
||||
// electron-vite-vue#298
|
||||
win.loadURL(url);
|
||||
// Open devTool if the app is not packaged
|
||||
win.webContents.openDevTools();
|
||||
} else {
|
||||
win.loadFile(indexHtml);
|
||||
}
|
||||
|
||||
// Test actively push message to the Electron-Renderer
|
||||
//win.webContents.on("did-finish-load", () => {
|
||||
// win?.webContents.send("main-process-message", new Date().toLocaleString());
|
||||
//});
|
||||
|
||||
// Make all links open with the browser, not with the application
|
||||
win.webContents.setWindowOpenHandler(({ url }) => {
|
||||
if (url.startsWith("https:")) shell.openExternal(url);
|
||||
return { action: "deny" };
|
||||
});
|
||||
// win.webContents.on('will-navigate', (event, url) => { }) #344
|
||||
|
||||
win.once("ready-to-show", () => {
|
||||
//
|
||||
});
|
||||
}
|
||||
|
||||
//app.whenReady().then(
|
||||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
|
||||
console.log(platform());
|
||||
//Generate daemon binary path
|
||||
var serverPath = "";
|
||||
console.log(process.env);
|
||||
|
||||
// Attempt to find Installation Folder
|
||||
console.log(app.getAppPath());
|
||||
console.log(join(app.getAppPath(), "..", ".."));
|
||||
console.log(join(app.getAppPath(), "..", "..", ".."));
|
||||
|
||||
//var basePath = join(app.getAppPath(), '..', '..', '..') || join(process.env.PWD, '..') || join(process.env.INIT_CWD, '..') || join(process.env.DIST, '..', '..', '..');
|
||||
var basePath = join(app.getAppPath(), "..", "..", "..");
|
||||
switch (platform().toLowerCase()) {
|
||||
//case "darwin":
|
||||
//serverPath = join(basePath, "freedata-server", "freedata-server.exe");
|
||||
//serverProcess = spawn(serverPath, [], { detached: true });
|
||||
//serverProcess.unref(); // Allow the server process to continue running independently of the parent process
|
||||
// break;
|
||||
//case "linux":
|
||||
//serverPath = join(basePath, "freedata-server", "freedata-server.exe");
|
||||
//serverProcess = spawn(serverPath, [], { detached: true });
|
||||
//serverProcess.unref(); // Allow the server process to continue running independently of the parent process
|
||||
// break;
|
||||
case "win32":
|
||||
serverPath = join(basePath, "freedata-server", "freedata-server.exe");
|
||||
console.log(`Starting server with path: ${serverPath}`);
|
||||
serverProcess = spawn(
|
||||
"cmd.exe",
|
||||
["/c", "start", "cmd.exe", "/c", serverPath],
|
||||
{ shell: true },
|
||||
);
|
||||
console.log(`Started server | PID: ${serverProcess.pid}`);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("Unhandled OS Platform: ", platform());
|
||||
serverProcess = null;
|
||||
serverPath = null;
|
||||
break;
|
||||
}
|
||||
|
||||
serverProcess.on("error", (err) => {
|
||||
console.error("Failed to start server process:", err);
|
||||
serverProcess = null;
|
||||
serverPath = null;
|
||||
});
|
||||
serverProcess.stdout.on("data", (data) => {
|
||||
//console.log(`stdout: ${data}`);
|
||||
});
|
||||
|
||||
serverProcess.stderr.on("data", (data) => {
|
||||
console.error(`stderr: ${data}`);
|
||||
});
|
||||
});
|
||||
|
||||
app.on("before-quit", () => {
|
||||
close_sub_processes();
|
||||
});
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
win = null;
|
||||
if (process.platform !== "darwin") app.quit();
|
||||
});
|
||||
|
||||
app.on("second-instance", () => {
|
||||
if (win) {
|
||||
// Focus on the main window if the user tried to open another
|
||||
if (win.isMinimized()) win.restore();
|
||||
win.focus();
|
||||
}
|
||||
});
|
||||
|
||||
app.on("activate", () => {
|
||||
const allWindows = BrowserWindow.getAllWindows();
|
||||
if (allWindows.length) {
|
||||
allWindows[0].focus();
|
||||
} else {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
||||
// New window example arg: new windows url
|
||||
ipcMain.handle("open-win", (_, arg) => {
|
||||
const childWindow = new BrowserWindow({
|
||||
webPreferences: {
|
||||
preload,
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
},
|
||||
});
|
||||
|
||||
if (process.env.VITE_DEV_SERVER_URL) {
|
||||
childWindow.loadURL(`${url}#${arg}`);
|
||||
} else {
|
||||
childWindow.loadFile(indexHtml, { hash: arg });
|
||||
}
|
||||
});
|
||||
|
||||
function close_sub_processes() {
|
||||
console.log("Closing sub processes...");
|
||||
|
||||
if (serverProcess != null) {
|
||||
try {
|
||||
console.log(`Killing server process with PID: ${serverProcess.pid}`);
|
||||
|
||||
switch (platform().toLowerCase()) {
|
||||
//case "darwin":
|
||||
// process.kill(serverProcess.pid);
|
||||
// break;
|
||||
//case "linux":
|
||||
// process.kill(serverProcess.pid);
|
||||
// break;
|
||||
case "win32":
|
||||
// For Windows, use taskkill to ensure all child processes are also terminated
|
||||
spawn("taskkill", ["/pid", serverProcess.pid.toString(), "/f", "/t"]);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("Unhandled OS Platform: ", platform());
|
||||
serverProcess = null;
|
||||
serverPath = null;
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error killing server process: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
114
gui/electron/preload/index.ts
Normal file
114
gui/electron/preload/index.ts
Normal file
|
@ -0,0 +1,114 @@
|
|||
import { ipcRenderer } from "electron";
|
||||
|
||||
function domReady(
|
||||
condition: DocumentReadyState[] = ["complete", "interactive"],
|
||||
) {
|
||||
return new Promise((resolve) => {
|
||||
if (condition.includes(document.readyState)) {
|
||||
resolve(true);
|
||||
} else {
|
||||
document.addEventListener("readystatechange", () => {
|
||||
if (condition.includes(document.readyState)) {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const safeDOM = {
|
||||
append(parent: HTMLElement, child: HTMLElement) {
|
||||
if (!Array.from(parent.children).find((e) => e === child)) {
|
||||
return parent.appendChild(child);
|
||||
}
|
||||
},
|
||||
remove(parent: HTMLElement, child: HTMLElement) {
|
||||
if (Array.from(parent.children).find((e) => e === child)) {
|
||||
return parent.removeChild(child);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* https://tobiasahlin.com/spinkit
|
||||
* https://connoratherton.com/loaders
|
||||
* https://projects.lukehaas.me/css-loaders
|
||||
* https://matejkustec.github.io/SpinThatShit
|
||||
*/
|
||||
function useLoading() {
|
||||
const className = `loaders-css__square-spin`;
|
||||
const styleContent = `
|
||||
@keyframes square-spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
background-image: url('icon_cube_border.png'); /* Replace with the URL of your image */
|
||||
background-size: cover; /* Scale the image to cover the entire container */
|
||||
}
|
||||
25% { transform: perspective(100px) rotateX(180deg) rotateY(0);
|
||||
background-image: url('icon_cube_border.png'); /* Replace with the URL of your image */
|
||||
background-size: cover; /* Scale the image to cover the entire container */
|
||||
}
|
||||
|
||||
50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg);
|
||||
background-image: url('icon_cube_border.png'); /* Replace with the URL of your image */
|
||||
background-size: cover; /* Scale the image to cover the entire container */
|
||||
}
|
||||
75% { transform: perspective(100px) rotateX(0) rotateY(180deg);
|
||||
background-image: url('icon_cube_border.png'); /* Replace with the URL of your image */
|
||||
background-size: cover; /* Scale the image to cover the entire container */
|
||||
}
|
||||
100% { transform: perspective(100px) rotateX(0) rotateY(0);
|
||||
background-image: url('icon_cube_border.png'); /* Replace with the URL of your image */
|
||||
background-size: cover; /* Scale the image to cover the entire container */
|
||||
}
|
||||
}
|
||||
.${className} > div {
|
||||
animation-fill-mode: both;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: #fff;
|
||||
animation: square-spin 6s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite;
|
||||
}
|
||||
.app-loading-wrap {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #282c34;
|
||||
z-index: 99999;
|
||||
}
|
||||
`;
|
||||
const oStyle = document.createElement("style");
|
||||
const oDiv = document.createElement("div");
|
||||
|
||||
oStyle.id = "app-loading-style";
|
||||
oStyle.innerHTML = styleContent;
|
||||
oDiv.className = "app-loading-wrap";
|
||||
oDiv.innerHTML = `<div class="${className}"><div></div></div>`;
|
||||
|
||||
return {
|
||||
appendLoading() {
|
||||
safeDOM.append(document.head, oStyle);
|
||||
safeDOM.append(document.body, oDiv);
|
||||
},
|
||||
removeLoading() {
|
||||
safeDOM.remove(document.head, oStyle);
|
||||
safeDOM.remove(document.body, oDiv);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const { appendLoading, removeLoading } = useLoading();
|
||||
domReady().then(appendLoading);
|
||||
|
||||
window.onmessage = (ev) => {
|
||||
ev.data.payload === "removeLoading" && removeLoading();
|
||||
};
|
||||
|
||||
setTimeout(removeLoading, 3999);
|
|
@ -1,37 +0,0 @@
|
|||
const fs = require("fs");
|
||||
const { ipcRenderer } = require("electron");
|
||||
|
||||
/**
|
||||
* Save config and update config setting globally
|
||||
* @param {string} config - config data
|
||||
* @param {string} configPath
|
||||
*/
|
||||
exports.saveConfig = function (config, configPath) {
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
ipcRenderer.send("set-config-global", config);
|
||||
};
|
||||
|
||||
/**
|
||||
* Binary to ASCII replacement
|
||||
* @param {string} data in normal/usual utf-8 format
|
||||
* @returns base64 encoded string
|
||||
*/
|
||||
exports.btoa_FD = function (data) {
|
||||
return Buffer.from(data, "utf-8").toString("base64");
|
||||
};
|
||||
/**
|
||||
* ASCII to Binary replacement
|
||||
* @param {string} data in base64 encoding
|
||||
* @returns utf-8 normal/usual string
|
||||
*/
|
||||
exports.atob_FD = function (data) {
|
||||
return Buffer.from(data, "base64").toString("utf-8");
|
||||
};
|
||||
/**
|
||||
* UTF8 to ASCII btoa
|
||||
* @param {string} data in base64 encoding
|
||||
* @returns base64 bota compatible data for use in browser
|
||||
*/
|
||||
exports.atob = function (data) {
|
||||
return window.btoa(Buffer.from(data, "base64").toString("utf8"));
|
||||
};
|
40
gui/index.html
Normal file
40
gui/index.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="script-src 'self' 'unsafe-inline';"
|
||||
/>
|
||||
<title>FreeDATA</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<script type="module">
|
||||
// debugging code for figuring out correct folder structure in build environment
|
||||
console.log(process.env);
|
||||
|
||||
import { readdir } from "node:fs/promises";
|
||||
import { readdirSync } from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
function walk(dir) {
|
||||
return readdirSync(dir, { withFileTypes: true }).flatMap((file) =>
|
||||
file.isDirectory() ? walk(join(dir, file.name)) : join(dir, file.name),
|
||||
);
|
||||
}
|
||||
|
||||
if (process.env["NODE_ENV"] == "production") {
|
||||
console.log(walk(process.env["APPDIR"]));
|
||||
console.log(walk(process.env["DIST"]));
|
||||
console.log(walk(process.env["DIST_ELECTRON"]));
|
||||
} else {
|
||||
console.log("running in " + process.env["NODE_ENV"]);
|
||||
}
|
||||
</script>
|
1017
gui/main.js
1017
gui/main.js
File diff suppressed because it is too large
Load diff
148
gui/package.json
148
gui/package.json
|
@ -1,110 +1,84 @@
|
|||
{
|
||||
"name": "FreeDATA",
|
||||
"version": "0.9.3-alpha.2",
|
||||
"description": "FreeDATA ",
|
||||
"main": "main.js",
|
||||
"description": "FreeDATA Client application for connecting to FreeDATA server",
|
||||
"private": true,
|
||||
"version": "0.14.5-alpha",
|
||||
"main": "dist-electron/main/index.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0",
|
||||
"npm": ">=9.0.0"
|
||||
"start": "vite",
|
||||
"dev": "vite",
|
||||
"test": "vitest --run",
|
||||
"check": "vue-tsc --noEmit",
|
||||
"build": "vue-tsc --noEmit && vite build && electron-builder -p never",
|
||||
"release": "vue-tsc --noEmit && vite build && electron-builder -p onTag",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint --ext .js,.vue src",
|
||||
"lint-fix": "eslint --ext .js,.vue --fix src",
|
||||
"install-deps": "npm install && npm update"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/DJ2LS/FreeDATA.git"
|
||||
},
|
||||
"keywords": [
|
||||
"TNC",
|
||||
"Modem",
|
||||
"GUI",
|
||||
"FreeDATA",
|
||||
"codec2"
|
||||
],
|
||||
"author": "DJ2LS",
|
||||
"license": "LGPL-2.1",
|
||||
"license": "GPL-3.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/DJ2LS/FreeDATA/issues"
|
||||
},
|
||||
"homepage": "https://freedata.app",
|
||||
"dependencies": {
|
||||
"@electron/asar": "^3.2.3",
|
||||
"@electron/osx-sign": "^1.0.4",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"blob-util": "^2.0.2",
|
||||
"bootstrap": "^5.3.0",
|
||||
"bootstrap-icons": "^1.10.5",
|
||||
"bootswatch": "^5.2.3",
|
||||
"browser-image-compression": "^2.0.0",
|
||||
"chart.js": "^4.2.1",
|
||||
"chartjs-plugin-annotation": "^2.1.2",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-updater": "^5.3.0",
|
||||
"emoji-picker-element": "^1.15.1",
|
||||
"emoji-picker-element-data": "^1.3.0",
|
||||
"express-pouchdb": "^4.2.0",
|
||||
"mime": "^3.0.0",
|
||||
"pouchdb": "^8.0.1",
|
||||
"pouchdb-browser": "^8.0.1",
|
||||
"pouchdb-express-router": "^0.0.11",
|
||||
"pouchdb-find": "^8.0.1",
|
||||
"pouchdb-replication": "^8.0.1",
|
||||
"pouchdb-upsert": "^2.2.0",
|
||||
"qth-locator": "^2.1.0",
|
||||
"utf8": "^3.0.0",
|
||||
"uuid": "^9.0.0"
|
||||
"@electron/notarize": "2.2.1",
|
||||
"@electron/universal": "2.0.1",
|
||||
"@popperjs/core": "2.11.8",
|
||||
"@vueuse/electron": "10.7.2",
|
||||
"blob-util": "2.0.2",
|
||||
"bootstrap": "5.3.2",
|
||||
"bootstrap-icons": "1.11.3",
|
||||
"browser-image-compression": "2.0.2",
|
||||
"chart.js": "4.4.1",
|
||||
"chartjs-plugin-annotation": "3.0.1",
|
||||
"electron-log": "5.1.1",
|
||||
"emoji-picker-element": "1.21.0",
|
||||
"emoji-picker-element-data": "1.6.0",
|
||||
"file-saver": "2.0.5",
|
||||
"gridstack": "10.0.1",
|
||||
"mime": "4.0.1",
|
||||
"nconf": "^0.12.1",
|
||||
"noto-color-emoji": "^1.0.1",
|
||||
"pinia": "2.1.7",
|
||||
"qth-locator": "2.1.0",
|
||||
"socket.io": "4.7.4",
|
||||
"uuid": "^9.0.1",
|
||||
"vue": "3.4.21",
|
||||
"vue-chartjs": "5.3.0",
|
||||
"vuemoji-picker": "0.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/notarize": "^1.2.3",
|
||||
"electron": "^23.0.0",
|
||||
"electron-builder": "^23.6.0",
|
||||
"electron-builder-notarize": "^1.5.1"
|
||||
},
|
||||
"build": {
|
||||
"productName": "FreeDATA",
|
||||
"appId": "app.freedata",
|
||||
"afterSign": "electron-builder-notarize",
|
||||
"npmRebuild": "false",
|
||||
"directories": {
|
||||
"buildResources": "build",
|
||||
"output": "dist"
|
||||
},
|
||||
"mac": {
|
||||
"target": [
|
||||
"default"
|
||||
],
|
||||
"icon": "build/icon.png",
|
||||
"hardenedRuntime": true,
|
||||
"entitlements": "build/entitlements.plist",
|
||||
"entitlementsInherit": "build/entitlements.plist",
|
||||
"gatekeeperAssess": false
|
||||
},
|
||||
"win": {
|
||||
"icon": "build/icon.png",
|
||||
"target": [
|
||||
"nsis"
|
||||
]
|
||||
},
|
||||
"linux": {
|
||||
"icon": "build/icon.png",
|
||||
"target": [
|
||||
"AppImage"
|
||||
],
|
||||
"category": "Development"
|
||||
},
|
||||
"publish": {
|
||||
"provider": "github",
|
||||
"releaseType": "release"
|
||||
},
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "../tnc/dist/tnc/",
|
||||
"to": "tnc",
|
||||
"filter": [
|
||||
"**/*",
|
||||
"!**/.git"
|
||||
]
|
||||
}
|
||||
]
|
||||
"@types/nconf": "^0.10.6",
|
||||
"@typescript-eslint/eslint-plugin": "6.21.0",
|
||||
"@vitejs/plugin-vue": "5.0.4",
|
||||
"electron": "28.2.6",
|
||||
"electron-builder": "24.9.1",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-config-standard-with-typescript": "43.0.1",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-n": "16.6.2",
|
||||
"eslint-plugin-prettier": "5.1.3",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"eslint-plugin-vue": "9.22.0",
|
||||
"typescript": "5.3.3",
|
||||
"vite": "5.1.7",
|
||||
"vite-plugin-electron": "0.28.2",
|
||||
"vite-plugin-electron-renderer": "0.14.5",
|
||||
"vitest": "1.3.1",
|
||||
"vue": "3.4.21",
|
||||
"vue-tsc": "1.8.27"
|
||||
}
|
||||
}
|
||||
|
|
2920
gui/preload-chat.js
2920
gui/preload-chat.js
File diff suppressed because it is too large
Load diff
|
@ -1,186 +0,0 @@
|
|||
const path = require("path");
|
||||
const { ipcRenderer } = require("electron");
|
||||
|
||||
// https://stackoverflow.com/a/26227660
|
||||
var appDataFolder =
|
||||
process.env.APPDATA ||
|
||||
(process.platform == "darwin"
|
||||
? process.env.HOME + "/Library/Application Support"
|
||||
: process.env.HOME + "/.config");
|
||||
var configFolder = path.join(appDataFolder, "FreeDATA");
|
||||
var configPath = path.join(configFolder, "config.json");
|
||||
const config = require(configPath);
|
||||
|
||||
// WINDOW LISTENER
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
document
|
||||
.getElementById("enable_filter_info")
|
||||
.addEventListener("click", () => {
|
||||
if (document.getElementById("enable_filter_info").checked) {
|
||||
display_class("table-info", true);
|
||||
} else {
|
||||
display_class("table-info", false);
|
||||
}
|
||||
});
|
||||
|
||||
document
|
||||
.getElementById("enable_filter_debug")
|
||||
.addEventListener("click", () => {
|
||||
if (document.getElementById("enable_filter_debug").checked) {
|
||||
display_class("table-debug", true);
|
||||
} else {
|
||||
display_class("table-debug", false);
|
||||
}
|
||||
});
|
||||
|
||||
document
|
||||
.getElementById("enable_filter_warning")
|
||||
.addEventListener("click", () => {
|
||||
if (document.getElementById("enable_filter_warning").checked) {
|
||||
display_class("table-warning", true);
|
||||
} else {
|
||||
display_class("table-warning", false);
|
||||
}
|
||||
});
|
||||
|
||||
document
|
||||
.getElementById("enable_filter_error")
|
||||
.addEventListener("click", () => {
|
||||
if (document.getElementById("enable_filter_error").checked) {
|
||||
display_class("table-danger", true);
|
||||
} else {
|
||||
display_class("table-danger", false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function display_class(class_name, state) {
|
||||
var collection = document.getElementsByClassName(class_name);
|
||||
console.log(collection);
|
||||
for (let i = 0; i < collection.length; i++) {
|
||||
if (state == true) {
|
||||
collection[i].style.display = "table-row";
|
||||
} else {
|
||||
collection[i].style.display = "None";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ipcRenderer.on("action-update-log", (event, arg) => {
|
||||
var entry = arg.entry;
|
||||
|
||||
// remove ANSI characters from string, caused by color logging
|
||||
// https://stackoverflow.com/a/29497680
|
||||
entry = entry.replace(
|
||||
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
||||
""
|
||||
);
|
||||
|
||||
var tbl = document.getElementById("log");
|
||||
var row = document.createElement("tr");
|
||||
|
||||
var timestamp = document.createElement("td");
|
||||
var timestampText = document.createElement("span");
|
||||
|
||||
//datetime = new Date();
|
||||
//timestampText.innerText = datetime.toISOString();
|
||||
timestampText.innerText = entry.slice(0, 19);
|
||||
timestamp.appendChild(timestampText);
|
||||
|
||||
var type = document.createElement("td");
|
||||
var typeText = document.createElement("span");
|
||||
// typeText.innerText = entry.slice(10, 30).match(/[\[](.*)[^\]]/g);
|
||||
console.log(entry.match(/\[[^\]]+\]/g));
|
||||
|
||||
try {
|
||||
typeText.innerText = entry.match(/\[[^\]]+\]/g)[0];
|
||||
} catch (e) {
|
||||
typeText.innerText = "-";
|
||||
}
|
||||
|
||||
// let res = str.match(/[\[](.*)[^\]]/g);
|
||||
|
||||
type.appendChild(typeText);
|
||||
|
||||
var area = document.createElement("td");
|
||||
var areaText = document.createElement("span");
|
||||
//areaText.innerText = entry.slice(10, 50).match(/[\] \[](.*)[^\]]/g);
|
||||
//areaText.innerText = entry.match(/\[[^\]]+\]/g)[1];
|
||||
|
||||
try {
|
||||
areaText.innerText = entry.match(/\[[^\]]+\]/g)[1];
|
||||
} catch (e) {
|
||||
areaText.innerText = "-";
|
||||
}
|
||||
area.appendChild(areaText);
|
||||
|
||||
var logEntry = document.createElement("td");
|
||||
var logEntryText = document.createElement("span");
|
||||
try {
|
||||
logEntryText.innerText = entry.split("]")[2];
|
||||
} catch (e) {
|
||||
logEntryText.innerText = "-";
|
||||
}
|
||||
logEntry.appendChild(logEntryText);
|
||||
|
||||
row.appendChild(timestamp);
|
||||
row.appendChild(type);
|
||||
row.appendChild(area);
|
||||
row.appendChild(logEntry);
|
||||
|
||||
//row.classList.add("table-blablubb");
|
||||
/*
|
||||
if (logEntryText.innerText.includes('ALSA lib pcm')) {
|
||||
row.classList.add("table-secondary");
|
||||
}
|
||||
*/
|
||||
if (typeText.innerText.includes("info")) {
|
||||
row.classList.add("table-info");
|
||||
}
|
||||
if (typeText.innerText.includes("debug")) {
|
||||
row.classList.add("table-secondary");
|
||||
}
|
||||
|
||||
if (typeText.innerText.includes("warning")) {
|
||||
row.classList.add("table-warning");
|
||||
}
|
||||
|
||||
if (typeText.innerText.includes("error")) {
|
||||
row.classList.add("table-danger");
|
||||
}
|
||||
|
||||
if (document.getElementById("enable_filter_info").checked) {
|
||||
row.style.display = "table-row";
|
||||
display_class("table-info", true);
|
||||
} else {
|
||||
row.style.display = "None";
|
||||
display_class("table-info", false);
|
||||
}
|
||||
if (document.getElementById("enable_filter_debug").checked) {
|
||||
row.style.display = "table-row";
|
||||
display_class("table-secondary", true);
|
||||
} else {
|
||||
row.style.display = "None";
|
||||
display_class("table-secondary", false);
|
||||
}
|
||||
if (document.getElementById("enable_filter_warning").checked) {
|
||||
row.style.display = "table-row";
|
||||
display_class("table-warning", true);
|
||||
} else {
|
||||
row.style.display = "None";
|
||||
display_class("table-warning", false);
|
||||
}
|
||||
if (document.getElementById("enable_filter_error").checked) {
|
||||
row.style.display = "table-row";
|
||||
display_class("table-danger", true);
|
||||
} else {
|
||||
row.style.display = "None";
|
||||
display_class("table-danger", false);
|
||||
}
|
||||
|
||||
tbl.appendChild(row);
|
||||
|
||||
// scroll to bottom of page
|
||||
// https://stackoverflow.com/a/11715670
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
});
|
3820
gui/preload-main.js
3820
gui/preload-main.js
File diff suppressed because it is too large
Load diff
Before Width: | Height: | Size: 590 KiB After Width: | Height: | Size: 590 KiB |
14
gui/setup.md
Normal file
14
gui/setup.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
https://getbootstrap.com/docs/5.3/getting-started/vite/
|
||||
https://vuejs.org/guide/essentials/event-handling.html#inline-handlers
|
||||
https://linuxhint.com/install-use-bootstrap-with-vue-js/
|
||||
https://github.com/electron-vite
|
||||
https://github.com/electron-vite/electron-vite-vue
|
||||
https://github.com/vuejs/create-vue
|
||||
https://vue-community.org/guide/ecosystem/desktop-apps.html#electron
|
||||
https://blog.logrocket.com/building-app-electron-vue/
|
||||
|
||||
Folder structure
|
||||
dist-electron: Automatically compiled source from vite
|
||||
electron: Source code folder for Electron stuff
|
||||
public: Public data
|
||||
src: VueJS source code
|
928
gui/sock.js
928
gui/sock.js
|
@ -1,928 +0,0 @@
|
|||
var net = require("net");
|
||||
const path = require("path");
|
||||
const { ipcRenderer } = require("electron");
|
||||
const FD = require("./freedata");
|
||||
const log = require("electron-log");
|
||||
const socketLog = log.scope("tnc");
|
||||
//const utf8 = require("utf8");
|
||||
|
||||
// https://stackoverflow.com/a/26227660
|
||||
var appDataFolder =
|
||||
process.env.APPDATA ||
|
||||
(process.platform == "darwin"
|
||||
? process.env.HOME + "/Library/Application Support"
|
||||
: process.env.HOME + "/.config");
|
||||
var configFolder = path.join(appDataFolder, "FreeDATA");
|
||||
var configPath = path.join(configFolder, "config.json");
|
||||
const config = require(configPath);
|
||||
|
||||
var client = new net.Socket();
|
||||
var socketchunk = ""; // Current message, per connection.
|
||||
|
||||
// split character
|
||||
const split_char = "\0;\1;";
|
||||
|
||||
// globals for getting new data only if available so we are saving bandwidth
|
||||
var rxBufferLengthTnc = 0;
|
||||
var rxBufferLengthGui = 0;
|
||||
//var rxMsgBufferLengthTnc = 0;
|
||||
//var rxMsgBufferLengthGui = 0;
|
||||
|
||||
// global to keep track of TNC connection error emissions
|
||||
var tncShowConnectStateError = 1;
|
||||
|
||||
// global for storing ip information
|
||||
var tnc_port = config.tnc_port;
|
||||
var tnc_host = config.tnc_host;
|
||||
|
||||
// network connection Timeout
|
||||
setTimeout(connectTNC, 2000);
|
||||
|
||||
function connectTNC() {
|
||||
//exports.connectTNC = function(){
|
||||
//socketLog.info('connecting to TNC...')
|
||||
|
||||
//clear message buffer after reconnecting or initial connection
|
||||
socketchunk = "";
|
||||
|
||||
if (config.tnclocation == "localhost") {
|
||||
client.connect(3000, "127.0.0.1");
|
||||
} else {
|
||||
client.connect(tnc_port, tnc_host);
|
||||
}
|
||||
}
|
||||
|
||||
client.on("connect", function (data) {
|
||||
socketLog.info("TNC connection established");
|
||||
let Data = {
|
||||
busy_state: "-",
|
||||
arq_state: "-",
|
||||
//channel_state: "-",
|
||||
frequency: "-",
|
||||
mode: "-",
|
||||
bandwidth: "-",
|
||||
dbfs_level: 0,
|
||||
};
|
||||
ipcRenderer.send("request-update-tnc-state", Data);
|
||||
|
||||
// also update tnc connection state
|
||||
ipcRenderer.send("request-update-tnc-connection", {
|
||||
tnc_connection: client.readyState,
|
||||
});
|
||||
|
||||
tncShowConnectStateError = 1;
|
||||
});
|
||||
|
||||
client.on("error", function (data) {
|
||||
if (tncShowConnectStateError == 1) {
|
||||
socketLog.error("TNC connection error");
|
||||
tncShowConnectStateError = 0;
|
||||
}
|
||||
setTimeout(connectTNC, 500);
|
||||
client.destroy();
|
||||
let Data = {
|
||||
tnc_connection: client.readyState,
|
||||
busy_state: "-",
|
||||
arq_state: "-",
|
||||
//channel_state: "-",
|
||||
frequency: "-",
|
||||
mode: "-",
|
||||
bandwidth: "-",
|
||||
dbfs_level: 0,
|
||||
};
|
||||
ipcRenderer.send("request-update-tnc-state", Data);
|
||||
ipcRenderer.send("request-update-tnc-connection", {
|
||||
tnc_connection: client.readyState,
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
client.on('close', function(data) {
|
||||
socketLog.info(' TNC connection closed');
|
||||
setTimeout(connectTNC, 2000)
|
||||
});
|
||||
*/
|
||||
|
||||
client.on("end", function (data) {
|
||||
socketLog.info("TNC connection ended");
|
||||
ipcRenderer.send("request-update-tnc-connection", {
|
||||
tnc_connection: client.readyState,
|
||||
});
|
||||
client.destroy();
|
||||
|
||||
setTimeout(connectTNC, 500);
|
||||
});
|
||||
|
||||
writeTncCommand = function (command) {
|
||||
//socketLog.info(command)
|
||||
// we use the writingCommand function to update our TCPIP state because we are calling this function a lot
|
||||
// if socket opened, we are able to run commands
|
||||
|
||||
if (client.readyState == "open") {
|
||||
client.write(command + "\n");
|
||||
}
|
||||
|
||||
if (client.readyState == "closed") {
|
||||
socketLog.info("CLOSED!");
|
||||
}
|
||||
|
||||
if (client.readyState == "opening") {
|
||||
socketLog.info("connecting to TNC...");
|
||||
}
|
||||
};
|
||||
|
||||
client.on("data", function (socketdata) {
|
||||
ipcRenderer.send("request-update-tnc-connection", {
|
||||
tnc_connection: client.readyState,
|
||||
});
|
||||
|
||||
/*
|
||||
inspired by:
|
||||
stackoverflow.com questions 9070700 nodejs-net-createserver-large-amount-of-data-coming-in
|
||||
*/
|
||||
|
||||
socketdata = socketdata.toString("utf8"); // convert data to string
|
||||
socketchunk += socketdata; // append data to buffer so we can stick long data together
|
||||
|
||||
// check if we received begin and end of json data
|
||||
if (socketchunk.startsWith('{"') && socketchunk.endsWith('"}\n')) {
|
||||
var data = "";
|
||||
|
||||
// split data into chunks if we received multiple commands
|
||||
socketchunk = socketchunk.split("\n");
|
||||
//don't think this is needed anymore
|
||||
//data = JSON.parse(socketchunk[0])
|
||||
|
||||
// search for empty entries in socketchunk and remove them
|
||||
for (i = 0; i < socketchunk.length; i++) {
|
||||
if (socketchunk[i] === "") {
|
||||
socketchunk.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
//iterate through socketchunks array to execute multiple commands in row
|
||||
for (i = 0; i < socketchunk.length; i++) {
|
||||
//check if data is not empty
|
||||
if (socketchunk[i].length > 0) {
|
||||
//try to parse JSON
|
||||
try {
|
||||
data = JSON.parse(socketchunk[i]);
|
||||
} catch (e) {
|
||||
socketLog.info("Throwing away data!!!!\n" + e); // "SyntaxError
|
||||
//socketLog.info(e); // "SyntaxError
|
||||
socketLog.info(socketchunk[i]);
|
||||
socketchunk = "";
|
||||
//If we're here, I don't think we want to process any data that may be in data variable
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (data["command"] == "tnc_state") {
|
||||
//socketLog.info(data)
|
||||
// set length of RX Buffer to global variable
|
||||
rxBufferLengthTnc = data["rx_buffer_length"];
|
||||
//rxMsgBufferLengthTnc = data["rx_msg_buffer_length"];
|
||||
|
||||
let Data = {
|
||||
mycallsign: data["mycallsign"],
|
||||
mygrid: data["mygrid"],
|
||||
ptt_state: data["ptt_state"],
|
||||
busy_state: data["tnc_state"],
|
||||
arq_state: data["arq_state"],
|
||||
arq_session: data["arq_session"],
|
||||
//channel_state: data['CHANNEL_STATE'],
|
||||
frequency: data["frequency"],
|
||||
speed_level: data["speed_level"],
|
||||
mode: data["mode"],
|
||||
bandwidth: data["bandwidth"],
|
||||
dbfs_level: data["audio_dbfs"],
|
||||
fft: data["fft"],
|
||||
channel_busy: data["channel_busy"],
|
||||
channel_busy_slot: data["channel_busy_slot"],
|
||||
scatter: data["scatter"],
|
||||
info: data["info"],
|
||||
rx_buffer_length: data["rx_buffer_length"],
|
||||
rx_msg_buffer_length: data["rx_msg_buffer_length"],
|
||||
tx_n_max_retries: data["tx_n_max_retries"],
|
||||
arq_tx_n_frames_per_burst: data["arq_tx_n_frames_per_burst"],
|
||||
arq_tx_n_bursts: data["arq_tx_n_bursts"],
|
||||
arq_tx_n_current_arq_frame: data["arq_tx_n_current_arq_frame"],
|
||||
arq_tx_n_total_arq_frames: data["arq_tx_n_total_arq_frames"],
|
||||
arq_rx_frame_n_bursts: data["arq_rx_frame_n_bursts"],
|
||||
arq_rx_n_current_arq_frame: data["arq_rx_n_current_arq_frame"],
|
||||
arq_n_arq_frames_per_data_frame:
|
||||
data["arq_n_arq_frames_per_data_frame"],
|
||||
arq_bytes_per_minute: data["arq_bytes_per_minute"],
|
||||
arq_seconds_until_finish: data["arq_seconds_until_finish"],
|
||||
arq_compression_factor: data["arq_compression_factor"],
|
||||
total_bytes: data["total_bytes"],
|
||||
arq_transmission_percent: data["arq_transmission_percent"],
|
||||
stations: data["stations"],
|
||||
beacon_state: data["beacon_state"],
|
||||
hamlib_status: data["hamlib_status"],
|
||||
listen: data["listen"],
|
||||
audio_recording: data["audio_recording"],
|
||||
speed_list: data["speed_list"],
|
||||
strength: data["strength"],
|
||||
is_codec2_traffic: data["is_codec2_traffic"],
|
||||
//speed_table: [{"bpm" : 5200, "snr": -3, "timestamp":1673555399},{"bpm" : 2315, "snr": 12, "timestamp":1673555500}],
|
||||
};
|
||||
|
||||
ipcRenderer.send("request-update-tnc-state", Data);
|
||||
//continue to next for loop iteration, nothing else needs to be done here
|
||||
continue;
|
||||
}
|
||||
|
||||
// ----------- catch tnc messages START -----------
|
||||
if (data["freedata"] == "tnc-message") {
|
||||
switch (data["fec"]) {
|
||||
case "is_writing":
|
||||
// RX'd FECiswriting
|
||||
ipcRenderer.send("request-show-fec-toast-iswriting", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "broadcast":
|
||||
// RX'd FEC BROADCAST
|
||||
var encoded_data = FD.atob_FD(data["data"]);
|
||||
var splitted_data = encoded_data.split(split_char);
|
||||
var messageArray = [];
|
||||
if (splitted_data[0] == "m") {
|
||||
messageArray.push(data);
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
let Messages = {
|
||||
data: messageArray,
|
||||
};
|
||||
ipcRenderer.send("request-new-msg-received", Messages);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data["cq"]) {
|
||||
case "transmitting":
|
||||
// CQ TRANSMITTING
|
||||
ipcRenderer.send("request-show-cq-toast-transmitting", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// CQ RECEIVED
|
||||
ipcRenderer.send("request-show-cq-toast-received", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data["qrv"]) {
|
||||
case "transmitting":
|
||||
// QRV TRANSMITTING
|
||||
ipcRenderer.send("request-show-qrv-toast-transmitting", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// QRV RECEIVED
|
||||
ipcRenderer.send("request-show-qrv-toast-received", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data["beacon"]) {
|
||||
case "transmitting":
|
||||
// BEACON TRANSMITTING
|
||||
ipcRenderer.send("request-show-beacon-toast-transmitting", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// BEACON RECEIVED
|
||||
ipcRenderer.send("request-show-beacon-toast-received", {
|
||||
data: [data],
|
||||
});
|
||||
ipcRenderer.send("request-new-msg-received", { data: [data] });
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data["ping"]) {
|
||||
case "transmitting":
|
||||
// PING TRANSMITTING
|
||||
ipcRenderer.send("request-show-ping-toast-transmitting", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// PING RECEIVED
|
||||
ipcRenderer.send("request-show-ping-toast-received", {
|
||||
data: [data],
|
||||
});
|
||||
ipcRenderer.send("request-new-msg-received", { data: [data] });
|
||||
break;
|
||||
|
||||
case "acknowledge":
|
||||
// PING ACKNOWLEDGE
|
||||
ipcRenderer.send("request-show-ping-toast-received-ack", {
|
||||
data: [data],
|
||||
});
|
||||
ipcRenderer.send("request-new-msg-received", { data: [data] });
|
||||
break;
|
||||
}
|
||||
|
||||
// ARQ SESSION && freedata == tnc-message
|
||||
if (data["arq"] == "session") {
|
||||
switch (data["status"]) {
|
||||
case "connecting":
|
||||
// ARQ Open
|
||||
ipcRenderer.send("request-show-arq-toast-session-connecting", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "connected":
|
||||
// ARQ Opening
|
||||
ipcRenderer.send("request-show-arq-toast-session-connected", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "waiting":
|
||||
// ARQ Opening
|
||||
ipcRenderer.send("request-show-arq-toast-session-waiting", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "close":
|
||||
// ARQ Closing
|
||||
ipcRenderer.send("request-show-arq-toast-session-close", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "failed":
|
||||
// ARQ Failed
|
||||
ipcRenderer.send("request-show-arq-toast-session-failed", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
// ARQ TRANSMISSION && freedata == tnc-message
|
||||
if (data["arq"] == "transmission") {
|
||||
switch (data["status"]) {
|
||||
case "opened":
|
||||
// ARQ Open
|
||||
ipcRenderer.send("request-show-arq-toast-datachannel-opened", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "opening":
|
||||
// ARQ Opening IRS/ISS
|
||||
if (data["irs"] == "False") {
|
||||
ipcRenderer.send("request-show-arq-toast-datachannel-opening", {
|
||||
data: [data],
|
||||
});
|
||||
ipcRenderer.send("request-update-transmission-status", {
|
||||
data: [data],
|
||||
});
|
||||
} else {
|
||||
ipcRenderer.send(
|
||||
"request-show-arq-toast-datachannel-received-opener",
|
||||
{ data: [data] }
|
||||
);
|
||||
ipcRenderer.send("request-update-reception-status", {
|
||||
data: [data],
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "waiting":
|
||||
// ARQ waiting
|
||||
ipcRenderer.send("request-show-arq-toast-datachannel-waiting", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "receiving":
|
||||
// ARQ RX
|
||||
ipcRenderer.send("request-update-reception-status", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "failed":
|
||||
// ARQ TX Failed
|
||||
if (data["reason"] == "protocol version missmatch") {
|
||||
ipcRenderer.send(
|
||||
"request-show-arq-toast-transmission-failed-ver",
|
||||
{ data: [data] }
|
||||
);
|
||||
} else {
|
||||
ipcRenderer.send("request-show-arq-toast-transmission-failed", {
|
||||
data: [data],
|
||||
});
|
||||
}
|
||||
switch (data["irs"]) {
|
||||
case "True":
|
||||
ipcRenderer.send("request-update-reception-status", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
default:
|
||||
ipcRenderer.send("request-update-transmission-status", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// ARQ Received
|
||||
ipcRenderer.send("request-show-arq-toast-transmission-received", {
|
||||
data: [data],
|
||||
});
|
||||
|
||||
ipcRenderer.send("request-update-reception-status", {
|
||||
data: [data],
|
||||
});
|
||||
|
||||
dataArray = [];
|
||||
messageArray = [];
|
||||
|
||||
socketLog.info(data);
|
||||
// we need to encode here to do a deep check for checking if file or message
|
||||
//var encoded_data = atob(data['data'])
|
||||
var encoded_data = FD.atob_FD(data["data"]);
|
||||
var splitted_data = encoded_data.split(split_char);
|
||||
|
||||
if (splitted_data[0] == "f") {
|
||||
dataArray.push(data);
|
||||
}
|
||||
|
||||
if (splitted_data[0] == "m") {
|
||||
messageArray.push(data);
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
rxBufferLengthGui = dataArray.length;
|
||||
let Files = {
|
||||
data: dataArray,
|
||||
};
|
||||
ipcRenderer.send("request-update-rx-buffer", Files);
|
||||
ipcRenderer.send("request-new-msg-received", Files);
|
||||
|
||||
//rxMsgBufferLengthGui = messageArray.length;
|
||||
let Messages = {
|
||||
data: messageArray,
|
||||
};
|
||||
ipcRenderer.send("request-new-msg-received", Messages);
|
||||
break;
|
||||
|
||||
case "transmitting":
|
||||
// ARQ transmitting
|
||||
ipcRenderer.send(
|
||||
"request-show-arq-toast-transmission-transmitting",
|
||||
{ data: [data] }
|
||||
);
|
||||
ipcRenderer.send("request-update-transmission-status", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
|
||||
case "transmitted":
|
||||
// ARQ transmitted
|
||||
ipcRenderer.send(
|
||||
"request-show-arq-toast-transmission-transmitted",
|
||||
{ data: [data] }
|
||||
);
|
||||
ipcRenderer.send("request-update-transmission-status", {
|
||||
data: [data],
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------- catch tnc info messages END -----------
|
||||
|
||||
// if we manually checking for the rx buffer we are getting an array of multiple data
|
||||
if (data["command"] == "rx_buffer") {
|
||||
socketLog.info(data);
|
||||
// iterate through buffer list and sort it to file or message array
|
||||
dataArray = [];
|
||||
messageArray = [];
|
||||
|
||||
for (i = 0; i < data["data-array"].length; i++) {
|
||||
try {
|
||||
// we need to encode here to do a deep check for checking if file or message
|
||||
//var encoded_data = atob(data['data-array'][i]['data'])
|
||||
var encoded_data = FD.atob_FD(data["data-array"][i]["data"]);
|
||||
var splitted_data = encoded_data.split(split_char);
|
||||
|
||||
if (splitted_data[0] == "f") {
|
||||
dataArray.push(data["data-array"][i]);
|
||||
}
|
||||
|
||||
if (splitted_data[0] == "m") {
|
||||
messageArray.push(data["data-array"][i]);
|
||||
}
|
||||
} catch (e) {
|
||||
socketLog.info(e);
|
||||
}
|
||||
}
|
||||
|
||||
rxBufferLengthGui = dataArray.length;
|
||||
let Files = {
|
||||
data: dataArray,
|
||||
};
|
||||
ipcRenderer.send("request-update-rx-buffer", Files);
|
||||
|
||||
//rxMsgBufferLengthGui = messageArray.length;
|
||||
let Messages = {
|
||||
data: messageArray,
|
||||
};
|
||||
//ipcRenderer.send('request-update-rx-msg-buffer', Messages);
|
||||
ipcRenderer.send("request-new-msg-received", Messages);
|
||||
}
|
||||
}
|
||||
|
||||
//finally delete message buffer
|
||||
socketchunk = "";
|
||||
}
|
||||
});
|
||||
|
||||
function hexToBytes(hex) {
|
||||
for (var bytes = [], c = 0; c < hex.length; c += 2)
|
||||
bytes.push(parseInt(hex.substr(c, 2), 16));
|
||||
return bytes;
|
||||
}
|
||||
|
||||
//Get TNC State
|
||||
exports.getTncState = function () {
|
||||
command = '{"type" : "get", "command" : "tnc_state"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
//Get DATA State
|
||||
exports.getDataState = function () {
|
||||
command = '{"type" : "get", "command" : "data_state"}';
|
||||
//writeTncCommand(command)
|
||||
};
|
||||
|
||||
// Send Ping
|
||||
exports.sendPing = function (dxcallsign) {
|
||||
command =
|
||||
'{"type" : "ping", "command" : "ping", "dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// Send CQ
|
||||
exports.sendCQ = function () {
|
||||
command = '{"type" : "broadcast", "command" : "cqcqcq"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// Set AUDIO Level
|
||||
exports.setTxAudioLevel = function (value) {
|
||||
command =
|
||||
'{"type" : "set", "command" : "tx_audio_level", "value" : "' + value + '"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// Send File
|
||||
exports.sendFile = function (
|
||||
dxcallsign,
|
||||
mode,
|
||||
frames,
|
||||
filename,
|
||||
filetype,
|
||||
data,
|
||||
checksum
|
||||
) {
|
||||
socketLog.info(data);
|
||||
socketLog.info(filetype);
|
||||
socketLog.info(filename);
|
||||
|
||||
var datatype = "f";
|
||||
|
||||
data =
|
||||
datatype +
|
||||
split_char +
|
||||
filename +
|
||||
split_char +
|
||||
filetype +
|
||||
split_char +
|
||||
checksum +
|
||||
split_char +
|
||||
data;
|
||||
socketLog.info(data);
|
||||
//socketLog.info(btoa(data))
|
||||
//Btoa / atob will not work with charsets > 8 bits (i.e. the emojis); should probably move away from using it
|
||||
//TODO: Will need to update anyother occurences and throughly test
|
||||
//data = btoa(data)
|
||||
data = FD.btoa_FD(data);
|
||||
|
||||
command =
|
||||
'{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'", "mode" : "' +
|
||||
mode +
|
||||
'", "n_frames" : "' +
|
||||
frames +
|
||||
'", "data" : "' +
|
||||
data +
|
||||
'"}]}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// Send Message
|
||||
exports.sendMessage = function (
|
||||
dxcallsign,
|
||||
mode,
|
||||
frames,
|
||||
data,
|
||||
checksum,
|
||||
uuid,
|
||||
command
|
||||
) {
|
||||
data = FD.btoa_FD(
|
||||
"m" +
|
||||
split_char +
|
||||
command +
|
||||
split_char +
|
||||
checksum +
|
||||
split_char +
|
||||
uuid +
|
||||
split_char +
|
||||
data
|
||||
);
|
||||
command =
|
||||
'{"type" : "arq", "command" : "send_raw", "uuid" : "' +
|
||||
uuid +
|
||||
'", "parameter" : [{"dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'", "mode" : "' +
|
||||
mode +
|
||||
'", "n_frames" : "' +
|
||||
frames +
|
||||
'", "data" : "' +
|
||||
data +
|
||||
'", "attempts": "10"}]}';
|
||||
socketLog.info(command);
|
||||
socketLog.info("-------------------------------------");
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// Send Request message
|
||||
//It would be then „m + split + request + split + request-type“
|
||||
function sendRequest(dxcallsign, mode, frames, data, command) {
|
||||
data = FD.btoa_FD("m" + split_char + command + split_char + data);
|
||||
command =
|
||||
'{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'", "mode" : "' +
|
||||
mode +
|
||||
'", "n_frames" : "' +
|
||||
frames +
|
||||
'", "data" : "' +
|
||||
data +
|
||||
'", "attempts": "10"}]}';
|
||||
socketLog.info(command);
|
||||
socketLog.info("--------------REQ--------------------");
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// Send Response message
|
||||
//It would be then „m + split + request + split + request-type“
|
||||
function sendResponse(dxcallsign, mode, frames, data, command) {
|
||||
data = FD.btoa_FD("m" + split_char + command + split_char + data);
|
||||
command =
|
||||
'{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'", "mode" : "' +
|
||||
mode +
|
||||
'", "n_frames" : "' +
|
||||
frames +
|
||||
'", "data" : "' +
|
||||
data +
|
||||
'", "attempts": "10"}]}';
|
||||
socketLog.info(command);
|
||||
socketLog.info("--------------RES--------------------");
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
//Send station info request
|
||||
exports.sendRequestInfo = function (dxcallsign) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendRequest(dxcallsign, 255, 1, "0", "req");
|
||||
};
|
||||
|
||||
//Send shared folder file list request
|
||||
exports.sendRequestSharedFolderList = function (dxcallsign) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendRequest(dxcallsign, 255, 1, "1", "req");
|
||||
};
|
||||
|
||||
//Send shared file request
|
||||
exports.sendRequestSharedFile = function (dxcallsign, file) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendRequest(dxcallsign, 255, 1, "2" + file, "req");
|
||||
};
|
||||
|
||||
//Send station info response
|
||||
exports.sendResponseInfo = function (dxcallsign, userinfo) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendResponse(dxcallsign, 255, 1, userinfo, "res-0");
|
||||
};
|
||||
|
||||
//Send shared folder response
|
||||
exports.sendResponseSharedFolderList = function (dxcallsign, sharedFolderList) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendResponse(dxcallsign, 255, 1, sharedFolderList, "res-1");
|
||||
};
|
||||
|
||||
//Send shared file response
|
||||
exports.sendResponseSharedFile = function (
|
||||
dxcallsign,
|
||||
sharedFile,
|
||||
sharedFileData
|
||||
) {
|
||||
console.log(
|
||||
"In sendResponseSharedFile",
|
||||
dxcallsign,
|
||||
sharedFile,
|
||||
sharedFileData
|
||||
);
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendResponse(dxcallsign, 255, 1, sharedFile + "/" + sharedFileData, "res-2");
|
||||
};
|
||||
|
||||
//STOP TRANSMISSION
|
||||
exports.stopTransmission = function () {
|
||||
command = '{"type" : "arq", "command": "stop_transmission"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// Get RX BUffer
|
||||
exports.getRxBuffer = function () {
|
||||
command = '{"type" : "get", "command" : "rx_buffer"}';
|
||||
|
||||
// call command only if new data arrived
|
||||
if (rxBufferLengthGui != rxBufferLengthTnc) {
|
||||
writeTncCommand(command);
|
||||
}
|
||||
};
|
||||
|
||||
// START BEACON
|
||||
exports.startBeacon = function (interval) {
|
||||
command =
|
||||
'{"type" : "broadcast", "command" : "start_beacon", "parameter": "' +
|
||||
interval +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// STOP BEACON
|
||||
exports.stopBeacon = function () {
|
||||
command = '{"type" : "broadcast", "command" : "stop_beacon"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// OPEN ARQ SESSION
|
||||
exports.connectARQ = function (dxcallsign) {
|
||||
command =
|
||||
'{"type" : "arq", "command" : "connect", "dxcallsign": "' +
|
||||
dxcallsign +
|
||||
'", "attempts": "10"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// CLOSE ARQ SESSION
|
||||
exports.disconnectARQ = function () {
|
||||
command = '{"type" : "arq", "command" : "disconnect"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// SEND TEST FRAME
|
||||
exports.sendTestFrame = function () {
|
||||
command = '{"type" : "set", "command" : "send_test_frame"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// SEND FEC
|
||||
exports.sendFEC = function (mode, payload) {
|
||||
command =
|
||||
'{"type" : "fec", "command" : "transmit", "mode" : "' +
|
||||
mode +
|
||||
'", "payload" : "' +
|
||||
payload +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// SEND FEC IS WRITING
|
||||
exports.sendFecIsWriting = function (mycallsign) {
|
||||
command =
|
||||
'{"type" : "fec", "command" : "transmit_is_writing", "mycallsign" : "' +
|
||||
mycallsign +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// SEND FEC TO BROADCASTCHANNEL
|
||||
exports.sendBroadcastChannel = function (channel, data_out, uuid) {
|
||||
let checksum = "";
|
||||
let command = "";
|
||||
let data = FD.btoa_FD(
|
||||
"m" +
|
||||
split_char +
|
||||
channel +
|
||||
//split_char +
|
||||
//checksum +
|
||||
split_char +
|
||||
uuid +
|
||||
split_char +
|
||||
data_out
|
||||
);
|
||||
console.log(data.length);
|
||||
let payload = data;
|
||||
command =
|
||||
'{"type" : "fec", "command" : "transmit", "mode": "datac4", "wakeup": "True", "payload" : "' +
|
||||
payload +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// RECORD AUDIO
|
||||
exports.record_audio = function () {
|
||||
command = '{"type" : "set", "command" : "record_audio"}';
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// SET FREQUENCY
|
||||
exports.set_frequency = function (frequency) {
|
||||
command =
|
||||
'{"type" : "set", "command" : "frequency", "frequency": ' + frequency + "}";
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
// SET MODE
|
||||
exports.set_mode = function (mode) {
|
||||
command = '{"type" : "set", "command" : "mode", "mode": "' + mode + '"}';
|
||||
console.log(command);
|
||||
writeTncCommand(command);
|
||||
};
|
||||
|
||||
ipcRenderer.on("action-update-tnc-ip", (event, arg) => {
|
||||
client.destroy();
|
||||
let Data = {
|
||||
busy_state: "-",
|
||||
arq_state: "-",
|
||||
//channel_state: "-",
|
||||
frequency: "-",
|
||||
mode: "-",
|
||||
bandwidth: "-",
|
||||
dbfs_level: 0,
|
||||
};
|
||||
ipcRenderer.send("request-update-tnc-state", Data);
|
||||
tnc_port = arg.port;
|
||||
tnc_host = arg.adress;
|
||||
connectTNC();
|
||||
});
|
||||
|
||||
// https://stackoverflow.com/a/50579690
|
||||
// crc32 calculation
|
||||
//console.log(crc32('abc'));
|
||||
//console.log(crc32('abc').toString(16).toUpperCase()); // hex
|
||||
var crc32 = function (r) {
|
||||
for (var a, o = [], c = 0; c < 256; c++) {
|
||||
a = c;
|
||||
for (var f = 0; f < 8; f++) a = 1 & a ? 3988292384 ^ (a >>> 1) : a >>> 1;
|
||||
o[c] = a;
|
||||
}
|
||||
for (var n = -1, t = 0; t < r.length; t++)
|
||||
n = (n >>> 8) ^ o[255 & (n ^ r.charCodeAt(t))];
|
||||
return (-1 ^ n) >>> 0;
|
||||
};
|
7
gui/src/App.vue
Normal file
7
gui/src/App.vue
Normal file
|
@ -0,0 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import FreeDATAMain from "./components/main.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FreeDATAMain />
|
||||
</template>
|
2312
gui/src/assets/waterfall/spectrum.js
Normal file
2312
gui/src/assets/waterfall/spectrum.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,844 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" data-bs-theme="light">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self';" />
|
||||
<!-- Bootstrap CSS -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
id="bootstrap_theme"
|
||||
href="../node_modules/bootstrap/dist/css/bootstrap.min.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="../node_modules/bootstrap-icons/font/bootstrap-icons.css"
|
||||
/>
|
||||
<!-- Custom CSS -->
|
||||
<link rel="stylesheet" type="text/css" href="styles.css" />
|
||||
<title>FreeDATA - CHAT</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- bootstrap -->
|
||||
<script src="../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- chart.js -->
|
||||
<script src="../node_modules/chart.js/dist/chart.umd.js"></script>
|
||||
<!--<script src="../node_modules/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.min.js"></script>-->
|
||||
<!--<script type="module" src="../node_modules/emoji-picker-element/index.js"></script>-->
|
||||
<script
|
||||
type="module"
|
||||
src="../node_modules/emoji-picker-element/picker.js"
|
||||
></script>
|
||||
<script
|
||||
type="module"
|
||||
src="../node_modules/emoji-picker-element/database.js"
|
||||
></script>
|
||||
<div
|
||||
class="position-absolute container w-100 h-100 bottom-0 end-0 mb-5"
|
||||
style="z-index: 100; display: none"
|
||||
id="emojipickercontainer"
|
||||
>
|
||||
<emoji-picker
|
||||
locale="en"
|
||||
class="position-absolute bottom-0 end-0 p-1 mb-2"
|
||||
data-source="../node_modules/emoji-picker-element-data/en/emojibase/data.json"
|
||||
></emoji-picker>
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<div class="row h-100">
|
||||
<div class="col-4 p-2">
|
||||
<! ------Chats area ---------------------------------------------------------------------->
|
||||
<div class="container-fluid m-0 p-0">
|
||||
<div class="input-group bottom-0 m-0 w-100">
|
||||
<input
|
||||
class="form-control w-50"
|
||||
maxlength="9"
|
||||
style="text-transform: uppercase"
|
||||
id="chatModuleNewDxCall"
|
||||
placeholder="DX CALL"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-sm btn-success"
|
||||
id="createNewChatButton"
|
||||
type="button"
|
||||
title="Start a new chat (enter dx call sign first)"
|
||||
>
|
||||
<i class="bi bi-pencil-square" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id="userModalButton"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#userModal"
|
||||
class="btn btn-sm btn-primary ms-2"
|
||||
title="My station info"
|
||||
>
|
||||
<i class="bi bi-person" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="sharedFolderButton"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#sharedFolderModal"
|
||||
class="btn btn-sm btn-primary"
|
||||
title="My shared folder"
|
||||
>
|
||||
<i class="bi bi-files" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="m-2" />
|
||||
<div class="overflow-auto vh-100">
|
||||
<div
|
||||
class="list-group overflow-auto"
|
||||
id="list-tab"
|
||||
role="tablist"
|
||||
style="height: calc(100vh - 70px)"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-8 border vh-100 p-0">
|
||||
<! ------ chat navbar ---------------------------------------------------------------------->
|
||||
<div class="container-fluid m-2 p-0">
|
||||
<div class="input-group bottom-0">
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary me"
|
||||
id="ping"
|
||||
type="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Ping remote station"
|
||||
>
|
||||
Ping
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id="userModalDXButton"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#userModalDX"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
title="Request remote station's information"
|
||||
>
|
||||
<i class="bi bi-person" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id="sharedFolderDXButton"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#sharedFolderModalDX"
|
||||
class="btn btn-sm btn-outline-secondary me-2"
|
||||
title="Request remote station's shared files"
|
||||
>
|
||||
<i class="bi bi-files" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-small btn-outline-primary dropdown-toggle me-2"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
data-bs-auto-close="outside"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Message filter"
|
||||
>
|
||||
<i class="bi bi-funnel-fill"></i>
|
||||
</button>
|
||||
<form class="dropdown-menu p-4" id="frmFilter">
|
||||
<div class="mb-1">
|
||||
<div class="form-check">
|
||||
<input
|
||||
checked="true"
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="chkMessage"
|
||||
/>
|
||||
<label class="form-check-label" for="chkMessage">
|
||||
All Messages
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<div class="form-check">
|
||||
<input
|
||||
checked="false"
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="chkNewMessage"
|
||||
/>
|
||||
|
||||
<label class="form-check-label" for="chkNewMessage">
|
||||
Unread Messages
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="chkPing"
|
||||
/>
|
||||
<label class="form-check-label" for="chkPing">
|
||||
Pings
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<div class="form-check">
|
||||
<input
|
||||
checked="true"
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="chkPingAck"
|
||||
/>
|
||||
<label class="form-check-label" for="chkPingAck">
|
||||
Ping-Acks
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="chkBeacon"
|
||||
/>
|
||||
<label class="form-check-label" for="chkBeacon">
|
||||
Beacons
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="chkRequest"
|
||||
/>
|
||||
<label class="form-check-label" for="chkRequest">
|
||||
Requests
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="chkResponse"
|
||||
/>
|
||||
<label class="form-check-label" for="chkResponse">
|
||||
Responses
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" id="btnFilter">
|
||||
Refresh
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<button
|
||||
id="chatSettingsDropDown"
|
||||
type="button"
|
||||
class="btn btn-outline-secondary dropdown-toggle"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
title="More options...."
|
||||
>
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="chatSettingsDropDown">
|
||||
<li>
|
||||
<a
|
||||
class="dropdown-item bg-danger text-white"
|
||||
id="delete_selected_chat"
|
||||
href="#"
|
||||
><i
|
||||
class="bi bi-person-x"
|
||||
style="font-size: 1rem"
|
||||
></i>
|
||||
Delete chat</a>
|
||||
</li>
|
||||
<div class="dropdown-divider"></div>
|
||||
<li>
|
||||
<button
|
||||
class="dropdown-item"
|
||||
id="openHelpModalchat"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#chatHelpModal"
|
||||
>
|
||||
<i
|
||||
class="bi bi-question-circle"
|
||||
style="font-size: 1rem"
|
||||
></i>
|
||||
Help
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<span
|
||||
class="input-group-text ms-2"
|
||||
id="txtConnectedWithChat"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
title="Connected with"
|
||||
>------</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="m-0" />
|
||||
<! ------messages area ---------------------------------------------------------------------->
|
||||
<div
|
||||
class="container overflow-auto"
|
||||
id="message-container"
|
||||
style="height: calc(100% - 150px)"
|
||||
>
|
||||
<div class="tab-content" id="nav-tabContent"></div>
|
||||
<!--<div class="container position-absolute bottom-0">-->
|
||||
</div>
|
||||
<!-- </div>-->
|
||||
<div class="container-fluid mt-2 p-0">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="expand_textarea"
|
||||
class="btn-check"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<label
|
||||
class="btn d-flex justify-content-center"
|
||||
id="expand_textarea_label"
|
||||
for="expand_textarea"
|
||||
><i
|
||||
id="expand_textarea_button"
|
||||
class="bi bi-chevron-compact-up"
|
||||
></i
|
||||
></label>
|
||||
|
||||
<div class="input-group bottom-0 ms-2">
|
||||
<!--<input class="form-control" maxlength="8" style="max-width: 6rem; text-transform:uppercase; display:none" id="chatModuleDxCall" placeholder="DX CALL"></input>-->
|
||||
<!--<button class="btn btn-sm btn-primary me-2" id="emojipickerbutton" type="button">-->
|
||||
<div class="input-group-text">
|
||||
<i
|
||||
id="emojipickerbutton"
|
||||
class="bi bi-emoji-smile p-0"
|
||||
style="font-size: 1rem"
|
||||
></i>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
class="form-control"
|
||||
rows="1"
|
||||
id="chatModuleMessage"
|
||||
placeholder="Message - Send with [Enter]"
|
||||
></textarea>
|
||||
|
||||
<div class="input-group-text me-3">
|
||||
<i
|
||||
class="bi bi-paperclip"
|
||||
style="font-size: 1rem"
|
||||
id="selectFilesButton"
|
||||
></i>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-secondary d-none invisible"
|
||||
id="sendMessage"
|
||||
type="button"
|
||||
>
|
||||
<i class="bi bi-send" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- user modal -->
|
||||
|
||||
<div
|
||||
class="modal fade"
|
||||
id="userModal"
|
||||
tabindex="-1"
|
||||
aria-labelledby="userModalLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog" style="max-width: 600px">
|
||||
<div class="modal-content">
|
||||
<div class="card mb-1 border-0">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-4">
|
||||
<div class="row position-relative p-0 m-0">
|
||||
<div class="col p-0 m-0">
|
||||
<img
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktcGVyc29uLWJvdW5kaW5nLWJveCIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNMS41IDFhLjUuNSAwIDAgMC0uNS41djNhLjUuNSAwIDAgMS0xIDB2LTNBMS41IDEuNSAwIDAgMSAxLjUgMGgzYS41LjUgMCAwIDEgMCAxaC0zek0xMSAuNWEuNS41IDAgMCAxIC41LS41aDNBMS41IDEuNSAwIDAgMSAxNiAxLjV2M2EuNS41IDAgMCAxLTEgMHYtM2EuNS41IDAgMCAwLS41LS41aC0zYS41LjUgMCAwIDEtLjUtLjV6TS41IDExYS41LjUgMCAwIDEgLjUuNXYzYS41LjUgMCAwIDAgLjUuNWgzYS41LjUgMCAwIDEgMCAxaC0zQTEuNSAxLjUgMCAwIDEgMCAxNC41di0zYS41LjUgMCAwIDEgLjUtLjV6bTE1IDBhLjUuNSAwIDAgMSAuNS41djNhMS41IDEuNSAwIDAgMS0xLjUgMS41aC0zYS41LjUgMCAwIDEgMC0xaDNhLjUuNSAwIDAgMCAuNS0uNXYtM2EuNS41IDAgMCAxIC41LS41eiIvPgogIDxwYXRoIGQ9Ik0zIDE0cy0xIDAtMS0xIDEtNCA2LTQgNiAzIDYgNC0xIDEtMSAxSDN6bTgtOWEzIDMgMCAxIDEtNiAwIDMgMyAwIDAgMSA2IDB6Ii8+Cjwvc3ZnPg=="
|
||||
class="img-fluid rounded-start w-100"
|
||||
alt="..."
|
||||
id="user_info_image"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="col position-absolute image-overlay text-white justify-content-center align-items-center d-flex align-middle h-100 opacity-0"
|
||||
id="userImageSelector"
|
||||
>
|
||||
<i class="bi bi-upload" style="font-size: 2.2rem"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card-body">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-pass"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Callsign"
|
||||
id="user_info_callsign"
|
||||
aria-label="Call"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-person-vcard"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="name"
|
||||
id="user_info_name"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-sunrise"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="age"
|
||||
id="user_info_age"
|
||||
aria-label="age"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-house"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Location"
|
||||
id="user_info_location"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-pin-map"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Grid"
|
||||
id="user_info_gridsquare"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-projector"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Radio"
|
||||
id="user_info_radio"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-broadcast-pin"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Antenna"
|
||||
id="user_info_antenna"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-envelope"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Email"
|
||||
id="user_info_email"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-globe"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Website"
|
||||
id="user_info_website"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-info-circle"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Comments"
|
||||
id="user_info_comments"
|
||||
aria-label="Comments"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
id="userInfoSave"
|
||||
>
|
||||
Save & Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- dx user modal -->
|
||||
<div
|
||||
class="modal fade"
|
||||
id="userModalDX"
|
||||
tabindex="-1"
|
||||
aria-labelledby="userModalDXLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog" style="max-width: 600px">
|
||||
<div class="modal-content">
|
||||
<div class="card mb-1 border-0">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-4">
|
||||
<img
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktcGVyc29uLWJvdW5kaW5nLWJveCIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNMS41IDFhLjUuNSAwIDAgMC0uNS41djNhLjUuNSAwIDAgMS0xIDB2LTNBMS41IDEuNSAwIDAgMSAxLjUgMGgzYS41LjUgMCAwIDEgMCAxaC0zek0xMSAuNWEuNS41IDAgMCAxIC41LS41aDNBMS41IDEuNSAwIDAgMSAxNiAxLjV2M2EuNS41IDAgMCAxLTEgMHYtM2EuNS41IDAgMCAwLS41LS41aC0zYS41LjUgMCAwIDEtLjUtLjV6TS41IDExYS41LjUgMCAwIDEgLjUuNXYzYS41LjUgMCAwIDAgLjUuNWgzYS41LjUgMCAwIDEgMCAxaC0zQTEuNSAxLjUgMCAwIDEgMCAxNC41di0zYS41LjUgMCAwIDEgLjUtLjV6bTE1IDBhLjUuNSAwIDAgMSAuNS41djNhMS41IDEuNSAwIDAgMS0xLjUgMS41aC0zYS41LjUgMCAwIDEgMC0xaDNhLjUuNSAwIDAgMCAuNS0uNXYtM2EuNS41IDAgMCAxIC41LS41eiIvPgogIDxwYXRoIGQ9Ik0zIDE0cy0xIDAtMS0xIDEtNCA2LTQgNiAzIDYgNC0xIDEtMSAxSDN6bTgtOWEzIDMgMCAxIDEtNiAwIDMgMyAwIDAgMSA2IDB6Ii8+Cjwvc3ZnPg=="
|
||||
class="img-fluid rounded-start w-100"
|
||||
alt="..."
|
||||
id="dx_user_info_image"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card-body">
|
||||
<h5>
|
||||
<span
|
||||
class="badge bg-secondary"
|
||||
id="dx_user_info_callsign"
|
||||
></span>
|
||||
-
|
||||
<span
|
||||
class="badge bg-secondary"
|
||||
id="dx_user_info_name"
|
||||
></span>
|
||||
<span
|
||||
class="badge bg-secondary"
|
||||
id="dx_user_info_age"
|
||||
></span>
|
||||
</h5>
|
||||
|
||||
<ul class="card-text list-unstyled">
|
||||
<li>
|
||||
<strong class="col"><i class="bi bi-house"></i> </strong
|
||||
><span id="dx_user_info_location"></span> (<span
|
||||
id="dx_user_info_gridsquare"
|
||||
></span
|
||||
>)
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"
|
||||
><i class="bi bi-envelope"></i> </strong
|
||||
><span id="dx_user_info_email"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"><i class="bi bi-globe"></i> </strong
|
||||
><span id="dx_user_info_website"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"
|
||||
><i class="bi bi-broadcast-pin"></i> </strong
|
||||
><span id="dx_user_info_antenna"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"
|
||||
><i class="bi bi-projector"></i> </strong
|
||||
><span id="dx_user_info_radio"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"
|
||||
><i class="bi bi-info-circle"></i> </strong
|
||||
><span id="dx_user_info_comments"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm m-0 p-0">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-warning w-75"
|
||||
aria-label="Request"
|
||||
id="requestUserInfo"
|
||||
>
|
||||
Request user data (about 20kBytes!)
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary w-25"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- user shared folder -->
|
||||
<div
|
||||
class="modal fade"
|
||||
id="sharedFolderModal"
|
||||
tabindex="-1"
|
||||
aria-labelledby="sharedFolderModalLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="sharedFolderModalLabel">
|
||||
My Shared folder
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
id="openSharedFilesFolder"
|
||||
>
|
||||
<i class="bi bi-archive"></i>
|
||||
</button>
|
||||
</h1>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="center mb-1">
|
||||
<div class="badge text-bg-info">
|
||||
<i class="bi bi-info"></i> Change folder in settings!
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<!-- START OF TABLE FOR SHARED FOLDER -->
|
||||
<table
|
||||
class="table table-sm table-hover table-bordered align-middle"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Size</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sharedFolderTable"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- HELP MODAL -->
|
||||
<div
|
||||
class="modal fade"
|
||||
data-bs-backdrop="static"
|
||||
tabindex="-1"
|
||||
id="chatHelpModal"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Chat Help</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
Welcome to the chat window. Heard stations are listed in the
|
||||
list on the left. Clicking on a station will show messages
|
||||
sent and/or received from the selected station. Additional
|
||||
help is available on various extra features below.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<button type="button" class="btn btn-sm btn-primary ms-2">
|
||||
<i class="bi bi-person" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
<p class="card-text">
|
||||
Set your station information and picture. This information can
|
||||
be requested by a remote station and can be enabled/disabled
|
||||
via settings.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-secondary ms-2"
|
||||
>
|
||||
<i class="bi bi-person" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
<p class="card-text">
|
||||
Request the selected station's information.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-secondary ms-2"
|
||||
>
|
||||
<i class="bi bi-files" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
<p class="card-text">
|
||||
Request the selected station's shared file(s) list. Clicking
|
||||
<button type="button" class="btn btn-sm btn-primary ms-2">
|
||||
<i class="bi bi-files" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
will allow you to preview your shared files. Shared file can
|
||||
be enabled/disabled in settings.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-small btn-outline-primary dropdown-toggle me-2"
|
||||
>
|
||||
<i class="bi bi-funnel-fill"></i>
|
||||
</button>
|
||||
<p class="card-text">
|
||||
The filter button allows you to show or hide certain types of
|
||||
messages. A lot of data is logged and this allows you to
|
||||
modify what is shown. By default sent and received messages
|
||||
and ping acknowlegements are displayed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- dx user shared folder -->
|
||||
<div
|
||||
class="modal fade"
|
||||
id="sharedFolderModalDX"
|
||||
tabindex="-1"
|
||||
aria-labelledby="sharedFolderModalDXLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="sharedFolderModalDXLabel">
|
||||
Shared folder
|
||||
</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary m-2"
|
||||
aria-label="Request"
|
||||
id="requestSharedFolderList"
|
||||
>
|
||||
<i class="bi bi-arrow-repeat"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid">
|
||||
<div class="table-responsive">
|
||||
<!-- START OF TABLE FOR SHARED FOLDER DX -->
|
||||
<table
|
||||
class="table table-sm table-hover table-bordered align-middle"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Size</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sharedFolderTableDX"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="input-group input-group-sm m-0 p-0"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
647
gui/src/components/chat.vue
Normal file
647
gui/src/components/chat.vue
Normal file
|
@ -0,0 +1,647 @@
|
|||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
// disable typescript check beacuse of error with beacon histogram options
|
||||
|
||||
import chat_conversations from "./chat_conversations.vue";
|
||||
import chat_messages from "./chat_messages.vue";
|
||||
import chat_new_message from "./chat_new_message.vue";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useChatStore } from "../store/chatStore.js";
|
||||
const chat = useChatStore(pinia);
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
BarElement,
|
||||
} from "chart.js";
|
||||
|
||||
import { Bar } from "vue-chartjs";
|
||||
import { watch, nextTick, ref, computed } from "vue";
|
||||
import annotationPlugin from "chartjs-plugin-annotation";
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
BarElement,
|
||||
annotationPlugin,
|
||||
);
|
||||
|
||||
var beaconHistogramOptions = {
|
||||
type: "bar",
|
||||
bezierCurve: false, //remove curves from your plot
|
||||
scaleShowLabels: false, //remove labels
|
||||
tooltipEvents: [], //remove trigger from tooltips so they will'nt be show
|
||||
pointDot: false, //remove the points markers
|
||||
scaleShowGridLines: true, //set to false to remove the grids background
|
||||
maintainAspectRatio: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
annotation: {
|
||||
annotations: [
|
||||
{
|
||||
type: "line",
|
||||
mode: "horizontal",
|
||||
scaleID: "y",
|
||||
value: 0,
|
||||
borderColor: "darkgrey", // Set the color to dark grey for the zero line
|
||||
borderWidth: 0.5, // Set the line width
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
scales: {
|
||||
x: {
|
||||
position: "bottom",
|
||||
display: false,
|
||||
min: -10,
|
||||
max: 15,
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
display: false,
|
||||
min: -5,
|
||||
max: 10,
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const beaconHistogramData = computed(() => ({
|
||||
labels: chat.beaconLabelArray,
|
||||
datasets: [
|
||||
{
|
||||
data: chat.beaconDataArray,
|
||||
tension: 0.1,
|
||||
borderColor: "rgb(0, 255, 0)",
|
||||
|
||||
backgroundColor: function (context) {
|
||||
var value = context.dataset.data[context.dataIndex];
|
||||
return value >= 0 ? "green" : "red";
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
const messagesContainer = ref(null);
|
||||
watch(
|
||||
() => chat.scrollTrigger,
|
||||
(newVal, oldVal) => {
|
||||
//console.log("Trigger changed from", oldVal, "to", newVal); // Debugging line
|
||||
nextTick(() => {
|
||||
if (messagesContainer.value) {
|
||||
messagesContainer.value.scrollTop =
|
||||
messagesContainer.value.scrollHeight;
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container-fluid m-0 p-0">
|
||||
<div class="row h-100 ms-0 mt-0 me-1">
|
||||
<div class="col-3 m-0 p-0 h-100 bg-light">
|
||||
<!------Chats area ---------------------------------------------------------------------->
|
||||
<div class="container-fluid vh-100 overflow-auto m-0 p-0">
|
||||
<chat_conversations />
|
||||
</div>
|
||||
<div class="h-100">
|
||||
<div
|
||||
class="list-group overflow-auto"
|
||||
id="list-tab-chat"
|
||||
role="tablist"
|
||||
style="height: calc(100vh - 70px)"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9 border-start vh-100 p-0">
|
||||
<div class="d-flex flex-column vh-100">
|
||||
<!-- Top Navbar -->
|
||||
<nav class="navbar sticky-top z-0 bg-body-tertiary shadow">
|
||||
<div class="input-group mb-0 p-0 w-25">
|
||||
<button type="button" class="btn btn-outline-secondary" disabled>
|
||||
Beacons
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="form-floating border border-secondary-subtle border-1 rounded-end"
|
||||
>
|
||||
<Bar
|
||||
:data="beaconHistogramData"
|
||||
:options="beaconHistogramOptions"
|
||||
width="300"
|
||||
height="50"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Chat Messages Area -->
|
||||
<div class="flex-grow-1 overflow-auto" ref="messagesContainer">
|
||||
<chat_messages />
|
||||
</div>
|
||||
|
||||
<chat_new_message />
|
||||
</div>
|
||||
|
||||
<!------ new message area ---------------------------------------------------------------------->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- user modal -->
|
||||
|
||||
<div
|
||||
class="modal fade"
|
||||
id="userModal"
|
||||
tabindex="-1"
|
||||
aria-labelledby="userModalLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog" style="max-width: 600px">
|
||||
<div class="modal-content">
|
||||
<div class="card mb-1 border-0">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-4">
|
||||
<div class="row position-relative p-0 m-0">
|
||||
<div class="col p-0 m-0">
|
||||
<img
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktcGVyc29uLWJvdW5kaW5nLWJveCIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNMS41IDFhLjUuNSAwIDAgMC0uNS41djNhLjUuNSAwIDAgMS0xIDB2LTNBMS41IDEuNSAwIDAgMSAxLjUgMGgzYS41LjUgMCAwIDEgMCAxaC0zek0xMSAuNWEuNS41IDAgMCAxIC41LS41aDNBMS41IDEuNSAwIDAgMSAxNiAxLjV2M2EuNS41IDAgMCAxLTEgMHYtM2EuNS41IDAgMCAwLS41LS41aC0zYS41LjUgMCAwIDEtLjUtLjV6TS41IDExYS41LjUgMCAwIDEgLjUuNXYzYS41LjUgMCAwIDAgLjUuNWgzYS41LjUgMCAwIDEgMCAxaC0zQTEuNSAxLjUgMCAwIDEgMCAxNC41di0zYS41LjUgMCAwIDEgLjUtLjV6bTE1IDBhLjUuNSAwIDAgMSAuNS41djNhMS41IDEuNSAwIDAgMS0xLjUgMS41aC0zYS41LjUgMCAwIDEgMC0xaDNhLjUuNSAwIDAgMCAuNS0uNXYtM2EuNS41IDAgMCAxIC41LS41eiIvPgogIDxwYXRoIGQ9Ik0zIDE0cy0xIDAtMS0xIDEtNCA2LTQgNiAzIDYgNC0xIDEtMSAxSDN6bTgtOWEzIDMgMCAxIDEtNiAwIDMgMyAwIDAgMSA2IDB6Ii8+Cjwvc3ZnPg=="
|
||||
class="img-fluid rounded-start w-100"
|
||||
alt="..."
|
||||
id="user_info_image"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="col position-absolute image-overlay text-white justify-content-center align-items-center d-flex align-middle h-100 opacity-0"
|
||||
id="userImageSelector"
|
||||
>
|
||||
<i class="bi bi-upload" style="font-size: 2.2rem"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card-body">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-pass"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Callsign"
|
||||
id="user_info_callsign"
|
||||
aria-label="Call"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-person-vcard"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="name"
|
||||
id="user_info_name"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-sunrise"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="age"
|
||||
id="user_info_age"
|
||||
aria-label="age"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-house"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Location"
|
||||
id="user_info_location"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-pin-map"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Grid"
|
||||
id="user_info_gridsquare"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-projector"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Radio"
|
||||
id="user_info_radio"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-broadcast-pin"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Antenna"
|
||||
id="user_info_antenna"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-envelope"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Email"
|
||||
id="user_info_email"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-globe"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Website"
|
||||
id="user_info_website"
|
||||
aria-label="Name"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text"
|
||||
><i class="bi bi-info-circle"></i
|
||||
></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Comments"
|
||||
id="user_info_comments"
|
||||
aria-label="Comments"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
id="userInfoSave"
|
||||
>
|
||||
Save & Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- dx user modal -->
|
||||
<div
|
||||
class="modal fade"
|
||||
id="userModalDX"
|
||||
tabindex="-1"
|
||||
aria-labelledby="userModalDXLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog" style="max-width: 600px">
|
||||
<div class="modal-content">
|
||||
<div class="card mb-1 border-0">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-4">
|
||||
<img
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktcGVyc29uLWJvdW5kaW5nLWJveCIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNMS41IDFhLjUuNSAwIDAgMC0uNS41djNhLjUuNSAwIDAgMS0xIDB2LTNBMS41IDEuNSAwIDAgMSAxLjUgMGgzYS41LjUgMCAwIDEgMCAxaC0zek0xMSAuNWEuNS41IDAgMCAxIC41LS41aDNBMS41IDEuNSAwIDAgMSAxNiAxLjV2M2EuNS41IDAgMCAxLTEgMHYtM2EuNS41IDAgMCAwLS41LS41aC0zYS41LjUgMCAwIDEtLjUtLjV6TS41IDExYS41LjUgMCAwIDEgLjUuNXYzYS41LjUgMCAwIDAgLjUuNWgzYS41LjUgMCAwIDEgMCAxaC0zQTEuNSAxLjUgMCAwIDEgMCAxNC41di0zYS41LjUgMCAwIDEgLjUtLjV6bTE1IDBhLjUuNSAwIDAgMSAuNS41djNhMS41IDEuNSAwIDAgMS0xLjUgMS41aC0zYS41LjUgMCAwIDEgMC0xaDNhLjUuNSAwIDAgMCAuNS0uNXYtM2EuNS41IDAgMCAxIC41LS41eiIvPgogIDxwYXRoIGQ9Ik0zIDE0cy0xIDAtMS0xIDEtNCA2LTQgNiAzIDYgNC0xIDEtMSAxSDN6bTgtOWEzIDMgMCAxIDEtNiAwIDMgMyAwIDAgMSA2IDB6Ii8+Cjwvc3ZnPg=="
|
||||
class="img-fluid rounded-start w-100"
|
||||
alt="..."
|
||||
id="dx_user_info_image"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card-body">
|
||||
<h5>
|
||||
<span
|
||||
class="badge bg-secondary"
|
||||
id="dx_user_info_callsign"
|
||||
></span>
|
||||
-
|
||||
<span
|
||||
class="badge bg-secondary"
|
||||
id="dx_user_info_name"
|
||||
></span>
|
||||
<span class="badge bg-secondary" id="dx_user_info_age"></span>
|
||||
</h5>
|
||||
|
||||
<ul class="card-text list-unstyled">
|
||||
<li>
|
||||
<strong class="col"><i class="bi bi-house"></i> </strong
|
||||
><span id="dx_user_info_location"></span> (<span
|
||||
id="dx_user_info_gridsquare"
|
||||
></span
|
||||
>)
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"><i class="bi bi-envelope"></i> </strong
|
||||
><span id="dx_user_info_email"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"><i class="bi bi-globe"></i> </strong
|
||||
><span id="dx_user_info_website"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"
|
||||
><i class="bi bi-broadcast-pin"></i> </strong
|
||||
><span id="dx_user_info_antenna"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"><i class="bi bi-projector"></i> </strong
|
||||
><span id="dx_user_info_radio"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="col"
|
||||
><i class="bi bi-info-circle"></i> </strong
|
||||
><span id="dx_user_info_comments"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm m-0 p-0">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-warning w-75"
|
||||
aria-label="Request"
|
||||
id="requestUserInfo"
|
||||
>
|
||||
Request user data (about 20kBytes!)
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary w-25"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- user shared folder -->
|
||||
<div
|
||||
class="modal fade"
|
||||
id="sharedFolderModal"
|
||||
tabindex="-1"
|
||||
aria-labelledby="sharedFolderModalLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="sharedFolderModalLabel">
|
||||
My Shared folder
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
id="openSharedFilesFolder"
|
||||
>
|
||||
<i class="bi bi-archive"></i>
|
||||
</button>
|
||||
</h1>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="center mb-1">
|
||||
<div class="badge text-bg-info">
|
||||
<i class="bi bi-info"></i> Change folder in settings!
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<!-- START OF TABLE FOR SHARED FOLDER -->
|
||||
<table
|
||||
class="table table-sm table-hover table-bordered align-middle"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Size</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sharedFolderTable"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- HELP MODAL -->
|
||||
<div
|
||||
class="modal fade"
|
||||
data-bs-backdrop="static"
|
||||
tabindex="-1"
|
||||
id="chatHelpModal"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Chat Help</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
Welcome to the chat window. Heard stations are listed in the
|
||||
list on the left. Clicking on a station will show messages sent
|
||||
and/or received from the selected station. Additional help is
|
||||
available on various extra features below.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<button type="button" class="btn btn-sm btn-primary ms-2">
|
||||
<i class="bi bi-person" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
<p class="card-text">
|
||||
Set your station information and picture. This information can
|
||||
be requested by a remote station and can be enabled/disabled via
|
||||
settings.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-secondary ms-2"
|
||||
>
|
||||
<i class="bi bi-person" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
<p class="card-text">
|
||||
Request the selected station's information.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-secondary ms-2"
|
||||
>
|
||||
<i class="bi bi-files" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
<p class="card-text">
|
||||
Request the selected station's shared file(s) list. Clicking
|
||||
<button type="button" class="btn btn-sm btn-primary ms-2">
|
||||
<i class="bi bi-files" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
will allow you to preview your shared files. Shared file can be
|
||||
enabled/disabled in settings.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-small btn-outline-primary dropdown-toggle me-2"
|
||||
>
|
||||
<i class="bi bi-funnel-fill"></i>
|
||||
</button>
|
||||
<p class="card-text">
|
||||
The filter button allows you to show or hide certain types of
|
||||
messages. A lot of data is logged and this allows you to modify
|
||||
what is shown. By default sent and received messages and ping
|
||||
acknowlegements are displayed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- dx user shared folder -->
|
||||
<div
|
||||
class="modal fade"
|
||||
id="sharedFolderModalDX"
|
||||
tabindex="-1"
|
||||
aria-labelledby="sharedFolderModalDXLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="sharedFolderModalDXLabel">
|
||||
Shared folder
|
||||
</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary m-2"
|
||||
aria-label="Request"
|
||||
id="requestSharedFolderList"
|
||||
>
|
||||
<i class="bi bi-arrow-repeat"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid">
|
||||
<div class="table-responsive">
|
||||
<!-- START OF TABLE FOR SHARED FOLDER DX -->
|
||||
<table
|
||||
class="table table-sm table-hover table-bordered align-middle"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Size</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sharedFolderTableDX"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="input-group input-group-sm m-0 p-0"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
96
gui/src/components/chat_conversations.vue
Normal file
96
gui/src/components/chat_conversations.vue
Normal file
|
@ -0,0 +1,96 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useChatStore } from "../store/chatStore.js";
|
||||
import { getBeaconDataByCallsign } from "../js/api.js";
|
||||
|
||||
import { ref, computed } from "vue";
|
||||
|
||||
const chat = useChatStore(pinia);
|
||||
|
||||
function chatSelected(callsign) {
|
||||
chat.selectedCallsign = callsign.toUpperCase();
|
||||
// scroll message container to bottom
|
||||
chat.triggerScrollToBottom();
|
||||
|
||||
processBeaconData(callsign);
|
||||
}
|
||||
|
||||
async function processBeaconData(callsign) {
|
||||
// fetch beacon data when selecting a callsign
|
||||
let beacons = await getBeaconDataByCallsign(callsign);
|
||||
chat.beaconLabelArray = beacons.map((entry) => entry.timestamp);
|
||||
chat.beaconDataArray = beacons.map((entry) => entry.snr);
|
||||
}
|
||||
|
||||
function getDateTime(timestamp) {
|
||||
let date = new Date(timestamp);
|
||||
let hours = date.getHours().toString().padStart(2, "0");
|
||||
let minutes = date.getMinutes().toString().padStart(2, "0");
|
||||
let seconds = date.getSeconds().toString().padStart(2, "0");
|
||||
return `${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
const newChatCall = ref(null);
|
||||
|
||||
function newChat() {
|
||||
let callsign = this.newChatCall.value;
|
||||
callsign = callsign.toUpperCase().trim();
|
||||
if (callsign === "") return;
|
||||
this.newChatCall.value = "";
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<nav class="navbar sticky-top bg-body-tertiary shadow">
|
||||
<button
|
||||
class="btn btn-outline-primary w-100"
|
||||
data-bs-target="#newChatModal"
|
||||
data-bs-toggle="modal"
|
||||
>
|
||||
<i class="bi bi-pencil-square"> Start a new chat</i>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div
|
||||
class="list-group bg-body-tertiary m-0 p-1"
|
||||
id="chat-list-tab"
|
||||
role="chat-tablist"
|
||||
>
|
||||
<template
|
||||
v-for="(details, callsign, key) in chat.callsign_list"
|
||||
:key="callsign"
|
||||
>
|
||||
<a
|
||||
class="list-group-item list-group-item-action list-group-item-secondary rounded-2 border-0 mb-2"
|
||||
:class="{ active: key == 0 }"
|
||||
:id="`list-chat-list-${callsign}`"
|
||||
data-bs-toggle="list"
|
||||
:href="`#list-${callsign}-messages`"
|
||||
role="tab"
|
||||
aria-controls="list-{{callsign}}-messages"
|
||||
@click="chatSelected(callsign)"
|
||||
>
|
||||
<div class="row">
|
||||
<div class="col-9 text-truncate">
|
||||
<strong>{{ callsign }}</strong>
|
||||
<br />
|
||||
<small> {{ details.body }} </small>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<small> {{ getDateTime(details.timestamp) }} </small>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary ms-2 border-0"
|
||||
data-bs-target="#deleteChatModal"
|
||||
data-bs-toggle="modal"
|
||||
@click="chatSelected(callsign)"
|
||||
>
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
90
gui/src/components/chat_messages.vue
Normal file
90
gui/src/components/chat_messages.vue
Normal file
|
@ -0,0 +1,90 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useChatStore } from "../store/chatStore.js";
|
||||
const chat = useChatStore(pinia);
|
||||
|
||||
import SentMessage from "./chat_messages_sent.vue"; // Import the chat_messages_sent component
|
||||
import ReceivedMessage from "./chat_messages_received.vue"; // Import the chat_messages_sent component
|
||||
import ReceivedBroadcastMessage from "./chat_messages_broadcast_received.vue"; // Import the chat_messages_sent component for broadcasts
|
||||
import SentBroadcastMessage from "./chat_messages_broadcast_sent.vue"; // Import the chat_messages_sent component for broadcasts
|
||||
|
||||
//helper function for saving the last messages day for disaplying the day based divider
|
||||
var prevChatMessageDay = "";
|
||||
|
||||
function getDateTime(timestampRaw) {
|
||||
let date = new Date(timestampRaw);
|
||||
let year = date.getFullYear();
|
||||
let month = (date.getMonth() + 1).toString().padStart(2, "0"); // Months are zero-indexed
|
||||
let day = date.getDate().toString().padStart(2, "0");
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tab-content p-3" id="nav-tabContent-chat-messages">
|
||||
<template
|
||||
v-for="(details, callsign, key) in chat.callsign_list"
|
||||
:key="callsign"
|
||||
>
|
||||
<div
|
||||
class="tab-pane fade show"
|
||||
:class="{ active: key == 0 }"
|
||||
:id="`list-${callsign}-messages`"
|
||||
role="tabpanel"
|
||||
:aria-labelledby="`list-chat-list-${callsign}`"
|
||||
>
|
||||
<template v-for="item in chat.sorted_chat_list[callsign]">
|
||||
<div v-if="prevChatMessageDay !== getDateTime(item.timestamp)">
|
||||
<div class="separator my-2">
|
||||
{{ (prevChatMessageDay = getDateTime(item.timestamp)) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="item.direction === 'transmit'">
|
||||
<sent-message :message="item" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="item.direction === 'receive'">
|
||||
<received-message :message="item" />
|
||||
</div>
|
||||
<!--
|
||||
<div v-if="item.type === 'broadcast_transmit'">
|
||||
<sent-broadcast-message :message="item" />
|
||||
</div>
|
||||
<div v-else-if="item.type === 'broadcast_received'">
|
||||
<received-broadcast-message :message="item" />
|
||||
</div>
|
||||
-->
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* https://stackoverflow.com/a/26634224 */
|
||||
.separator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.separator::before,
|
||||
.separator::after {
|
||||
content: "";
|
||||
flex: 1;
|
||||
border-bottom: 1px solid #adb5bd;
|
||||
}
|
||||
|
||||
.separator:not(:empty)::before {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
.separator:not(:empty)::after {
|
||||
margin-left: 0.25em;
|
||||
}
|
||||
</style>
|
48
gui/src/components/chat_messages_action_menu.vue
Normal file
48
gui/src/components/chat_messages_action_menu.vue
Normal file
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<div class="message-actions-menu">
|
||||
<!-- Add your action buttons here (e.g., Delete, Copy, Quote) -->
|
||||
<button @click="onDelete">Delete</button>
|
||||
<button @click="onCopy">Copy</button>
|
||||
<button @click="onQuote">Quote</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
onDelete() {
|
||||
// Implement delete action
|
||||
this.$emit("delete");
|
||||
},
|
||||
onCopy() {
|
||||
// Implement copy action
|
||||
this.$emit("copy");
|
||||
},
|
||||
onQuote() {
|
||||
// Implement quote action
|
||||
this.$emit("quote");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Style the message actions menu as needed */
|
||||
.message-actions-menu {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: none; /* Initially hidden */
|
||||
/* Add styling for buttons and menu */
|
||||
}
|
||||
|
||||
/* Style individual action buttons */
|
||||
.message-actions-menu button {
|
||||
/* Add button styles here */
|
||||
}
|
||||
|
||||
/* Style menu display on hover */
|
||||
.card:hover .message-actions-menu {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
96
gui/src/components/chat_messages_broadcast_received.vue
Normal file
96
gui/src/components/chat_messages_broadcast_received.vue
Normal file
|
@ -0,0 +1,96 @@
|
|||
<template>
|
||||
<div class="row justify-content-start mb-2">
|
||||
<div :class="messageWidthClass">
|
||||
<div class="card bg-light border-0 text-dark">
|
||||
<div class="card-header" v-if="getFileContent['filesize'] !== 0">
|
||||
<p class="card-text">
|
||||
{{ getFileContent["filename"] }} |
|
||||
{{ getFileContent["filesize"] }} Bytes |
|
||||
{{ getFileContent["filetype"] }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<p class="card-text">{{ message.msg }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-footer p-0 bg-light border-top-0">
|
||||
<p class="text-muted p-0 m-0 me-1 text-end">{{ getDateTime }}</p>
|
||||
<!-- Display formatted timestamp in card-footer -->
|
||||
</div>
|
||||
|
||||
<span
|
||||
class="position-absolute top-0 start-100 translate-middle badge rounded-1 bg-secondary border border-white"
|
||||
>
|
||||
{{ message.broadcast_sender }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete button outside of the card -->
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-outline-secondary border-0" @click="deleteMessage">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { deleteMessageFromDB } from "../js/chatHandler";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
message: Object,
|
||||
},
|
||||
computed: {
|
||||
getFileContent() {
|
||||
try {
|
||||
var filename = Object.keys(this.message._attachments)[0];
|
||||
var filesize = this.message._attachments[filename]["length"];
|
||||
var filetype = filename.split(".")[1];
|
||||
|
||||
// ensure filesize is 0 for hiding message header if no data is available
|
||||
if (
|
||||
typeof filename === "undefined" ||
|
||||
filename === "" ||
|
||||
filename === "-" ||
|
||||
filename === "null"
|
||||
) {
|
||||
filesize = 0;
|
||||
}
|
||||
|
||||
return { filename: filename, filesize: filesize, filetype: filetype };
|
||||
} catch (e) {
|
||||
console.log("file not loaded from database - empty?");
|
||||
// we are only checking against filesize for displaying attachments
|
||||
return { filesize: 0 };
|
||||
}
|
||||
},
|
||||
messageWidthClass() {
|
||||
// Calculate a Bootstrap grid class based on message length
|
||||
// Adjust the logic as needed to fit your requirements
|
||||
if (this.message.msg.length <= 50) {
|
||||
return "col-4";
|
||||
} else if (this.message.msg.length <= 100) {
|
||||
return "col-6";
|
||||
} else {
|
||||
return "col-9";
|
||||
}
|
||||
},
|
||||
deleteMessage() {
|
||||
deleteMessageFromDB(this.message._id);
|
||||
},
|
||||
getDateTime() {
|
||||
var datetime = new Date(this.message.timestamp * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
},
|
||||
);
|
||||
return datetime;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
114
gui/src/components/chat_messages_broadcast_sent.vue
Normal file
114
gui/src/components/chat_messages_broadcast_sent.vue
Normal file
|
@ -0,0 +1,114 @@
|
|||
<template>
|
||||
<div class="row justify-content-end mb-2">
|
||||
<!-- control area -->
|
||||
<div class="col-auto p-0 m-0">
|
||||
<button
|
||||
class="btn btn-outline-secondary border-0 me-1"
|
||||
@click="repeatMessage"
|
||||
>
|
||||
<i class="bi bi-arrow-repeat"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary border-0" @click="deleteMessage">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- message area -->
|
||||
<div :class="messageWidthClass">
|
||||
<div class="card bg-primary text-white">
|
||||
<div class="card-header" v-if="getFileContent['filesize'] !== 0">
|
||||
<p class="card-text">
|
||||
{{ getFileContent["filename"] }} |
|
||||
{{ getFileContent["filesize"] }} Bytes |
|
||||
{{ getFileContent["filetype"] }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<p class="card-text">{{ message.msg }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-footer p-0 bg-primary border-top-0">
|
||||
<p class="text p-0 m-0 me-1 text-end">{{ getDateTime }}</p>
|
||||
<!-- Display formatted timestamp in card-footer -->
|
||||
</div>
|
||||
|
||||
<div class="card-footer p-0 border-top-0" v-if="message.percent < 100">
|
||||
<div class="progress bg-secondary" :style="{ height: '10px' }">
|
||||
<div
|
||||
class="progress-bar progress-bar-striped overflow-visible"
|
||||
role="progressbar"
|
||||
:style="{ width: message.percent + '%', height: '10px' }"
|
||||
:aria-valuenow="message.percent"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
>
|
||||
{{ message.percent }} % with {{ message.bytesperminute }} bpm (
|
||||
{{ message.status }} )
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
repeatMessageTransmission,
|
||||
deleteMessageFromDB,
|
||||
} from "../js/chatHandler";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
message: Object,
|
||||
},
|
||||
computed: {
|
||||
getFileContent() {
|
||||
var filename = Object.keys(this.message._attachments)[0];
|
||||
var filesize = this.message._attachments[filename]["length"];
|
||||
var filetype = filename.split(".")[1];
|
||||
|
||||
// ensure filesize is 0 for hiding message header if no data is available
|
||||
if (
|
||||
typeof filename === "undefined" ||
|
||||
filename === "" ||
|
||||
filename === "-" ||
|
||||
filename === "null"
|
||||
) {
|
||||
filesize = 0;
|
||||
}
|
||||
|
||||
return { filename: filename, filesize: filesize, filetype: filetype };
|
||||
},
|
||||
messageWidthClass() {
|
||||
// Calculate a Bootstrap grid class based on message length
|
||||
// Adjust the logic as needed to fit your requirements
|
||||
if (this.message.msg.length <= 50) {
|
||||
return "col-4";
|
||||
} else if (this.message.msg.length <= 100) {
|
||||
return "col-6";
|
||||
} else {
|
||||
return "col-9";
|
||||
}
|
||||
},
|
||||
repeatMessage() {
|
||||
repeatMessageTransmission(this.message._id);
|
||||
},
|
||||
deleteMessage() {
|
||||
deleteMessageFromDB(this.message._id);
|
||||
},
|
||||
|
||||
getDateTime() {
|
||||
var datetime = new Date(this.message.timestamp * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
},
|
||||
);
|
||||
return datetime;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
141
gui/src/components/chat_messages_received.vue
Normal file
141
gui/src/components/chat_messages_received.vue
Normal file
|
@ -0,0 +1,141 @@
|
|||
<template>
|
||||
<div class="row justify-content-start mb-2">
|
||||
<div :class="messageWidthClass">
|
||||
<div class="card bg-light border-0 text-dark">
|
||||
<div
|
||||
v-for="attachment in message.attachments"
|
||||
:key="attachment.id"
|
||||
class="card-header"
|
||||
>
|
||||
<div class="btn-group w-100" role="group">
|
||||
<button class="btn btn-light text-truncate" disabled>
|
||||
{{ attachment.name }}
|
||||
</button>
|
||||
<button
|
||||
@click="
|
||||
downloadAttachment(attachment.hash_sha512, attachment.name)
|
||||
"
|
||||
class="btn btn-light w-25"
|
||||
>
|
||||
<i class="bi bi-download strong"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<p class="card-text">{{ message.body }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-footer p-0 bg-light border-top-0">
|
||||
<p class="text-muted p-0 m-0 me-1 text-end">{{ getDateTime }}</p>
|
||||
<!-- Display formatted timestamp in card-footer -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete button outside of the card -->
|
||||
<div class="col-auto">
|
||||
<button
|
||||
disabled
|
||||
class="btn btn-outline-secondary border-0 me-1"
|
||||
@click="showMessageInfo"
|
||||
data-bs-target="#messageInfoModal"
|
||||
data-bs-toggle="modal"
|
||||
>
|
||||
<i class="bi bi-info-circle"></i>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-outline-secondary border-0" @click="deleteMessage">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
deleteMessageFromDB,
|
||||
requestMessageInfo,
|
||||
getMessageAttachment,
|
||||
} from "../js/messagesHandler";
|
||||
import { atob_FD } from "../js/freedata";
|
||||
|
||||
// pinia store setup
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useChatStore } from "../store/chatStore.js";
|
||||
const chat = useChatStore(pinia);
|
||||
|
||||
export default {
|
||||
props: {
|
||||
message: Object,
|
||||
},
|
||||
methods: {
|
||||
showMessageInfo() {
|
||||
requestMessageInfo(this.message.id);
|
||||
//let infoModal = Modal.getOrCreateInstance(document.getElementById('messageInfoModal'))
|
||||
//console.log(this.infoModal)
|
||||
//this.infoModal.show()
|
||||
},
|
||||
deleteMessage() {
|
||||
deleteMessageFromDB(this.message.id);
|
||||
},
|
||||
async downloadAttachment(hash_sha512, fileName) {
|
||||
try {
|
||||
const jsondata = await getMessageAttachment(hash_sha512);
|
||||
const byteCharacters = atob(jsondata.data);
|
||||
const byteArrays = [];
|
||||
|
||||
for (let offset = 0; offset < byteCharacters.length; offset += 512) {
|
||||
const slice = byteCharacters.slice(offset, offset + 512);
|
||||
const byteNumbers = new Array(slice.length);
|
||||
for (let i = 0; i < slice.length; i++) {
|
||||
byteNumbers[i] = slice.charCodeAt(i);
|
||||
}
|
||||
const byteArray = new Uint8Array(byteNumbers);
|
||||
byteArrays.push(byteArray);
|
||||
}
|
||||
|
||||
const blob = new Blob(byteArrays, { type: jsondata.type });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
// Creating a temporary anchor element to download the file
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = url;
|
||||
anchor.download = fileName;
|
||||
document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
|
||||
// Cleanup
|
||||
document.body.removeChild(anchor);
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error("Failed to download the attachment:", error);
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
messageWidthClass() {
|
||||
// Calculate a Bootstrap grid class based on message length
|
||||
// Adjust the logic as needed to fit your requirements
|
||||
if (this.message.body.length <= 50) {
|
||||
return "col-4";
|
||||
} else if (this.message.body.length <= 100) {
|
||||
return "col-6";
|
||||
} else {
|
||||
return "col-9";
|
||||
}
|
||||
},
|
||||
|
||||
getDateTime() {
|
||||
let date = new Date(this.message.timestamp);
|
||||
let hours = date.getHours().toString().padStart(2, "0");
|
||||
let minutes = date.getMinutes().toString().padStart(2, "0");
|
||||
let seconds = date.getSeconds().toString().padStart(2, "0");
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
186
gui/src/components/chat_messages_sent.vue
Normal file
186
gui/src/components/chat_messages_sent.vue
Normal file
|
@ -0,0 +1,186 @@
|
|||
<template>
|
||||
<div class="row justify-content-end mb-2">
|
||||
<!-- control area -->
|
||||
<div class="col-auto p-0 m-0">
|
||||
<button
|
||||
class="btn btn-outline-secondary border-0 me-1"
|
||||
@click="repeatMessage"
|
||||
>
|
||||
<i class="bi bi-arrow-repeat"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
disabled
|
||||
class="btn btn-outline-secondary border-0 me-1"
|
||||
@click="showMessageInfo"
|
||||
data-bs-target="#messageInfoModal"
|
||||
data-bs-toggle="modal"
|
||||
>
|
||||
<i class="bi bi-info-circle"></i>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-outline-secondary border-0" @click="deleteMessage">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- message area -->
|
||||
<div :class="messageWidthClass">
|
||||
<div class="card bg-secondary text-white">
|
||||
<div
|
||||
v-for="attachment in message.attachments"
|
||||
:key="attachment.id"
|
||||
class="card-header"
|
||||
>
|
||||
<div class="btn-group w-100" role="group">
|
||||
<button class="btn btn-light text-truncate" disabled>
|
||||
{{ attachment.name }}
|
||||
</button>
|
||||
<button
|
||||
@click="
|
||||
downloadAttachment(attachment.hash_sha512, attachment.name)
|
||||
"
|
||||
class="btn btn-light w-25"
|
||||
>
|
||||
<i class="bi bi-download strong"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<p class="card-text">{{ message.body }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-footer p-0 bg-secondary border-top-0">
|
||||
<p class="text p-0 m-0 me-1 text-end">
|
||||
{{ message.status }} | {{ getDateTime }}
|
||||
</p>
|
||||
<!-- Display formatted timestamp in card-footer -->
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="card-footer p-0 border-top-0"
|
||||
v-if="message.percent < 100 || message.status === 'failed'"
|
||||
>
|
||||
<div
|
||||
class="progress rounded-0 rounded-bottom"
|
||||
hidden
|
||||
:style="{ height: '10px' }"
|
||||
v-bind:class="{
|
||||
'bg-danger': message.status == 'failed',
|
||||
'bg-primary': message.status == 'transmitting',
|
||||
'bg-secondary': message.status == 'transmitted',
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped overflow-visible"
|
||||
role="progressbar"
|
||||
:style="{ width: message.percent + '%', height: '10px' }"
|
||||
:aria-valuenow="message.percent"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
>
|
||||
{{ message.percent }} % with {{ message.bytesperminute }} bpm (
|
||||
{{ message.status }} )
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { atob_FD } from "../js/freedata";
|
||||
|
||||
import {
|
||||
repeatMessageTransmission,
|
||||
deleteMessageFromDB,
|
||||
requestMessageInfo,
|
||||
getMessageAttachment,
|
||||
} from "../js/messagesHandler";
|
||||
|
||||
// pinia store setup
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useChatStore } from "../store/chatStore.js";
|
||||
const chat = useChatStore(pinia);
|
||||
|
||||
export default {
|
||||
props: {
|
||||
message: Object,
|
||||
},
|
||||
|
||||
methods: {
|
||||
repeatMessage() {
|
||||
repeatMessageTransmission(this.message.id);
|
||||
},
|
||||
|
||||
deleteMessage() {
|
||||
deleteMessageFromDB(this.message.id);
|
||||
},
|
||||
showMessageInfo() {
|
||||
console.log("requesting message info.....");
|
||||
requestMessageInfo(this.message.id);
|
||||
//let infoModal = Modal.getOrCreateInstance(document.getElementById('messageInfoModal'))
|
||||
//console.log(this.infoModal)
|
||||
//this.infoModal.show()
|
||||
},
|
||||
async downloadAttachment(hash_sha512, fileName) {
|
||||
try {
|
||||
const jsondata = await getMessageAttachment(hash_sha512);
|
||||
const byteCharacters = atob(jsondata.data);
|
||||
const byteArrays = [];
|
||||
|
||||
for (let offset = 0; offset < byteCharacters.length; offset += 512) {
|
||||
const slice = byteCharacters.slice(offset, offset + 512);
|
||||
const byteNumbers = new Array(slice.length);
|
||||
for (let i = 0; i < slice.length; i++) {
|
||||
byteNumbers[i] = slice.charCodeAt(i);
|
||||
}
|
||||
const byteArray = new Uint8Array(byteNumbers);
|
||||
byteArrays.push(byteArray);
|
||||
}
|
||||
|
||||
const blob = new Blob(byteArrays, { type: jsondata.type });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
// Creating a temporary anchor element to download the file
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = url;
|
||||
anchor.download = fileName;
|
||||
document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
|
||||
// Cleanup
|
||||
document.body.removeChild(anchor);
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error("Failed to download the attachment:", error);
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
messageWidthClass() {
|
||||
// Calculate a Bootstrap grid class based on message length
|
||||
// Adjust the logic as needed to fit your requirements
|
||||
if (this.message.body.length <= 50) {
|
||||
return "col-4";
|
||||
} else if (this.message.body.length <= 100) {
|
||||
return "col-6";
|
||||
} else {
|
||||
return "col-9";
|
||||
}
|
||||
},
|
||||
|
||||
getDateTime() {
|
||||
let date = new Date(this.message.timestamp);
|
||||
let hours = date.getHours().toString().padStart(2, "0");
|
||||
let minutes = date.getMinutes().toString().padStart(2, "0");
|
||||
let seconds = date.getSeconds().toString().padStart(2, "0");
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
313
gui/src/components/chat_new_message.vue
Normal file
313
gui/src/components/chat_new_message.vue
Normal file
|
@ -0,0 +1,313 @@
|
|||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
|
||||
import { setActivePinia } from 'pinia';
|
||||
import pinia from '../store/index';
|
||||
setActivePinia(pinia);
|
||||
|
||||
|
||||
import { useStateStore } from '../store/stateStore.js';
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { useChatStore } from '../store/chatStore.js';
|
||||
const chat = useChatStore(pinia);
|
||||
|
||||
|
||||
import chat_navbar from './chat_navbar.vue'
|
||||
import chat_conversations from './chat_conversations.vue'
|
||||
import chat_messages from './chat_messages.vue'
|
||||
|
||||
import { newMessage } from '../js/messagesHandler.ts'
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend
|
||||
} from 'chart.js'
|
||||
import { Line } from 'vue-chartjs'
|
||||
import { ref, computed, nextTick } from 'vue';
|
||||
|
||||
|
||||
import { VuemojiPicker, EmojiClickEventDetail } from 'vuemoji-picker'
|
||||
|
||||
const handleEmojiClick = (detail: EmojiClickEventDetail) => {
|
||||
chat.inputText += detail.unicode
|
||||
|
||||
}
|
||||
|
||||
|
||||
const chatModuleMessage=ref(null);
|
||||
|
||||
|
||||
// Function to trigger the hidden file input
|
||||
function triggerFileInput() {
|
||||
fileInput.value.click();
|
||||
}
|
||||
|
||||
|
||||
// Use a ref for storing multiple files
|
||||
const selectedFiles = ref([]);
|
||||
const fileInput = ref(null);
|
||||
|
||||
function handleFileSelection(event) {
|
||||
// Reset previously selected files
|
||||
selectedFiles.value = [];
|
||||
|
||||
// Process each file
|
||||
for (let file of event.target.files) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
// Convert file content to base64
|
||||
const base64Content = btoa(reader.result); // Adjust this line if necessary
|
||||
selectedFiles.value.push({
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
content: base64Content, // Store base64 encoded content
|
||||
});
|
||||
};
|
||||
reader.readAsBinaryString(file); // Read the file content as binary string
|
||||
}
|
||||
}
|
||||
|
||||
function removeFile(index) {
|
||||
selectedFiles.value.splice(index, 1);
|
||||
// Check if the selectedFiles array is empty
|
||||
if (selectedFiles.value.length === 0) {
|
||||
// Reset the file input if there are no files left
|
||||
resetFile();
|
||||
}
|
||||
}
|
||||
|
||||
function transmitNewMessage() {
|
||||
// Check if a callsign is selected, default to the first one if not
|
||||
if (typeof(chat.selectedCallsign) === 'undefined') {
|
||||
chat.selectedCallsign = Object.keys(chat.callsign_list)[0];
|
||||
}
|
||||
|
||||
|
||||
|
||||
chat.inputText = chat.inputText.trim();
|
||||
|
||||
// Proceed only if there is text or files selected
|
||||
if (chat.inputText.length === 0 && selectedFiles.value.length === 0) return;
|
||||
|
||||
const attachments = selectedFiles.value.map(file => {
|
||||
return {
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
data: file.content
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
if (chat.selectedCallsign.startsWith("BC-")) {
|
||||
// Handle broadcast message differently if needed
|
||||
return "new broadcast";
|
||||
} else {
|
||||
// If there are attachments, send them along with the message
|
||||
if (attachments.length > 0) {
|
||||
newMessage(chat.selectedCallsign, chat.inputText, attachments);
|
||||
} else {
|
||||
// Send text only if no attachments are selected
|
||||
newMessage(chat.selectedCallsign, chat.inputText);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup after sending message
|
||||
chat.inputText = '';
|
||||
chatModuleMessage.value = "";
|
||||
resetFile()
|
||||
|
||||
}
|
||||
|
||||
function resetFile(event){
|
||||
if (fileInput.value) {
|
||||
fileInput.value.value = ''; // Reset the file input
|
||||
}
|
||||
// Clear the selected files array to reset the state of attachments
|
||||
selectedFiles.value = [];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
// calculate time needed for transmitting a file
|
||||
function calculateTimeNeeded(){
|
||||
|
||||
var calculatedSpeedPerMinutePER0 = []
|
||||
var calculatedSpeedPerMinutePER25 = []
|
||||
var calculatedSpeedPerMinutePER75 = []
|
||||
|
||||
// bpm vs snr with PER == 0
|
||||
var snrList = [
|
||||
{snr: -10, bpm: 100},
|
||||
{snr: -5, bpm: 300},
|
||||
{snr: 0, bpm: 800},
|
||||
{snr: 5, bpm: 2500},
|
||||
{snr: 10, bpm: 5300}
|
||||
];
|
||||
|
||||
for (let i = 0; i < snrList.length; i++) {
|
||||
|
||||
var result = snrList.find(obj => {
|
||||
return obj.snr === snrList[i].snr
|
||||
})
|
||||
|
||||
calculatedSpeedPerMinutePER0.push(totalSize / result.bpm)
|
||||
calculatedSpeedPerMinutePER25.push(totalSize / (result.bpm * 0.75))
|
||||
calculatedSpeedPerMinutePER75.push(totalSize / (result.bpm * 0.25))
|
||||
|
||||
}
|
||||
|
||||
chat.chartSpeedPER0 = calculatedSpeedPerMinutePER0
|
||||
chat.chartSpeedPER25 = calculatedSpeedPerMinutePER25
|
||||
chat.chartSpeedPER75 = calculatedSpeedPerMinutePER75
|
||||
|
||||
}
|
||||
|
||||
|
||||
const speedChartData = computed(() => ({
|
||||
labels: ['-10', '-5', '0', '5', '10'],
|
||||
datasets: [
|
||||
{ data: chat.chartSpeedPER0, label: 'PER 0%' ,tension: 0.1, borderColor: 'rgb(0, 255, 0)' },
|
||||
{ data: chat.chartSpeedPER25, label: 'PER 25%' ,tension: 0.1, borderColor: 'rgb(255, 255, 0)'},
|
||||
{ data: chat.chartSpeedPER75, label: 'PER 75%' ,tension: 0.1, borderColor: 'rgb(255, 0, 0)' }
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
));
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
|
||||
|
||||
<nav class="navbar sticky-bottom bg-body-tertiary border-top mb-5">
|
||||
<div class="container-fluid p-0">
|
||||
|
||||
|
||||
|
||||
<!-- Hidden file input -->
|
||||
<input type="file" multiple ref="fileInput" @change="handleFileSelection" style="display: none;" />
|
||||
|
||||
|
||||
|
||||
<div class="container-fluid px-0">
|
||||
<div class="d-flex flex-row overflow-auto bg-light">
|
||||
<div v-for="(file, index) in selectedFiles" :key="index" class="pe-2">
|
||||
<div class="card" style=" min-width: 10rem; max-width: 10rem;">
|
||||
<!-- Card Header with Remove Button -->
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span class="text-truncate">{{ file.name }}</span>
|
||||
<button class="btn btn-close" @click="removeFile(index)"></button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">...</p>
|
||||
</div>
|
||||
<div class="card-footer text-muted">
|
||||
{{ file.type }}
|
||||
</div>
|
||||
<div class="card-footer text-muted">
|
||||
{{ file.size }} bytes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<Line :data="speedChartData" />
|
||||
-->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="input-group bottom-0 ms-2">
|
||||
|
||||
<button type="button" class="btn btn-outline-secondary border-0 rounded-pill me-1"
|
||||
data-bs-toggle="modal" data-bs-target="#emojiPickerModal"
|
||||
data-bs-backdrop="false"
|
||||
>
|
||||
<i
|
||||
id="emojipickerbutton"
|
||||
class="bi bi-emoji-smile p-0"
|
||||
style="font-size: 1rem"
|
||||
></i>
|
||||
</button>
|
||||
|
||||
|
||||
|
||||
<!-- trigger file selection modal -->
|
||||
|
||||
<button type="button" class="btn btn-outline-secondary border-0 rounded-pill me-1" @click="triggerFileInput">
|
||||
<i class="bi bi-paperclip" style="font-size: 1.2rem"></i>
|
||||
|
||||
</button>
|
||||
|
||||
<textarea
|
||||
class="form-control border rounded-pill"
|
||||
rows="1"
|
||||
ref="chatModuleMessage"
|
||||
placeholder="Message - Send with [Enter]"
|
||||
v-model="chat.inputText"
|
||||
@keyup.enter.exact="transmitNewMessage()"
|
||||
></textarea>
|
||||
|
||||
|
||||
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-secondary ms-1 me-2 rounded-pill"
|
||||
@click="transmitNewMessage()"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="bi bi-send ms-4 me-4"
|
||||
style="font-size: 1.2rem"
|
||||
></i>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
<!-- Emoji Picker Modal -->
|
||||
<div class="modal fade" id="emojiPickerModal" aria-hidden="true" >
|
||||
<div class="modal-dialog modal-dialog-centered modal-sm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body p-0">
|
||||
<VuemojiPicker @emojiClick="handleEmojiClick"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
1027
gui/src/components/dynamic_components.vue
Normal file
1027
gui/src/components/dynamic_components.vue
Normal file
File diff suppressed because it is too large
Load diff
15
gui/src/components/grid/button.vue
Normal file
15
gui/src/components/grid/button.vue
Normal file
|
@ -0,0 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
const props = defineProps(["btnText", "btnID"]);
|
||||
|
||||
function emitClick() {
|
||||
window.dispatchEvent(new CustomEvent("add-widget", { detail: props.btnID }));
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<button
|
||||
class="btn btn-small btn-outline-secondary mb-1"
|
||||
v-on:click="emitClick"
|
||||
>
|
||||
{{ btnText }}
|
||||
</button>
|
||||
</template>
|
17
gui/src/components/grid/grid_CQ.vue
Normal file
17
gui/src/components/grid/grid_CQ.vue
Normal file
|
@ -0,0 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { sendModemCQ } from "../../js/api.js";
|
||||
</script>
|
||||
<template>
|
||||
<div class="fill h-100" style="width: calc(100% - 24px)">
|
||||
<a
|
||||
class="btn btn-sm btn-secondary d-flex justify-content-center align-items-center object-fill border rounded w-100 h-100"
|
||||
@click="sendModemCQ"
|
||||
title="Send a CQ call!"
|
||||
>CQ</a
|
||||
>
|
||||
</div>
|
||||
</template>
|
140
gui/src/components/grid/grid_active_audio.vue
Normal file
140
gui/src/components/grid/grid_active_audio.vue
Normal file
|
@ -0,0 +1,140 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
</script>
|
||||
<template>
|
||||
<div class="card w-100 h-100">
|
||||
<div class="card-header p-0 mb-1">
|
||||
<i class="bi bi-volume-up" style="font-size: 1rem"></i>
|
||||
<strong>Audio</strong>
|
||||
</div>
|
||||
<div class="card-body pt-0 pb-0">
|
||||
<div class="container-wide">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div
|
||||
class="progress mb-0 rounded-0 rounded-top"
|
||||
style="height: 22px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
||||
id="noise_level"
|
||||
role="progressbar"
|
||||
:style="{ width: state.s_meter_strength_percent + '%' }"
|
||||
aria-valuenow="{{state.s_meter_strength_percent}}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<p
|
||||
class="justify-content-center d-flex position-absolute w-100"
|
||||
id="noise_level_value"
|
||||
>
|
||||
S-Meter(dB): {{ state.s_meter_strength_raw }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="progress mb-0 rounded-0 rounded-bottom"
|
||||
style="height: 8px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 1%"
|
||||
aria-valuenow="1"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar bg-success"
|
||||
role="progressbar"
|
||||
style="width: 89%"
|
||||
aria-valuenow="50"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 20%"
|
||||
aria-valuenow="20"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-danger"
|
||||
role="progressbar"
|
||||
style="width: 29%"
|
||||
aria-valuenow="29"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div
|
||||
class="progress mb-0 rounded-0 rounded-top"
|
||||
style="height: 22px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
||||
id="dbfs_level"
|
||||
role="progressbar"
|
||||
:style="{ width: state.dbfs_level_percent + '%' }"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<p
|
||||
class="justify-content-center d-flex position-absolute w-100"
|
||||
id="dbfs_level_value"
|
||||
>
|
||||
{{ state.dbfs_level }} dBFS
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="progress mb-0 rounded-0 rounded-bottom"
|
||||
style="height: 8px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 1%"
|
||||
aria-valuenow="1"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar bg-success"
|
||||
role="progressbar"
|
||||
style="width: 89%"
|
||||
aria-valuenow="50"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 20%"
|
||||
aria-valuenow="20"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-danger"
|
||||
role="progressbar"
|
||||
style="width: 29%"
|
||||
aria-valuenow="29"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
93
gui/src/components/grid/grid_active_broadcasts.vue
Normal file
93
gui/src/components/grid/grid_active_broadcasts.vue
Normal file
|
@ -0,0 +1,93 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { sendModemCQ, sendModemPing, setModemBeacon } from "../../js/api.js";
|
||||
|
||||
function transmitPing() {
|
||||
sendModemPing(dxcallPing.value.toUpperCase());
|
||||
}
|
||||
|
||||
function startStopBeacon() {
|
||||
if (state.beacon_state === true) {
|
||||
setModemBeacon(false);
|
||||
} else {
|
||||
setModemBeacon(true);
|
||||
}
|
||||
}
|
||||
var dxcallPing = ref("");
|
||||
window.addEventListener(
|
||||
"stationSelected",
|
||||
function (eventdata) {
|
||||
let evt = <CustomEvent>eventdata;
|
||||
dxcallPing.value = evt.detail;
|
||||
},
|
||||
false,
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="card h-100">
|
||||
<div class="card-header p-0">
|
||||
<i class="bi bi-broadcast" style="font-size: 1.2rem"></i>
|
||||
<strong>Broadcasts</strong>
|
||||
</div>
|
||||
<div class="card-body overflow-auto p-0">
|
||||
<div class="input-group input-group-sm mb-0">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="max-width: 6rem; min-width: 3rem; text-transform: uppercase"
|
||||
placeholder="DXcall"
|
||||
pattern="[A-Z]*"
|
||||
maxlength="11"
|
||||
aria-label="Input group"
|
||||
aria-describedby="btnGroupAddon"
|
||||
v-model="dxcallPing"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
id="sendPing"
|
||||
type="button"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Send a ping request to a remote station"
|
||||
@click="transmitPing()"
|
||||
>
|
||||
Ping
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary ms-1"
|
||||
id="sendCQ"
|
||||
type="button"
|
||||
title="Send a CQ to the world"
|
||||
@click="sendModemCQ()"
|
||||
>
|
||||
Call CQ
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id="startBeacon"
|
||||
class="btn btn-sm ms-1"
|
||||
@click="startStopBeacon()"
|
||||
v-bind:class="{
|
||||
'btn-success': state.beacon_state === true,
|
||||
'btn-outline-secondary': state.beacon_state === false,
|
||||
}"
|
||||
title="Toggle beacon mode. The interval can be set in settings. While sending a beacon, you can receive ping requests and open a datachannel. If a datachannel is opened, the beacon pauses."
|
||||
>
|
||||
Toggle beacon
|
||||
</button>
|
||||
</div>
|
||||
<!-- end of row-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
105
gui/src/components/grid/grid_active_broadcasts_vert.vue
Normal file
105
gui/src/components/grid/grid_active_broadcasts_vert.vue
Normal file
|
@ -0,0 +1,105 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { sendModemCQ, sendModemPing, setModemBeacon } from "../../js/api.js";
|
||||
|
||||
function transmitPing() {
|
||||
sendModemPing(dxcallPing.value.toUpperCase());
|
||||
}
|
||||
|
||||
function startStopBeacon() {
|
||||
if (state.beacon_state === true) {
|
||||
setModemBeacon(false);
|
||||
} else {
|
||||
setModemBeacon(true);
|
||||
}
|
||||
}
|
||||
var dxcallPing = ref("");
|
||||
window.addEventListener(
|
||||
"stationSelected",
|
||||
function (eventdata) {
|
||||
let evt = <CustomEvent>eventdata;
|
||||
dxcallPing.value = evt.detail;
|
||||
},
|
||||
false,
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="card h-100">
|
||||
<div class="card-header p-0">
|
||||
<i class="bi bi-broadcast" style="font-size: 1.2rem"></i>
|
||||
<strong>Broadcasts</strong>
|
||||
</div>
|
||||
<div class="card-body overflow-auto p-0">
|
||||
<div class="container text-center">
|
||||
<div class="row mb-2 mt-2">
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group w-100">
|
||||
<div class="form-floating">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="text-transform: uppercase"
|
||||
id="floatingInput"
|
||||
placeholder="dx-callsign"
|
||||
v-model="dxcallPing"
|
||||
maxlength="11"
|
||||
pattern="[A-Z]*"
|
||||
/>
|
||||
<label for="floatingInput">DX-Callsign</label>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
id="sendPing"
|
||||
type="button"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Send a ping request to a remote station"
|
||||
@click="transmitPing()"
|
||||
>
|
||||
<strong>Ping</strong>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-check form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id="flexSwitchBeacon"
|
||||
v-model="state.beacon_state"
|
||||
@click="startStopBeacon()"
|
||||
/>
|
||||
<label class="form-check-label" for="flexSwitchBeacon"
|
||||
>Beacon</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary w-100"
|
||||
id="sendCQ"
|
||||
type="button"
|
||||
title="Send a CQ to the world"
|
||||
@click="sendModemCQ()"
|
||||
>
|
||||
<h3>CQ CQ CQ</h3>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
94
gui/src/components/grid/grid_active_heard_stations.vue
Normal file
94
gui/src/components/grid/grid_active_heard_stations.vue
Normal file
|
@ -0,0 +1,94 @@
|
|||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
const { distance } = require("qth-locator");
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings } from "../../store/settingsStore.js";
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
function getDateTime(timestampRaw) {
|
||||
var datetime = new Date(timestampRaw * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hourCycle: "h23",
|
||||
year: "2-digit",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
},
|
||||
);
|
||||
return datetime;
|
||||
}
|
||||
|
||||
function getMaidenheadDistance(dxGrid) {
|
||||
try {
|
||||
return parseInt(distance(settings.remote.STATION.mygrid, dxGrid));
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
function pushToPing(origin)
|
||||
{
|
||||
window.dispatchEvent(new CustomEvent("stationSelected", {bubbles:true, detail: origin }));
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="card h-100">
|
||||
<!--325px-->
|
||||
<div class="card-header p-0">
|
||||
<i class="bi bi-list-columns-reverse" style="font-size: 1.2rem"></i>
|
||||
<strong>Heard stations</strong>
|
||||
</div>
|
||||
|
||||
<div class="card-body overflow-auto p-0">
|
||||
<div class="table-responsive">
|
||||
<!-- START OF TABLE FOR HEARD STATIONS -->
|
||||
<table class="table table-sm table-striped" id="tblHeardStationList">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" id="thTime">Time</th>
|
||||
<th scope="col" id="thFreq">Freq</th>
|
||||
<th scope="col" id="thDxcall">DXCall</th>
|
||||
<th scope="col" id="thDxgrid">Grid</th>
|
||||
<th scope="col" id="thDist">Dist</th>
|
||||
<th scope="col" id="thType">Type</th>
|
||||
<th scope="col" id="thSnr">SNR</th>
|
||||
<!--<th scope="col">Off</th>-->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="gridHeardStations">
|
||||
<!--https://vuejs.org/guide/essentials/list.html-->
|
||||
<tr v-for="item in state.heard_stations" :key="item.origin" @click="pushToPing(item.origin)">
|
||||
<td>
|
||||
{{ getDateTime(item.timestamp) }}
|
||||
</td>
|
||||
<td>{{ item.frequency / 1000 }} kHz</td>
|
||||
<td>
|
||||
{{ item.origin }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.gridsquare }}
|
||||
</td>
|
||||
<td>{{ getMaidenheadDistance(item.gridsquare) }} km</td>
|
||||
<td>
|
||||
{{ item.activity_type }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.snr }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- END OF HEARD STATIONS TABLE -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
78
gui/src/components/grid/grid_active_heard_stations_mini.vue
Normal file
78
gui/src/components/grid/grid_active_heard_stations_mini.vue
Normal file
|
@ -0,0 +1,78 @@
|
|||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
const { distance } = require("qth-locator");
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings } from "../../store/settingsStore.js";
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
function getDateTime(timestampRaw) {
|
||||
var datetime = new Date(timestampRaw * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hourCycle: "h23",
|
||||
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
},
|
||||
);
|
||||
return datetime;
|
||||
}
|
||||
|
||||
function getMaidenheadDistance(dxGrid) {
|
||||
try {
|
||||
return parseInt(distance(settings.remote.STATION.mygrid, dxGrid));
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
function pushToPing(origin)
|
||||
{
|
||||
window.dispatchEvent(new CustomEvent("stationSelected", {bubbles:true, detail: origin }));
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="card h-100">
|
||||
<div class="card-header p-0">
|
||||
<i class="bi bi-list-columns-reverse" style="font-size: 1.2rem"></i>
|
||||
<strong>Heard stations</strong>
|
||||
</div>
|
||||
|
||||
<div class="card-body overflow-auto p-0">
|
||||
<div class="table-responsive">
|
||||
<!-- START OF TABLE FOR HEARD STATIONS -->
|
||||
<table class="table table-sm table-striped" id="tblHeardStationList">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" id="thTime">Time</th>
|
||||
<th scope="col" id="thDxcall">DXCall</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="miniHeardStations">
|
||||
<!--https://vuejs.org/guide/essentials/list.html-->
|
||||
<tr v-for="item in state.heard_stations" :key="item.origin" @click="pushToPing(item.origin)">
|
||||
<td>
|
||||
<span class="fs-6">{{ getDateTime(item.timestamp) }}</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span>{{ item.origin }}</span>
|
||||
</td>
|
||||
<!--<td>{{ item.offset }}</td>-->
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- END OF HEARD STATIONS TABLE -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
102
gui/src/components/grid/grid_active_rig_control.vue
Normal file
102
gui/src/components/grid/grid_active_rig_control.vue
Normal file
|
@ -0,0 +1,102 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
import { setRadioParametersFrequency, setRadioParametersMode, setRadioParametersRFLevel } from "../../js/api";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
function set_radio_parameter_frequency(){
|
||||
setRadioParametersFrequency(state.new_frequency)
|
||||
}
|
||||
|
||||
function set_radio_parameter_mode(){
|
||||
setRadioParametersMode(state.mode)
|
||||
}
|
||||
|
||||
function set_radio_parameter_rflevel(){
|
||||
setRadioParametersRFLevel(state.rf_level)
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card h-100">
|
||||
<div class="card-header p-0">
|
||||
<i class="bi bi-house-door" style="font-size: 1.2rem"></i>
|
||||
<strong>Radio control</strong>
|
||||
</div>
|
||||
|
||||
<div class="card-body overflow-auto p-0">
|
||||
<div class="input-group input-group-sm bottom-0 m-0">
|
||||
<div class="me-2">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text">QRG</span>
|
||||
<span class="input-group-text"
|
||||
>{{ state.frequency / 1000 }} kHz</span
|
||||
>
|
||||
|
||||
<button
|
||||
class="btn btn-secondary dropdown-toggle"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
type="button"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#offcanvasFrequency"
|
||||
aria-controls="offcanvasExample"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="me-2">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text">Mode</span>
|
||||
<select
|
||||
class="form-control"
|
||||
v-model="state.mode"
|
||||
@click="set_radio_parameter_mode()"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
>
|
||||
<option selected value="">---</option>
|
||||
<option value="USB">USB</option>
|
||||
<option value="USB-D">USB-D</option>
|
||||
<option value="PKTUSB">PKT-U</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="me-2">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text">% Power</span>
|
||||
<select
|
||||
class="form-control"
|
||||
v-model="state.rf_level"
|
||||
@click="set_radio_parameter_rflevel()"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
>
|
||||
<option value="0">-</option>
|
||||
<option value="10">10</option>
|
||||
<option value="20">20</option>
|
||||
<option value="30">30</option>
|
||||
<option value="40">40</option>
|
||||
<option value="50">50</option>
|
||||
<option value="60">60</option>
|
||||
<option value="70">70</option>
|
||||
<option value="80">80</option>
|
||||
<option value="90">90</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
363
gui/src/components/grid/grid_active_stats.vue
Normal file
363
gui/src/components/grid/grid_active_stats.vue
Normal file
|
@ -0,0 +1,363 @@
|
|||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
// reason for no check is, that we have some mixing of typescript and chart js which seems to be not to be fixed that easy
|
||||
import { ref, computed, onMounted, nextTick } from "vue";
|
||||
import { initWaterfall, setColormap } from "../../js/waterfallHandler.js";
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings } from "../../store/settingsStore.js";
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from "chart.js";
|
||||
import { Line, Scatter } from "vue-chartjs";
|
||||
|
||||
const localSpectrumView = ref("waterfall");
|
||||
function selectStatsControl(item) {
|
||||
switch (item) {
|
||||
case "wf":
|
||||
localSpectrumView.value = "waterfall";
|
||||
break;
|
||||
case "scatter":
|
||||
localSpectrumView.value = "scatter";
|
||||
break;
|
||||
case "chart":
|
||||
localSpectrumView.value = "chart";
|
||||
break;
|
||||
default:
|
||||
localSpectrumView.value = "waterfall";
|
||||
}
|
||||
//saveSettingsToFile();
|
||||
}
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
);
|
||||
|
||||
// https://www.chartjs.org/docs/latest/samples/line/segments.html
|
||||
const skipped = (speedCtx, value) =>
|
||||
speedCtx.p0.skip || speedCtx.p1.skip ? value : undefined;
|
||||
const down = (speedCtx, value) =>
|
||||
speedCtx.p0.parsed.y > speedCtx.p1.parsed.y ? value : undefined;
|
||||
|
||||
var transmissionSpeedChartOptions = {
|
||||
//type: "line",
|
||||
responsive: true,
|
||||
animations: true,
|
||||
maintainAspectRatio: false,
|
||||
cubicInterpolationMode: "monotone",
|
||||
tension: 0.4,
|
||||
scales: {
|
||||
SNR: {
|
||||
type: "linear",
|
||||
ticks: { beginAtZero: false, color: "rgb(255, 99, 132)" },
|
||||
position: "right",
|
||||
},
|
||||
SPEED: {
|
||||
type: "linear",
|
||||
ticks: { beginAtZero: false, color: "rgb(120, 100, 120)" },
|
||||
position: "left",
|
||||
grid: {
|
||||
drawOnChartArea: false, // only want the grid lines for one axis to show up
|
||||
},
|
||||
},
|
||||
x: { ticks: { beginAtZero: true } },
|
||||
},
|
||||
};
|
||||
|
||||
const transmissionSpeedChartData = computed(() => ({
|
||||
labels: state.arq_speed_list_timestamp,
|
||||
datasets: [
|
||||
{
|
||||
type: "line",
|
||||
label: "SNR[dB]",
|
||||
data: state.arq_speed_list_snr,
|
||||
borderColor: "rgb(75, 192, 192, 1.0)",
|
||||
pointRadius: 1,
|
||||
segment: {
|
||||
borderColor: (speedCtx) =>
|
||||
skipped(speedCtx, "rgb(0,0,0,0.4)") ||
|
||||
down(speedCtx, "rgb(192,75,75)"),
|
||||
borderDash: (speedCtx) => skipped(speedCtx, [3, 3]),
|
||||
},
|
||||
spanGaps: true,
|
||||
backgroundColor: "rgba(75, 192, 192, 0.2)",
|
||||
order: 1,
|
||||
yAxisID: "SNR",
|
||||
},
|
||||
{
|
||||
type: "bar",
|
||||
label: "Speed[bpm]",
|
||||
data: state.arq_speed_list_bpm,
|
||||
borderColor: "rgb(120, 100, 120, 1.0)",
|
||||
backgroundColor: "rgba(120, 100, 120, 0.2)",
|
||||
order: 0,
|
||||
yAxisID: "SPEED",
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
const scatterChartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
x: {
|
||||
type: "linear",
|
||||
position: "bottom",
|
||||
grid: {
|
||||
display: true,
|
||||
lineWidth: 1, // Set the line width for x-axis grid lines
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
type: "linear",
|
||||
position: "left",
|
||||
grid: {
|
||||
display: true,
|
||||
lineWidth: 1, // Set the line width for y-axis grid lines
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// dummy data
|
||||
//state.scatter = [{"x":"166","y":"46"},{"x":"-193","y":"-139"},{"x":"-165","y":"-291"},{"x":"311","y":"-367"},{"x":"389","y":"199"},{"x":"78","y":"372"},{"x":"242","y":"-431"},{"x":"-271","y":"-248"},{"x":"28","y":"-130"},{"x":"-20","y":"187"},{"x":"74","y":"362"},{"x":"-316","y":"-229"},{"x":"-180","y":"261"},{"x":"321","y":"360"},{"x":"438","y":"-288"},{"x":"378","y":"-94"},{"x":"462","y":"-163"},{"x":"-265","y":"248"},{"x":"210","y":"314"},{"x":"230","y":"-320"},{"x":"261","y":"-244"},{"x":"-283","y":"-373"}]
|
||||
|
||||
const scatterChartData = computed(() => ({
|
||||
datasets: [
|
||||
{
|
||||
type: "scatter",
|
||||
fill: true,
|
||||
data: state.scatter,
|
||||
label: "Scatter",
|
||||
tension: 0.1,
|
||||
borderColor: "rgb(0, 255, 0)",
|
||||
},
|
||||
],
|
||||
}));
|
||||
var localSpectrum;
|
||||
//Define and generate a unique ID for canvas
|
||||
const localSpectrumID = ref("");
|
||||
localSpectrumID.value =
|
||||
"gridwfid-" + (Math.random() + 1).toString(36).substring(7);
|
||||
onMounted(() => {
|
||||
// This code will be executed after the component is mounted to the DOM
|
||||
// You can access DOM elements or perform other initialization here
|
||||
//const myElement = this.$refs.waterfall; // Access the DOM element with ref
|
||||
|
||||
// init waterfall
|
||||
localSpectrum = initWaterfall(localSpectrumID.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card h-100">
|
||||
<div class="card-header p-1">
|
||||
<div class="btn-group" role="group">
|
||||
<div
|
||||
class="list-group bg-body-tertiary list-group-horizontal"
|
||||
id="list-tab"
|
||||
role="tablist"
|
||||
>
|
||||
<a
|
||||
class="py-0 list-group-item list-group-item-dark list-group-item-action"
|
||||
data-bs-toggle="list"
|
||||
role="tab"
|
||||
aria-controls="list-waterfall"
|
||||
v-bind:class="{
|
||||
active: localSpectrumView == 'waterfall',
|
||||
}"
|
||||
@click="selectStatsControl('wf')"
|
||||
><strong><i class="bi bi-water"></i></strong
|
||||
></a>
|
||||
<a
|
||||
class="py-0 list-group-item list-group-item-dark list-group-item-action"
|
||||
data-bs-toggle="list"
|
||||
role="tab"
|
||||
aria-controls="list-scatter"
|
||||
v-bind:class="{
|
||||
active: localSpectrumView == 'scatter',
|
||||
}"
|
||||
@click="selectStatsControl('scatter')"
|
||||
><strong><i class="bi bi-border-outer"></i></strong
|
||||
></a>
|
||||
<a
|
||||
class="py-0 list-group-item list-group-item-dark list-group-item-action"
|
||||
data-bs-toggle="list"
|
||||
role="tab"
|
||||
aria-controls="list-chart"
|
||||
v-bind:class="{ active: localSpectrumView == 'chart' }"
|
||||
@click="selectStatsControl('chart')"
|
||||
><strong><i class="bi bi-graph-up-arrow"></i></strong
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group" role="group" aria-label="Busy indicators">
|
||||
<button
|
||||
class="btn btn-sm ms-1 p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.channel_busy_slot[0] === true,
|
||||
'btn-outline-secondary': state.channel_busy_slot[0] === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S1
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.channel_busy_slot[1] === true,
|
||||
'btn-outline-secondary': state.channel_busy_slot[1] === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S2
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.channel_busy_slot[2] === true,
|
||||
'btn-outline-secondary': state.channel_busy_slot[2] === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S3
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.channel_busy_slot[3] === true,
|
||||
'btn-outline-secondary': state.channel_busy_slot[3] === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S4
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.channel_busy_slot[4] === true,
|
||||
'btn-outline-secondary': state.channel_busy_slot[4] === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S5
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
title="Recieving data: illuminates <strong class='text-success'>green</strong> if receiving codec2 data"
|
||||
v-bind:class="{
|
||||
'btn-success': state.is_codec2_traffic === true,
|
||||
'btn-outline-secondary': state.is_codec2_traffic === false,
|
||||
}"
|
||||
>
|
||||
data
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body w-100 h-100 overflow-auto p-2">
|
||||
<div class="tab-content h-100 w-100" id="nav-stats-tabContent">
|
||||
<div
|
||||
class="tab-pane fade h-100 w-100"
|
||||
v-bind:class="{
|
||||
'show active': localSpectrumView == 'waterfall',
|
||||
}"
|
||||
role="stats_tabpanel"
|
||||
aria-labelledby="list-waterfall-list"
|
||||
>
|
||||
<canvas v-bind:id="localSpectrumID" class="force-gpu"></canvas>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade h-100 w-100"
|
||||
v-bind:class="{
|
||||
'show active': localSpectrumView == 'scatter',
|
||||
}"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-scatter-list"
|
||||
>
|
||||
<Scatter :data="scatterChartData" :options="scatterChartOptions" />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade h-100 w-100"
|
||||
v-bind:class="{ 'show active': localSpectrumView == 'chart' }"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-chart-list"
|
||||
>
|
||||
<Line
|
||||
:data="transmissionSpeedChartData"
|
||||
:options="transmissionSpeedChartOptions"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--278px-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
43
gui/src/components/grid/grid_activities.vue
Normal file
43
gui/src/components/grid/grid_activities.vue
Normal file
|
@ -0,0 +1,43 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
function getDateTime(timestampRaw) {
|
||||
var datetime = new Date(timestampRaw * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hourCycle: "h23",
|
||||
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
},
|
||||
);
|
||||
return datetime;
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="card h-100">
|
||||
<div class="card-header p-0">
|
||||
<i class="bi bi-card-list" style="font-size: 1.2rem"></i>
|
||||
<strong>Activity</strong>
|
||||
</div>
|
||||
<div class="card-body overflow-auto m-0 p-0" style="align-items: start">
|
||||
<div v-for="item in state.activities" :key="item[0]">
|
||||
<h6 style="text-align: start" class="mb-0">
|
||||
{{ item[1].origin }} -
|
||||
<span>{{ getDateTime(item[1].timestamp) }}</span>
|
||||
</h6>
|
||||
<p class="mb-2" style="text-align: start; font-size: smaller">
|
||||
{{ item[1].activity_type }} - {{ item[1].direction }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
36
gui/src/components/grid/grid_beacon.vue
Normal file
36
gui/src/components/grid/grid_beacon.vue
Normal file
|
@ -0,0 +1,36 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { setModemBeacon } from "../../js/api.js";
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
function startStopBeacon() {
|
||||
if (state.beacon_state === true) {
|
||||
setModemBeacon(false);
|
||||
} else {
|
||||
setModemBeacon(true);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="fill h-100" style="width: calc(100% - 24px)">
|
||||
<a
|
||||
class="btn btn-sm btn-secondary d-flex justify-content-center align-items-center object-fill border rounded w-100 h-100"
|
||||
@click="startStopBeacon"
|
||||
title="Enable/disable periodic beacons"
|
||||
>Beacon
|
||||
<span
|
||||
class=""
|
||||
role="status"
|
||||
v-bind:class="{
|
||||
'spinner-grow spinner-grow-sm': state.beacon_state === true,
|
||||
disabled: state.beacon_state === false,
|
||||
}"
|
||||
>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
61
gui/src/components/grid/grid_dbfs.vue
Normal file
61
gui/src/components/grid/grid_dbfs.vue
Normal file
|
@ -0,0 +1,61 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
</script>
|
||||
<template>
|
||||
<div class="progress mb-0 me-4 rounded-0 rounded-top" style="height: 22px">
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
||||
id="dbfs_level"
|
||||
role="progressbar"
|
||||
:style="{ width: state.dbfs_level_percent + '%' }"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<p
|
||||
class="justify-content-center d-flex position-absolute w-100"
|
||||
id="dbfs_level_value"
|
||||
>
|
||||
{{ state.dbfs_level }} dBFS
|
||||
</p>
|
||||
</div>
|
||||
<div class="progress mb-0 me-4 rounded-0 rounded-bottom" style="height: 8px">
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 1%"
|
||||
aria-valuenow="1"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar bg-success"
|
||||
role="progressbar"
|
||||
style="width: 89%"
|
||||
aria-valuenow="50"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 20%"
|
||||
aria-valuenow="20"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-danger"
|
||||
role="progressbar"
|
||||
style="width: 29%"
|
||||
aria-valuenow="29"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
17
gui/src/components/grid/grid_frequency.vue
Normal file
17
gui/src/components/grid/grid_frequency.vue
Normal file
|
@ -0,0 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
</script>
|
||||
<template>
|
||||
<a
|
||||
class="d-flex justify-content-center align-items-center object-fill border rounded w-100 h-100 link-underline link-underline-opacity-0 link-underline-opacity-75-hover text-bg-light"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#offcanvasFrequency"
|
||||
>
|
||||
{{ state.frequency / 1000 }} kHz
|
||||
</a>
|
||||
</template>
|
30
gui/src/components/grid/grid_mycall small.vue
Normal file
30
gui/src/components/grid/grid_mycall small.vue
Normal file
|
@ -0,0 +1,30 @@
|
|||
<script setup lang="ts">
|
||||
import { reactive, ref } from "vue";
|
||||
import { setActivePinia } from "pinia";
|
||||
import { setConfig } from "../../js/api";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore } from "../../store/settingsStore.js";
|
||||
</script>
|
||||
<template>
|
||||
<div class="w-100">
|
||||
<div class="input-group input-group-sm" style="width: calc(100% - 24px)">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
disabled
|
||||
style="min-width: 3em; background-color: transparent"
|
||||
v-model="settingsStore.remote.STATION.mycall"
|
||||
/>
|
||||
<span class="input-group-text">-</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
disabled
|
||||
style="min-width: 2em; max-width: 2.5em; background-color: transparent"
|
||||
v-model="settingsStore.remote.STATION.myssid"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
20
gui/src/components/grid/grid_mycall.vue
Normal file
20
gui/src/components/grid/grid_mycall.vue
Normal file
|
@ -0,0 +1,20 @@
|
|||
<script setup lang="ts">
|
||||
import { reactive, ref } from "vue";
|
||||
import { setActivePinia } from "pinia";
|
||||
import { setConfig } from "../../js/api";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore } from "../../store/settingsStore.js";
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="d-flex justify-content-center align-items-center object-fill border rounded w-100 h-100"
|
||||
>
|
||||
<h2>
|
||||
{{ settingsStore.remote.STATION.mycall }}-{{
|
||||
settingsStore.remote.STATION.myssid
|
||||
}}
|
||||
</h2>
|
||||
</div>
|
||||
</template>
|
53
gui/src/components/grid/grid_ping.vue
Normal file
53
gui/src/components/grid/grid_ping.vue
Normal file
|
@ -0,0 +1,53 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
import { sendModemPing } from "../../js/api.js";
|
||||
|
||||
const state = useStateStore(pinia);
|
||||
function transmitPing() {
|
||||
sendModemPing(dxcallPing.value.toUpperCase());
|
||||
}
|
||||
var dxcallPing = ref("");
|
||||
|
||||
window.addEventListener(
|
||||
"stationSelected",
|
||||
function (eventdata) {
|
||||
let evt = <CustomEvent>eventdata;
|
||||
dxcallPing.value = evt.detail;
|
||||
},
|
||||
false,
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="input-group" style="width: calc(100% - 24px)">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="min-width: 3rem; text-transform: uppercase; height: 31px"
|
||||
placeholder="DXcall"
|
||||
pattern="[A-Z]*"
|
||||
maxlength="11"
|
||||
aria-label="Input group"
|
||||
aria-describedby="btnGroupAddon"
|
||||
v-model="dxcallPing"
|
||||
/>
|
||||
<a
|
||||
class="btn btn-sm btn-secondary"
|
||||
style="max-width: 3em"
|
||||
id="sendPing"
|
||||
type="button"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Send a ping request to a remote station"
|
||||
@click="transmitPing()"
|
||||
>
|
||||
Ping
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
16
gui/src/components/grid/grid_ptt.vue
Normal file
16
gui/src/components/grid/grid_ptt.vue
Normal file
|
@ -0,0 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="d-flex justify-content-center align-items-center object-fill border rounded w-100 h-100"
|
||||
:class="state.ptt_state === true ? 'text-bg-warning' : 'text-bg-white'"
|
||||
>
|
||||
<h2>ON AIR</h2>
|
||||
</div>
|
||||
</template>
|
61
gui/src/components/grid/grid_s-meter.vue
Normal file
61
gui/src/components/grid/grid_s-meter.vue
Normal file
|
@ -0,0 +1,61 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
</script>
|
||||
<template>
|
||||
<div class="progress mb-0 me-4 rounded-0 rounded-top" style="height: 22px">
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
||||
id="noise_level"
|
||||
role="progressbar"
|
||||
:style="{ width: state.s_meter_strength_percent + '%' }"
|
||||
aria-valuenow="{{state.s_meter_strength_percent}}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<p
|
||||
class="justify-content-center d-flex position-absolute w-100"
|
||||
id="noise_level_value"
|
||||
>
|
||||
S-Meter(dB): {{ state.s_meter_strength_raw }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="progress mb-0 me-4 rounded-0 rounded-bottom" style="height: 8px">
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 1%"
|
||||
aria-valuenow="1"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar bg-success"
|
||||
role="progressbar"
|
||||
style="width: 89%"
|
||||
aria-valuenow="50"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 20%"
|
||||
aria-valuenow="20"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-danger"
|
||||
role="progressbar"
|
||||
style="width: 29%"
|
||||
aria-valuenow="29"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
91
gui/src/components/grid/grid_scatter.vue
Normal file
91
gui/src/components/grid/grid_scatter.vue
Normal file
|
@ -0,0 +1,91 @@
|
|||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
// reason for no check is, that we have some mixing of typescript and chart js which seems to be not to be fixed that easy
|
||||
import { computed, onMounted } from "vue";
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from "chart.js";
|
||||
import { Scatter } from "vue-chartjs";
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
);
|
||||
|
||||
// https://www.chartjs.org/docs/latest/samples/line/segments.html
|
||||
const scatterChartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
x: {
|
||||
type: "linear",
|
||||
position: "bottom",
|
||||
grid: {
|
||||
display: true,
|
||||
lineWidth: 1, // Set the line width for x-axis grid lines
|
||||
},
|
||||
ticks: {
|
||||
display: true,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
type: "linear",
|
||||
position: "left",
|
||||
grid: {
|
||||
display: true,
|
||||
lineWidth: 1, // Set the line width for y-axis grid lines
|
||||
},
|
||||
ticks: {
|
||||
display: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const scatterChartData = computed(() => ({
|
||||
datasets: [
|
||||
{
|
||||
type: "scatter",
|
||||
fill: true,
|
||||
data: state.scatter,
|
||||
label: "Scatter",
|
||||
tension: 0.1,
|
||||
borderColor: "rgb(0, 255, 0)",
|
||||
},
|
||||
],
|
||||
}));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-100 h-100">
|
||||
<Scatter :data="scatterChartData" :options="scatterChartOptions" />
|
||||
</div>
|
||||
<!--278px-->
|
||||
</template>
|
25
gui/src/components/grid/grid_stop.vue
Normal file
25
gui/src/components/grid/grid_stop.vue
Normal file
|
@ -0,0 +1,25 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { stopTransmission } from "../../js/api";
|
||||
|
||||
function stopAllTransmissions() {
|
||||
console.log("stopping transmissions");
|
||||
stopTransmission();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<a
|
||||
class="btn btn-outline-danger d-flex border justify-content-center align-items-center object-fill rounded w-100 h-100"
|
||||
id="stop_transmission_connection"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
@click="stopAllTransmissions()"
|
||||
title="Abort session and stop transmissions"
|
||||
>
|
||||
<i class="bi bi-sign-stop-fill h1"></i>
|
||||
</a>
|
||||
</template>
|
18
gui/src/components/grid/grid_tune.vue
Normal file
18
gui/src/components/grid/grid_tune.vue
Normal file
|
@ -0,0 +1,18 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
</script>
|
||||
<template>
|
||||
<div class="fill h-100" style="width: calc(100% - 24px)">
|
||||
<a
|
||||
class="btn btn-sm btn-secondary d-flex justify-content-center align-items-center object-fill border rounded w-100 h-100"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#audioModal"
|
||||
title="Tune"
|
||||
>Tune</a
|
||||
>
|
||||
</div>
|
||||
</template>
|
384
gui/src/components/main.vue
Normal file
384
gui/src/components/main.vue
Normal file
|
@ -0,0 +1,384 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import main_modals from "./main_modals.vue";
|
||||
import main_top_navbar from "./main_top_navbar.vue";
|
||||
import settings_view from "./settings.vue";
|
||||
import main_footer_navbar from "./main_footer_navbar.vue";
|
||||
|
||||
import chat from "./chat.vue";
|
||||
import main_modem_healthcheck from "./main_modem_healthcheck.vue";
|
||||
import Dynamic_components from "./dynamic_components.vue";
|
||||
|
||||
import { getFreedataMessages } from "../js/api";
|
||||
import { getRemote } from "../store/settingsStore.js";
|
||||
import { loadAllData } from "../js/eventHandler";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-------------------------------- INFO TOASTS ---------------->
|
||||
<div aria-live="polite" aria-atomic="true" class="position-relative z-3">
|
||||
<div
|
||||
class="toast-container position-absolute top-0 end-0 p-3"
|
||||
id="mainToastContainer"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-1 p-0 bg-body-secondary border-end">
|
||||
<div
|
||||
class="d-flex flex-sm-column flex-row flex-nowrap align-items-center sticky-top"
|
||||
>
|
||||
<div
|
||||
class="list-group bg-body-secondary"
|
||||
id="main-list-tab"
|
||||
role="tablist"
|
||||
style="margin-top: 100px"
|
||||
@click="loadAllData"
|
||||
>
|
||||
<main_modem_healthcheck />
|
||||
|
||||
<a
|
||||
class="list-group-item list-group-item-dark list-group-item-action border-0 rounded-3 mb-2 active"
|
||||
id="list-grid-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-grid"
|
||||
role="tab"
|
||||
aria-controls="list-grid"
|
||||
title="Grid"
|
||||
><i class="bi bi-grid h3"></i
|
||||
></a>
|
||||
|
||||
<a
|
||||
class="list-group-item list-group-item-dark list-group-item-action border-0 rounded-3 mb-2"
|
||||
id="list-chat-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-chat"
|
||||
role="tab"
|
||||
aria-controls="list-chat"
|
||||
title="Chat"
|
||||
@click="getFreedataMessages"
|
||||
><i class="bi bi-chat-text h3"></i
|
||||
></a>
|
||||
|
||||
<a
|
||||
class="list-group-item list-group-item-dark list-group-item-action d-none border-0 rounded-3 mb-2"
|
||||
id="list-mesh-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-mesh"
|
||||
role="tab"
|
||||
aria-controls="list-mesh"
|
||||
><i class="bi bi-rocket h3"></i
|
||||
></a>
|
||||
|
||||
<a
|
||||
class="list-group-item list-group-item-dark list-group-item-action d-none border-0 rounded-3 mb-2"
|
||||
id="list-logger-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-logger"
|
||||
role="tab"
|
||||
aria-controls="list-logger"
|
||||
><i class="bi bi-activity h3"></i
|
||||
></a>
|
||||
<a
|
||||
class="list-group-item list-group-item-dark list-group-item-action border-0 rounded-3 mb-2"
|
||||
id="list-settings-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-settings"
|
||||
role="tab"
|
||||
aria-controls="list-settings"
|
||||
title="Settings"
|
||||
@click="loadAllData"
|
||||
><i class="bi bi-gear-wide-connected h3"></i
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-11 min-vh-100 m-0 p-0">
|
||||
<!-- content -->
|
||||
|
||||
<!-- TODO: Remove the top navbar entirely if not needed
|
||||
<main_top_navbar />
|
||||
-->
|
||||
|
||||
<div class="tab-content" id="nav-tabContent-settings">
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="list-home"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-home-list"
|
||||
>
|
||||
<!-- TOP NAVBAR -->
|
||||
<div
|
||||
id="blurdiv"
|
||||
style="
|
||||
-webkit-filter: blur(0px);
|
||||
filter: blur(0px);
|
||||
height: 100vh;
|
||||
"
|
||||
>
|
||||
<!--beginn of blur div -->
|
||||
<!-------------------------------- MAIN AREA ---------------->
|
||||
|
||||
<!------------------------------------------------------------------------------------------>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane fade d-none"
|
||||
id="list-mesh"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-mesh-list"
|
||||
>
|
||||
<div class="container">
|
||||
<nav>
|
||||
<div class="nav nav-tabs" id="nav-tab-mesh" role="tablist-mesh">
|
||||
<button
|
||||
class="nav-link active"
|
||||
id="nav-route-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#nav-route"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="nav-route"
|
||||
aria-selected="true"
|
||||
>
|
||||
Routes
|
||||
</button>
|
||||
<button
|
||||
class="nav-link"
|
||||
id="nav-signaling-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#nav-signaling"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="nav-signaling"
|
||||
aria-selected="false"
|
||||
>
|
||||
Signaling
|
||||
</button>
|
||||
<button
|
||||
class="nav-link"
|
||||
id="nav-actions-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#nav-actions"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="nav-actions"
|
||||
aria-selected="false"
|
||||
>
|
||||
Actions
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="tab-content d-none" id="nav-tabContent-Mesh">
|
||||
<div
|
||||
class="tab-pane fade show active vw-100 vh-90 overflow-auto"
|
||||
id="nav-route"
|
||||
role="tabpanel"
|
||||
aria-labelledby="nav-route-tab"
|
||||
>
|
||||
<div class="container-fluid">
|
||||
<div
|
||||
class="table-responsive overflow-auto"
|
||||
style="max-width: 99vw; max-height: 99vh"
|
||||
>
|
||||
<table class="table table-hover table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Timestamp</th>
|
||||
<th scope="col">DXCall</th>
|
||||
<th scope="col">Router</th>
|
||||
<th scope="col">Hops</th>
|
||||
<th scope="col">Score</th>
|
||||
<th scope="col">SNR</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="mesh-table"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="nav-signaling"
|
||||
role="tabpanel"
|
||||
aria-labelledby="nav-signaling-tab"
|
||||
>
|
||||
<div class="container-fluid">
|
||||
<div
|
||||
class="table-responsive overflow-auto"
|
||||
style="max-width: 99vw; max-height: 99vh"
|
||||
>
|
||||
<table class="table table-hover table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Timestamp</th>
|
||||
<th scope="col">Destination</th>
|
||||
<th scope="col">Origin</th>
|
||||
<th scope="col">Frametype</th>
|
||||
<th scope="col">Payload</th>
|
||||
<th scope="col">Attempt</th>
|
||||
<th scope="col">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="mesh-signalling-table"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="nav-actions"
|
||||
role="tabpanel-mesh"
|
||||
aria-labelledby="nav-actions-tab"
|
||||
>
|
||||
<div class="input-group mt-1">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="max-width: 6rem; text-transform: uppercase"
|
||||
placeholder="DXcall"
|
||||
pattern="[A-Z]*"
|
||||
id="dxCallMesh"
|
||||
maxlength="11"
|
||||
aria-label="Input group"
|
||||
aria-describedby="btnGroupAddon"
|
||||
/>
|
||||
<button
|
||||
id="transmit_mesh_ping"
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
>
|
||||
mesh ping
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane fade show active"
|
||||
id="list-grid"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-grid-list"
|
||||
>
|
||||
<Dynamic_components />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="list-chat"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-chat-list"
|
||||
>
|
||||
<chat />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="list-logger"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-logger-list"
|
||||
>
|
||||
<div class="container">
|
||||
<nav
|
||||
class="navbar fixed-top bg-body-tertiary border-bottom"
|
||||
style="margin-left: 87px"
|
||||
>
|
||||
<div class="container-fluid">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="btn-check"
|
||||
id="enable_filter_info"
|
||||
autocomplete="off"
|
||||
checked
|
||||
/>
|
||||
<label class="btn btn-outline-info" for="enable_filter_info"
|
||||
>info</label
|
||||
>
|
||||
|
||||
<input
|
||||
type="checkbox"
|
||||
class="btn-check"
|
||||
id="enable_filter_debug"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<label
|
||||
class="btn btn-outline-primary"
|
||||
for="enable_filter_debug"
|
||||
>debug</label
|
||||
>
|
||||
|
||||
<input
|
||||
type="checkbox"
|
||||
class="btn-check"
|
||||
id="enable_filter_warning"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<label
|
||||
class="btn btn-outline-warning"
|
||||
for="enable_filter_warning"
|
||||
>warning</label
|
||||
>
|
||||
|
||||
<input
|
||||
type="checkbox"
|
||||
class="btn-check"
|
||||
id="enable_filter_error"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<label
|
||||
class="btn btn-outline-danger"
|
||||
for="enable_filter_error"
|
||||
>error</label
|
||||
>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid mt-5">
|
||||
<div class="tableFixHead">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Timestamp</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Area</th>
|
||||
<th scope="col">Log entry</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="log">
|
||||
<!--
|
||||
<tr>
|
||||
<th scope="row">1</th>
|
||||
<td>Mark</td>
|
||||
<td>Otto</td>
|
||||
<td>@mdo</td>
|
||||
</tr>
|
||||
-->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<settings_view />
|
||||
|
||||
<!---------------------------------------------------------------------- FOOTER AREA ------------------------------------------------------------>
|
||||
|
||||
<div class="container">
|
||||
<main_footer_navbar />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main_modals />
|
||||
</template>
|
279
gui/src/components/main_footer_navbar.vue
Normal file
279
gui/src/components/main_footer_navbar.vue
Normal file
|
@ -0,0 +1,279 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-1">123</div>
|
||||
<div class="col-11">
|
||||
<nav
|
||||
class="navbar fixed-bottom navbar-expand-xl bg-body-tertiary border-top p-2"
|
||||
>
|
||||
<div class="col">
|
||||
<div class="btn-toolbar" role="toolbar">
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-1"
|
||||
v-bind:class="{
|
||||
'btn-danger': state.ptt_state == true,
|
||||
'btn-secondary': state.ptt_state == false,
|
||||
}"
|
||||
id="ptt_state"
|
||||
type="button"
|
||||
style="pointer-events: auto"
|
||||
disabled
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
data-bs-title="PTT trigger state"
|
||||
>
|
||||
<i class="bi bi-broadcast-pin" style="font-size: 0.8rem"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-1"
|
||||
id="busy_state"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-danger': state.busy_state === true,
|
||||
'btn-secondary': state.busy_state === false,
|
||||
}"
|
||||
data-bs-title="Modem state"
|
||||
disabled
|
||||
style="pointer-events: auto"
|
||||
>
|
||||
<i class="bi bi-cpu" style="font-size: 0.8rem"></i>
|
||||
</button>
|
||||
<!--
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-1"
|
||||
id="arq_session"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-secondary': state.arq_session_state === 'disconnected',
|
||||
'btn-warning': state.arq_session_state === 'connected',
|
||||
}"
|
||||
disabled
|
||||
style="pointer-events: auto"
|
||||
data-bs-title="Session state"
|
||||
>
|
||||
<i class="bi bi-arrow-left-right" style="font-size: 0.8rem"></i>
|
||||
</button>
|
||||
-->
|
||||
<!--
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-1"
|
||||
id="arq_state"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-title="Data channel state"
|
||||
v-bind:class="{
|
||||
'btn-secondary': state.arq_state === 'False',
|
||||
'btn-warning': state.arq_state === 'True',
|
||||
}"
|
||||
disabled
|
||||
style="pointer-events: auto"
|
||||
>
|
||||
<i class="bi bi-file-earmark-binary" style="font-size: 0.8rem"></i>
|
||||
</button>
|
||||
-->
|
||||
<!--
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-1"
|
||||
id="rigctld_state"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
title="rigctld state: <strong class='text-success'>CONNECTED</strong> / <strong class='text-secondary'>UNKNOWN</strong>"
|
||||
>
|
||||
<i class="bi bi-usb-symbol" style="font-size: 0.8rem"></i>
|
||||
</button>
|
||||
-->
|
||||
<!--
|
||||
<button
|
||||
class="btn btn-sm btn-secondary disabled me-3"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.channel_busy === true,
|
||||
'btn-secondary': state.channel_busy === false,
|
||||
}"
|
||||
style="pointer-events: auto"
|
||||
data-bs-title="Channel busy"
|
||||
>
|
||||
<i class="bi bi-hourglass"></i>
|
||||
</button>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-4 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-title="What's the frequency, Kenneth?"
|
||||
style="pointer-events: auto"
|
||||
>
|
||||
{{ state.frequency / 1000 }} kHz
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-0"
|
||||
type="button"
|
||||
title="Speed level"
|
||||
>
|
||||
<i class="bi bi-speedometer2" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-4 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
data-bs-titel="speed level"
|
||||
>
|
||||
<i
|
||||
class="bi"
|
||||
style="font-size: 1rem"
|
||||
v-bind:class="{
|
||||
'bi-reception-0': state.speed_level == 0,
|
||||
'bi-reception-1': state.speed_level == 1,
|
||||
'bi-reception-2': state.speed_level == 2,
|
||||
'bi-reception-3': state.speed_level == 3,
|
||||
'bi-reception-4': state.speed_level == 4,
|
||||
}"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-0"
|
||||
type="button"
|
||||
title="Bytes transfered"
|
||||
>
|
||||
<i
|
||||
class="bi bi-file-earmark-binary"
|
||||
style="font-size: 1rem"
|
||||
></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-4 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
data-bs-title="total bytes of transmission"
|
||||
>
|
||||
{{ state.arq_total_bytes }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-0"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
data-bs-title="Current or last connected with station"
|
||||
>
|
||||
<i
|
||||
class="bi bi-file-earmark-binary"
|
||||
style="font-size: 1rem"
|
||||
></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-secondary disabled me-1"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
data-bs-title="the dxcallsign of the connected station"
|
||||
>
|
||||
{{ state.dxcallsign }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div style="margin-right: 2px">
|
||||
<div
|
||||
class="progress w-100"
|
||||
style="height: 20px; min-width: 200px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
||||
id="transmission_progress"
|
||||
role="progressbar"
|
||||
:style="{ width: state.arq_transmission_percent + '%' }"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<p
|
||||
class="justify-content-center m-0 d-flex position-absolute w-100 text-dark"
|
||||
>
|
||||
Message Progress
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
hidden
|
||||
class="progress mb-0 rounded-0 rounded-bottom"
|
||||
style="height: 10px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
id="transmission_timeleft"
|
||||
role="progressbar"
|
||||
:style="{
|
||||
width: state.arq_seconds_until_timeout_percent + '%',
|
||||
}"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
>
|
||||
<p
|
||||
class="justify-content-center m-0 d-flex position-absolute w-100 text-dark"
|
||||
>
|
||||
timeout in {{ state.arq_seconds_until_timeout }}s
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
1309
gui/src/components/main_modals.vue
Normal file
1309
gui/src/components/main_modals.vue
Normal file
File diff suppressed because it is too large
Load diff
27
gui/src/components/main_modem_healthcheck.vue
Normal file
27
gui/src/components/main_modem_healthcheck.vue
Normal file
|
@ -0,0 +1,27 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { getOverallHealth } from "../js/eventHandler.js";
|
||||
</script>
|
||||
<template>
|
||||
<a
|
||||
class="btn border btn-outline-secondary list-group-item mb-5"
|
||||
data-bs-html="false"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#modemCheck"
|
||||
title="Check FreeDATA status"
|
||||
:class="
|
||||
getOverallHealth() > 4
|
||||
? 'bg-danger'
|
||||
: getOverallHealth() < 2
|
||||
? ''
|
||||
: 'bg-warning'
|
||||
"
|
||||
><i class="bi bi-activity h3"></i>
|
||||
</a>
|
||||
</template>
|
431
gui/src/components/main_startup_check.vue
Normal file
431
gui/src/components/main_startup_check.vue
Normal file
|
@ -0,0 +1,431 @@
|
|||
<script setup lang="ts">
|
||||
import { Modal } from "bootstrap";
|
||||
import { onMounted } from "vue";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
|
||||
import { sendModemCQ } from "../js/api.js";
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { useAudioStore } from "../store/audioStore";
|
||||
const audioStore = useAudioStore();
|
||||
import { useSerialStore } from "../store/serialStore";
|
||||
const serialStore = useSerialStore();
|
||||
|
||||
import {
|
||||
getVersion,
|
||||
setConfig,
|
||||
startModem,
|
||||
stopModem,
|
||||
getModemState,
|
||||
} from "../js/api";
|
||||
|
||||
const version = import.meta.env.PACKAGE_VERSION;
|
||||
|
||||
// start modemCheck modal once on startup
|
||||
onMounted(() => {
|
||||
getVersion().then((res) => {
|
||||
state.modem_version = res;
|
||||
});
|
||||
new Modal("#modemCheck", {}).show();
|
||||
});
|
||||
|
||||
function getModemStateLocal() {
|
||||
// Returns active/inactive if modem is running for modem status label
|
||||
if (state.is_modem_running == true) return "Active";
|
||||
else return "Inactive";
|
||||
}
|
||||
function getNetworkState() {
|
||||
// Returns active/inactive if modem is running for modem status label
|
||||
if (state.modem_connection === "connected") return "Connected";
|
||||
else return "Disconnected";
|
||||
}
|
||||
|
||||
function getRigControlStuff() {
|
||||
switch (settings.remote.RADIO.control) {
|
||||
case "disabled":
|
||||
return true;
|
||||
case "rigctld":
|
||||
case "rigctld_bundle":
|
||||
case "tci":
|
||||
return state.radio_status;
|
||||
default:
|
||||
console.error(
|
||||
"Unknown radio control mode " + settings.remote.RADIO.control,
|
||||
);
|
||||
return "Unknown control type" + settings.remote.RADIO.control;
|
||||
}
|
||||
}
|
||||
|
||||
function testHamlib() {
|
||||
sendModemCQ();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="modal modal-lg fade"
|
||||
id="modemCheck"
|
||||
data-bs-backdrop="static"
|
||||
data-bs-keyboard="false"
|
||||
tabindex="-1"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5">Modem check</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="accordion" id="startupCheckAccordion">
|
||||
<!-- Network Section -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-target="#networkStatusCollapse"
|
||||
data-bs-toggle="collapse"
|
||||
>
|
||||
Network
|
||||
<span
|
||||
class="badge ms-2 bg-success"
|
||||
:class="
|
||||
state.modem_connection === 'connected'
|
||||
? 'bg-success'
|
||||
: 'bg-danger'
|
||||
"
|
||||
>{{ getNetworkState() }}</span
|
||||
>
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
id="networkStatusCollapse"
|
||||
class="accordion-collapse collapse"
|
||||
>
|
||||
<div class="accordion-body">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-25">Modem port</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="modem port (def 5000)"
|
||||
id="modem_port"
|
||||
maxlength="5"
|
||||
max="65534"
|
||||
min="1025"
|
||||
v-model="settings.local.port"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-25">Modem host</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="modem host (default 127.0.0.1)"
|
||||
id="modem_port"
|
||||
v-model="settings.local.host"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Modem Section -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-target="#modemStatusCollapse"
|
||||
data-bs-toggle="collapse"
|
||||
>
|
||||
Modem
|
||||
<span
|
||||
class="badge ms-2"
|
||||
:class="
|
||||
state.is_modem_running === true
|
||||
? 'bg-success'
|
||||
: 'bg-danger'
|
||||
"
|
||||
>{{ getModemStateLocal() }}</span
|
||||
>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="modemStatusCollapse" class="accordion-collapse collapse">
|
||||
<div class="accordion-body">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50"
|
||||
>Manual modem restart</label
|
||||
>
|
||||
<label class="input-group-text">
|
||||
<button
|
||||
type="button"
|
||||
id="startModem"
|
||||
class="btn btn-sm btn-outline-success"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Start the Modem. Please set your audio and radio settings first!"
|
||||
@click="startModem"
|
||||
v-bind:class="{
|
||||
disabled: state.is_modem_running === true,
|
||||
}"
|
||||
>
|
||||
<i class="bi bi-play-fill"></i>
|
||||
</button> </label
|
||||
><label class="input-group-text">
|
||||
<button
|
||||
type="button"
|
||||
id="stopModem"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Stop the Modem."
|
||||
@click="stopModem"
|
||||
v-bind:class="{
|
||||
disabled: state.is_modem_running === false,
|
||||
}"
|
||||
>
|
||||
<i class="bi bi-stop-fill"></i>
|
||||
</button>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Audio Input Device -->
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50"
|
||||
>Audio Input device</label
|
||||
>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.AUDIO.input_device"
|
||||
>
|
||||
<option
|
||||
v-for="device in audioStore.audioInputs"
|
||||
:value="device.id"
|
||||
>
|
||||
{{ device.name }} [{{ device.api }}]
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Audio Output Device -->
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50"
|
||||
>Audio Output device</label
|
||||
>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.AUDIO.output_device"
|
||||
>
|
||||
<option
|
||||
v-for="device in audioStore.audioOutputs"
|
||||
:value="device.id"
|
||||
>
|
||||
{{ device.name }} [{{ device.api }}]
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Radio Control Section -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-target="#radioControlCollapse"
|
||||
data-bs-toggle="collapse"
|
||||
>
|
||||
Radio control
|
||||
<span
|
||||
class="badge ms-2"
|
||||
:class="
|
||||
getRigControlStuff() === true ? 'bg-success' : 'bg-danger'
|
||||
"
|
||||
>{{
|
||||
getRigControlStuff() === true ? "Online" : "Offline"
|
||||
}}</span
|
||||
>
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
id="radioControlCollapse"
|
||||
class="accordion-collapse collapse"
|
||||
>
|
||||
<div class="accordion-body">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px"
|
||||
>Rig control method</span
|
||||
>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="rigcontrol_radiocontrol"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.RADIO.control"
|
||||
>
|
||||
<option selected value="disabled">
|
||||
Disabled (no rig control; use with VOX)
|
||||
</option>
|
||||
<option selected value="rigctld">
|
||||
Rigctld (external Hamlib)
|
||||
</option>
|
||||
<option selected value="rigctld_bundle">
|
||||
Rigctld (internal Hamlib)
|
||||
</option>
|
||||
<option selected value="tci">TCI</option>
|
||||
</select>
|
||||
</div>
|
||||
<div
|
||||
:class="
|
||||
settings.remote.RADIO.control == 'rigctld_bundle'
|
||||
? ''
|
||||
: 'd-none'
|
||||
"
|
||||
>
|
||||
<!-- Shown when rigctld is selected-->
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px"
|
||||
>Radio port</span
|
||||
>
|
||||
|
||||
<select
|
||||
@change="onChange"
|
||||
v-model="settings.remote.RADIO.serial_port"
|
||||
class="form-select form-select-sm"
|
||||
>
|
||||
<option
|
||||
v-for="device in serialStore.serialDevices"
|
||||
:value="device.port"
|
||||
:key="device.port"
|
||||
>
|
||||
{{ device.description }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-25">Rigctld Test</label>
|
||||
|
||||
<label class="input-group-text">
|
||||
<button
|
||||
type="button"
|
||||
id="testHamlib"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
@click="testHamlib"
|
||||
title="Test your hamlib settings and toggle PTT once. Button will become <strong class='text-success'>green</strong> on success and <strong class='text-danger'>red</strong> if fails."
|
||||
>
|
||||
Send a CQ
|
||||
</button>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
:class="
|
||||
settings.remote.RADIO.control == 'tci' ? '' : 'd-none'
|
||||
"
|
||||
>
|
||||
<!-- Shown when tci is selected-->
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-25">TCI IP address</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="TCI IP"
|
||||
id="rigcontrol_tci_ip"
|
||||
aria-label="Device IP"
|
||||
v-model="settings.remote.TCI.tci_ip"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-25">TCI port</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="TCI port"
|
||||
id="rigcontrol_tci_port"
|
||||
aria-label="Device Port"
|
||||
v-model="settings.remote.TCI.tci_port"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Version Section -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-target="#versionCheckCollapse"
|
||||
data-bs-toggle="collapse"
|
||||
>
|
||||
Version
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
id="versionCheckCollapse"
|
||||
class="accordion-collapse collapse"
|
||||
>
|
||||
<div class="accordion-body">
|
||||
<button
|
||||
class="btn btn-secondary btn-sm ms-1 me-1"
|
||||
type="button"
|
||||
disabled
|
||||
>
|
||||
GUI version | {{ version }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-secondary btn-sm ms-1 me-1"
|
||||
type="button"
|
||||
disabled
|
||||
>
|
||||
Modem version | {{ state.modem_version }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">
|
||||
Continue
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
26
gui/src/components/main_top_navbar.vue
Normal file
26
gui/src/components/main_top_navbar.vue
Normal file
|
@ -0,0 +1,26 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { settingsStore as settings } from "../store/settingsStore.js";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="navbar bg-body-tertiary border-bottom z-0">
|
||||
<div class="mx-auto">
|
||||
<span class="badge bg-secondary me-4">
|
||||
Modem Connection {{ state.modem_connection }}
|
||||
</span>
|
||||
<span class="badge bg-secondary me-4">
|
||||
Modem {{ state.is_modem_running }}
|
||||
</span>
|
||||
<span class="badge bg-secondary me-4">
|
||||
RIG Control {{ state.rigctld_started }}
|
||||
</span>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
204
gui/src/components/settings.vue
Normal file
204
gui/src/components/settings.vue
Normal file
|
@ -0,0 +1,204 @@
|
|||
<script setup lang="ts">
|
||||
import settings_station from "./settings_station.vue";
|
||||
import settings_gui from "./settings_gui.vue";
|
||||
import settings_chat from "./settings_chat.vue";
|
||||
import settings_rigcontrol from "./settings_rigcontrol.vue";
|
||||
import settings_modem from "./settings_modem.vue";
|
||||
import settings_web from "./settings_web.vue";
|
||||
import settings_exp from "./settings_exp.vue";
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="list-settings"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-settings-list"
|
||||
>
|
||||
<div class="container">
|
||||
<div class="card text-center">
|
||||
<div class="card-header">
|
||||
<!-- SETTINGS Nav tabs -->
|
||||
<ul class="nav nav-tabs card-header-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link active"
|
||||
id="station-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#station"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="home"
|
||||
aria-selected="true"
|
||||
>
|
||||
Station
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="gui-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#gui"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="home"
|
||||
aria-selected="true"
|
||||
>
|
||||
GUI
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="chat-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#chat"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="home"
|
||||
aria-selected="true"
|
||||
>
|
||||
Chat
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="rigcontrol-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#rigcontrol"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="profile"
|
||||
aria-selected="false"
|
||||
>
|
||||
Rig Control
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="modem-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#modem"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="profile"
|
||||
aria-selected="false"
|
||||
>
|
||||
Modem
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="web-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#web"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="messages"
|
||||
aria-selected="false"
|
||||
>
|
||||
Web
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="experiments-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#experiments"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="settings"
|
||||
aria-selected="false"
|
||||
>
|
||||
Exp
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
class="card-body overflow-auto"
|
||||
style="height: calc(100vh - 105px)"
|
||||
>
|
||||
<!-- SETTINGS Nav Tab panes -->
|
||||
|
||||
<!-- Station tab contents-->
|
||||
<div class="tab-content">
|
||||
<div
|
||||
class="tab-pane active"
|
||||
id="station"
|
||||
role="tabpanel"
|
||||
aria-labelledby="station-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_station />
|
||||
</div>
|
||||
|
||||
<!-- GUI tab contents-->
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="gui"
|
||||
role="tabpanel"
|
||||
aria-labelledby="gui-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_gui />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="chat"
|
||||
role="tabpanel"
|
||||
aria-labelledby="chat-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_chat />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="rigcontrol"
|
||||
role="tabpanel"
|
||||
aria-labelledby="rigcontrol-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_rigcontrol />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="modem"
|
||||
role="tabpanel"
|
||||
aria-labelledby="modem-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_modem />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="web"
|
||||
role="tabpanel"
|
||||
aria-labelledby="web-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_web />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="experiments"
|
||||
role="tabpanel"
|
||||
aria-labelledby="experiments-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_exp />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
28
gui/src/components/settings_chat.vue
Normal file
28
gui/src/components/settings_chat.vue
Normal file
|
@ -0,0 +1,28 @@
|
|||
<script setup lang="ts">
|
||||
import { setConfig } from "../js/api";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable message auto repeat</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline ms-2">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.MESSAGES.enable_auto_repeat"
|
||||
/>
|
||||
<label class="form-check-label" for="enableMessagesAutoRepeatSwitch"
|
||||
>Re-send message on beacon</label
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
35
gui/src/components/settings_exp.vue
Normal file
35
gui/src/components/settings_exp.vue
Normal file
|
@ -0,0 +1,35 @@
|
|||
<script setup lang="ts">
|
||||
import { setConfig } from "../js/api";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings } from "../store/settingsStore.js";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable MESH protocol</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline ms-2">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enableMeshSwitch"
|
||||
@change="setConfig"
|
||||
v-model="settings.remote.MESH.enable_protocol"
|
||||
/>
|
||||
<label class="form-check-label" for="enableMeshSwitch"
|
||||
>experimental! REALLY!</label
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="center">
|
||||
<div class="badge text-bg-danger">
|
||||
<i class="bi bi-shield-exclamation"></i> These options may not work and
|
||||
are for experienced users only!
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
63
gui/src/components/settings_gui.vue
Normal file
63
gui/src/components/settings_gui.vue
Normal file
|
@ -0,0 +1,63 @@
|
|||
<script setup lang="ts">
|
||||
import { setColormap } from "../js/waterfallHandler";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings } from "../store/settingsStore.js";
|
||||
|
||||
function saveSettings() {
|
||||
//saveSettingsToFile();
|
||||
setColormap();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-50">Waterfall theme</span>
|
||||
<select
|
||||
class="form-select form-select-sm w-50"
|
||||
id="wftheme_selector"
|
||||
@change="saveSettings"
|
||||
v-model="settings.local.wf_theme"
|
||||
>
|
||||
<option value="2">Default</option>
|
||||
<option value="0">Turbo</option>
|
||||
<option value="1">Fosphor</option>
|
||||
<option value="3">Inferno</option>
|
||||
<option value="4">Magma</option>
|
||||
<option value="5">Jet</option>
|
||||
<option value="6">Binary</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-50">Update channel</span>
|
||||
<select
|
||||
class="form-select form-select-sm w-50"
|
||||
id="update_channel_selector"
|
||||
@change="saveSettings"
|
||||
v-model="settings.local.update_channel"
|
||||
>
|
||||
<option value="latest">Stable</option>
|
||||
<option value="beta">Beta</option>
|
||||
<option value="alpha">Alpha</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable notifications</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="NotificationSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.local.enable_sys_notification"
|
||||
/>
|
||||
<label class="form-check-label" for="NotificationSwitch"
|
||||
>Show system pop-ups</label
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
531
gui/src/components/settings_hamlib.vue
Normal file
531
gui/src/components/settings_hamlib.vue
Normal file
|
@ -0,0 +1,531 @@
|
|||
<script setup lang="ts">
|
||||
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
|
||||
import { useSerialStore } from "../store/serialStore";
|
||||
const serialStore = useSerialStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<hr class="m-2" />
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Rigctld IP</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="rigctld IP"
|
||||
id="hamlib_rigctld_ip"
|
||||
aria-label="Device IP"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.RIGCTLD.ip"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Rigctld port</span>
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
placeholder="rigctld port"
|
||||
id="hamlib_rigctld_port"
|
||||
aria-label="Device Port"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.RIGCTLD.port"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<hr class="m-2" />
|
||||
<div
|
||||
:class="settings.remote.RADIO.control == 'rigctld_bundle' ? '' : 'd-none'"
|
||||
>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Radio model</span>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="hamlib_deviceid"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.RADIO.model_id"
|
||||
>
|
||||
<option selected value="0">-- ignore --</option>
|
||||
<option value="1">Hamlib Dummy</option>
|
||||
<option value="2">Hamlib NET rigctl</option>
|
||||
<option value="4">FLRig FLRig</option>
|
||||
<option value="5">TRXManager TRXManager 5.7.630+</option>
|
||||
<option value="6">Hamlib Dummy No VFO</option>
|
||||
<option value="29001">ADAT www.adat.ch (29001)</option>
|
||||
<option value="25016">AE9RB Si570 (25016)</option>
|
||||
<option value="25017">AE9RB Si570 (25017)</option>
|
||||
<option value="17001">Alinco DX-77 (17001)</option>
|
||||
<option value="17002">Alinco DX-SR8 (17002)</option>
|
||||
<option value="25006">AmQRP DDS-60 (25006)</option>
|
||||
<option value="25013">AMSAT-UK FUNcube (25013)</option>
|
||||
<option value="25018">AMSAT-UK FUNcube (25018)</option>
|
||||
<option value="5008">AOR AR2700 (5008)</option>
|
||||
<option value="5006">AOR AR3000A (5006)</option>
|
||||
<option value="5005">AOR AR3030 (5005)</option>
|
||||
<option value="5004">AOR AR5000 (5004)</option>
|
||||
<option value="5014">AOR AR5000A (5014)</option>
|
||||
<option value="5003">AOR AR7030 (5003)</option>
|
||||
<option value="5015">AOR AR7030 (5015)</option>
|
||||
<option value="5002">AOR AR8000 (5002)</option>
|
||||
<option value="5001">AOR AR8200 (5001)</option>
|
||||
<option value="5013">AOR AR8600 (5013)</option>
|
||||
<option value="5016">AOR SR2200 (5016)</option>
|
||||
<option value="32001">Barrett 2050 (32001)</option>
|
||||
<option value="32003">Barrett 4050 (32003)</option>
|
||||
<option value="32002">Barrett 950 (32002)</option>
|
||||
<option value="34001">CODAN Envoy (34001)</option>
|
||||
<option value="34002">CODAN NGT (34002)</option>
|
||||
<option value="25003">Coding Technologies (25003)</option>
|
||||
<option value="31002">Dorji DRA818U (31002)</option>
|
||||
<option value="31001">Dorji DRA818V (31001)</option>
|
||||
<option value="9002">Drake R-8A (9002)</option>
|
||||
<option value="9003">Drake R-8B (9003)</option>
|
||||
<option value="23003">DTTS Microwave (23003)</option>
|
||||
<option value="23004">DTTS Microwave (23004)</option>
|
||||
<option value="33001">ELAD FDM-DUO (33001)</option>
|
||||
<option value="2021">Elecraft K2 (2021)</option>
|
||||
<option value="2029">Elecraft K3 (2029)</option>
|
||||
<option value="2043">Elecraft K3S (2043)</option>
|
||||
<option value="2047">Elecraft K4 (2047)</option>
|
||||
<option value="2044">Elecraft KX2 (2044)</option>
|
||||
<option value="2045">Elecraft KX3 (2045)</option>
|
||||
<option value="2038">Elecraft XG3 (2038)</option>
|
||||
<option value="25001">Elektor Elektor (25001)</option>
|
||||
<option value="25007">Elektor Elektor (25007)</option>
|
||||
<option value="25012">FiFi FiFi-SDR (25012)</option>
|
||||
<option value="2036">FlexRadio 6xxx (2036)</option>
|
||||
<option value="23001">Flex-radio SDR-1000 (23001)</option>
|
||||
<option value="2048">FlexRadio/ANAN PowerSDR/Thetis (2048)</option>
|
||||
<option value="25015">Funkamateur FA-SDR (25015)</option>
|
||||
<option value="35001">GOMSPACE GS100 (35001)</option>
|
||||
<option value="2046">Hilberling PT-8000A (2046)</option>
|
||||
<option value="25019">HobbyPCB RS-HFIQ (25019)</option>
|
||||
<option value="3054">Icom IC (3054)</option>
|
||||
<option value="3002">Icom IC-1275 (3002)</option>
|
||||
<option value="3003">Icom IC-271 (3003)</option>
|
||||
<option value="3072">Icom IC-2730 (3072)</option>
|
||||
<option value="3004">Icom IC-275 (3004)</option>
|
||||
<option value="3005">Icom IC-375 (3005)</option>
|
||||
<option value="3006">Icom IC-471 (3006)</option>
|
||||
<option value="3007">Icom IC-475 (3007)</option>
|
||||
<option value="3008">Icom IC-575 (3008)</option>
|
||||
<option value="3060">Icom IC-7000 (3060)</option>
|
||||
<option value="3055">Icom IC-703 (3055)</option>
|
||||
<option value="3085">Icom IC-705 (3085)</option>
|
||||
<option value="3009">Icom IC-706 (3009)</option>
|
||||
<option value="3010">Icom IC-706MkII (3010)</option>
|
||||
<option value="3011">Icom IC-706MkIIG (3011)</option>
|
||||
<option value="3012">Icom IC-707 (3012)</option>
|
||||
<option value="3070">Icom IC-7100 (3070)</option>
|
||||
<option value="3013">Icom IC-718 (3013)</option>
|
||||
<option value="3061">Icom IC-7200 (3061)</option>
|
||||
<option value="3014">Icom IC-725 (3014)</option>
|
||||
<option value="3015">Icom IC-726 (3015)</option>
|
||||
<option value="3016">Icom IC-728 (3016)</option>
|
||||
<option value="3017">Icom IC-729 (3017)</option>
|
||||
<option value="3073">Icom IC-7300 (3073)</option>
|
||||
<option value="3019">Icom IC-735 (3019)</option>
|
||||
<option value="3020">Icom IC-736 (3020)</option>
|
||||
<option value="3021">Icom IC-737 (3021)</option>
|
||||
<option value="3022">Icom IC-738 (3022)</option>
|
||||
<option value="3067">Icom IC-7410 (3067)</option>
|
||||
<option value="3023">Icom IC-746 (3023)</option>
|
||||
<option value="3046">Icom IC-746PRO (3046)</option>
|
||||
<option value="3024">Icom IC-751 (3024)</option>
|
||||
<option value="3026">Icom IC-756 (3026)</option>
|
||||
<option value="3027">Icom IC-756PRO (3027)</option>
|
||||
<option value="3047">Icom IC-756PROII (3047)</option>
|
||||
<option value="3057">Icom IC-756PROIII (3057)</option>
|
||||
<option value="3063">Icom IC-7600 (3063)</option>
|
||||
<option value="3028">Icom IC-761 (3028)</option>
|
||||
<option value="3078">Icom IC-7610 (3078)</option>
|
||||
<option value="3029">Icom IC-765 (3029)</option>
|
||||
<option value="3062">Icom IC-7700 (3062)</option>
|
||||
<option value="3030">Icom IC-775 (3030)</option>
|
||||
<option value="3045">Icom IC-78 (3045)</option>
|
||||
<option value="3056">Icom IC-7800 (3056)</option>
|
||||
<option value="3031">Icom IC-781 (3031)</option>
|
||||
<option value="3075">Icom IC-7850/7851 (3075)</option>
|
||||
<option value="3032">Icom IC-820H (3032)</option>
|
||||
<option value="3034">Icom IC-821H (3034)</option>
|
||||
<option value="3044">Icom IC-910 (3044)</option>
|
||||
<option value="3068">Icom IC-9100 (3068)</option>
|
||||
<option value="3065">Icom IC-92D (3065)</option>
|
||||
<option value="3035">Icom IC-970 (3035)</option>
|
||||
<option value="3081">Icom IC-9700 (3081)</option>
|
||||
<option value="3086">Icom IC-F8101 (3086)</option>
|
||||
<option value="30001">Icom IC-M700PRO (30001)</option>
|
||||
<option value="30003">Icom IC-M710 (30003)</option>
|
||||
<option value="30002">Icom IC-M802 (30002)</option>
|
||||
<option value="30004">Icom IC-M803 (30004)</option>
|
||||
<option value="4002">Icom IC-PCR100 (4002)</option>
|
||||
<option value="4001">Icom IC-PCR1000 (4001)</option>
|
||||
<option value="4003">Icom IC-PCR1500 (4003)</option>
|
||||
<option value="4004">Icom IC-PCR2500 (4004)</option>
|
||||
<option value="3036">Icom IC-R10 (3036)</option>
|
||||
<option value="3058">Icom IC-R20 (3058)</option>
|
||||
<option value="3080">Icom IC-R30 (3080)</option>
|
||||
<option value="3077">Icom IC-R6 (3077)</option>
|
||||
<option value="3040">Icom IC-R7000 (3040)</option>
|
||||
<option value="3037">Icom IC-R71 (3037)</option>
|
||||
<option value="3041">Icom IC-R7100 (3041)</option>
|
||||
<option value="3038">Icom IC-R72 (3038)</option>
|
||||
<option value="3039">Icom IC-R75 (3039)</option>
|
||||
<option value="3042">Icom ICR-8500 (3042)</option>
|
||||
<option value="3079">Icom IC-R8600 (3079)</option>
|
||||
<option value="3043">Icom IC-R9000 (3043)</option>
|
||||
<option value="3066">Icom IC-R9500 (3066)</option>
|
||||
<option value="3069">Icom IC-RX7 (3069)</option>
|
||||
<option value="3083">Icom ID-31 (3083)</option>
|
||||
<option value="3082">Icom ID-4100 (3082)</option>
|
||||
<option value="3084">Icom ID-51 (3084)</option>
|
||||
<option value="3071">Icom ID-5100 (3071)</option>
|
||||
<option value="6001">JRC JST-145 (6001)</option>
|
||||
<option value="6002">JRC JST-245 (6002)</option>
|
||||
<option value="6005">JRC NRD-525 (6005)</option>
|
||||
<option value="6006">JRC NRD-535D (6006)</option>
|
||||
<option value="6007">JRC NRD-545 (6007)</option>
|
||||
<option value="18001">Kachina 505DSP (18001)</option>
|
||||
<option value="2015">Kenwood R-5000 (2015)</option>
|
||||
<option value="2033">Kenwood TH-D72A (2033)</option>
|
||||
<option value="2042">Kenwood TH-D74 (2042)</option>
|
||||
<option value="2017">Kenwood TH-D7A (2017)</option>
|
||||
<option value="2019">Kenwood TH-F6A (2019)</option>
|
||||
<option value="2020">Kenwood TH-F7E (2020)</option>
|
||||
<option value="2023">Kenwood TH-G71 (2023)</option>
|
||||
<option value="2026">Kenwood TM-D700 (2026)</option>
|
||||
<option value="2034">Kenwood TM-D710(G) (2034)</option>
|
||||
<option value="2027">Kenwood TM-V7 (2027)</option>
|
||||
<option value="2035">Kenwood TM-V71(A) (2035)</option>
|
||||
<option value="2030">Kenwood TRC-80 (2030)</option>
|
||||
<option value="2025">Kenwood TS-140S (2025)</option>
|
||||
<option value="2014">Kenwood TS-2000 (2014)</option>
|
||||
<option value="2002">Kenwood TS-440S (2002)</option>
|
||||
<option value="2003">Kenwood TS-450S (2003)</option>
|
||||
<option value="2028">Kenwood TS-480 (2028)</option>
|
||||
<option value="2001">Kenwood TS-50S (2001)</option>
|
||||
<option value="2004">Kenwood TS-570D (2004)</option>
|
||||
<option value="2016">Kenwood TS-570S (2016)</option>
|
||||
<option value="2031">Kenwood TS-590S (2031)</option>
|
||||
<option value="2037">Kenwood TS-590SG (2037)</option>
|
||||
<option value="2024">Kenwood TS-680S (2024)</option>
|
||||
<option value="2005">Kenwood TS-690S (2005)</option>
|
||||
<option value="2006">Kenwood TS-711 (2006)</option>
|
||||
<option value="2007">Kenwood TS-790 (2007)</option>
|
||||
<option value="2008">Kenwood TS-811 (2008)</option>
|
||||
<option value="2009">Kenwood TS-850 (2009)</option>
|
||||
<option value="2010">Kenwood TS-870S (2010)</option>
|
||||
<option value="2041">Kenwood TS-890S (2041)</option>
|
||||
<option value="2022">Kenwood TS-930 (2022)</option>
|
||||
<option value="2011">Kenwood TS-940S (2011)</option>
|
||||
<option value="2012">Kenwood TS-950S (2012)</option>
|
||||
<option value="2013">Kenwood TS-950SDX (2013)</option>
|
||||
<option value="2039">Kenwood TS-990S (2039)</option>
|
||||
<option value="25011">KTH-SDR kit (25011)</option>
|
||||
<option value="2050">Lab599 TX-500 (2050)</option>
|
||||
<option value="10004">Lowe HF-235 (10004)</option>
|
||||
<option value="1045">M0NKA mcHF (1045)</option>
|
||||
<option value="2049">Malachite DSP (2049)</option>
|
||||
<option value="3074">Microtelecom Perseus (3074)</option>
|
||||
<option value="25008">mRS miniVNA (25008)</option>
|
||||
<option value="25014">N2ADR HiQSDR (25014)</option>
|
||||
<option value="2040">OpenHPSDR PiHPSDR (2040)</option>
|
||||
<option value="3053">Optoelectronics OptoScan456 (3053)</option>
|
||||
<option value="3052">Optoelectronics OptoScan535 (3052)</option>
|
||||
<option value="28001">Philips/Simoco PRM8060 (28001)</option>
|
||||
<option value="11005">Racal RA3702 (11005)</option>
|
||||
<option value="11003">Racal RA6790/GM (11003)</option>
|
||||
<option value="8004">Radio Shack (8004)</option>
|
||||
<option value="24001">RFT EKD-500 (24001)</option>
|
||||
<option value="27002">Rohde&Schwarz EB200 (27002)</option>
|
||||
<option value="27004">Rohde&Schwarz EK895/6 (27004)</option>
|
||||
<option value="27001">Rohde&Schwarz ESMC (27001)</option>
|
||||
<option value="27003">Rohde&Schwarz XK2100 (27003)</option>
|
||||
<option value="25002">SAT-Schneider DRT1 (25002)</option>
|
||||
<option value="2051">SDRPlay SDRUno (2051)</option>
|
||||
<option value="2032">SigFox Transfox (2032)</option>
|
||||
<option value="14004">Skanti TRP (14004)</option>
|
||||
<option value="14002">Skanti TRP8000 (14002)</option>
|
||||
<option value="25009">SoftRock Si570 (25009)</option>
|
||||
<option value="22001">TAPR DSP-10 (22001)</option>
|
||||
<option value="3064">Ten-Tec Delta (3064)</option>
|
||||
<option value="3051">Ten-Tec Omni (3051)</option>
|
||||
<option value="16003">Ten-Tec RX-320 (16003)</option>
|
||||
<option value="16012">Ten-Tec RX-331 (16012)</option>
|
||||
<option value="16004">Ten-Tec RX-340 (16004)</option>
|
||||
<option value="16005">Ten-Tec RX-350 (16005)</option>
|
||||
<option value="16007">Ten-Tec TT-516 (16007)</option>
|
||||
<option value="16002">Ten-Tec TT-538 (16002)</option>
|
||||
<option value="16001">Ten-Tec TT-550 (16001)</option>
|
||||
<option value="16008">Ten-Tec TT-565 (16008)</option>
|
||||
<option value="16009">Ten-Tec TT-585 (16009)</option>
|
||||
<option value="16011">Ten-Tec TT-588 (16011)</option>
|
||||
<option value="16013">Ten-Tec TT-599 (16013)</option>
|
||||
<option value="8002">Uniden BC245xlt (8002)</option>
|
||||
<option value="8006">Uniden BC250D (8006)</option>
|
||||
<option value="8001">Uniden BC780xlt (8001)</option>
|
||||
<option value="8003">Uniden BC895xlt (8003)</option>
|
||||
<option value="8012">Uniden BC898T (8012)</option>
|
||||
<option value="8010">Uniden BCD-396T (8010)</option>
|
||||
<option value="8011">Uniden BCD-996T (8011)</option>
|
||||
<option value="1033">Vertex Standard (1033)</option>
|
||||
<option value="26001">Video4Linux SW/FM (26001)</option>
|
||||
<option value="26002">Video4Linux2 SW/FM (26002)</option>
|
||||
<option value="12004">Watkins-Johnson WJ-8888 (12004)</option>
|
||||
<option value="15001">Winradio WR-1000 (15001)</option>
|
||||
<option value="15002">Winradio WR-1500 (15002)</option>
|
||||
<option value="15003">Winradio WR-1550 (15003)</option>
|
||||
<option value="15004">Winradio WR-3100 (15004)</option>
|
||||
<option value="15005">Winradio WR-3150 (15005)</option>
|
||||
<option value="15006">Winradio WR-3500 (15006)</option>
|
||||
<option value="15007">Winradio WR-3700 (15007)</option>
|
||||
<option value="15009">Winradio WR-G313 (15009)</option>
|
||||
<option value="3088">Xiegu G90 (3088)</option>
|
||||
<option value="3076">Xiegu X108G (3076)</option>
|
||||
<option value="3089">Xiegu X5105 (3089)</option>
|
||||
<option value="3087">Xiegu X6100 (3087)</option>
|
||||
<option value="1017">Yaesu FRG-100 (1017)</option>
|
||||
<option value="1019">Yaesu FRG-8800 (1019)</option>
|
||||
<option value="1018">Yaesu FRG-9600 (1018)</option>
|
||||
<option value="1021">Yaesu FT-100 (1021)</option>
|
||||
<option value="1003">Yaesu FT-1000D (1003)</option>
|
||||
<option value="1024">Yaesu FT-1000MP (1024)</option>
|
||||
<option value="1029">Yaesu FT-2000 (1029)</option>
|
||||
<option value="1027">Yaesu FT-450 (1027)</option>
|
||||
<option value="1046">Yaesu FT-450D (1046)</option>
|
||||
<option value="1039">Yaesu FT-600 (1039)</option>
|
||||
<option value="1047">Yaesu FT-650 (1047)</option>
|
||||
<option value="1049">Yaesu FT-710 (1049)</option>
|
||||
<option value="1010">Yaesu FT-736R (1010)</option>
|
||||
<option value="1005">Yaesu FT-747GX (1005)</option>
|
||||
<option value="1006">Yaesu FT-757GX (1006)</option>
|
||||
<option value="1007">Yaesu FT-757GXII (1007)</option>
|
||||
<option value="1009">Yaesu FT-767GX (1009)</option>
|
||||
<option value="1020">Yaesu FT-817 (1020)</option>
|
||||
<option value="1041">Yaesu FT-818 (1041)</option>
|
||||
<option value="1011">Yaesu FT-840 (1011)</option>
|
||||
<option value="1001">Yaesu FT-847 (1001)</option>
|
||||
<option value="1038">Yaesu FT-847UNI (1038)</option>
|
||||
<option value="1022">Yaesu FT-857 (1022)</option>
|
||||
<option value="1015">Yaesu FT-890 (1015)</option>
|
||||
<option value="1036">Yaesu FT-891 (1036)</option>
|
||||
<option value="1023">Yaesu FT-897 (1023)</option>
|
||||
<option value="1043">Yaesu FT-897D (1043)</option>
|
||||
<option value="1013">Yaesu FT-900 (1013)</option>
|
||||
<option value="1014">Yaesu FT-920 (1014)</option>
|
||||
<option value="1028">Yaesu FT-950 (1028)</option>
|
||||
<option value="1031">Yaesu FT-980 (1031)</option>
|
||||
<option value="1016">Yaesu FT-990 (1016)</option>
|
||||
<option value="1048">Yaesu FT-990 (1048)</option>
|
||||
<option value="1035">Yaesu FT-991 (1035)</option>
|
||||
<option value="1042">Yaesu FTDX-10 (1042)</option>
|
||||
<option value="1040">Yaesu FTDX-101D (1040)</option>
|
||||
<option value="1044">Yaesu FTDX-101MP (1044)</option>
|
||||
<option value="1034">Yaesu FTDX-1200 (1034)</option>
|
||||
<option value="1037">Yaesu FTDX-3000 (1037)</option>
|
||||
<option value="1032">Yaesu FTDX-5000 (1032)</option>
|
||||
<option value="1030">Yaesu FTDX-9000 (1030)</option>
|
||||
<option value="1004">Yaesu MARK-V (1004)</option>
|
||||
<option value="1025">Yaesu MARK-V (1025)</option>
|
||||
<option value="1026">Yaesu VR-5000 (1026)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Radio port</span>
|
||||
|
||||
<select
|
||||
@change="onChange"
|
||||
v-model="settings.remote.RADIO.serial_port"
|
||||
class="form-select form-select-sm"
|
||||
>
|
||||
<option
|
||||
v-for="device in serialStore.serialDevices"
|
||||
:value="device.port"
|
||||
:key="device.port"
|
||||
>
|
||||
{{ device.description }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Serial speed</span>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="hamlib_serialspeed"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.RADIO.serial_speed"
|
||||
>
|
||||
<option selected value="0">-- ignore --</option>
|
||||
<option value="1200">1200</option>
|
||||
<option value="2400">2400</option>
|
||||
<option value="4800">4800</option>
|
||||
<option value="9600">9600</option>
|
||||
<option value="14400">14400</option>
|
||||
<option value="19200">19200</option>
|
||||
<option value="28800">28800</option>
|
||||
<option value="38400">38400</option>
|
||||
<option value="57600">57600</option>
|
||||
<option value="115200">115200</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Data bits</span>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="hamlib_data_bits"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.RADIO.data_bits"
|
||||
>
|
||||
<option selected value="0">-- ignore --</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Stop bits</span>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="hamlib_stop_bits"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.RADIO.stop_bits"
|
||||
>
|
||||
<option selected value="0">-- ignore --</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px"
|
||||
>Serial handshake</span
|
||||
>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="hamlib_handshake"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.RADIO.serial_handshake"
|
||||
>
|
||||
<option selected value="ignore">-- ignore --</option>
|
||||
<option value="None">None (Default)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">PTT device port</span>
|
||||
|
||||
<select
|
||||
@change="onChange"
|
||||
v-model="settings.remote.RADIO.ptt_port"
|
||||
class="form-select form-select-sm"
|
||||
>
|
||||
<option
|
||||
v-for="device in serialStore.serialDevices"
|
||||
:value="device.port"
|
||||
:key="device.port"
|
||||
>
|
||||
{{ device.description }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">PTT type</span>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="hamlib_pttprotocol"
|
||||
style="width: 0.5rem"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.RADIO.ptt_type"
|
||||
>
|
||||
<option selected value="ignore">-- ignore --</option>
|
||||
<option value="NONE">NONE</option>
|
||||
<option value="RIG">RIG</option>
|
||||
<option value="USB">USB</option>
|
||||
<option value="RTS">Serial RTS</option>
|
||||
<option value="PARALLEL">Rig PARALLEL</option>
|
||||
<option value="MICDATA">Rig MICDATA</option>
|
||||
<option value="CM108">Rig CM108</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">DCD</span>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="hamlib_dcd"
|
||||
style="width: 0.5rem"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.RADIO.serial_dcd"
|
||||
>
|
||||
<option selected value="ignore">-- ignore --</option>
|
||||
<option value="NONE">NONE</option>
|
||||
<option value="RIG">RIG/CAT</option>
|
||||
<option value="DSR">DSR</option>
|
||||
<option value="CTS">CTS</option>
|
||||
<option value="CD">CD</option>
|
||||
<option value="PARALLEL">PARALLEL</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">DTR</span>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="hamlib_dtrstate"
|
||||
style="width: 0.5rem"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.RADIO.serial_dtr"
|
||||
>
|
||||
<option selected value="ignore">-- ignore --</option>
|
||||
<option value="OFF">OFF</option>
|
||||
<option value="ON">ON</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Rigctld command</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="hamlib_rigctld_command"
|
||||
aria-label="Device Port"
|
||||
aria-describedby="basic-addon1"
|
||||
disabled
|
||||
placeholder="auto populated from above settings"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-outline-secondary"
|
||||
type="button"
|
||||
id="btnHamlibCopyCommand"
|
||||
>
|
||||
<i id="btnHamlibCopyCommandBi" class="bi bi-clipboard"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px"
|
||||
>Rigctld custom arguments</span
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="not typically needed"
|
||||
id="hamlib_rigctld_custom_args"
|
||||
aria-label="Custom arguments"
|
||||
aria-describedby="basic-addon1"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.RIGCTLD.arguments"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
196
gui/src/components/settings_modem.vue
Normal file
196
gui/src/components/settings_modem.vue
Normal file
|
@ -0,0 +1,196 @@
|
|||
<script setup lang="ts">
|
||||
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
|
||||
import pinia from "../store/index";
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { startModem, stopModem } from "../js/api.js";
|
||||
|
||||
import { useAudioStore } from "../store/audioStore";
|
||||
const audioStore = useAudioStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
id="startModem"
|
||||
class="btn btn-sm btn-outline-success"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Start the Modem. Please set your audio and radio settings first!"
|
||||
@click="startModem"
|
||||
v-bind:class="{ disabled: state.is_modem_running === true }"
|
||||
>
|
||||
<i class="bi bi-play-fill"></i>
|
||||
<span class="ms-2">start modem</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="stopModem"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Stop the Modem."
|
||||
@click="stopModem"
|
||||
v-bind:class="{ disabled: state.is_modem_running === false }"
|
||||
>
|
||||
<i class="bi bi-stop-fill"></i>
|
||||
<span class="ms-2">stop modem</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Modem port</span>
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
placeholder="modem port"
|
||||
id="modem_port"
|
||||
maxlength="5"
|
||||
max="65534"
|
||||
min="1025"
|
||||
v-model.number="settings.local.port"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Modem host</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="modem host"
|
||||
id="modem_port"
|
||||
v-model="settings.local.host"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Audio Input Device -->
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Audio Input device</label>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.AUDIO.input_device"
|
||||
>
|
||||
<option v-for="device in audioStore.audioInputs" :value="device.id">
|
||||
{{ device.name }} [{{ device.api }}]
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Audio Output Device -->
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Audio Output device</label>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.AUDIO.output_device"
|
||||
>
|
||||
<option v-for="device in audioStore.audioOutputs" :value="device.id">
|
||||
{{ device.name }} [{{ device.api }}]
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Audio rx level-->
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-25">RX Audio Level</span>
|
||||
<span class="input-group-text w-25">{{
|
||||
settings.remote.AUDIO.rx_audio_level
|
||||
}}</span>
|
||||
<span class="input-group-text w-50">
|
||||
<input
|
||||
type="range"
|
||||
class="form-range"
|
||||
min="-30"
|
||||
max="20"
|
||||
step="1"
|
||||
id="audioLevelRX"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.AUDIO.rx_audio_level"
|
||||
/></span>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-25">TX Audio Level</span>
|
||||
<span class="input-group-text w-25">{{
|
||||
settings.remote.AUDIO.tx_audio_level
|
||||
}}</span>
|
||||
<span class="input-group-text w-50">
|
||||
<input
|
||||
type="range"
|
||||
class="form-range"
|
||||
min="-30"
|
||||
max="20"
|
||||
step="1"
|
||||
id="audioLevelTX"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.AUDIO.tx_audio_level"
|
||||
/></span>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">TX delay in ms</label>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
id="tx_delay"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.MODEM.tx_delay"
|
||||
>
|
||||
<option value="0">0</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
<option value="150">150</option>
|
||||
<option value="200">200</option>
|
||||
<option value="250">250</option>
|
||||
<option value="300">300</option>
|
||||
<option value="350">350</option>
|
||||
<option value="400">400</option>
|
||||
<option value="450">450</option>
|
||||
<option value="500">500</option>
|
||||
<option value="550">550</option>
|
||||
<option value="600">600</option>
|
||||
<option value="650">650</option>
|
||||
<option value="700">700</option>
|
||||
<option value="750">750</option>
|
||||
<option value="800">800</option>
|
||||
<option value="850">850</option>
|
||||
<option value="900">900</option>
|
||||
<option value="950">950</option>
|
||||
<option value="1000">1000</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Maximum used bandwidth</label>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
id="maximum_bandwidth"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.MODEM.maximum_bandwidth"
|
||||
>
|
||||
<option value="250">250 Hz</option>
|
||||
<option value="563">563 Hz</option>
|
||||
<option value="1700">1700 Hz</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Respond to CQ</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="respondCQSwitch"
|
||||
v-model="settings.remote.MODEM.respond_to_cq"
|
||||
@change="onChange"
|
||||
/>
|
||||
<label class="form-check-label" for="respondCQSwitch">QRV</label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
79
gui/src/components/settings_rigcontrol.vue
Normal file
79
gui/src/components/settings_rigcontrol.vue
Normal file
|
@ -0,0 +1,79 @@
|
|||
<script setup lang="ts">
|
||||
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
|
||||
import settings_hamlib from "./settings_hamlib.vue";
|
||||
import settings_tci from "./settings_tci.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Rig Control</span>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="rigcontrol_radiocontrol"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.RADIO.control"
|
||||
>
|
||||
<option selected value="disabled">
|
||||
Disabled / VOX (no rig control - use with VOX)
|
||||
</option>
|
||||
<option selected value="rigctld">Rigctld (external Hamlib)</option>
|
||||
<option selected value="rigctld_bundle">Rigctld (internal Hamlib)</option>
|
||||
<option selected value="tci">TCI</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<hr class="m-2" />
|
||||
|
||||
<nav>
|
||||
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
||||
<button
|
||||
class="nav-link active"
|
||||
id="nav-home-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#nav-hamlib"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="nav-home"
|
||||
aria-selected="true"
|
||||
>
|
||||
Hamlib
|
||||
</button>
|
||||
<button
|
||||
class="nav-link"
|
||||
id="nav-profile-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#nav-tci"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="nav-profile"
|
||||
aria-selected="false"
|
||||
>
|
||||
TCI
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="tab-content" id="nav-tabContent">
|
||||
<div
|
||||
class="tab-pane fade show active"
|
||||
id="nav-hamlib"
|
||||
role="tabpanel"
|
||||
aria-labelledby="nav-hamlib-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_hamlib />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="nav-tci"
|
||||
role="tabpanel"
|
||||
aria-labelledby="nav-tci-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_tci />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="m-2" />
|
||||
</template>
|
85
gui/src/components/settings_station.vue
Normal file
85
gui/src/components/settings_station.vue
Normal file
|
@ -0,0 +1,85 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
settingsStore as settings,
|
||||
onChange,
|
||||
getRemote,
|
||||
} from "../store/settingsStore.js";
|
||||
import {
|
||||
validateCallsignWithSSID,
|
||||
validateCallsignWithoutSSID,
|
||||
} from "../js/freedata";
|
||||
function validateCall() {
|
||||
//ensure callsign is uppercase:
|
||||
let call = settings.remote.STATION.mycall;
|
||||
settings.remote.STATION.mycall = call.toUpperCase();
|
||||
|
||||
if (validateCallsignWithoutSSID(settings.remote.STATION.mycall))
|
||||
//Send new callsign to modem if valid
|
||||
onChange();
|
||||
//Reload settings from modem as invalid callsign was passed in
|
||||
else getRemote();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<!-- station callsign -->
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px"
|
||||
>Your station callsign</span
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="text-transform: uppercase"
|
||||
placeholder="Enter your callsign and save it"
|
||||
id="myCall"
|
||||
aria-label="Station Callsign"
|
||||
aria-describedby="basic-addon1"
|
||||
v-model="settings.remote.STATION.mycall"
|
||||
@change="validateCall"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- station ssid -->
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Call SSID</span>
|
||||
<select
|
||||
class="form-select form-select-sm w-50"
|
||||
id="myCallSSID"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.STATION.myssid"
|
||||
>
|
||||
<option selected value="0">0</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
<option value="10">10</option>
|
||||
<option value="11">11</option>
|
||||
<option value="12">12</option>
|
||||
<option value="13">13</option>
|
||||
<option value="14">14</option>
|
||||
<option value="15">15</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- station grid locator -->
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Grid Locator</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Your grid locator"
|
||||
id="myGrid"
|
||||
maxlength="6"
|
||||
aria-label="Station Grid Locator"
|
||||
aria-describedby="basic-addon1"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.STATION.mygrid"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
33
gui/src/components/settings_tci.vue
Normal file
33
gui/src/components/settings_tci.vue
Normal file
|
@ -0,0 +1,33 @@
|
|||
<script setup lang="ts">
|
||||
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<hr class="m-2" />
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">TCI IP Address</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="TCI IP"
|
||||
id="rigcontrol_tci_ip"
|
||||
aria-label="Device IP"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.TCI.tci_ip"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">TCI port</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="TCI port"
|
||||
id="rigcontrol_tci_port"
|
||||
aria-label="Device Port"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.TCI.tci_port"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
44
gui/src/components/settings_web.vue
Normal file
44
gui/src/components/settings_web.vue
Normal file
|
@ -0,0 +1,44 @@
|
|||
<script setup lang="ts">
|
||||
import { setConfig } from "../js/api";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Explorer publishing</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="ExplorerSwitch"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.STATION.enable_explorer"
|
||||
/>
|
||||
<label class="form-check-label" for="ExplorerSwitch">Publish</label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Explorer stats publishing</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="ExplorerStatsSwitch"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.STATION.enable_stats"
|
||||
/>
|
||||
<label class="form-check-label" for="ExplorerStatsSwitch"
|
||||
>Publish stats</label
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
|
@ -1,285 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!--<meta http-equiv="Content-Security-Policy" content="script-src 'self';">-->
|
||||
<!-- Bootstrap CSS -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="../node_modules/bootstrap/dist/css/bootstrap.min.css"
|
||||
/>
|
||||
<title>Send & Receive Data</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div class="container mt-1">
|
||||
<div class="row mb-1">
|
||||
<div class="col">
|
||||
<div class="card text-dark bg-light mb-0">
|
||||
<div class="card-header">Select data</div>
|
||||
<div class="card-body">
|
||||
<div class="input-group input-group-sm mb-0">
|
||||
<input
|
||||
type="file"
|
||||
class="form-control"
|
||||
id="inputGroupFile02"
|
||||
/>
|
||||
<label class="input-group-text" for="inputGroupFile02"
|
||||
>kB</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--col-->
|
||||
</div>
|
||||
<!--row-->
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<div class="card text-dark bg-light mb-0">
|
||||
<div class="card-header">Transmission</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-2">
|
||||
<div class="col-auto">
|
||||
<div class="input-group input-group-sm">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="max-width: 6rem"
|
||||
placeholder="DX Call"
|
||||
id="dxCall"
|
||||
maxlength="6"
|
||||
aria-label="Input group example"
|
||||
aria-describedby="btnGroupAddon"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
id="sendPing"
|
||||
class="btn btn-primary"
|
||||
>
|
||||
Ping
|
||||
</button>
|
||||
<span class="input-group-text" id="tnc_running_state"
|
||||
>ACK</span
|
||||
>
|
||||
<span class="input-group-text" id="tnc_running_state"
|
||||
>0000 km</span
|
||||
>
|
||||
<span class="input-group-text" id="tnc_running_state"
|
||||
>0 dB</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text" id="basic-addon1"
|
||||
>Mode</span
|
||||
>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm example"
|
||||
id="hamlib_deviceport"
|
||||
>
|
||||
<option selected value="DATAC1">DATAC1</option>
|
||||
<option value="DATAC3">DATAC3</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text" id="basic-addon1"
|
||||
>Frames</span
|
||||
>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm example"
|
||||
id="hamlib_deviceport"
|
||||
>
|
||||
<option selected value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="input-group input-group-sm">
|
||||
<button
|
||||
type="button"
|
||||
id="startTransmission"
|
||||
class="btn btn-success"
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
<!--<button type="button" id="stopTNC"class="btn btn-danger">STOP</button>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--col-->
|
||||
</div>
|
||||
<!--row-->
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card text-dark bg-light mb-0">
|
||||
<div class="card-header">Info</div>
|
||||
<div class="card-body">123</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
<!--row-->
|
||||
</div>
|
||||
<!--container-->
|
||||
</div>
|
||||
<!---------------------------------------------------------------------- FOOTER AREA ------------------------------------------------------------>
|
||||
<nav class="navbar fixed-bottom navbar-light bg-light">
|
||||
<div class="container-fluid">
|
||||
<div
|
||||
class="btn-toolbar"
|
||||
role="toolbar"
|
||||
aria-label="Toolbar with button groups"
|
||||
>
|
||||
<div
|
||||
class="btn-group btn-group-sm me-2"
|
||||
role="group"
|
||||
aria-label="First group"
|
||||
>
|
||||
<button class="btn btn-secondary" id="ptt_state" type="button">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
class="bi bi-broadcast-pin"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
d="M3.05 3.05a7 7 0 0 0 0 9.9.5.5 0 0 1-.707.707 8 8 0 0 1 0-11.314.5.5 0 0 1 .707.707zm2.122 2.122a4 4 0 0 0 0 5.656.5.5 0 1 1-.708.708 5 5 0 0 1 0-7.072.5.5 0 0 1 .708.708zm5.656-.708a.5.5 0 0 1 .708 0 5 5 0 0 1 0 7.072.5.5 0 1 1-.708-.708 4 4 0 0 0 0-5.656.5.5 0 0 1 0-.708zm2.122-2.12a.5.5 0 0 1 .707 0 8 8 0 0 1 0 11.313.5.5 0 0 1-.707-.707 7 7 0 0 0 0-9.9.5.5 0 0 1 0-.707zM6 8a2 2 0 1 1 2.5 1.937V15.5a.5.5 0 0 1-1 0V9.937A2 2 0 0 1 6 8z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="btn-group btn-group-sm me-2"
|
||||
role="group"
|
||||
aria-label="Second group"
|
||||
>
|
||||
<button class="btn btn-secondary" id="busy_state" type="button">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
class="bi bi-cpu"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
d="M5 0a.5.5 0 0 1 .5.5V2h1V.5a.5.5 0 0 1 1 0V2h1V.5a.5.5 0 0 1 1 0V2h1V.5a.5.5 0 0 1 1 0V2A2.5 2.5 0 0 1 14 4.5h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14a2.5 2.5 0 0 1-2.5 2.5v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14A2.5 2.5 0 0 1 2 11.5H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2A2.5 2.5 0 0 1 4.5 2V.5A.5.5 0 0 1 5 0zm-.5 3A1.5 1.5 0 0 0 3 4.5v7A1.5 1.5 0 0 0 4.5 13h7a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 11.5 3h-7zM5 6.5A1.5 1.5 0 0 1 6.5 5h3A1.5 1.5 0 0 1 11 6.5v3A1.5 1.5 0 0 1 9.5 11h-3A1.5 1.5 0 0 1 5 9.5v-3zM6.5 6a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="btn-group btn-group-sm me-2"
|
||||
role="group"
|
||||
aria-label="Second group"
|
||||
>
|
||||
<button class="btn btn-secondary" id="arq_state" type="button">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
class="bi bi-arrow-left-right"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M1 11.5a.5.5 0 0 0 .5.5h11.793l-3.147 3.146a.5.5 0 0 0 .708.708l4-4a.5.5 0 0 0 0-.708l-4-4a.5.5 0 0 0-.708.708L13.293 11H1.5a.5.5 0 0 0-.5.5zm14-7a.5.5 0 0 1-.5.5H2.707l3.147 3.146a.5.5 0 1 1-.708.708l-4-4a.5.5 0 0 1 0-.708l4-4a.5.5 0 1 1 .708.708L2.707 4H14.5a.5.5 0 0 1 .5.5z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="btn-group btn-group-sm me-2"
|
||||
role="group"
|
||||
aria-label="Third group"
|
||||
>
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
id="signalling_state"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
class="bi bi-journal-code"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8.646 5.646a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1 0 .708l-2 2a.5.5 0 0 1-.708-.708L10.293 8 8.646 6.354a.5.5 0 0 1 0-.708zm-1.292 0a.5.5 0 0 0-.708 0l-2 2a.5.5 0 0 0 0 .708l2 2a.5.5 0 0 0 .708-.708L5.707 8l1.647-1.646a.5.5 0 0 0 0-.708z"
|
||||
/>
|
||||
<path
|
||||
d="M3 0h10a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-1h1v1a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v1H1V2a2 2 0 0 1 2-2z"
|
||||
/>
|
||||
<path
|
||||
d="M1 5v-.5a.5.5 0 0 1 1 0V5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0V8h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0v.5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn btn-secondary" id="data_state" type="button">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
class="bi bi-journal-richtext"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
d="M7.5 3.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0zm-.861 1.542 1.33.886 1.854-1.855a.25.25 0 0 1 .289-.047L11 4.75V7a.5.5 0 0 1-.5.5h-5A.5.5 0 0 1 5 7v-.5s1.54-1.274 1.639-1.208zM5 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z"
|
||||
/>
|
||||
<path
|
||||
d="M3 0h10a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-1h1v1a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v1H1V2a2 2 0 0 1 2-2z"
|
||||
/>
|
||||
<path
|
||||
d="M1 5v-.5a.5.5 0 0 1 1 0V5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0V8h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0v.5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group input-group-sm me-2">
|
||||
<span class="input-group-text" id="basic-addon1">Bytes/s</span>
|
||||
<span class="input-group-text" id="basic-addon1">----</span>
|
||||
</div>
|
||||
<div class="progress" style="height: 100%; width: 200px">
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-primary"
|
||||
id="arq-progress"
|
||||
role="progressbar"
|
||||
style="width: 25%"
|
||||
aria-valuenow="25"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
>
|
||||
25%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<script src="../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,261 +0,0 @@
|
|||
<option value="1">Hamlib Dummy</option>
|
||||
<option value="2">Hamlib NET rigctl</option>
|
||||
<option value="4">FLRig FLRig</option>
|
||||
<option value="5">TRXManager TRXManager 5.7.630+</option>
|
||||
<option value="6">Hamlib Dummy No VFO</option>
|
||||
<option value="1001">Yaesu FT-847</option>
|
||||
<option value="1003">Yaesu FT-1000D</option>
|
||||
<option value="1004">Yaesu MARK-V FT-1000MP</option>
|
||||
<option value="1005">Yaesu FT-747GX</option>
|
||||
<option value="1006">Yaesu FT-757GX</option>
|
||||
<option value="1007">Yaesu FT-757GXII</option>
|
||||
<option value="1009">Yaesu FT-767GX</option>
|
||||
<option value="1010">Yaesu FT-736R</option>
|
||||
<option value="1011">Yaesu FT-840</option>
|
||||
<option value="1013">Yaesu FT-900</option>
|
||||
<option value="1014">Yaesu FT-920</option>
|
||||
<option value="1015">Yaesu FT-890</option>
|
||||
<option value="1016">Yaesu FT-990</option>
|
||||
<option value="1017">Yaesu FRG-100</option>
|
||||
<option value="1018">Yaesu FRG-9600</option>
|
||||
<option value="1019">Yaesu FRG-8800</option>
|
||||
<option value="1020">Yaesu FT-817</option>
|
||||
<option value="1021">Yaesu FT-100</option>
|
||||
<option value="1022">Yaesu FT-857</option>
|
||||
<option value="1023">Yaesu FT-897</option>
|
||||
<option value="1024">Yaesu FT-1000MP</option>
|
||||
<option value="1025">Yaesu MARK-V Field FT-1000MP</option>
|
||||
<option value="1026">Yaesu VR-5000</option>
|
||||
<option value="1027">Yaesu FT-450</option>
|
||||
<option value="1028">Yaesu FT-950</option>
|
||||
<option value="1029">Yaesu FT-2000</option>
|
||||
<option value="1030">Yaesu FTDX-9000</option>
|
||||
<option value="1031">Yaesu FT-980</option>
|
||||
<option value="1032">Yaesu FTDX-5000</option>
|
||||
<option value="1033">Vertex Standard VX-1700</option>
|
||||
<option value="1034">Yaesu FTDX-1200</option>
|
||||
<option value="1035">Yaesu FT-991</option>
|
||||
<option value="1036">Yaesu FT-891</option>
|
||||
<option value="1037">Yaesu FTDX-3000</option>
|
||||
<option value="1038">Yaesu FT-847UNI</option>
|
||||
<option value="1039">Yaesu FT-600</option>
|
||||
<option value="1040">Yaesu FTDX-101D</option>
|
||||
<option value="1041">Yaesu FT-818</option>
|
||||
<option value="1042">Yaesu FTDX-10</option>
|
||||
<option value="1043">Yaesu FT-897D</option>
|
||||
<option value="1044">Yaesu FTDX-101MP</option>
|
||||
<option value="2001">Kenwood TS-50S</option>
|
||||
<option value="2002">Kenwood TS-440S</option>
|
||||
<option value="2003">Kenwood TS-450S</option>
|
||||
<option value="2004">Kenwood TS-570D</option>
|
||||
<option value="2005">Kenwood TS-690S</option>
|
||||
<option value="2006">Kenwood TS-711</option>
|
||||
<option value="2007">Kenwood TS-790</option>
|
||||
<option value="2008">Kenwood TS-811</option>
|
||||
<option value="2009">Kenwood TS-850</option>
|
||||
<option value="2010">Kenwood TS-870S</option>
|
||||
<option value="2011">Kenwood TS-940S</option>
|
||||
<option value="2012">Kenwood TS-950S</option>
|
||||
<option value="2013">Kenwood TS-950SDX</option>
|
||||
<option value="2014">Kenwood TS-2000</option>
|
||||
<option value="2015">Kenwood R-5000</option>
|
||||
<option value="2016">Kenwood TS-570S</option>
|
||||
<option value="2017">Kenwood TH-D7A</option>
|
||||
<option value="2019">Kenwood TH-F6A</option>
|
||||
<option value="2020">Kenwood TH-F7E</option>
|
||||
<option value="2021">Elecraft K2</option>
|
||||
<option value="2022">Kenwood TS-930</option>
|
||||
<option value="2023">Kenwood TH-G71</option>
|
||||
<option value="2024">Kenwood TS-680S</option>
|
||||
<option value="2025">Kenwood TS-140S</option>
|
||||
<option value="2026">Kenwood TM-D700</option>
|
||||
<option value="2027">Kenwood TM-V7</option>
|
||||
<option value="2028">Kenwood TS-480</option>
|
||||
<option value="2029">Elecraft K3</option>
|
||||
<option value="2030">Kenwood TRC-80</option>
|
||||
<option value="2031">Kenwood TS-590S</option>
|
||||
<option value="2032">SigFox Transfox</option>
|
||||
<option value="2033">Kenwood TH-D72A</option>
|
||||
<option value="2034">Kenwood TM-D710(G)</option>
|
||||
<option value="2036">FlexRadio 6xxx</option>
|
||||
<option value="2037">Kenwood TS-590SG</option>
|
||||
<option value="2038">Elecraft XG3</option>
|
||||
<option value="2039">Kenwood TS-990s</option>
|
||||
<option value="2040">OpenHPSDR PiHPSDR</option>
|
||||
<option value="2041">Kenwood TS-890S</option>
|
||||
<option value="2042">Kenwood TH-D74</option>
|
||||
<option value="2043">Elecraft K3S</option>
|
||||
<option value="2044">Elecraft KX2</option>
|
||||
<option value="2045">Elecraft KX3</option>
|
||||
<option value="2046">Hilberling PT-8000A</option>
|
||||
<option value="2047">Elecraft K4</option>
|
||||
<option value="2048">FlexRadio/ANAN PowerSDR/Thetis</option>
|
||||
<option value="2049">Malachite DSP</option>
|
||||
<option value="3002">Icom IC-1275</option>
|
||||
<option value="3003">Icom IC-271</option>
|
||||
<option value="3004">Icom IC-275</option>
|
||||
<option value="3006">Icom IC-471</option>
|
||||
<option value="3007">Icom IC-475</option>
|
||||
<option value="3009">Icom IC-706</option>
|
||||
<option value="3010">Icom IC-706MkII</option>
|
||||
<option value="3011">Icom IC-706MkIIG</option>
|
||||
<option value="3012">Icom IC-707</option>
|
||||
<option value="3013">Icom IC-718</option>
|
||||
<option value="3014">Icom IC-725</option>
|
||||
<option value="3015">Icom IC-726</option>
|
||||
<option value="3016">Icom IC-728</option>
|
||||
<option value="3017">Icom IC-729</option>
|
||||
<option value="3019">Icom IC-735</option>
|
||||
<option value="3020">Icom IC-736</option>
|
||||
<option value="3021">Icom IC-737</option>
|
||||
<option value="3022">Icom IC-738</option>
|
||||
<option value="3023">Icom IC-746</option>
|
||||
<option value="3024">Icom IC-751</option>
|
||||
<option value="3026">Icom IC-756</option>
|
||||
<option value="3027">Icom IC-756PRO</option>
|
||||
<option value="3028">Icom IC-761</option>
|
||||
<option value="3029">Icom IC-765</option>
|
||||
<option value="3030">Icom IC-775</option>
|
||||
<option value="3031">Icom IC-781</option>
|
||||
<option value="3032">Icom IC-820H</option>
|
||||
<option value="3034">Icom IC-821H</option>
|
||||
<option value="3035">Icom IC-970</option>
|
||||
<option value="3036">Icom IC-R10</option>
|
||||
<option value="3037">Icom IC-R71</option>
|
||||
<option value="3038">Icom IC-R72</option>
|
||||
<option value="3039">Icom IC-R75</option>
|
||||
<option value="3040">Icom IC-R7000</option>
|
||||
<option value="3041">Icom IC-R7100</option>
|
||||
<option value="3042">Icom ICR-8500</option>
|
||||
<option value="3043">Icom IC-R9000</option>
|
||||
<option value="3044">Icom IC-910</option>
|
||||
<option value="3045">Icom IC-78</option>
|
||||
<option value="3046">Icom IC-746PRO</option>
|
||||
<option value="3047">Icom IC-756PROII</option>
|
||||
<option value="3051">Ten-Tec Omni VI Plus</option>
|
||||
<option value="3052">Optoelectronics OptoScan535</option>
|
||||
<option value="3053">Optoelectronics OptoScan456</option>
|
||||
<option value="3054">Icom IC ID-1</option>
|
||||
<option value="3055">Icom IC-703</option>
|
||||
<option value="3056">Icom IC-7800</option>
|
||||
<option value="3057">Icom IC-756PROIII</option>
|
||||
<option value="3058">Icom IC-R20</option>
|
||||
<option value="3060">Icom IC-7000</option>
|
||||
<option value="3061">Icom IC-7200</option>
|
||||
<option value="3062">Icom IC-7700</option>
|
||||
<option value="3063">Icom IC-7600</option>
|
||||
<option value="3064">Ten-Tec Delta II</option>
|
||||
<option value="3065">Icom IC-92D</option>
|
||||
<option value="3066">Icom IC-R9500</option>
|
||||
<option value="3067">Icom IC-7410</option>
|
||||
<option value="3068">Icom IC-9100</option>
|
||||
<option value="3069">Icom IC-RX7</option>
|
||||
<option value="3070">Icom IC-7100</option>
|
||||
<option value="3071">Icom ID-5100</option>
|
||||
<option value="3072">Icom IC-2730</option>
|
||||
<option value="3073">Icom IC-7300</option>
|
||||
<option value="3074">Microtelecom Perseus</option>
|
||||
<option value="3075">Icom IC-785x</option>
|
||||
<option value="3076">Xeigu X108G</option>
|
||||
<option value="3077">Icom IC-R6</option>
|
||||
<option value="3078">Icom IC-7610</option>
|
||||
<option value="3079">Icom IC-R8600</option>
|
||||
<option value="3080">Icom IC-R30</option>
|
||||
<option value="3081">Icom IC-9700</option>
|
||||
<option value="3082">Icom ID-4100</option>
|
||||
<option value="3083">Icom ID-31</option>
|
||||
<option value="3084">Icom ID-51</option>
|
||||
<option value="3085">Icom IC-705</option>
|
||||
<option value="4001">Icom IC-PCR1000</option>
|
||||
<option value="4002">Icom IC-PCR100</option>
|
||||
<option value="4003">Icom IC-PCR1500</option>
|
||||
<option value="4004">Icom IC-PCR2500</option>
|
||||
<option value="5001">AOR AR8200</option>
|
||||
<option value="5002">AOR AR8000</option>
|
||||
<option value="5003">AOR AR7030</option>
|
||||
<option value="5004">AOR AR5000</option>
|
||||
<option value="5005">AOR AR3030</option>
|
||||
<option value="5006">AOR AR3000A</option>
|
||||
<option value="5008">AOR AR2700</option>
|
||||
<option value="5013">AOR AR8600</option>
|
||||
<option value="5014">AOR AR5000A</option>
|
||||
<option value="5015">AOR AR7030 Plus</option>
|
||||
<option value="5016">AOR SR2200</option>
|
||||
<option value="6005">JRC NRD-525</option>
|
||||
<option value="6006">JRC NRD-535D</option>
|
||||
<option value="6007">JRC NRD-545 DSP</option>
|
||||
<option value="8001">Uniden BC780xlt</option>
|
||||
<option value="8002">Uniden BC245xlt</option>
|
||||
<option value="8003">Uniden BC895xlt</option>
|
||||
<option value="8004">Radio Shack PRO-2052</option>
|
||||
<option value="8006">Uniden BC250D</option>
|
||||
<option value="8010">Uniden BCD-396T</option>
|
||||
<option value="8011">Uniden BCD-996T</option>
|
||||
<option value="8012">Uniden BC898T</option>
|
||||
<option value="9002">Drake R-8A</option>
|
||||
<option value="9003">Drake R-8B</option>
|
||||
<option value="10004">Lowe HF-235</option>
|
||||
<option value="11003">Racal RA6790/GM</option>
|
||||
<option value="11005">Racal RA3702</option>
|
||||
<option value="12004">Watkins-Johnson WJ-8888</option>
|
||||
<option value="14002">Skanti TRP8000</option>
|
||||
<option value="14004">Skanti TRP 8255 S R</option>
|
||||
<option value="15001">Winradio WR-1000</option>
|
||||
<option value="15002">Winradio WR-1500</option>
|
||||
<option value="15003">Winradio WR-1550</option>
|
||||
<option value="15004">Winradio WR-3100</option>
|
||||
<option value="15005">Winradio WR-3150</option>
|
||||
<option value="15006">Winradio WR-3500</option>
|
||||
<option value="15007">Winradio WR-3700</option>
|
||||
<option value="15009">Winradio WR-G313</option>
|
||||
<option value="16001">Ten-Tec TT-550</option>
|
||||
<option value="16002">Ten-Tec TT-538 Jupiter</option>
|
||||
<option value="16003">Ten-Tec RX-320</option>
|
||||
<option value="16004">Ten-Tec RX-340</option>
|
||||
<option value="16005">Ten-Tec RX-350</option>
|
||||
<option value="16007">Ten-Tec TT-516 Argonaut V</option>
|
||||
<option value="16008">Ten-Tec TT-565 Orion</option>
|
||||
<option value="16009">Ten-Tec TT-585 Paragon</option>
|
||||
<option value="16011">Ten-Tec TT-588 Omni VII</option>
|
||||
<option value="16012">Ten-Tec RX-331</option>
|
||||
<option value="16013">Ten-Tec TT-599 Eagle</option>
|
||||
<option value="17001">Alinco DX-77</option>
|
||||
<option value="17002">Alinco DX-SR8</option>
|
||||
<option value="18001">Kachina 505DSP</option>
|
||||
<option value="22001">TAPR DSP-10</option>
|
||||
<option value="23001">Flex-radio SDR-1000</option>
|
||||
<option value="23003">DTTS Microwave Society DttSP IPC</option>
|
||||
<option value="23004">DTTS Microwave Society DttSP UDP</option>
|
||||
<option value="24001">RFT EKD-500</option>
|
||||
<option value="25001">Elektor Elektor 3/04</option>
|
||||
<option value="25002">SAT-Schneider DRT1</option>
|
||||
<option value="25003">Coding Technologies Digital World Traveller</option>
|
||||
<option value="25006">AmQRP DDS-60</option>
|
||||
<option value="25007">Elektor Elektor SDR-USB</option>
|
||||
<option value="25008">mRS miniVNA</option>
|
||||
<option value="25009">SoftRock Si570 AVR-USB</option>
|
||||
<option value="25011">KTH-SDR kit Si570 PIC-USB</option>
|
||||
<option value="25012">FiFi FiFi-SDR</option>
|
||||
<option value="25013">AMSAT-UK FUNcube Dongle</option>
|
||||
<option value="25014">N2ADR HiQSDR</option>
|
||||
<option value="25015">Funkamateur FA-SDR</option>
|
||||
<option value="25016">AE9RB Si570 Peaberry V1</option>
|
||||
<option value="25017">AE9RB Si570 Peaberry V2</option>
|
||||
<option value="25018">AMSAT-UK FUNcube Dongle Pro+</option>
|
||||
<option value="25019">HobbyPCB RS-HFIQ</option>
|
||||
<option value="26001">Video4Linux SW/FM radio</option>
|
||||
<option value="26002">Video4Linux2 SW/FM radio</option>
|
||||
<option value="27001">Rohde&Schwarz ESMC</option>
|
||||
<option value="27002">Rohde&Schwarz EB200</option>
|
||||
<option value="27003">Rohde&Schwarz XK2100</option>
|
||||
<option value="28001">Philips/Simoco PRM8060</option>
|
||||
<option value="29001">ADAT www.adat.ch ADT-200A</option>
|
||||
<option value="30001">Icom IC-M700PRO</option>
|
||||
<option value="30002">Icom IC-M802</option>
|
||||
<option value="30003">Icom IC-M710</option>
|
||||
<option value="30004">Icom IC-M803</option>
|
||||
<option value="31001">Dorji DRA818V</option>
|
||||
<option value="31002">Dorji DRA818U</option>
|
||||
<option value="32001">Barrett 2050</option>
|
||||
<option value="32002">Barrett 950</option>
|
||||
<option value="33001">ELAD FDM-DUO</option>
|
Binary file not shown.
Before Width: | Height: | Size: 110 KiB |
Binary file not shown.
Before Width: | Height: | Size: 590 KiB |
3918
gui/src/index.html
3918
gui/src/index.html
File diff suppressed because it is too large
Load diff
192
gui/src/js/api.js
Normal file
192
gui/src/js/api.js
Normal file
|
@ -0,0 +1,192 @@
|
|||
import { settingsStore as settings } from "../store/settingsStore.js";
|
||||
import {
|
||||
validateCallsignWithSSID,
|
||||
validateCallsignWithoutSSID,
|
||||
} from "./freedata";
|
||||
|
||||
import { processFreedataMessages } from "./messagesHandler";
|
||||
|
||||
function buildURL(params, endpoint) {
|
||||
const url = "http://" + params.host + ":" + params.port + endpoint;
|
||||
return url;
|
||||
}
|
||||
|
||||
async function apiGet(endpoint) {
|
||||
try {
|
||||
const response = await fetch(buildURL(settings.local, endpoint));
|
||||
if (!response.ok) {
|
||||
throw new Error(`REST response not ok: ${response.statusText}`);
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
//console.error("Error getting from REST:", error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiPost(endpoint, payload = {}) {
|
||||
try {
|
||||
const response = await fetch(buildURL(settings.local, endpoint), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`REST response not ok: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error posting to REST:", error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiDelete(endpoint, payload = {}) {
|
||||
try {
|
||||
const response = await fetch(buildURL(settings.local, endpoint), {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`REST response not ok: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error deleting from REST:", error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getVersion() {
|
||||
let data = await apiGet("/version").then((res) => {
|
||||
return res;
|
||||
});
|
||||
|
||||
if (typeof data !== "undefined" && typeof data.version !== "undefined") {
|
||||
return data.version;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export async function getConfig() {
|
||||
return await apiGet("/config");
|
||||
}
|
||||
|
||||
export async function setConfig(config) {
|
||||
return await apiPost("/config", config);
|
||||
}
|
||||
|
||||
export async function getAudioDevices() {
|
||||
return await apiGet("/devices/audio");
|
||||
}
|
||||
|
||||
export async function getSerialDevices() {
|
||||
return await apiGet("/devices/serial");
|
||||
}
|
||||
|
||||
export async function setModemBeacon(enabled = false) {
|
||||
return await apiPost("/modem/beacon", { enabled: enabled });
|
||||
}
|
||||
|
||||
export async function sendModemCQ() {
|
||||
return await apiPost("/modem/cqcqcq");
|
||||
}
|
||||
|
||||
export async function sendModemPing(dxcall) {
|
||||
if (
|
||||
validateCallsignWithSSID(dxcall) === false &&
|
||||
validateCallsignWithoutSSID(dxcall) === true
|
||||
) {
|
||||
dxcall = String(dxcall).toUpperCase().trim();
|
||||
dxcall = dxcall + "-0";
|
||||
}
|
||||
dxcall = String(dxcall).toUpperCase().trim();
|
||||
if (validateCallsignWithSSID(dxcall))
|
||||
return await apiPost("/modem/ping_ping", { dxcall: dxcall });
|
||||
}
|
||||
|
||||
export async function sendModemARQRaw(mycall, dxcall, data, uuid) {
|
||||
return await apiPost("/modem/send_arq_raw", {
|
||||
mycallsign: mycall,
|
||||
dxcall: dxcall,
|
||||
data: data,
|
||||
uuid: uuid,
|
||||
});
|
||||
}
|
||||
|
||||
export async function stopTransmission() {
|
||||
return await apiPost("/modem/stop_transmission");
|
||||
}
|
||||
|
||||
export async function sendModemTestFrame() {
|
||||
return await apiPost("/modem/send_test_frame");
|
||||
}
|
||||
|
||||
export async function startModem() {
|
||||
return await apiPost("/modem/start");
|
||||
}
|
||||
|
||||
export async function stopModem() {
|
||||
return await apiPost("/modem/stop");
|
||||
}
|
||||
|
||||
export async function getModemState() {
|
||||
return await apiGet("/modem/state");
|
||||
}
|
||||
|
||||
export async function setRadioParametersFrequency(frequency) {
|
||||
return await apiPost("/radio", {
|
||||
radio_frequency: frequency,
|
||||
});
|
||||
}
|
||||
export async function setRadioParametersMode(mode) {
|
||||
return await apiPost("/radio", {
|
||||
radio_mode: mode,
|
||||
});
|
||||
}
|
||||
export async function setRadioParametersRFLevel(rf_level) {
|
||||
return await apiPost("/radio", {
|
||||
radio_rf_level: rf_level,
|
||||
});
|
||||
}
|
||||
export async function getRadioStatus() {
|
||||
return await apiGet("/radio");
|
||||
}
|
||||
|
||||
export async function getFreedataMessages() {
|
||||
let res = await apiGet("/freedata/messages");
|
||||
processFreedataMessages(res);
|
||||
}
|
||||
|
||||
export async function getFreedataAttachmentBySha512(data_sha512) {
|
||||
let res = await apiGet(`/freedata/messages/attachment/${data_sha512}`);
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function sendFreedataMessage(destination, body, attachments) {
|
||||
return await apiPost("/freedata/messages", {
|
||||
destination: destination,
|
||||
body: body,
|
||||
attachments: attachments,
|
||||
});
|
||||
}
|
||||
|
||||
export async function retransmitFreedataMessage(id) {
|
||||
return await apiPost(`/freedata/messages/${id}`);
|
||||
}
|
||||
|
||||
export async function deleteFreedataMessage(id) {
|
||||
return await apiDelete(`/freedata/messages/${id}`);
|
||||
}
|
||||
|
||||
export async function getBeaconDataByCallsign(callsign) {
|
||||
return await apiGet(`/freedata/beacons/${callsign}`);
|
||||
}
|
349
gui/src/js/eventHandler.js
Normal file
349
gui/src/js/eventHandler.js
Normal file
|
@ -0,0 +1,349 @@
|
|||
/*
|
||||
import {
|
||||
newMessageReceived,
|
||||
newBeaconReceived,
|
||||
updateTransmissionStatus,
|
||||
setStateSuccess,
|
||||
setStateFailed,
|
||||
} from "./chatHandler";
|
||||
*/
|
||||
import { displayToast } from "./popupHandler";
|
||||
import { getFreedataMessages, getModemState, getAudioDevices } from "./api";
|
||||
import { processFreedataMessages } from "./messagesHandler.ts";
|
||||
import { processRadioStatus } from "./radioHandler.ts";
|
||||
|
||||
import { useAudioStore } from "../store/audioStore";
|
||||
const audioStore = useAudioStore();
|
||||
import { useSerialStore } from "../store/serialStore";
|
||||
const serialStore = useSerialStore();
|
||||
|
||||
// ----------------- init pinia stores -------------
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const stateStore = useStateStore(pinia);
|
||||
|
||||
import {
|
||||
settingsStore as settings,
|
||||
getRemote,
|
||||
} from "../store/settingsStore.js";
|
||||
|
||||
export function loadAllData() {
|
||||
getModemState();
|
||||
getRemote();
|
||||
getOverallHealth();
|
||||
audioStore.loadAudioDevices();
|
||||
serialStore.loadSerialDevices();
|
||||
getFreedataMessages();
|
||||
processFreedataMessages();
|
||||
processRadioStatus();
|
||||
}
|
||||
|
||||
export function connectionFailed(endpoint, event) {
|
||||
stateStore.modem_connection = "disconnected";
|
||||
}
|
||||
export function stateDispatcher(data) {
|
||||
data = JSON.parse(data);
|
||||
//Leave commented when not needed, otherwise can lead to heap overflows due to the amount of data logged
|
||||
//console.debug(data);
|
||||
if (data["type"] == "state-change" || data["type"] == "state") {
|
||||
stateStore.modem_connection = "connected";
|
||||
|
||||
stateStore.busy_state = data["is_modem_busy"];
|
||||
|
||||
stateStore.channel_busy = data["channel_busy"];
|
||||
stateStore.is_codec2_traffic = data["is_codec2_traffic"];
|
||||
stateStore.is_modem_running = data["is_modem_running"];
|
||||
stateStore.dbfs_level = Math.round(data["audio_dbfs"]);
|
||||
stateStore.dbfs_level_percent = Math.round(
|
||||
Math.pow(10, data["audio_dbfs"] / 20) * 100,
|
||||
);
|
||||
|
||||
stateStore.s_meter_strength_raw = Math.round(data["s_meter_strength"]);
|
||||
stateStore.s_meter_strength_percent = Math.round(
|
||||
Math.pow(10, data["s_meter_strength"] / 20) * 100,
|
||||
);
|
||||
|
||||
stateStore.channel_busy_slot = data["channel_busy_slot"];
|
||||
stateStore.beacon_state = data["is_beacon_running"];
|
||||
stateStore.radio_status = data["radio_status"];
|
||||
stateStore.frequency = data["radio_frequency"];
|
||||
stateStore.mode = data["radio_mode"];
|
||||
//Reverse entries so most recent is first
|
||||
stateStore.activities = Object.entries(data["activities"]).reverse();
|
||||
build_HSL();
|
||||
}
|
||||
}
|
||||
|
||||
export function eventDispatcher(data) {
|
||||
data = JSON.parse(data);
|
||||
console.debug(data);
|
||||
|
||||
if (data["scatter"] !== undefined) {
|
||||
stateStore.scatter = JSON.parse(data["scatter"]);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data["message-db"]) {
|
||||
case "changed":
|
||||
console.log("fetching new messages...");
|
||||
var messages = getFreedataMessages();
|
||||
processFreedataMessages(messages);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data["ptt"]) {
|
||||
case true:
|
||||
case false:
|
||||
// get ptt state as a first test
|
||||
//console.warn("PTT state true")
|
||||
stateStore.ptt_state = data.ptt;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data["modem"]) {
|
||||
case "started":
|
||||
displayToast("success", "bi-arrow-left-right", "Modem started", 5000);
|
||||
loadAllData();
|
||||
return;
|
||||
|
||||
case "stopped":
|
||||
displayToast("warning", "bi-arrow-left-right", "Modem stopped", 5000);
|
||||
return;
|
||||
|
||||
case "restarted":
|
||||
displayToast("secondary", "bi-bootstrap-reboot", "Modem restarted", 5000);
|
||||
loadAllData();
|
||||
return;
|
||||
|
||||
case "failed":
|
||||
displayToast(
|
||||
"danger",
|
||||
"bi-bootstrap-reboot",
|
||||
"Modem startup failed | bad config?",
|
||||
5000,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var message = "";
|
||||
|
||||
switch (data["type"]) {
|
||||
case "hello-client":
|
||||
message = "Connected to server";
|
||||
displayToast("success", "bi-ethernet", message, 5000);
|
||||
stateStore.modem_connection = "connected";
|
||||
|
||||
loadAllData();
|
||||
|
||||
return;
|
||||
|
||||
switch (data["received"]) {
|
||||
case "PING":
|
||||
message = `Ping request from: ${data.dxcallsign}, SNR: ${data.snr}`;
|
||||
displayToast("success", "bi-check-circle", message, 5000);
|
||||
return;
|
||||
|
||||
case "PING_ACK":
|
||||
message = `Ping acknowledged from: ${data.dxcallsign}, SNR: ${data.snr}`;
|
||||
displayToast("success", "bi-check-circle", message, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
case "arq":
|
||||
if (data["arq-transfer-outbound"]) {
|
||||
switch (data["arq-transfer-outbound"].state) {
|
||||
case "NEW":
|
||||
message = `Type: ${data.type}, Session ID: ${data["arq-transfer-outbound"].session_id}, DXCall: ${data["arq-transfer-outbound"].dxcall}, Total Bytes: ${data["arq-transfer-outbound"].total_bytes}, State: ${data["arq-transfer-outbound"].state}`;
|
||||
displayToast("success", "bi-check-circle", message, 5000);
|
||||
stateStore.dxcallsign = data["arq-transfer-outbound"].dxcall;
|
||||
stateStore.arq_transmission_percent = 0;
|
||||
stateStore.arq_total_bytes = 0;
|
||||
return;
|
||||
case "OPEN_SENT":
|
||||
console.info("state OPEN_SENT needs to be implemented");
|
||||
return;
|
||||
|
||||
case "INFO_SENT":
|
||||
console.info("state INFO_SENT needs to be implemented");
|
||||
return;
|
||||
|
||||
case "BURST_SENT":
|
||||
message = `Type: ${data.type}, Session ID: ${data["arq-transfer-outbound"].session_id}, DXCall: ${data["arq-transfer-outbound"].dxcall}, Received Bytes: ${data["arq-transfer-outbound"].received_bytes}/${data["arq-transfer-outbound"].total_bytes}, State: ${data["arq-transfer-outbound"].state}`;
|
||||
displayToast("info", "bi-info-circle", message, 5000);
|
||||
stateStore.arq_transmission_percent =
|
||||
(data["arq-transfer-outbound"].received_bytes /
|
||||
data["arq-transfer-outbound"].total_bytes) *
|
||||
100;
|
||||
stateStore.arq_total_bytes =
|
||||
data["arq-transfer-outbound"].received_bytes;
|
||||
stateStore.arq_speed_list_timestamp =
|
||||
data["arq-transfer-outbound"].statistics.time_histogram;
|
||||
stateStore.arq_speed_list_bpm =
|
||||
data["arq-transfer-outbound"].statistics.bpm_histogram;
|
||||
stateStore.arq_speed_list_snr =
|
||||
data["arq-transfer-outbound"].statistics.snr_histogram;
|
||||
return;
|
||||
|
||||
case "ABORTING":
|
||||
console.info("state ABORTING needs to be implemented");
|
||||
return;
|
||||
|
||||
case "ABORTED":
|
||||
message = `Type: ${data.type}, Session ID: ${
|
||||
data["arq-transfer-outbound"].session_id
|
||||
}, DXCall: ${data["arq-transfer-outbound"].dxcall}, Total Bytes: ${
|
||||
data["arq-transfer-outbound"].total_bytes
|
||||
}, Success: ${
|
||||
data["arq-transfer-outbound"].success ? "Yes" : "No"
|
||||
}, State: ${data["arq-transfer-outbound"].state}, Data: ${
|
||||
data["arq-transfer-outbound"].data ? "Available" : "Not Available"
|
||||
}`;
|
||||
displayToast("warning", "bi-exclamation-triangle", message, 5000);
|
||||
return;
|
||||
|
||||
case "FAILED":
|
||||
message = `Type: ${data.type}, Session ID: ${
|
||||
data["arq-transfer-outbound"].session_id
|
||||
}, DXCall: ${data["arq-transfer-outbound"].dxcall}, Total Bytes: ${
|
||||
data["arq-transfer-outbound"].total_bytes
|
||||
}, Success: ${
|
||||
data["arq-transfer-outbound"].success ? "Yes" : "No"
|
||||
}, State: ${data["arq-transfer-outbound"].state}, Data: ${
|
||||
data["arq-transfer-outbound"].data ? "Available" : "Not Available"
|
||||
}`;
|
||||
displayToast("danger", "bi-x-octagon", message, 5000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (data["arq-transfer-inbound"]) {
|
||||
switch (data["arq-transfer-inbound"].state) {
|
||||
case "NEW":
|
||||
message = `Type: ${data.type}, Session ID: ${data["arq-transfer-inbound"].session_id}, DXCall: ${data["arq-transfer-inbound"].dxcall}, State: ${data["arq-transfer-inbound"].state}`;
|
||||
displayToast("info", "bi-info-circle", message, 5000);
|
||||
stateStore.dxcallsign = data["arq-transfer-inbound"].dxcall;
|
||||
stateStore.arq_transmission_percent = 0;
|
||||
stateStore.arq_total_bytes = 0;
|
||||
stateStore.arq_speed_list_timestamp =
|
||||
data["arq-transfer-inbound"].statistics.time_histogram;
|
||||
stateStore.arq_speed_list_bpm =
|
||||
data["arq-transfer-inbound"].statistics.bpm_histogram;
|
||||
stateStore.arq_speed_list_snr =
|
||||
data["arq-transfer-inbound"].statistics.snr_histogram;
|
||||
|
||||
return;
|
||||
|
||||
case "OPEN_ACK_SENT":
|
||||
message = `Session ID: ${data["arq-transfer-inbound"].session_id}, DXCall: ${data["arq-transfer-inbound"].dxcall}, Total Bytes: ${data["arq-transfer-inbound"].total_bytes}, State: ${data["arq-transfer-inbound"].state}`;
|
||||
displayToast("info", "bi-arrow-left-right", message, 5000);
|
||||
stateStore.arq_transmission_percent =
|
||||
(data["arq-transfer-inbound"].received_bytes /
|
||||
data["arq-transfer-inbound"].total_bytes) *
|
||||
100;
|
||||
stateStore.arq_total_bytes =
|
||||
data["arq-transfer-inbound"].received_bytes;
|
||||
return;
|
||||
|
||||
case "INFO_ACK_SENT":
|
||||
message = `Type: ${data.type}, Session ID: ${data["arq-transfer-inbound"].session_id}, DXCall: ${data["arq-transfer-inbound"].dxcall}, Received Bytes: ${data["arq-transfer-inbound"].received_bytes}/${data["arq-transfer-inbound"].total_bytes}, State: ${data["arq-transfer-inbound"].state}`;
|
||||
displayToast("info", "bi-info-circle", message, 5000);
|
||||
stateStore.arq_transmission_percent =
|
||||
(data["arq-transfer-inbound"].received_bytes /
|
||||
data["arq-transfer-inbound"].total_bytes) *
|
||||
100;
|
||||
stateStore.arq_total_bytes =
|
||||
data["arq-transfer-inbound"].received_bytes;
|
||||
return;
|
||||
|
||||
case "BURST_REPLY_SENT":
|
||||
message = `Type: ${data.type}, Session ID: ${data["arq-transfer-inbound"].session_id}, DXCall: ${data["arq-transfer-inbound"].dxcall}, Received Bytes: ${data["arq-transfer-inbound"].received_bytes}/${data["arq-transfer-inbound"].total_bytes}, State: ${data["arq-transfer-inbound"].state}`;
|
||||
displayToast("info", "bi-info-circle", message, 5000);
|
||||
stateStore.arq_transmission_percent =
|
||||
(data["arq-transfer-inbound"].received_bytes /
|
||||
data["arq-transfer-inbound"].total_bytes) *
|
||||
100;
|
||||
stateStore.arq_total_bytes =
|
||||
data["arq-transfer-inbound"].received_bytes;
|
||||
return;
|
||||
|
||||
case "ENDED":
|
||||
message = `Type: ${data.type}, Session ID: ${data["arq-transfer-inbound"].session_id}, DXCall: ${data["arq-transfer-inbound"].dxcall}, Received Bytes: ${data["arq-transfer-inbound"].received_bytes}/${data["arq-transfer-inbound"].total_bytes}, State: ${data["arq-transfer-inbound"].state}`;
|
||||
displayToast("info", "bi-info-circle", message, 5000);
|
||||
// Forward data to chat module
|
||||
newMessageReceived(
|
||||
data["arq-transfer-inbound"].data,
|
||||
data["arq-transfer-inbound"],
|
||||
);
|
||||
stateStore.arq_transmission_percent =
|
||||
(data["arq-transfer-inbound"].received_bytes /
|
||||
data["arq-transfer-inbound"].total_bytes) *
|
||||
100;
|
||||
stateStore.arq_total_bytes =
|
||||
data["arq-transfer-inbound"].received_bytes;
|
||||
return;
|
||||
|
||||
case "ABORTED":
|
||||
console.info("state ABORTED needs to be implemented");
|
||||
return;
|
||||
|
||||
case "FAILED":
|
||||
message = `Type: ${data.type}, Session ID: ${data["arq-transfer-outbound"].session_id}, DXCall: ${data["arq-transfer-outbound"].dxcall}, Received Bytes: ${data["arq-transfer-outbound"].received_bytes}/${data["arq-transfer-outbound"].total_bytes}, State: ${data["arq-transfer-outbound"].state}`;
|
||||
displayToast("info", "bi-info-circle", message, 5000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function build_HSL() {
|
||||
//Use data from activities to build HSL list
|
||||
for (let i = 0; i < stateStore.activities.length; i++) {
|
||||
if (
|
||||
stateStore.activities[i][1].direction != "received" ||
|
||||
stateStore.activities[i][1].origin == undefined
|
||||
) {
|
||||
//Ignore stations without origin and not received type
|
||||
//console.warn("HSL: Ignoring " + stateStore.activities[i][0]);
|
||||
continue;
|
||||
}
|
||||
let found = false;
|
||||
for (let ii = 0; ii < stateStore.heard_stations.length; ii++) {
|
||||
if (
|
||||
stateStore.heard_stations[ii].origin ==
|
||||
stateStore.activities[i][1].origin
|
||||
) {
|
||||
//Station already in HSL, check if newer than one in HSL
|
||||
found = true;
|
||||
if (
|
||||
stateStore.heard_stations[ii].timestamp <
|
||||
stateStore.activities[i][1].timestamp
|
||||
) {
|
||||
//Update existing entry in HSL
|
||||
stateStore.heard_stations[ii] = stateStore.activities[i][1];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found == false) {
|
||||
//Station not in HSL, let us add it
|
||||
stateStore.heard_stations.push(stateStore.activities[i][1]);
|
||||
}
|
||||
}
|
||||
stateStore.heard_stations.sort((a, b) => b.timestamp - a.timestamp); // b - a for reverse sort
|
||||
}
|
||||
|
||||
export function getOverallHealth() {
|
||||
//Return a number indicating health for icon bg color; lower the number the healthier
|
||||
let health = 0;
|
||||
if (stateStore.modem_connection !== "connected") {
|
||||
health += 5;
|
||||
stateStore.is_modem_running = false;
|
||||
stateStore.radio_status = false;
|
||||
}
|
||||
if (!stateStore.is_modem_running) health += 3;
|
||||
if (stateStore.radio_status === false) health += 2;
|
||||
if (process.env.FDUpdateAvail === "1") health += 1;
|
||||
return health;
|
||||
}
|
56
gui/src/js/event_sock.js
Normal file
56
gui/src/js/event_sock.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
import {
|
||||
eventDispatcher,
|
||||
stateDispatcher,
|
||||
connectionFailed,
|
||||
} from "../js/eventHandler.js";
|
||||
import { addDataToWaterfall } from "../js/waterfallHandler.js";
|
||||
|
||||
// ----------------- init pinia stores -------------
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings } from "../store/settingsStore.js";
|
||||
|
||||
function connect(endpoint, dispatcher) {
|
||||
let socket = new WebSocket(
|
||||
"ws://" + settings.local.host + ":" + settings.local.port + "/" + endpoint,
|
||||
);
|
||||
|
||||
// handle opening
|
||||
socket.addEventListener("open", function (event) {
|
||||
//console.log("Connected to the WebSocket server: " + endpoint);
|
||||
});
|
||||
|
||||
// handle data
|
||||
socket.addEventListener("message", function (event) {
|
||||
dispatcher(event.data);
|
||||
return;
|
||||
});
|
||||
|
||||
// handle errors
|
||||
socket.addEventListener("error", function (event) {
|
||||
//console.error("WebSocket error:", event);
|
||||
connectionFailed(endpoint, event);
|
||||
});
|
||||
|
||||
// handle closing and reconnect
|
||||
socket.addEventListener("close", function (event) {
|
||||
//console.log("WebSocket connection closed:", event.code);
|
||||
|
||||
// Reconnect handler
|
||||
if (!event.wasClean) {
|
||||
setTimeout(() => {
|
||||
//console.log("Reconnecting to websocket");
|
||||
connect(endpoint, dispatcher);
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initial connection attempts to endpoints
|
||||
export function initConnections() {
|
||||
connect("states", stateDispatcher);
|
||||
connect("events", eventDispatcher);
|
||||
connect("fft", addDataToWaterfall);
|
||||
}
|
130
gui/src/js/freedata.ts
Normal file
130
gui/src/js/freedata.ts
Normal file
|
@ -0,0 +1,130 @@
|
|||
const os = require("os");
|
||||
const path = require("path");
|
||||
|
||||
/**
|
||||
* Binary to ASCII replacement
|
||||
* @param {string} data in normal/usual utf-8 format
|
||||
* @returns base64 encoded string
|
||||
*/
|
||||
export function btoa_FD(data) {
|
||||
//exports.btoa_FD = function (data) {
|
||||
return Buffer.from(data, "utf-8").toString("base64");
|
||||
}
|
||||
/**
|
||||
* ASCII to Binary replacement
|
||||
* @param {string} data in base64 encoding
|
||||
* @returns utf-8 normal/usual string
|
||||
*/
|
||||
export function atob_FD(data) {
|
||||
//exports.atob_FD = function (data) {
|
||||
return Buffer.from(data, "base64").toString("utf-8");
|
||||
}
|
||||
/**
|
||||
* UTF8 to ASCII btoa
|
||||
* @param {string} data in base64 encoding
|
||||
* @returns base64 bota compatible data for use in browser
|
||||
*/
|
||||
export function atob(data) {
|
||||
//exports.atob = function (data) {
|
||||
return window.btoa(Buffer.from(data, "base64").toString("utf8"));
|
||||
}
|
||||
//https://medium.com/@asadise/sorting-a-json-array-according-one-property-in-javascript-18b1d22cd9e9
|
||||
/**
|
||||
* Sort a json collection by a property ascending
|
||||
* @param {string} property property to sort on
|
||||
* @returns sorted json collection
|
||||
*/
|
||||
export function sortByProperty(property) {
|
||||
return function (a, b) {
|
||||
if (a[property] > b[property]) return 1;
|
||||
else if (a[property] < b[property]) return -1;
|
||||
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
//https://medium.com/@asadise/sorting-a-json-array-according-one-property-in-javascript-18b1d22cd9e9
|
||||
/**
|
||||
* Sort a json collection by a property descending
|
||||
* @param {string} property property to sort on
|
||||
* @returns sorted json collection
|
||||
*/
|
||||
export function sortByPropertyDesc(property) {
|
||||
return function (a, b) {
|
||||
if (a[property] < b[property]) return 1;
|
||||
else if (a[property] > b[property]) return -1;
|
||||
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a call sign with ssid
|
||||
* @param {string} callsign callsign to check
|
||||
* @returns true or false if callsign appears to be valid with an SSID
|
||||
*/
|
||||
export function validateCallsignWithSSID(callsign: string) {
|
||||
var patt = new RegExp("^[A-Za-z0-9]{1,7}-[0-9]{1,3}$");
|
||||
callsign = callsign;
|
||||
if (
|
||||
callsign === undefined ||
|
||||
callsign === "" ||
|
||||
patt.test(callsign) === false
|
||||
) {
|
||||
console.error(
|
||||
"Call sign given is not in correct format or missing; callsign passed is: " +
|
||||
callsign,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Validate/check if a call sign has an SSID
|
||||
* @param {string} callsign callsign to check
|
||||
* @returns true or false if callsign appears to be valid without an SSID
|
||||
*/
|
||||
export function validateCallsignWithoutSSID(callsign: string) {
|
||||
var patt = new RegExp("^[A-Za-z0-9]{1,7}$");
|
||||
|
||||
if (
|
||||
callsign === undefined ||
|
||||
callsign === "" ||
|
||||
patt.test(callsign) === false
|
||||
) {
|
||||
console.error(
|
||||
"Call sign given is not in correct format or missing; callsign passed is: " +
|
||||
callsign,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function getAppDataPath() {
|
||||
const platform = os.platform();
|
||||
let appDataPath;
|
||||
|
||||
// Check if running in GitHub Actions
|
||||
const isGitHubActions = process.env.GITHUB_ACTIONS === "true";
|
||||
if (isGitHubActions) {
|
||||
return "/home/runner/work/FreeDATA/FreeDATA/gui/config";
|
||||
}
|
||||
|
||||
switch (platform) {
|
||||
case "darwin": // macOS
|
||||
appDataPath = path.join(os.homedir(), "Library", "Application Support");
|
||||
break;
|
||||
case "win32": // Windows
|
||||
appDataPath =
|
||||
process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
|
||||
break;
|
||||
case "linux": // Linux
|
||||
appDataPath = path.join(os.homedir(), ".config");
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unsupported platform");
|
||||
}
|
||||
|
||||
return appDataPath;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue