Compare commits
16 Commits
394150a2ee
...
85a178e825
| Author | SHA1 | Date |
|---|---|---|
|
|
85a178e825 | |
|
|
b0d296e007 | |
|
|
e3692467e3 | |
|
|
c03c89d613 | |
|
|
05e706e150 | |
|
|
d7128000be | |
|
|
2306e54515 | |
|
|
d71ac936fa | |
|
|
9ab39832b5 | |
|
|
68eb853987 | |
|
|
94840a93dd | |
|
|
9172534f31 | |
|
|
882f4acad8 | |
|
|
6c223f01c9 | |
|
|
d4d6ff5344 | |
|
|
1cc863d3c9 |
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
[Interface]
|
||||
PrivateKey = CEzCd25ufxbOjvRhMqLfkcdP/OziG5i6OkXCfMZrI1I=
|
||||
Address = 10.0.0.2/24
|
||||
PrivateKey = OPasvZsdAdU46BJLXOLMqy4BgZq/UvKtjTRpTTVSGX8=
|
||||
Address = 10.0.0.96/32
|
||||
DNS = 1.1.1.1
|
||||
|
||||
[Peer]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
|
|
@ -37,14 +38,18 @@
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.loader-logo img {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
.loader-logo svg {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
margin-left: 12px;
|
||||
margin-top: 8px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
|
@ -55,8 +60,266 @@
|
|||
|
||||
<!-- Place your logo here -->
|
||||
<div class="loader-logo">
|
||||
<img src="your-logo.png" alt="App Logo">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100"
|
||||
zoomAndPan="magnify" viewBox="0 0 368.25 242.999999" height="100" preserveAspectRatio="xMidYMid meet"
|
||||
version="1.0">
|
||||
<defs>
|
||||
<clipPath id="17e5ee130b">
|
||||
<path d="M 137.171875 1.269531 L 231 1.269531 L 231 99 L 137.171875 99 Z M 137.171875 1.269531 "
|
||||
clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="0262fc8075">
|
||||
<path d="M 284 124 L 364.921875 124 L 364.921875 206 L 284 206 Z M 284 124 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="0914a8505c">
|
||||
<path d="M 3 231 L 14 231 L 14 241.671875 L 3 241.671875 Z M 3 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="da83339c1d">
|
||||
<path d="M 17 231 L 20 231 L 20 241.671875 L 17 241.671875 Z M 17 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="97dbddcd78">
|
||||
<path d="M 25 231 L 34 231 L 34 241.671875 L 25 241.671875 Z M 25 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="3223358027">
|
||||
<path d="M 36 231 L 46 231 L 46 241.671875 L 36 241.671875 Z M 36 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="f960afe448">
|
||||
<path d="M 49 231 L 59 231 L 59 241.671875 L 49 241.671875 Z M 49 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="04b92ea6b9">
|
||||
<path d="M 61 231 L 72 231 L 72 241.671875 L 61 241.671875 Z M 61 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="7b4adb9303">
|
||||
<path d="M 75 231 L 84 231 L 84 241.671875 L 75 241.671875 Z M 75 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="53357fd403">
|
||||
<path d="M 93 231 L 104 231 L 104 241.671875 L 93 241.671875 Z M 93 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="bcd03dbca6">
|
||||
<path d="M 106 231 L 117 231 L 117 241.671875 L 106 241.671875 Z M 106 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="ff8639fe11">
|
||||
<path d="M 120 231 L 130 231 L 130 241.671875 L 120 241.671875 Z M 120 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="3990de54cb">
|
||||
<path d="M 134 231 L 137 231 L 137 241.671875 L 134 241.671875 Z M 134 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="045cbd7804">
|
||||
<path d="M 141 231 L 151 231 L 151 241.671875 L 141 241.671875 Z M 141 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="7de9a85e20">
|
||||
<path d="M 154 231 L 163 231 L 163 241.671875 L 154 241.671875 Z M 154 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="e68f5b3fba">
|
||||
<path d="M 165 231 L 176 231 L 176 241.671875 L 165 241.671875 Z M 165 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="65464ab719">
|
||||
<path d="M 179 231 L 189 231 L 189 241.671875 L 179 241.671875 Z M 179 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="b82da71650">
|
||||
<path d="M 192 231 L 202 231 L 202 241.671875 L 192 241.671875 Z M 192 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="fe4eaf6826">
|
||||
<path d="M 204 231 L 215 231 L 215 241.671875 L 204 241.671875 Z M 204 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="79d174fbbf">
|
||||
<path d="M 225 231 L 234 231 L 234 241.671875 L 225 241.671875 Z M 225 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="b0be87ffda">
|
||||
<path d="M 236 230 L 249 230 L 249 241.671875 L 236 241.671875 Z M 236 230 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="444dbef42a">
|
||||
<path d="M 252 231 L 262 231 L 262 241.671875 L 252 241.671875 Z M 252 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="314903e6e8">
|
||||
<path d="M 272 230 L 283 230 L 283 241.671875 L 272 241.671875 Z M 272 230 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="2fd70bc44d">
|
||||
<path d="M 286 230 L 299 230 L 299 241.671875 L 286 241.671875 Z M 286 230 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="032e9be536">
|
||||
<path d="M 302 230 L 312 230 L 312 241.671875 L 302 241.671875 Z M 302 230 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="77ab9ec736">
|
||||
<path d="M 314 231 L 324 231 L 324 241.671875 L 314 241.671875 Z M 314 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="bf7e46828b">
|
||||
<path d="M 326 230 L 339 230 L 339 241.671875 L 326 241.671875 Z M 326 230 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="b78234ca4f">
|
||||
<path d="M 343 231 L 352 231 L 352 241.671875 L 343 241.671875 Z M 343 231 " clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
<clipPath id="2b0e76334b">
|
||||
<path d="M 355 231 L 364.921875 231 L 364.921875 241.671875 L 355 241.671875 Z M 355 231 "
|
||||
clip-rule="nonzero" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#17e5ee130b)">
|
||||
<path fill="#060165"
|
||||
d="M 181.777344 1.269531 L 137.171875 1.269531 L 137.171875 53.847656 C 137.171875 78.601562 157.222656 98.652344 181.980469 98.652344 C 208.785156 98.652344 230.679688 76.761719 230.679688 49.960938 C 230.679688 23.160156 208.785156 1.269531 181.777344 1.269531 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<path fill="#ffffff"
|
||||
d="M 173.386719 31.140625 C 173.386719 36.253906 169.089844 40.550781 163.976562 40.550781 C 158.859375 40.550781 154.5625 44.847656 154.5625 49.960938 C 154.5625 55.074219 158.859375 59.371094 163.976562 59.371094 L 182.800781 59.371094 C 187.914062 59.371094 192.210938 55.074219 192.210938 49.960938 L 192.210938 31.140625 C 192.210938 26.023438 187.914062 21.726562 182.800781 21.726562 C 177.683594 21.726562 173.386719 26.023438 173.386719 31.140625 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
<path fill="#05c3ec"
|
||||
d="M 194.257812 68.78125 C 194.257812 63.667969 198.554688 59.371094 203.671875 59.371094 C 208.785156 59.371094 213.082031 55.074219 213.082031 49.960938 C 213.082031 44.847656 208.785156 40.550781 203.671875 40.550781 L 184.84375 40.550781 C 179.730469 40.550781 175.433594 44.847656 175.433594 49.960938 L 175.433594 68.78125 C 175.433594 73.898438 179.730469 78.191406 184.84375 78.191406 C 189.960938 78.191406 194.257812 73.898438 194.257812 68.78125 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
<path fill="#060165"
|
||||
d="M 3.140625 205.640625 L 3.140625 124.460938 L 34.589844 124.460938 C 40.664062 124.460938 46.304688 125.542969 51.292969 127.5 C 56.28125 129.453125 60.835938 132.273438 64.304688 135.746094 C 67.992188 139.21875 70.8125 143.558594 72.765625 148.550781 C 74.71875 153.546875 75.800781 158.972656 75.800781 165.050781 C 75.800781 171.125 74.71875 176.554688 72.765625 181.546875 C 70.8125 186.539062 67.992188 190.878906 64.304688 194.351562 C 60.617188 198.042969 56.28125 200.648438 51.292969 202.816406 C 46.304688 204.769531 40.664062 205.855469 34.589844 205.855469 L 3.140625 205.855469 Z M 18.976562 190.226562 L 34.375 190.226562 C 38.0625 190.226562 41.53125 189.578125 44.570312 188.492188 C 47.605469 187.1875 50.207031 185.667969 52.378906 183.5 C 54.546875 181.328125 56.28125 178.722656 57.367188 175.46875 C 58.449219 172.429688 59.101562 168.957031 59.101562 165.050781 C 59.101562 161.140625 58.449219 157.886719 57.367188 154.628906 C 56.28125 151.589844 54.546875 148.988281 52.378906 146.816406 C 50.207031 144.644531 47.605469 142.910156 44.570312 141.824219 C 41.53125 140.738281 38.0625 140.085938 34.375 140.085938 L 18.976562 140.085938 C 18.976562 140.085938 18.976562 190.226562 18.976562 190.226562 Z M 18.976562 190.226562 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
<path fill="#060165"
|
||||
d="M 84.695312 173.949219 C 84.695312 169.171875 85.5625 164.832031 87.296875 160.925781 C 89.03125 157.019531 91.417969 153.546875 94.238281 150.507812 C 97.273438 147.46875 100.746094 145.296875 104.648438 143.558594 C 108.769531 141.824219 113.109375 141.171875 117.664062 141.171875 C 122.21875 141.171875 126.773438 142.039062 130.675781 143.558594 C 134.796875 145.296875 138.265625 147.46875 141.304688 150.507812 C 144.339844 153.546875 146.726562 157.019531 148.246094 160.925781 C 149.980469 164.832031 150.847656 169.171875 150.847656 173.949219 C 150.847656 178.507812 149.980469 182.847656 148.246094 186.753906 C 146.507812 190.660156 144.125 194.136719 141.304688 197.171875 C 138.265625 200.210938 134.796875 202.382812 130.675781 204.121094 C 126.554688 205.855469 122.21875 206.507812 117.664062 206.507812 C 113.109375 206.507812 108.554688 205.640625 104.648438 204.121094 C 100.527344 202.382812 97.058594 200.210938 94.019531 197.171875 C 90.984375 194.136719 88.816406 190.660156 87.082031 186.753906 C 85.34375 182.847656 84.695312 178.722656 84.695312 173.949219 M 100.527344 173.949219 C 100.527344 176.335938 100.960938 178.507812 101.828125 180.675781 C 102.695312 182.847656 103.78125 184.800781 105.300781 186.320312 C 106.816406 187.839844 108.554688 189.140625 110.722656 190.226562 C 112.890625 191.097656 115.277344 191.746094 117.878906 191.746094 C 120.480469 191.746094 122.867188 191.3125 124.820312 190.226562 C 126.988281 189.359375 128.722656 188.058594 130.242188 186.320312 C 131.761719 184.800781 132.84375 182.847656 133.710938 180.675781 C 134.582031 178.507812 135.015625 176.335938 135.015625 173.949219 C 135.015625 171.5625 134.582031 169.390625 133.710938 167.21875 C 132.84375 165.050781 131.761719 163.09375 130.242188 161.578125 C 128.722656 160.058594 126.988281 158.753906 124.820312 157.667969 C 122.652344 156.800781 120.480469 156.148438 117.878906 156.148438 C 115.277344 156.148438 112.890625 156.582031 110.722656 157.667969 C 108.554688 158.539062 106.816406 159.839844 105.300781 161.578125 C 103.78125 163.09375 102.695312 165.050781 101.828125 167.21875 C 100.960938 169.390625 100.527344 171.777344 100.527344 173.949219 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
<path fill="#060165"
|
||||
d="M 159.523438 173.949219 C 159.523438 169.171875 160.390625 164.832031 162.125 160.925781 C 163.859375 157.019531 166.246094 153.546875 169.066406 150.507812 C 172.101562 147.46875 175.574219 145.296875 179.476562 143.558594 C 183.597656 141.824219 187.9375 141.171875 192.492188 141.171875 C 196.613281 141.171875 200.515625 141.824219 203.988281 143.125 C 207.671875 144.429688 210.710938 146.164062 213.53125 148.335938 C 216.347656 150.507812 218.734375 153.328125 220.46875 156.367188 C 222.421875 159.621094 223.507812 163.09375 224.15625 166.785156 L 207.890625 166.785156 C 206.804688 163.53125 205.070312 161.140625 202.25 159.1875 C 199.648438 157.234375 196.394531 156.148438 192.492188 156.148438 C 189.886719 156.148438 187.503906 156.582031 185.332031 157.667969 C 183.164062 158.539062 181.429688 160.058594 179.910156 161.578125 C 178.394531 163.3125 177.308594 165.050781 176.441406 167.21875 C 175.574219 169.390625 175.140625 171.5625 175.140625 174.164062 C 175.140625 176.554688 175.574219 178.722656 176.441406 180.894531 C 177.308594 183.066406 178.394531 185.019531 179.910156 186.539062 C 181.429688 188.273438 183.164062 189.578125 185.332031 190.445312 C 187.503906 191.3125 189.886719 191.964844 192.492188 191.964844 C 196.179688 191.964844 199.433594 191.097656 202.25 189.140625 C 204.855469 187.1875 206.804688 184.800781 207.890625 181.546875 L 224.15625 181.546875 C 223.507812 185.234375 222.207031 188.707031 220.46875 191.746094 C 218.519531 194.785156 216.347656 197.609375 213.53125 199.777344 C 210.710938 201.949219 207.457031 203.683594 203.988281 204.988281 C 200.300781 206.289062 196.613281 206.941406 192.492188 206.941406 C 187.71875 206.941406 183.382812 206.074219 179.476562 204.554688 C 175.355469 202.816406 171.886719 200.648438 169.066406 197.609375 C 166.03125 194.570312 163.859375 191.097656 162.125 187.1875 C 160.390625 182.847656 159.523438 178.722656 159.523438 173.949219 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
<path fill="#060165"
|
||||
d="M 238.253906 124.460938 L 261.03125 187.1875 L 284.019531 124.460938 L 301.15625 124.460938 L 271.007812 205.640625 L 251.269531 205.640625 L 221.121094 124.460938 Z M 238.253906 124.460938 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
<g clip-path="url(#0262fc8075)">
|
||||
<path fill="#060165"
|
||||
d="M 315.035156 124.460938 L 334.558594 124.460938 L 364.921875 205.640625 L 348.65625 205.640625 L 343.015625 190.226562 L 307.011719 190.226562 L 301.15625 205.640625 L 284.886719 205.640625 Z M 338.246094 177.203125 L 325.015625 141.605469 L 311.78125 177.203125 Z M 338.246094 177.203125 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#0914a8505c)">
|
||||
<path fill="#060165"
|
||||
d="M 7.261719 241.453125 L 3.359375 231.035156 L 5.3125 231.035156 L 8.5625 239.71875 L 11.816406 231.035156 L 13.554688 231.035156 L 9.648438 241.453125 Z M 7.261719 241.453125 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#da83339c1d)">
|
||||
<path fill="#060165"
|
||||
d="M 17.675781 231.035156 L 19.410156 231.035156 L 19.410156 241.671875 L 17.675781 241.671875 Z M 17.675781 231.035156 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#97dbddcd78)">
|
||||
<path fill="#060165"
|
||||
d="M 25.046875 241.453125 L 25.046875 231.035156 L 29.601562 231.035156 C 31.773438 231.035156 33.722656 231.902344 33.722656 234.289062 C 33.722656 235.808594 32.855469 236.679688 31.773438 237.113281 C 32.421875 237.546875 32.640625 238.199219 32.855469 238.847656 C 33.074219 240.152344 33.507812 241.238281 33.722656 241.453125 L 31.773438 241.453125 C 31.554688 241.019531 31.339844 240.367188 31.121094 239.28125 C 30.90625 238.199219 30.472656 237.328125 29.601562 237.328125 L 27 237.328125 L 27 241.453125 Z M 26.785156 235.808594 L 29.167969 235.808594 C 30.472656 235.808594 31.773438 235.59375 31.773438 234.074219 C 31.773438 232.554688 30.6875 232.335938 29.386719 232.335938 L 26.785156 232.335938 Z M 26.785156 235.808594 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#3223358027)">
|
||||
<path fill="#060165"
|
||||
d="M 40.230469 241.453125 L 40.230469 232.554688 L 36.761719 232.554688 L 36.761719 231.035156 L 45.652344 231.035156 L 45.652344 232.554688 L 41.964844 232.554688 L 41.964844 241.453125 Z M 40.230469 241.453125 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#f960afe448)">
|
||||
<path fill="#060165"
|
||||
d="M 51.292969 231.035156 L 51.292969 236.894531 C 51.292969 238.847656 51.941406 240.152344 54.113281 240.152344 C 56.0625 240.152344 56.933594 238.847656 56.933594 236.894531 L 56.933594 231.035156 L 58.667969 231.035156 L 58.667969 237.113281 C 58.667969 239.71875 57.148438 241.886719 54.113281 241.886719 C 51.074219 241.886719 49.558594 239.933594 49.558594 237.113281 L 49.558594 231.035156 Z M 51.292969 231.035156 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#04b92ea6b9)">
|
||||
<path fill="#060165"
|
||||
d="M 68.859375 238.632812 L 64.523438 238.632812 L 63.4375 241.453125 L 61.703125 241.453125 L 65.390625 231.035156 L 67.992188 231.035156 L 71.679688 241.453125 L 69.730469 241.453125 Z M 68.210938 237.113281 L 66.476562 232.335938 L 64.738281 237.113281 Z M 68.210938 237.113281 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#7b4adb9303)">
|
||||
<path fill="#060165"
|
||||
d="M 75.800781 241.453125 L 75.800781 231.035156 L 77.753906 231.035156 L 77.753906 239.933594 L 83.394531 239.933594 L 83.394531 241.453125 Z M 75.800781 241.453125 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#53357fd403)">
|
||||
<path fill="#060165"
|
||||
d="M 100.3125 238.632812 L 95.972656 238.632812 L 94.886719 241.453125 L 93.152344 241.453125 L 96.839844 231.035156 L 99.441406 231.035156 L 103.128906 241.453125 L 101.179688 241.453125 Z M 99.660156 237.113281 L 97.925781 232.335938 L 96.191406 237.113281 Z M 99.660156 237.113281 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#bcd03dbca6)">
|
||||
<path fill="#060165"
|
||||
d="M 108.554688 237.980469 C 108.769531 238.632812 109.421875 240.152344 111.804688 240.152344 C 113.757812 240.152344 114.410156 239.5 114.410156 238.632812 C 114.410156 237.980469 113.542969 237.546875 112.457031 237.328125 L 110.722656 237.113281 C 107.683594 236.460938 107.035156 235.375 107.035156 234.074219 C 107.035156 232.335938 108.554688 231.035156 111.589844 231.035156 C 115.058594 231.035156 116.144531 232.769531 116.359375 234.074219 L 114.625 234.507812 C 114.410156 233.640625 113.324219 232.769531 111.589844 232.769531 C 110.070312 232.769531 108.769531 233.421875 108.769531 234.289062 C 108.769531 235.160156 109.636719 235.59375 110.9375 235.808594 L 112.671875 236.027344 C 115.492188 236.460938 116.144531 237.761719 116.144531 238.847656 C 116.144531 240.800781 114.410156 241.886719 111.589844 241.886719 C 108.117188 241.886719 106.816406 239.933594 106.601562 238.632812 Z M 108.554688 237.980469 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#ff8639fe11)">
|
||||
<path fill="#060165"
|
||||
d="M 122 237.980469 C 122.21875 238.632812 122.867188 240.152344 125.253906 240.152344 C 127.207031 240.152344 127.855469 239.5 127.855469 238.632812 C 127.855469 237.980469 126.988281 237.546875 125.90625 237.328125 L 124.167969 237.113281 C 121.132812 236.460938 120.480469 235.375 120.480469 234.074219 C 120.480469 232.335938 122 231.035156 125.035156 231.035156 C 128.507812 231.035156 129.589844 232.769531 129.808594 234.074219 L 128.289062 234.074219 C 128.074219 233.207031 126.988281 232.335938 125.253906 232.335938 C 123.734375 232.335938 122.433594 232.988281 122.433594 233.855469 C 122.433594 234.726562 123.300781 235.160156 124.601562 235.375 L 126.339844 235.59375 C 129.15625 236.027344 129.808594 237.328125 129.808594 238.414062 C 129.808594 240.367188 128.074219 241.453125 125.253906 241.453125 C 121.785156 241.453125 120.480469 239.5 120.265625 238.199219 Z M 122 237.980469 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#3990de54cb)">
|
||||
<path fill="#060165"
|
||||
d="M 134.796875 231.035156 L 136.53125 231.035156 L 136.53125 241.671875 L 134.796875 241.671875 Z M 134.796875 231.035156 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#045cbd7804)">
|
||||
<path fill="#060165"
|
||||
d="M 143.039062 237.980469 C 143.257812 238.632812 143.90625 240.152344 146.292969 240.152344 C 148.246094 240.152344 148.894531 239.5 148.894531 238.632812 C 148.894531 237.980469 148.027344 237.546875 146.941406 237.328125 L 145.207031 237.113281 C 142.171875 236.460938 141.519531 235.375 141.519531 234.074219 C 141.519531 232.335938 143.039062 231.035156 146.074219 231.035156 C 149.546875 231.035156 150.628906 232.769531 150.847656 234.074219 L 149.113281 234.507812 C 148.894531 233.640625 147.8125 232.769531 146.074219 232.769531 C 144.558594 232.769531 143.257812 233.421875 143.257812 234.289062 C 143.257812 235.160156 144.125 235.59375 145.425781 235.808594 L 147.160156 236.027344 C 149.980469 236.460938 150.628906 237.761719 150.628906 238.847656 C 150.628906 240.800781 148.894531 241.886719 146.074219 241.886719 C 142.605469 241.886719 141.304688 239.933594 141.085938 238.632812 Z M 143.039062 237.980469 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#7de9a85e20)">
|
||||
<path fill="#060165"
|
||||
d="M 157.570312 241.453125 L 157.570312 232.554688 L 154.101562 232.554688 L 154.101562 231.035156 L 162.992188 231.035156 L 162.992188 232.554688 L 159.304688 232.554688 L 159.304688 241.453125 Z M 157.570312 241.453125 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#e68f5b3fba)">
|
||||
<path fill="#060165"
|
||||
d="M 172.753906 238.632812 L 168.414062 238.632812 L 167.332031 241.453125 L 165.597656 241.453125 L 169.285156 231.035156 L 171.886719 231.035156 L 175.574219 241.453125 L 173.839844 241.453125 Z M 172.320312 237.113281 L 170.585938 232.335938 L 168.847656 237.113281 Z M 172.320312 237.113281 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#65464ab719)">
|
||||
<path fill="#060165"
|
||||
d="M 179.042969 241.453125 L 179.042969 231.035156 L 180.777344 231.035156 L 186.636719 238.414062 L 186.636719 231.035156 L 188.371094 231.035156 L 188.371094 241.453125 L 186.851562 241.453125 L 180.996094 234.074219 L 180.996094 241.453125 Z M 179.042969 241.453125 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#b82da71650)">
|
||||
<path fill="#060165"
|
||||
d="M 196.179688 241.453125 L 196.179688 232.554688 L 192.492188 232.554688 L 192.492188 231.035156 L 201.382812 231.035156 L 201.382812 232.554688 L 197.914062 232.554688 L 197.914062 241.453125 Z M 196.179688 241.453125 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#fe4eaf6826)">
|
||||
<path fill="#060165"
|
||||
d="M 206.371094 237.980469 C 206.589844 238.632812 207.238281 240.152344 209.625 240.152344 C 211.578125 240.152344 212.226562 239.5 212.226562 238.632812 C 212.226562 237.980469 211.359375 237.546875 210.277344 237.328125 L 208.542969 237.113281 C 205.503906 236.460938 204.855469 235.375 204.855469 234.074219 C 204.855469 232.335938 206.371094 231.035156 209.410156 231.035156 C 212.878906 231.035156 213.964844 232.769531 214.179688 234.074219 L 212.445312 234.507812 C 212.226562 233.640625 211.144531 232.769531 209.410156 232.769531 C 207.890625 232.769531 206.589844 233.421875 206.589844 234.289062 C 206.589844 235.160156 207.457031 235.59375 208.757812 235.808594 L 210.492188 236.027344 C 213.3125 236.460938 213.964844 237.761719 213.964844 238.847656 C 213.964844 240.800781 212.230469 241.886719 209.410156 241.886719 C 205.9375 241.886719 204.636719 239.933594 204.421875 238.632812 Z M 206.371094 237.980469 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#79d174fbbf)">
|
||||
<path fill="#060165"
|
||||
d="M 225.460938 241.453125 L 225.460938 231.035156 L 233.050781 231.035156 L 233.050781 232.554688 L 227.195312 232.554688 L 227.195312 235.375 L 233.050781 235.375 L 233.050781 236.894531 L 227.195312 236.894531 L 227.195312 241.453125 Z M 225.460938 241.453125 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#b0be87ffda)">
|
||||
<path fill="#060165"
|
||||
d="M 242.375 230.816406 C 245.414062 230.816406 248.015625 232.988281 248.015625 236.242188 C 248.015625 239.5 245.414062 241.671875 242.375 241.671875 C 239.339844 241.671875 236.738281 239.5 236.738281 236.242188 C 236.738281 232.988281 239.339844 230.816406 242.375 230.816406 M 246.28125 236.242188 C 246.28125 234.074219 244.546875 232.335938 242.375 232.335938 C 240.207031 232.335938 238.472656 234.074219 238.472656 236.242188 C 238.472656 238.414062 240.207031 240.152344 242.375 240.152344 C 244.546875 240.152344 246.28125 238.414062 246.28125 236.242188 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#444dbef42a)">
|
||||
<path fill="#060165"
|
||||
d="M 252.789062 241.453125 L 252.789062 231.035156 L 257.34375 231.035156 C 259.511719 231.035156 261.464844 231.902344 261.464844 234.289062 C 261.464844 235.808594 260.597656 236.679688 259.511719 237.113281 C 260.164062 237.546875 260.378906 238.199219 260.597656 238.847656 C 260.8125 240.152344 261.246094 241.238281 261.464844 241.453125 L 259.511719 241.453125 C 259.292969 241.019531 259.078125 240.367188 258.859375 239.28125 C 258.644531 238.199219 258.210938 237.328125 257.34375 237.328125 L 254.738281 237.328125 L 254.738281 241.453125 Z M 254.523438 235.808594 L 256.910156 235.808594 C 258.210938 235.808594 259.511719 235.59375 259.511719 234.074219 C 259.511719 232.554688 258.425781 232.335938 257.125 232.335938 L 254.523438 232.335938 Z M 254.523438 235.808594 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#314903e6e8)">
|
||||
<path fill="#060165"
|
||||
d="M 272.523438 241.453125 L 272.523438 230.816406 L 277.078125 230.816406 C 280.550781 230.816406 282.9375 232.988281 282.9375 236.027344 C 282.9375 239.28125 280.550781 241.238281 277.078125 241.238281 Z M 274.261719 239.933594 L 276.863281 239.933594 C 279.25 239.933594 280.984375 238.632812 280.984375 236.242188 C 280.984375 233.855469 279.25 232.554688 276.863281 232.554688 L 274.261719 232.554688 Z M 274.261719 239.933594 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#2fd70bc44d)">
|
||||
<path fill="#060165"
|
||||
d="M 292.480469 230.816406 C 295.515625 230.816406 298.117188 232.988281 298.117188 236.242188 C 298.117188 239.5 295.515625 241.671875 292.480469 241.671875 C 289.441406 241.671875 286.839844 239.5 286.839844 236.242188 C 286.839844 232.988281 289.226562 230.816406 292.480469 230.816406 M 296.382812 236.242188 C 296.382812 234.074219 294.648438 232.335938 292.480469 232.335938 C 290.3125 232.335938 288.574219 234.074219 288.574219 236.242188 C 288.574219 238.414062 290.3125 240.152344 292.480469 240.152344 C 294.648438 240.152344 296.382812 238.414062 296.382812 236.242188 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#032e9be536)">
|
||||
<path fill="#060165"
|
||||
d="M 310.480469 238.847656 L 311.78125 239.933594 C 310.699219 241.019531 309.179688 241.671875 307.664062 241.671875 C 304.625 241.671875 302.023438 239.5 302.023438 236.242188 C 302.023438 232.988281 304.625 230.816406 307.664062 230.816406 C 309.398438 230.816406 310.914062 231.46875 311.78125 232.554688 L 310.480469 233.640625 C 309.832031 232.769531 308.746094 232.335938 307.445312 232.335938 C 305.277344 232.335938 303.542969 234.074219 303.542969 236.242188 C 303.542969 238.414062 305.277344 240.152344 307.445312 240.152344 C 308.964844 240.152344 309.832031 239.71875 310.480469 238.847656 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#77ab9ec736)">
|
||||
<path fill="#060165"
|
||||
d="M 318.289062 241.453125 L 318.289062 232.554688 L 314.820312 232.554688 L 314.820312 231.035156 L 323.710938 231.035156 L 323.710938 232.554688 L 320.242188 232.554688 L 320.242188 241.453125 Z M 318.289062 241.453125 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#bf7e46828b)">
|
||||
<path fill="#060165"
|
||||
d="M 332.605469 230.816406 C 335.640625 230.816406 338.246094 232.988281 338.246094 236.242188 C 338.246094 239.5 335.640625 241.671875 332.605469 241.671875 C 329.570312 241.671875 326.964844 239.5 326.964844 236.242188 C 326.964844 232.988281 329.570312 230.816406 332.605469 230.816406 M 336.507812 236.242188 C 336.507812 234.074219 334.773438 232.335938 332.605469 232.335938 C 330.4375 232.335938 328.699219 234.074219 328.699219 236.242188 C 328.699219 238.414062 330.4375 240.152344 332.605469 240.152344 C 334.773438 240.152344 336.507812 238.414062 336.507812 236.242188 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#b78234ca4f)">
|
||||
<path fill="#060165"
|
||||
d="M 343.015625 241.453125 L 343.015625 231.035156 L 347.570312 231.035156 C 349.738281 231.035156 351.691406 231.902344 351.691406 234.289062 C 351.691406 235.808594 350.824219 236.679688 349.738281 237.113281 C 350.390625 237.546875 350.605469 238.199219 350.824219 238.847656 C 351.042969 240.152344 351.476562 241.238281 351.691406 241.453125 L 349.523438 241.453125 C 349.304688 241.019531 349.089844 240.367188 348.871094 239.28125 C 348.65625 238.199219 348.222656 237.328125 347.355469 237.328125 L 344.75 237.328125 L 344.75 241.453125 Z M 344.75 235.808594 L 347.136719 235.808594 C 348.4375 235.808594 349.738281 235.59375 349.738281 234.074219 C 349.738281 232.554688 348.65625 232.335938 347.355469 232.335938 L 344.75 232.335938 Z M 344.75 235.808594 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g clip-path="url(#2b0e76334b)">
|
||||
<path fill="#060165"
|
||||
d="M 357.113281 237.980469 C 357.332031 238.632812 357.980469 240.152344 360.367188 240.152344 C 362.320312 240.152344 362.96875 239.5 362.96875 238.632812 C 362.96875 237.980469 362.101562 237.546875 361.019531 237.328125 L 359.28125 237.113281 C 356.246094 236.460938 355.597656 235.375 355.597656 234.074219 C 355.597656 232.335938 357.113281 231.035156 360.152344 231.035156 C 363.621094 231.035156 364.707031 232.769531 364.921875 234.074219 L 363.1875 234.507812 C 362.96875 233.640625 361.886719 232.769531 360.152344 232.769531 C 358.632812 232.769531 357.332031 233.421875 357.332031 234.289062 C 357.332031 235.160156 358.199219 235.59375 359.5 235.808594 L 361.234375 236.027344 C 364.054688 236.460938 364.707031 237.761719 364.707031 238.847656 C 364.707031 240.800781 362.96875 241.886719 360.152344 241.886719 C 356.679688 241.886719 355.378906 239.933594 355.160156 238.632812 Z M 357.113281 237.980469 "
|
||||
fill-opacity="1" fill-rule="nonzero" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
|
|
@ -4,15 +4,22 @@ import { getToken } from '../utils/tokenHelper.js';
|
|||
import chalk from "chalk";
|
||||
import { sendTokenExpired } from '../ipc/ipcHandler.js';
|
||||
import https from 'https';
|
||||
import { getBaseURL } from './baseURL.js';
|
||||
|
||||
const TOKEN_KEY = 'ACCESS_TOKEN';
|
||||
|
||||
const Base_URL = await getBaseURL()
|
||||
|
||||
const httpsAgent = new https.Agent({
|
||||
rejectUnauthorized: false
|
||||
});
|
||||
|
||||
|
||||
console.log('Base_URL$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$' ,Base_URL)
|
||||
|
||||
const api: AxiosInstance = axios.create({
|
||||
baseURL: 'https://quality-friendly-goldfish.ngrok-free.app/api/v1/va', // 🔁 your backend
|
||||
// baseURL: 'https://portal-sf.docva.com/api/v1/va', // 🔁 your backend
|
||||
baseURL: `${Base_URL}/api/v1/va`,
|
||||
httpsAgent
|
||||
// timeout: 10000,
|
||||
});
|
||||
|
|
@ -29,7 +36,7 @@ api.interceptors.request.use(
|
|||
|
||||
if (token) {
|
||||
config.headers['token'] = token;
|
||||
config.headers['ngrok-skip-browser-warning'] = 'true';
|
||||
config.headers['ngrok-skip-browser-warning'] = 'true';
|
||||
}
|
||||
|
||||
return config;
|
||||
|
|
@ -54,7 +61,7 @@ api.interceptors.response.use(
|
|||
(response: AxiosResponse) => response.data,
|
||||
async (error) => {
|
||||
|
||||
console.log(error , "Line 46")
|
||||
console.log(error, "Line 46")
|
||||
const status = error?.response?.status;
|
||||
|
||||
console.error('[Axios Error]', status);
|
||||
|
|
@ -69,4 +76,4 @@ api.interceptors.response.use(
|
|||
}
|
||||
);
|
||||
|
||||
export default api;
|
||||
export default api;
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// let CURRENT_BASE_URL = "https://portal-sf.docva.com/api/v1/va";
|
||||
let CURRENT_BASE_URL = "https://portal-sf.docva.com";
|
||||
|
||||
export const getBaseURL = () => CURRENT_BASE_URL;
|
||||
export const setBaseURL = (url: string) => {
|
||||
CURRENT_BASE_URL = url;
|
||||
};
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
|
||||
import axios from "axios";
|
||||
import https from "https";
|
||||
import { getBaseURL } from "./baseURL.js";
|
||||
|
||||
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
||||
|
||||
export const checkRegion = async () => {
|
||||
const baseURL = await getBaseURL();
|
||||
|
||||
console.log("Lin 11 BaseURL CHECK@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" ,baseURL)
|
||||
|
||||
try {
|
||||
|
||||
const res = await axios.get(`${baseURL}/health`, {
|
||||
headers: { "ngrok-skip-browser-warning": "true" },
|
||||
httpsAgent,
|
||||
timeout: 4000
|
||||
});
|
||||
|
||||
console.log(res , "checkRegion")
|
||||
|
||||
return {
|
||||
success: true,
|
||||
status: res.status,
|
||||
data: res.data
|
||||
};
|
||||
|
||||
} catch (error: any) {
|
||||
console.log(error , "Line 29")
|
||||
const status = error?.response?.status || 500;
|
||||
|
||||
console.log("Region Check Error:", status);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
status,
|
||||
message: "Region check failed"
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
@ -3,7 +3,9 @@ import { getToken, saveToken, clearToken } from "../utils/tokenHelper.js";
|
|||
import { getActiveApp, handleLogin, takeScreenshot, startWindowPolling, getRunningAppsList, captureSpecificApp } from "../utils/utilityFunctions.js";
|
||||
import api from "../api/axiosClient.js";
|
||||
import { getMainWindow } from '../main.js';
|
||||
import { checkRegion } from "../api/regionCheck.js";
|
||||
import { startVPN, stopVPN } from "../vpn.js";
|
||||
import { getBaseURL, setBaseURL } from "../api/baseURL.js";
|
||||
|
||||
|
||||
|
||||
|
|
@ -11,6 +13,22 @@ import { startVPN, stopVPN } from "../vpn.js";
|
|||
export const registerIPCHandlers = (mainWindow: BrowserWindow) => {
|
||||
console.log("⚙️ Registering IPC Handlers...");
|
||||
|
||||
|
||||
ipcMain.handle("GET_BASE_URL", () => {
|
||||
return getBaseURL();
|
||||
});
|
||||
|
||||
// UPDATE Base URL
|
||||
ipcMain.handle("UPDATE_BASE_URL", (event, url: string) => {
|
||||
setBaseURL(url);
|
||||
api.defaults.baseURL = `${url}/api/v1/va`;
|
||||
console.log("Base URL updated to:", api.defaults.baseURL);
|
||||
return {
|
||||
updated: true,
|
||||
baseURL: url
|
||||
};
|
||||
});
|
||||
|
||||
ipcMain.handle("get-token", async (_, key: "ACCESS_TOKEN" | "REFRESH_TOKEN") => {
|
||||
return await getToken(key);
|
||||
});
|
||||
|
|
@ -152,39 +170,30 @@ ipcMain.handle("stop-project-tracking", async (event, payload) => {
|
|||
// tabs opening and closing when and tracking start
|
||||
ipcMain.handle("tracking-tabs-opening-and-closing", async (event, payload) => {
|
||||
try {
|
||||
console.log("🟦 IPC tracking-tabs-opening-and-closing:", payload);
|
||||
console.log("🟦 IPC received payload:", payload);
|
||||
|
||||
let response;
|
||||
if (!payload.id) {
|
||||
// POST new log (new tab opened)
|
||||
response = await api.post("/log/project/track", {
|
||||
logId: payload.logId,
|
||||
projectId: payload.projectId,
|
||||
title: payload.title,
|
||||
appName: payload.appName,
|
||||
});
|
||||
console.log(" New log created:", response);
|
||||
} else {
|
||||
// PATCH existing log (same tab, update)
|
||||
response = await api.patch("/log/project/track", {
|
||||
id: payload.id,
|
||||
logId: payload.logId,
|
||||
projectId: payload.projectId,
|
||||
title: payload.title,
|
||||
appName: payload.appName,
|
||||
});
|
||||
console.log("Log updated:", response);
|
||||
}
|
||||
const response = await api.post("/log/project/track", payload);
|
||||
|
||||
return response;
|
||||
console.log("🟩 Log track API success:", response);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error syncing project tab tracking:", error);
|
||||
throw error;
|
||||
console.error("❌ Log track API error:", error);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: "Something went wrong",
|
||||
error,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
ipcMain.handle("get-project-logs", async (event, { projectID, fromDate, toDate }) => {
|
||||
try {
|
||||
console.log("📥 get-project-logs called with:", { projectID, fromDate, toDate });
|
||||
|
|
@ -535,6 +544,11 @@ ipcMain.handle("update-payment", async (event, data: Record<string, any>) => {
|
|||
|
||||
|
||||
|
||||
ipcMain.handle("check-region", async () => {
|
||||
return await checkRegion();
|
||||
});
|
||||
|
||||
|
||||
|
||||
export const sendTokenExpired = () => {
|
||||
console.log("⚠️ Sending token-expired event to renderer");
|
||||
|
|
|
|||
|
|
@ -178,6 +178,12 @@ app.on("ready", async () => {
|
|||
// HANDLE CLOSE EVENTS
|
||||
// ------------------------------------
|
||||
// handleCloseEvents(win);
|
||||
mainWindow.on("close", () => {
|
||||
if(mainWindow !== null) {
|
||||
mainWindow.webContents.executeJavaScript(`window.__allowClose__ = true`);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
|
@ -198,6 +204,7 @@ app.on('browser-window-focus', () => {
|
|||
// });
|
||||
|
||||
|
||||
|
||||
// ---------------------------
|
||||
// CUSTOM CLOSE BEHAVIOR
|
||||
// ---------------------------
|
||||
|
|
|
|||
|
|
@ -65,9 +65,14 @@ contextBridge.exposeInMainWorld('electron', {
|
|||
getLeaveList: () => ipcRenderer.invoke("get-leave-list"),
|
||||
getAdjustmentsList:(vaId: string) => ipcRenderer.invoke("get-adjustments-list",vaId),
|
||||
|
||||
checkRegion: () => ipcRenderer.invoke("check-region"),
|
||||
|
||||
connectVPN: () => ipcRenderer.invoke("vpn-connect"),
|
||||
disconnectVPN: () => ipcRenderer.invoke("vpn-disconnect"),
|
||||
|
||||
getBaseURL: () => ipcRenderer.invoke("GET_BASE_URL"),
|
||||
updateBaseURL: (url: string) => ipcRenderer.invoke("UPDATE_BASE_URL", url),
|
||||
|
||||
} satisfies Window['electron']);
|
||||
|
||||
// function ipcInvoke<Key extends keyof EventPayloadMapping>(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { saveToken } from "./tokenHelper.js";
|
|||
import { activeWindow } from "get-windows";
|
||||
import { exec } from "child_process";
|
||||
import { getUIPath } from "../pathResolver.js";
|
||||
import { getBaseURL } from "../api/baseURL.js";
|
||||
|
||||
const httpsAgent = new https.Agent({
|
||||
rejectUnauthorized: false
|
||||
|
|
@ -61,14 +62,16 @@ export const handleLogin = async (credentials: { email: string, password: string
|
|||
// Call your login API
|
||||
console.log("🟡 login Request received", credentials);
|
||||
|
||||
let baseURL = getBaseURL()
|
||||
|
||||
console.log("app.getAppPath()", app.getAppPath());
|
||||
console.log("UIPATH", getUIPath());
|
||||
console.log("Doom 1");
|
||||
console.log("Doom 1" , `${baseURL}/api/v1/va/login`);
|
||||
|
||||
try {
|
||||
console.log("➡️ Calling API...");
|
||||
const response = await axios.post(
|
||||
'https://quality-friendly-goldfish.ngrok-free.app/api/v1/va/login',
|
||||
`${baseURL}/api/v1/va/login`,
|
||||
{ userDetails: { ...credentials } },
|
||||
{ headers: { "ngrok-skip-browser-warning": 69420 }, httpsAgent }
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { exec, execFile } from "child_process";
|
|||
import fs from "fs";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import axios from "axios";
|
||||
|
||||
//
|
||||
// ------------------ HELPERS ------------------
|
||||
|
|
@ -51,20 +52,72 @@ function getWireGuardInstallerPath(): string {
|
|||
|
||||
|
||||
// OS-specific wg0.conf
|
||||
function getConfigPath(): string {
|
||||
// function getConfigPath(): string {
|
||||
// const base = isDev()
|
||||
// ? path.join(process.cwd(), "resources", "wireguard")
|
||||
// : path.join(process.resourcesPath, "wireguard");
|
||||
|
||||
// if (process.platform === "win32") {
|
||||
// return path.join(base, "windows", "wg0.conf");
|
||||
// }
|
||||
|
||||
// if (process.platform === "darwin") {
|
||||
// return path.join(base, "macos", "wg0.conf");
|
||||
// }
|
||||
|
||||
// throw new Error("Unsupported OS");
|
||||
// }
|
||||
|
||||
export async function getConfigPath(): Promise<string> {
|
||||
if (process.platform !== "win32") {
|
||||
throw new Error("Only Windows supported for dynamic WireGuard config");
|
||||
}
|
||||
console.log("Calling http://35.95.209.161/api/v1/vpn/connect")
|
||||
// 1️⃣ Call API to get dynamic config
|
||||
let response = null;
|
||||
try {
|
||||
response = await axios.post(
|
||||
"http://35.95.209.161/api/v1/vpn/connect",
|
||||
{},
|
||||
{ timeout: 15000 }
|
||||
);
|
||||
} catch (err: any) {
|
||||
console.error("VPN API error:", {
|
||||
message: err.message,
|
||||
code: err.code,
|
||||
response: err.response?.data,
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
console.log("Called http://35.95.209.161/api/v1/vpn/connect", JSON.stringify(response.data));
|
||||
|
||||
if (!response.data?.success || !response.data.clientConf) {
|
||||
throw new Error("Invalid VPN config response");
|
||||
}
|
||||
|
||||
const clientConf: string = response.data.clientConf;
|
||||
|
||||
// 2️⃣ Decide base directory (same idea as your existing logic)
|
||||
const base = isDev()
|
||||
? path.join(process.cwd(), "resources", "wireguard")
|
||||
: path.join(process.resourcesPath, "wireguard");
|
||||
? path.join(process.cwd(), "resources", "wireguard", "windows")
|
||||
: path.join(process.resourcesPath, "wireguard", "windows");
|
||||
|
||||
if (process.platform === "win32") {
|
||||
return path.join(base, "windows", "wg0.conf");
|
||||
}
|
||||
// Ensure directory exists
|
||||
fs.mkdirSync(base, { recursive: true });
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
return path.join(base, "macos", "wg0.conf");
|
||||
}
|
||||
// 3️⃣ Create temp / runtime conf file
|
||||
// IMPORTANT: name must stay stable (WireGuard service name depends on it)
|
||||
const confPath = path.join(base, "wg0.conf");
|
||||
|
||||
throw new Error("Unsupported OS");
|
||||
// 4️⃣ Write config to file
|
||||
fs.writeFileSync(confPath, clientConf, {
|
||||
encoding: "utf-8",
|
||||
mode: 0o600, // secure permissions
|
||||
});
|
||||
|
||||
console.log("VPN config written to:", confPath);
|
||||
// 5️⃣ Return path (same contract as before)
|
||||
return confPath;
|
||||
}
|
||||
|
||||
// Detect installed WireGuard.exe on Windows
|
||||
|
|
@ -145,16 +198,24 @@ async function ensureWireGuardInstalled(): Promise<string> {
|
|||
|
||||
|
||||
async function connectWindows(): Promise<string> {
|
||||
|
||||
try {
|
||||
const response = await axios.get('http://35.95.209.161')
|
||||
console.log("Response from test API:", response.data);
|
||||
} catch (error) {
|
||||
console.error("Error connecting to test API:", error);
|
||||
}
|
||||
|
||||
const cli = await ensureWireGuardInstalled();
|
||||
const conf = getConfigPath();
|
||||
const conf = await getConfigPath();
|
||||
|
||||
// Disable GUI popup and silent mode (ignore errors)
|
||||
await execPromise(`reg add "HKCU\\Software\\WireGuard" /v HideTrayIcon /t REG_DWORD /d 1 /f`).catch(() => {});
|
||||
await execPromise(`reg add "HKCU\\Software\\WireGuard" /v SuppressTray /t REG_DWORD /d 1 /f`).catch(() => {});
|
||||
await execPromise(`reg delete "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" /v WireGuard /f`).catch(() => {});
|
||||
await execPromise(`reg add "HKCU\\Software\\WireGuard" /v HideTrayIcon /t REG_DWORD /d 1 /f`).catch(() => { });
|
||||
await execPromise(`reg add "HKCU\\Software\\WireGuard" /v SuppressTray /t REG_DWORD /d 1 /f`).catch(() => { });
|
||||
await execPromise(`reg delete "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" /v WireGuard /f`).catch(() => { });
|
||||
|
||||
// Start background service
|
||||
await execPromise(`sc start WireGuardManager`).catch(() => {});
|
||||
await execPromise(`sc start WireGuardManager`).catch(() => { });
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile(cli, ["/installtunnelservice", conf], (err) => {
|
||||
|
|
|
|||
|
|
@ -97,22 +97,23 @@ import Vpn from "./pages/Vpn/Vpn";
|
|||
import Schedule from "./pages/Schedule/Schedule";
|
||||
import PaysSlips from "./pages/PaySlips/PaysSlips";
|
||||
import ApplyLeaveAndAdjustmentsTime from "./pages/LeaveAndAdjustments/ApplyLeaveAndAdjustmentsTime";
|
||||
import VpnGuard from "./pages/Vpn/VpnGuard";
|
||||
|
||||
|
||||
const App: React.FC = () => {
|
||||
|
||||
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
|
||||
// ✅ Get user directly from Redux
|
||||
const user = useSelector((state: RootState) => state.user.user);
|
||||
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(loadUserFromStorage());
|
||||
}, [dispatch]);
|
||||
|
||||
console.log("App.tsx → user:", user);
|
||||
console.log("App.tsx → user:", user);
|
||||
|
||||
return (
|
||||
<Router>
|
||||
|
|
@ -122,7 +123,9 @@ const App: React.FC = () => {
|
|||
path="/login"
|
||||
element={
|
||||
<PublicRoute user={user}>
|
||||
<Login />
|
||||
<VpnGuard>
|
||||
<Login />
|
||||
</VpnGuard>
|
||||
</PublicRoute>
|
||||
}
|
||||
/>
|
||||
|
|
@ -132,20 +135,22 @@ const App: React.FC = () => {
|
|||
path="/dashboard"
|
||||
element={
|
||||
<ProtectedRoute user={user}>
|
||||
{/* <VpnGuard> */}
|
||||
<DashboardLayout />
|
||||
{/* </VpnGuard> */}
|
||||
</ProtectedRoute>
|
||||
}
|
||||
>
|
||||
<Route index element={<Dashboard />} />
|
||||
<Route path="projects" element={<Projects />} />
|
||||
<Route path="leave-adjustments" element={<LeaveAndAdjustments />} />
|
||||
<Route path="leave-adjustments/apply" element={<ApplyLeaveAndAdjustmentsTime/>} />
|
||||
<Route path="leave-adjustments/apply" element={<ApplyLeaveAndAdjustmentsTime />} />
|
||||
<Route path="timesheet" element={<Timesheets />} />
|
||||
<Route path="settings" element={<Settings />} />
|
||||
<Route path="vpn" element={<Vpn />} />
|
||||
<Route path="help-support" element={<HelpSupport />} />
|
||||
<Route path="schedule" element={<Schedule/>} />
|
||||
<Route path="pays-slips" element={<PaysSlips/>} />
|
||||
<Route path="schedule" element={<Schedule />} />
|
||||
<Route path="pays-slips" element={<PaysSlips />} />
|
||||
</Route>
|
||||
|
||||
{/* Default Redirect */}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
// hooks/useActiveAppTracker.tsx
|
||||
import { useEffect, useRef, useCallback } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { setActiveWindow, WindowInfo } from "../store/windowSlice";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { RootState } from "../store";
|
||||
import { WindowInfo } from "../store/windowSlice";
|
||||
import {
|
||||
initializeProjectLogs,
|
||||
updateWindowLog,
|
||||
clearProjectLogs,
|
||||
debugLogsState,
|
||||
} from "../store/logsSlice";
|
||||
|
||||
interface DailyHours {
|
||||
todayTotal: string;
|
||||
|
|
@ -16,273 +24,223 @@ export const useActiveAppTracker = (
|
|||
onLimitExceeded?: () => void
|
||||
) => {
|
||||
const dispatch = useDispatch();
|
||||
const { logsByProject } = useSelector((state: RootState) => state.logs);
|
||||
const lastActiveRef = useRef<WindowInfo | null>(null);
|
||||
const lastLogIdRef = useRef<string | null>(null);
|
||||
const lastTitleRef = useRef<string | null>(null);
|
||||
const intervalIdRef = useRef<number | null>(null);
|
||||
const finalizeTrackingRef = useRef<(() => Promise<void>) | null>(null);
|
||||
const limitCheckedRef = useRef<boolean>(false);
|
||||
const isStoppingRef = useRef<boolean>(false); // ✅ Flag to prevent new requests
|
||||
const lastAppNameRef = useRef<string | null>(null);
|
||||
const intervalIdRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
const limitCheckedRef = useRef(false);
|
||||
const isStoppingRef = useRef(false);
|
||||
const checkCountRef = useRef(0);
|
||||
|
||||
const canSend = !!(activeProjectLogId && projectId && !isStoppingRef.current);
|
||||
// Initialize logs when tracking starts
|
||||
useEffect(() => {
|
||||
if (isTracking && activeProjectLogId && projectId) {
|
||||
// Initialize fresh logs for this tracking session
|
||||
dispatch(
|
||||
initializeProjectLogs({
|
||||
projectId,
|
||||
logId: activeProjectLogId,
|
||||
})
|
||||
);
|
||||
console.log(` Logs initialized for project: ${projectName} (${projectId})`);
|
||||
|
||||
// ✅ Parse time string to minutes
|
||||
const parseTimeToMinutes = (timeStr: string): number => {
|
||||
const hourMatch = timeStr.match(/(\d+)h/);
|
||||
const minMatch = timeStr.match(/(\d+)m/);
|
||||
|
||||
const hours = hourMatch ? parseInt(hourMatch[1]) : 0;
|
||||
const minutes = minMatch ? parseInt(minMatch[1]) : 0;
|
||||
|
||||
return hours * 60 + minutes;
|
||||
};
|
||||
|
||||
// ✅ Check if daily limit is exceeded
|
||||
const checkDailyLimit = (dailyHours?: DailyHours): boolean => {
|
||||
if (!dailyHours) return false;
|
||||
|
||||
const todayTotalMins = parseTimeToMinutes(dailyHours.todayTotal);
|
||||
const dailyLimitMins = parseTimeToMinutes(dailyHours.dailyLimit);
|
||||
const remainingMins = parseTimeToMinutes(dailyHours.remaining);
|
||||
|
||||
console.log("⏰ Daily Limit Check:", {
|
||||
todayTotal: `${todayTotalMins} mins`,
|
||||
dailyLimit: `${dailyLimitMins} mins`,
|
||||
remaining: `${remainingMins} mins`,
|
||||
exceeded: remainingMins <= 0
|
||||
});
|
||||
|
||||
if (remainingMins <= 0) {
|
||||
return true;
|
||||
// Reset refs
|
||||
lastActiveRef.current = null;
|
||||
lastTitleRef.current = null;
|
||||
lastAppNameRef.current = null;
|
||||
checkCountRef.current = 0;
|
||||
isStoppingRef.current = false;
|
||||
}
|
||||
}, [isTracking, activeProjectLogId, projectId, projectName, dispatch]);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// ✅ Send activity log (POST for new, PATCH for update)
|
||||
const sendActivityLog = async (
|
||||
projectLogId: string,
|
||||
projectId: string,
|
||||
title: string,
|
||||
appName: string,
|
||||
isNew: boolean
|
||||
) => {
|
||||
// ✅ CRITICAL: Block all requests when stopping
|
||||
if (isStoppingRef.current) {
|
||||
console.log("🚫 Stopping in progress — blocking activity log");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!canSend) {
|
||||
console.log("🚫 Skipping API — missing required IDs");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const payload: {
|
||||
id?: string;
|
||||
logId: string;
|
||||
projectId: string;
|
||||
title: string;
|
||||
appName: string;
|
||||
} = {
|
||||
logId: projectLogId,
|
||||
projectId,
|
||||
title,
|
||||
appName,
|
||||
};
|
||||
|
||||
if (!isNew && lastLogIdRef.current) {
|
||||
payload.id = lastLogIdRef.current;
|
||||
}
|
||||
|
||||
console.log(isNew ? "➡️ POST new log" : "✏️ PATCH update log", payload);
|
||||
|
||||
const res = await window.electron.trackingTabsOpeningClosing(payload);
|
||||
|
||||
console.log("📊 Activity log response:", res);
|
||||
|
||||
// Store the log ID for future updates
|
||||
if (isNew && res?.data?._id) {
|
||||
lastLogIdRef.current = res.data._id;
|
||||
console.log("💾 Stored log ID:", res.data._id);
|
||||
}
|
||||
|
||||
// ✅ Check daily limit after every API call
|
||||
if (res?.dailyHours) {
|
||||
const limitExceeded = checkDailyLimit(res.dailyHours);
|
||||
|
||||
if (limitExceeded && !limitCheckedRef.current) {
|
||||
limitCheckedRef.current = true;
|
||||
console.warn("🚨 Daily limit exceeded!");
|
||||
|
||||
if (onLimitExceeded) {
|
||||
setTimeout(() => {
|
||||
onLimitExceeded();
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.error("❌ Activity log error:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// ✅ Stop the interval immediately
|
||||
const stopInterval = useCallback(() => {
|
||||
console.log("🛑 Stopping activity tracking interval...");
|
||||
// Stop tracking intervals
|
||||
const stopIntervals = useCallback(() => {
|
||||
if (intervalIdRef.current) {
|
||||
clearInterval(intervalIdRef.current);
|
||||
intervalIdRef.current = null;
|
||||
console.log("✅ Interval cleared");
|
||||
}
|
||||
}, []);
|
||||
|
||||
// ✅ Send final PATCH before stopping (called by handleStopTracking)
|
||||
const sendFinalPatch = useCallback(async () => {
|
||||
console.log("=".repeat(60));
|
||||
console.log("🛑 FINAL PATCH INITIATED");
|
||||
console.log("=".repeat(60));
|
||||
|
||||
// ✅ STEP 1: Set stopping flag IMMEDIATELY
|
||||
isStoppingRef.current = true;
|
||||
console.log("🔒 Stopping flag set — no new requests allowed");
|
||||
|
||||
// ✅ STEP 2: Stop the interval IMMEDIATELY
|
||||
stopInterval();
|
||||
|
||||
if (!activeProjectLogId || !projectId) {
|
||||
console.log("⚠️ Skipping final PATCH — no active project log ID");
|
||||
return;
|
||||
// Debug function to check logs state
|
||||
const debugCurrentLogs = useCallback(() => {
|
||||
if (projectId) {
|
||||
dispatch(debugLogsState({ projectId }));
|
||||
}
|
||||
}, [projectId, dispatch]);
|
||||
|
||||
// ✅ STEP 3: Send ONE final PATCH for the last active window
|
||||
if (lastLogIdRef.current && lastTitleRef.current && lastActiveRef.current) {
|
||||
try {
|
||||
console.log("📊 Final Activity Details:");
|
||||
console.log(` • Window: ${lastTitleRef.current}`);
|
||||
console.log(` • App: ${lastActiveRef.current.owner.name}`);
|
||||
console.log(` • Log ID: ${lastLogIdRef.current}`);
|
||||
console.log(` • Project: ${projectId}`);
|
||||
// Finalize and send all logs when stopping tracking
|
||||
// hooks/useActiveAppTracker.tsx - finalizeAndSendLogs function
|
||||
const finalizeAndSendLogs = useCallback(
|
||||
async (vaId: string): Promise<boolean> => {
|
||||
console.log(" FINALIZING AND SENDING ALL LOGS");
|
||||
|
||||
const result = await sendActivityLog(
|
||||
activeProjectLogId,
|
||||
projectId,
|
||||
lastTitleRef.current,
|
||||
lastActiveRef.current.owner.name || "Unknown",
|
||||
false // PATCH (update existing log)
|
||||
);
|
||||
// Set stopping flag
|
||||
isStoppingRef.current = true;
|
||||
|
||||
console.log("✅ Final PATCH sent successfully!");
|
||||
// Stop intervals immediately
|
||||
stopIntervals();
|
||||
|
||||
if (result?.dailyHours) {
|
||||
console.log("📈 Final Daily Hours:");
|
||||
console.log(` • Today: ${result.dailyHours.todayTotal}`);
|
||||
console.log(` • Limit: ${result.dailyHours.dailyLimit}`);
|
||||
console.log(` • Remaining: ${result.dailyHours.remaining}`);
|
||||
if (!activeProjectLogId || !projectId) {
|
||||
console.log("⚠️ Skipping — no active project");
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("❌ Final PATCH failed:", error);
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
console.log("ℹ️ No active window to finalize");
|
||||
}
|
||||
const projectLogs = logsByProject[projectId];
|
||||
if (!projectLogs || projectLogs.logs.length === 0) {
|
||||
console.log("ℹ️ No logs found for project", projectId);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ✅ STEP 4: Clean up all refs
|
||||
console.log("🧹 Cleaning up tracking references...");
|
||||
lastActiveRef.current = null;
|
||||
lastTitleRef.current = null;
|
||||
lastLogIdRef.current = null;
|
||||
limitCheckedRef.current = false;
|
||||
console.log("✅ Cleanup complete");
|
||||
console.log("=".repeat(60));
|
||||
}, [activeProjectLogId, projectId, stopInterval]);
|
||||
try {
|
||||
// Create a DEEP copy of the logs
|
||||
const allLogs = JSON.parse(JSON.stringify(projectLogs.logs));
|
||||
|
||||
// ✅ Update ref whenever sendFinalPatch changes
|
||||
// Filter out logs with very short duration (less than 1000ms = 1 second)
|
||||
const validLogs = allLogs.filter((log:any) => {
|
||||
return log.totalTime >= 1000; // Minimum 1 second (1000ms)
|
||||
});
|
||||
|
||||
console.log(`📊 Total logs: ${allLogs.length}, Valid logs (≥1000ms): ${validLogs.length}`);
|
||||
|
||||
if (validLogs.length === 0) {
|
||||
console.log("ℹ️ No valid logs to send");
|
||||
dispatch(clearProjectLogs({ projectId }));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prepare payload with all valid logs - send directly as stored
|
||||
const payload = {
|
||||
vaId: vaId || "",
|
||||
projectId,
|
||||
logId: activeProjectLogId,
|
||||
logs: validLogs, // Already formatted correctly
|
||||
};
|
||||
|
||||
console.log("📤 Payload to send:", payload);
|
||||
// console.log("📊 Sending logs summary:", {
|
||||
// projectId,
|
||||
// projectName,
|
||||
// logCount: validLogs.length,
|
||||
// totalTimeMs: validLogs.reduce((sum, log) => sum + log.totalTime, 0),
|
||||
// totalTimeSec: validLogs.reduce((sum, log) => sum + Math.round(log.totalTime / 1000), 0),
|
||||
// sampleLog: validLogs[0] ? {
|
||||
// title: validLogs[0].title.substring(0, 40),
|
||||
// from: validLogs[0].from,
|
||||
// to: validLogs[0].to,
|
||||
// durationMs: validLogs[0].totalTime,
|
||||
// } : null,
|
||||
// });
|
||||
|
||||
let res = await window.electron.trackingTabsOpeningClosing(payload);
|
||||
console.log("Tracking response:", res);
|
||||
|
||||
dispatch(clearProjectLogs({ projectId }));
|
||||
|
||||
// Cleanup refs
|
||||
lastActiveRef.current = null;
|
||||
lastTitleRef.current = null;
|
||||
lastAppNameRef.current = null;
|
||||
limitCheckedRef.current = false;
|
||||
|
||||
console.log("✅ Logs sent and cleared successfully");
|
||||
console.log("=".repeat(60));
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
||||
console.error("❌ Error sending logs:", error);
|
||||
console.log("=".repeat(60));
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[
|
||||
activeProjectLogId,
|
||||
projectId,
|
||||
logsByProject,
|
||||
stopIntervals,
|
||||
dispatch,
|
||||
]
|
||||
);
|
||||
|
||||
// Main tracking logic - runs every 5 seconds
|
||||
useEffect(() => {
|
||||
finalizeTrackingRef.current = sendFinalPatch;
|
||||
}, [sendFinalPatch]);
|
||||
|
||||
// ✅ Main tracking interval effect
|
||||
useEffect(() => {
|
||||
// Reset flags when tracking starts
|
||||
if (isTracking) {
|
||||
limitCheckedRef.current = false;
|
||||
isStoppingRef.current = false;
|
||||
console.log("🟢 Tracking started — flags reset");
|
||||
}
|
||||
|
||||
if (!isTracking || !canSend) {
|
||||
console.log("🟡 Tracking inactive — cleaning up");
|
||||
stopInterval();
|
||||
if (!isTracking || !projectId || !activeProjectLogId) {
|
||||
console.log("⏸️ Tracking inactive — stopping interval");
|
||||
stopIntervals();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`🟢 Starting active tracker for: ${projectName}`);
|
||||
console.log(`🚀 Starting tracker for: ${projectName}`);
|
||||
|
||||
const fetchActiveApp = async () => {
|
||||
// ✅ Double-check stopping flag before each fetch
|
||||
const checkActiveWindow = async () => {
|
||||
if (isStoppingRef.current) {
|
||||
console.log("🚫 Stopping in progress — skipping fetch");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const details = (await window.electron.getActiveAppDetails()) as WindowInfo;
|
||||
if (!details || !details.id) return;
|
||||
|
||||
const title = details.title;
|
||||
const appName = details.owner.name;
|
||||
const prevTitle = lastTitleRef.current;
|
||||
const hasChanged = !prevTitle || prevTitle !== title;
|
||||
if (!details || !details.id || !details.owner?.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasChanged) {
|
||||
// PATCH previous window before creating new log
|
||||
if (lastLogIdRef.current && prevTitle) {
|
||||
console.log("🔄 Updating previous window:", prevTitle);
|
||||
await sendActivityLog(activeProjectLogId!, projectId!, prevTitle, appName, false);
|
||||
}
|
||||
const currentTitle = details.title;
|
||||
const currentAppName = details.owner.name;
|
||||
|
||||
// POST new window log
|
||||
console.log("🆕 New window detected:", title);
|
||||
await sendActivityLog(activeProjectLogId!, projectId!, title, appName, true);
|
||||
checkCountRef.current += 1;
|
||||
|
||||
// Update refs and Redux
|
||||
lastActiveRef.current = details;
|
||||
lastTitleRef.current = title;
|
||||
dispatch(setActiveWindow({ window: details, projectId, projectName }));
|
||||
} else {
|
||||
console.log("⏸️ Same window, skipping");
|
||||
// Update Redux with current window activity
|
||||
dispatch(
|
||||
updateWindowLog({
|
||||
projectId,
|
||||
title: currentTitle,
|
||||
appName: currentAppName,
|
||||
url: null,
|
||||
})
|
||||
);
|
||||
|
||||
// Update refs
|
||||
lastActiveRef.current = details;
|
||||
lastTitleRef.current = currentTitle;
|
||||
lastAppNameRef.current = currentAppName;
|
||||
|
||||
// Debug every 10 checks (every 50 seconds)
|
||||
if (checkCountRef.current % 10 === 0) {
|
||||
debugCurrentLogs();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ Failed to fetch active app:", error);
|
||||
console.error("❌ Failed to check active window:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Initial fetch
|
||||
fetchActiveApp();
|
||||
// Initial check
|
||||
checkActiveWindow();
|
||||
|
||||
// Set up interval (every 5 seconds)
|
||||
intervalIdRef.current = setInterval(fetchActiveApp, 5000);
|
||||
intervalIdRef.current = setInterval(checkActiveWindow, 5000);
|
||||
|
||||
// Cleanup on unmount
|
||||
// Cleanup
|
||||
return () => {
|
||||
console.log("🧹 Cleaning up tracker interval");
|
||||
stopInterval();
|
||||
console.log("🧹 Cleaning up tracker");
|
||||
stopIntervals();
|
||||
};
|
||||
}, [isTracking, activeProjectLogId, projectId, projectName, dispatch, stopInterval]);
|
||||
}, [
|
||||
isTracking,
|
||||
activeProjectLogId,
|
||||
projectId,
|
||||
projectName,
|
||||
stopIntervals,
|
||||
dispatch,
|
||||
debugCurrentLogs,
|
||||
]);
|
||||
|
||||
return {
|
||||
finalizeTracking: finalizeTrackingRef.current,
|
||||
stopInterval,
|
||||
finalizeAndSendLogs,
|
||||
stopIntervals,
|
||||
setIsStopping: (value: boolean) => {
|
||||
isStoppingRef.current = value;
|
||||
console.log(`🔒 Stopping flag manually set to: ${value}`);
|
||||
}
|
||||
},
|
||||
debugCurrentLogs,
|
||||
};
|
||||
};
|
||||
|
|
@ -39,6 +39,7 @@ import { canStartTracking } from "../utils/projectLimits";
|
|||
import { useTheme } from "../helper/ThemeToggle";
|
||||
import LogoutModal from "../modal/LogoutModal";
|
||||
import { parseTimeStringToSeconds } from "../utils/timeParser";
|
||||
import { clearAllLogs, clearProjectLogs } from "../store/logsSlice";
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
|
|
@ -100,6 +101,8 @@ const DashboardLayout = () => {
|
|||
const { projectsData, projectSummary, selectedProject, projectTrackingMap } =
|
||||
useSelector((state: any) => state.projects);
|
||||
|
||||
const { logsByProject } = useSelector((state: RootState) => state.logs);
|
||||
|
||||
const tracking = selectedProject
|
||||
? projectTrackingMap[selectedProject.projectId]
|
||||
: null;
|
||||
|
|
@ -116,7 +119,7 @@ const DashboardLayout = () => {
|
|||
{ label: "Schedule", path: "/dashboard/schedule", icon: <ClipboardClock size={18} /> },
|
||||
{ label: "Pays Slips", path: "/dashboard/pays-slips", icon: <CircleDollarSign size={18} /> },
|
||||
{ label: "Settings", path: "/dashboard/settings", icon: <SettingsIcon size={18} /> },
|
||||
{ label: "VPN", path: "/dashboard/vpn", icon: <GlobeLock size={18} /> },
|
||||
// { label: "VPN", path: "/dashboard/vpn", icon: <GlobeLock size={18} /> },
|
||||
{ label: "Help & Support", path: "/dashboard/help-support", icon: <HelpCircleIcon size={18} /> },
|
||||
];
|
||||
|
||||
|
|
@ -167,11 +170,13 @@ const DashboardLayout = () => {
|
|||
`Timer will continue running. Please stop manually when ready.`
|
||||
);
|
||||
|
||||
|
||||
|
||||
console.log("✅ Alert shown, timer continues running");
|
||||
}, [selectedProject]);
|
||||
|
||||
// Initialize useActiveAppTracker and store finalizeTracking in ref
|
||||
const { finalizeTracking, setIsStopping: setIsStoppingInTracker } = useActiveAppTracker(
|
||||
const { finalizeAndSendLogs, setIsStopping: setIsStoppingInTracker } = useActiveAppTracker(
|
||||
isTracking,
|
||||
activeProjectLogId,
|
||||
selectedProject?.projectId ?? null,
|
||||
|
|
@ -182,95 +187,81 @@ const DashboardLayout = () => {
|
|||
// Define handleStopTracking FIRST
|
||||
const handleStopTracking = useCallback(async () => {
|
||||
if (loadingStop || loadingStart) {
|
||||
console.log("⏸️ Already processing, skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedProject) {
|
||||
console.warn("⚠️ No selected project to stop");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("=".repeat(70));
|
||||
console.log("🛑 STOP TRACKING INITIATED");
|
||||
console.log("=".repeat(70));
|
||||
console.log(`📦 Project: ${selectedProject.projectName}`);
|
||||
console.log(`⏱️ Elapsed Time: ${elapsedTime}s`);
|
||||
|
||||
// Reset alert flag when stopping
|
||||
limitAlertShownRef.current = false;
|
||||
|
||||
|
||||
const currentStopTime = new Date().toISOString();
|
||||
const projectId = selectedProject.projectId;
|
||||
const vaId = projectSummary.vaId || "";
|
||||
|
||||
// ✅ CRITICAL: Set stopping flag IMMEDIATELY to block all new tab tracking
|
||||
console.log("🔒 Setting stopping flag...");
|
||||
// STEP 1: Set stopping flag in tracker
|
||||
if (setIsStoppingInTracker) {
|
||||
setIsStoppingInTracker(true);
|
||||
}
|
||||
|
||||
// Set loading state
|
||||
setIsStopping(true);
|
||||
setLoadingStop(true);
|
||||
|
||||
// STEP 2: Disconnect socket
|
||||
handleSocketDisconnect();
|
||||
|
||||
// STEP 1: Clear local timer to prevent further client-side updates
|
||||
console.log("⏹️ STEP 1: Clearing local timer...");
|
||||
// STEP 3: Clear local timer
|
||||
if (timerRef.current) {
|
||||
clearInterval(timerRef.current);
|
||||
timerRef.current = null;
|
||||
console.log("✅ Timer cleared");
|
||||
}
|
||||
|
||||
// STEP 2: Send final PATCH with last active window data
|
||||
console.log("📤 STEP 2: Sending final activity tracking...");
|
||||
if (stopTrackingRef.current) {
|
||||
// STEP 4: Finalize and send logs
|
||||
console.log("📤 STEP 4: Finalizing and sending logs...");
|
||||
let logsSent = false;
|
||||
if (finalizeAndSendLogs) {
|
||||
try {
|
||||
await stopTrackingRef.current(); // This calls sendFinalPatch() in the hook
|
||||
console.log("✅ Final activity tracking completed");
|
||||
logsSent = await finalizeAndSendLogs(vaId); // Pass vaId here
|
||||
console.log(logsSent ? "✅ Logs sent successfully" : "⚠️ Logs not sent");
|
||||
} catch (error) {
|
||||
console.error("❌ Error during final tracking:", error);
|
||||
console.error("❌ Error sending logs:", error);
|
||||
}
|
||||
} else {
|
||||
console.log("⚠️ No tracking ref available");
|
||||
}
|
||||
|
||||
// STEP 3: Send LOGOUT to backend
|
||||
console.log("🚪 STEP 3: Sending LOGOUT request...");
|
||||
// STEP 5: Send LOGOUT to backend
|
||||
console.log("🚪 STEP 5: Sending LOGOUT request...");
|
||||
try {
|
||||
const payload = {
|
||||
vaId: projectSummary.vaId,
|
||||
const logoutPayload = {
|
||||
vaId: vaId,
|
||||
logId: activeProjectLogId,
|
||||
projectId: projectId,
|
||||
type: "LOGOUT",
|
||||
time: currentStopTime
|
||||
};
|
||||
|
||||
console.log("📡 Logout payload:", payload);
|
||||
const res = await window.electron.stopProjectTracking(payload);
|
||||
console.log("✅ LOGOUT successful:", res);
|
||||
await window.electron.stopProjectTracking(logoutPayload);
|
||||
console.log("✅ LOGOUT successful");
|
||||
} catch (error) {
|
||||
console.error("❌ LOGOUT failed:", error);
|
||||
}
|
||||
|
||||
// STEP 4: Update Redux tracking state
|
||||
console.log("🔄 STEP 4: Updating Redux state...");
|
||||
dispatch(
|
||||
stopTrackingReducer({
|
||||
projectId,
|
||||
})
|
||||
);
|
||||
// STEP 6: Update Redux tracking state
|
||||
dispatch(stopTrackingReducer({ projectId }));
|
||||
|
||||
// STEP 6: Update UI (restore window)
|
||||
console.log("🪟 STEP 6: Restoring window...");
|
||||
await window.electron.toggleCloseBtn(true);
|
||||
await window.electron.resizeWindow(500, 600);
|
||||
toggleWindowSize.current = true;
|
||||
console.log("✅ Window restored");
|
||||
|
||||
// Allow React to process the state update
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
|
||||
// STEP 5: Refresh project list to get updated work hours from server
|
||||
console.log("📊 STEP 5: Refreshing project list...");
|
||||
// STEP 7: Refresh project list
|
||||
try {
|
||||
const projectListRes = await window.electron.getProjectList();
|
||||
console.log("✅ Project list fetched:", projectListRes);
|
||||
|
|
@ -349,20 +340,27 @@ const DashboardLayout = () => {
|
|||
console.log("🎉 TRACKING STOPPED SUCCESSFULLY");
|
||||
console.log("=".repeat(70));
|
||||
}
|
||||
|
||||
// STEP 8: Reset logs for this project
|
||||
if (logsSent && projectId) {
|
||||
dispatch(clearProjectLogs({ projectId }));
|
||||
}
|
||||
|
||||
console.log("🎉 TRACKING STOPPED SUCCESSFULLY");
|
||||
}, [
|
||||
selectedProject,
|
||||
projectSummary,
|
||||
elapsedTime,
|
||||
dispatch,
|
||||
loadingStop,
|
||||
loadingStart,
|
||||
setIsStoppingInTracker
|
||||
setIsStoppingInTracker,
|
||||
finalizeAndSendLogs,
|
||||
]);
|
||||
|
||||
// Update the ref whenever finalizeTracking changes
|
||||
useEffect(() => {
|
||||
stopTrackingRef.current = finalizeTracking;
|
||||
}, [finalizeTracking]);
|
||||
// useEffect(() => {
|
||||
// stopTrackingRef.current = finalizeTracking;
|
||||
// }, [finalizeTracking]);
|
||||
|
||||
const socketRef = useRef<Socket | null>(null);
|
||||
const intervalRef = useRef<number | null>(null);
|
||||
|
|
@ -402,6 +400,7 @@ const DashboardLayout = () => {
|
|||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
dispatch(clearAllLogs());
|
||||
const res = await window.electron.logout();
|
||||
if (res) {
|
||||
dispatch(clearUser());
|
||||
|
|
@ -457,6 +456,11 @@ const DashboardLayout = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
// Reset logs for this project if they exist
|
||||
if (logsByProject[project.projectId]) {
|
||||
dispatch(clearProjectLogs({ projectId: project.projectId }));
|
||||
}
|
||||
|
||||
// CRITICAL FIX: Get the current accumulated time BEFORE starting timer
|
||||
const currentAccumulatedTime = selectedData?.accumulatedTime || parseTimeStringToSeconds(project.totalWorkTime);
|
||||
|
||||
|
|
@ -588,7 +592,7 @@ const DashboardLayout = () => {
|
|||
if (!selectedProject?.projectId) return
|
||||
|
||||
console.log(selectedProject?.projectId, "SelectedProject ID")
|
||||
socketRef.current = io("https://quality-friendly-goldfish.ngrok-free.app", {
|
||||
socketRef.current = io("https://portal-sf.docva.com", {
|
||||
transports: ["websocket"],
|
||||
secure: true,
|
||||
rejectUnauthorized: false,
|
||||
|
|
@ -724,15 +728,26 @@ const DashboardLayout = () => {
|
|||
// return `${hrs}h ${mins}m `;
|
||||
};
|
||||
|
||||
|
||||
const [checkIp, setCheckIp] = useState(false)
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
fetch('https://api.ipify.org/?format=json').then((res) => console.log("IPPPPPPPPPPPPPP", res))
|
||||
}, 5000)
|
||||
}, [checkIp])
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col h-screen overflow-hidden">
|
||||
{/* <button
|
||||
onClick={() => {
|
||||
setCheckIp((prev) => !prev)
|
||||
window.electron.connectVPN().then((msg) => {
|
||||
console.log(msg);
|
||||
});
|
||||
}}
|
||||
className="text-purple-400"
|
||||
>
|
||||
Connect VPN
|
||||
</button>
|
||||
|
|
@ -741,6 +756,7 @@ const DashboardLayout = () => {
|
|||
onClick={() => {
|
||||
window.electron.disconnectVPN();
|
||||
}}
|
||||
className="text-red-400"
|
||||
>
|
||||
Disconnect VPN
|
||||
</button> */}
|
||||
|
|
@ -913,4 +929,6 @@ const DashboardLayout = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export default DashboardLayout;
|
||||
export default DashboardLayout;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,36 @@ import { Provider } from "react-redux";
|
|||
import { store } from "./store";
|
||||
import { ToastProvider } from './helper/ToastContext';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__allowClose__: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// renderer only
|
||||
window.addEventListener("online", (e) => {
|
||||
console.log("Network online (ignored)");
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
window.addEventListener("offline", (e) => {
|
||||
console.log("Network offline (ignored)");
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
// Extra safety
|
||||
window.__allowClose__ = false;
|
||||
|
||||
window.addEventListener("beforeunload", (e) => {
|
||||
if (!window.__allowClose__) {
|
||||
e.returnValue = false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<Provider store={store}>
|
||||
<ToastProvider>
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ const Dashboard: React.FC = () => {
|
|||
const projectSummary = useSelector((state: any) => state.projects.projectSummary);
|
||||
const projectTrackingMap = useSelector((state: any) => state.projects.projectTrackingMap);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const isLimitReached = selectedProject ? isProjectLimitReached(selectedProject) : false;
|
||||
|
||||
console.log("Line 111", projectsData, selectedProject, projectTrackingMap)
|
||||
|
||||
|
|
@ -129,14 +130,14 @@ const Dashboard: React.FC = () => {
|
|||
};
|
||||
|
||||
const fetchProjects = async () => {
|
||||
setIsRefreshing(true)
|
||||
setIsRefreshing(true);
|
||||
try {
|
||||
console.log("📡 Fetching projects...Dashboard");
|
||||
const res = await window.electron.getProjectList();
|
||||
console.log(" Project list response in dashboard: ", res.data?.[0]);
|
||||
console.log("Project list response in dashboard: ", res.data?.[0]);
|
||||
|
||||
if (res?.success && Array.isArray(res.data)) {
|
||||
// Store data redux store
|
||||
// Store data in redux store
|
||||
dispatch(setProjectsDataSlice(res.data));
|
||||
|
||||
dispatch(
|
||||
|
|
@ -151,35 +152,67 @@ const Dashboard: React.FC = () => {
|
|||
})
|
||||
);
|
||||
|
||||
// Auto-select first project
|
||||
if (res.data.length > 0) {
|
||||
const firstProject = res.data[0];
|
||||
// Check if we already have a selected project that's being tracked
|
||||
const currentlySelected = selectedProject;
|
||||
const currentlyTracking = isTracking;
|
||||
|
||||
// Find if the currently selected project still exists in the fresh data
|
||||
let projectToSelect = null;
|
||||
|
||||
console.log("Line 158" , firstProject)
|
||||
// Select the project
|
||||
dispatch(setSelectedProjectSlice(firstProject));
|
||||
|
||||
const initialElapsed = parseTimeStringToSeconds(firstProject.totalWorkTime);
|
||||
|
||||
// Initialize tracking data for first project
|
||||
dispatch(
|
||||
initializeProjectTracking({
|
||||
projectId: firstProject.projectId,
|
||||
initialTime: initialElapsed,
|
||||
})
|
||||
if (currentlySelected && currentlyTracking) {
|
||||
// We're tracking a project - try to preserve it
|
||||
const existingProject = res.data.find(
|
||||
(p: Project) => p.projectId === currentlySelected.projectId
|
||||
);
|
||||
|
||||
if (existingProject) {
|
||||
projectToSelect = existingProject;
|
||||
}
|
||||
}
|
||||
|
||||
fetchProfile()
|
||||
// If no project is selected OR tracked project no longer exists, auto-select first project
|
||||
if (!projectToSelect && res.data.length > 0) {
|
||||
projectToSelect = res.data[0];
|
||||
}
|
||||
|
||||
if (projectToSelect) {
|
||||
console.log("Line 158", projectToSelect);
|
||||
// Select the project
|
||||
dispatch(setSelectedProjectSlice(projectToSelect));
|
||||
|
||||
// Only initialize tracking if it doesn't exist already
|
||||
const trackingExists = projectTrackingMap[projectToSelect.projectId];
|
||||
if (!trackingExists) {
|
||||
const initialElapsed = parseTimeStringToSeconds(projectToSelect.totalWorkTime);
|
||||
dispatch(
|
||||
initializeProjectTracking({
|
||||
projectId: projectToSelect.projectId,
|
||||
initialTime: initialElapsed,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// Update project data but preserve tracking state
|
||||
dispatch(
|
||||
updateProjectTracking({
|
||||
projectId: projectToSelect.projectId,
|
||||
data: {
|
||||
...trackingExists,
|
||||
// Update elapsed time from server if needed
|
||||
elapsedTime: parseTimeStringToSeconds(projectToSelect.totalWorkTime),
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fetchProfile();
|
||||
} else {
|
||||
console.warn("⚠️ Unexpected response:", res);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("❌ Error fetching projects:", err);
|
||||
} finally {
|
||||
setIsRefreshing(false)
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -246,7 +279,7 @@ const Dashboard: React.FC = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<main className="flex-1 overflow-hidden no-scrollbar max-h-[85vh]">
|
||||
<main className="flex-1 overflow-hidden no-scrollbar ">
|
||||
{/* Header */}
|
||||
<div className="mb-2">
|
||||
<h1 className="text-md font-bold">Dashboard</h1>
|
||||
|
|
@ -272,6 +305,11 @@ const Dashboard: React.FC = () => {
|
|||
<p className="text-lg font-extrabold text-success">
|
||||
{formatTime(displayTime)}
|
||||
</p>
|
||||
|
||||
{/* <p className={`text-lg font-extrabold ${isLimitReached ? 'text-error' : 'text-success'}`}>
|
||||
{formatTime(displayTime)}
|
||||
{isLimitReached && " ⚠️"}
|
||||
</p> */}
|
||||
<div>
|
||||
<span className="text-base-content/70">Project:</span>{" "}
|
||||
<span className="font-semibold">
|
||||
|
|
@ -330,15 +368,16 @@ const Dashboard: React.FC = () => {
|
|||
{/* 🔹 Sticky Header */}
|
||||
<div className="sticky top-0 z-20 bg-base-100 border-b border-base-content/10 p-3 flex justify-between items-center">
|
||||
<p className="text-md font-bold">Projects</p>
|
||||
{!selectedData?.isTracking &&
|
||||
<button
|
||||
onClick={handleRefresh}
|
||||
disabled={isRefreshing}
|
||||
disabled={isRefreshing }
|
||||
className="btn btn-xs btn-ghost gap-1 text-warning"
|
||||
title="Refresh projects"
|
||||
>
|
||||
<RefreshCw className={`w-3 h-3 text-warning ${isRefreshing ? 'animate-spin' : ''}`} />
|
||||
Refresh
|
||||
</button>
|
||||
</button>}
|
||||
</div>
|
||||
|
||||
{/* 🔹 Table or Empty State or Skeleton */}
|
||||
|
|
@ -362,7 +401,7 @@ const Dashboard: React.FC = () => {
|
|||
minHeight: "auto",
|
||||
}}
|
||||
>
|
||||
<table className="table table-zebra w-full text-[12px]">
|
||||
<table className="table table-zebra w-full text-[12px] ">
|
||||
<thead className="sticky top-0 bg-base-200 z-10">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ const HelpSupport: React.FC = () => {
|
|||
<Mail size={18} className="text-primary" />
|
||||
<div>
|
||||
<p className="text-xs text-base-content/70">Email</p>
|
||||
<p className="font-medium text-sm">support@vatechcorp.com</p>
|
||||
<p className="font-medium text-sm">support@docva.com</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ const HelpSupport: React.FC = () => {
|
|||
<Phone size={18} className="text-primary" />
|
||||
<div>
|
||||
<p className="text-xs text-base-content/70">Phone</p>
|
||||
<p className="font-medium text-sm">+91 98765 43210</p>
|
||||
<p className="font-medium text-sm">+XXXX XXXXX</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -464,7 +464,7 @@ const AdjustmentTab: React.FC<AdjustmentTabProps> = ({ projects }) => {
|
|||
console.log("Timesheet Created Successfully");
|
||||
resetForm();
|
||||
// Show success toast instead of alert
|
||||
showToast("PTO Request submitted successfully!", "success");
|
||||
showToast("Timesheet Request submitted successfully!", "success");
|
||||
|
||||
// Navigate after a small delay
|
||||
setTimeout(() => {
|
||||
|
|
@ -680,4 +680,5 @@ const AdjustmentTab: React.FC<AdjustmentTabProps> = ({ projects }) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default AdjustmentTab;
|
||||
export default AdjustmentTab;
|
||||
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ const PaysSlips = () => {
|
|||
<th>Date</th>
|
||||
<th>Total Hours</th>
|
||||
<th>Total Pay</th>
|
||||
<th>Action</th>
|
||||
{/* <th>Action</th> */}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
|
|
@ -111,13 +111,13 @@ const PaysSlips = () => {
|
|||
|
||||
<td>${row.totalPayAmount.toFixed(2)}</td>
|
||||
|
||||
<td>
|
||||
{/* <td>
|
||||
<button
|
||||
className="btn btn-xs btn-primary"
|
||||
>
|
||||
View
|
||||
</button>
|
||||
</td>
|
||||
</td> */}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// src/ui/pages/Settings/ChangePasswordModal.tsx
|
||||
import React, { useState } from "react";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
import { useToast } from "../../helper/ToastContext";
|
||||
|
||||
interface ChangePasswordModalProps {
|
||||
isOpen: boolean;
|
||||
|
|
@ -19,12 +20,13 @@ const ChangePasswordModal: React.FC<ChangePasswordModalProps> = ({
|
|||
const [showOld, setShowOld] = useState(false);
|
||||
const [showNew, setShowNew] = useState(false);
|
||||
const [showConfirm, setShowConfirm] = useState(false);
|
||||
const { showToast } = useToast();
|
||||
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (newPassword !== confirmPassword) {
|
||||
alert("New passwords do not match!");
|
||||
showToast("New passwords do not match!" , "error");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -40,17 +42,17 @@ const ChangePasswordModal: React.FC<ChangePasswordModalProps> = ({
|
|||
const response = await window.electron.changePassword(payload);
|
||||
|
||||
if (response.success) {
|
||||
alert("Password changed successfully!");
|
||||
showToast("Password changed successfully!" , "success");
|
||||
setOldPassword("")
|
||||
setNewPassword("")
|
||||
setConfirmPassword("")
|
||||
onClose();
|
||||
} else {
|
||||
alert(response.message || "Failed to change password.");
|
||||
showToast((response.message || "Failed to change password.") , "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Password change error:", error);
|
||||
alert("Error changing password.");
|
||||
showToast("Error changing password." , "error");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { DateTime } from "luxon";
|
||||
import React from "react";
|
||||
|
||||
interface TabActivity {
|
||||
|
|
@ -27,6 +28,21 @@ interface Props {
|
|||
const ProjectLogsModal: React.FC<Props> = ({ isOpen, onClose, projectName, logs, loading }) => {
|
||||
if (!isOpen) return null;
|
||||
|
||||
|
||||
const getTimeDifference = (from: string, to: string) => {
|
||||
const start = DateTime.fromISO(from);
|
||||
const end = DateTime.fromISO(to);
|
||||
|
||||
const diff = end.diff(start, ["hours", "minutes", "seconds"]);
|
||||
|
||||
return {
|
||||
hours: diff.hours,
|
||||
minutes: diff.minutes,
|
||||
seconds: Math.floor(diff.seconds),
|
||||
formatted: `${diff.hours}h ${diff.minutes}m ${Math.floor(diff.seconds)}s`
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<dialog className="modal modal-open">
|
||||
<div className="modal-box max-h-[97vh] max-w-4xl bg-base-100 flex flex-col">
|
||||
|
|
@ -46,7 +62,11 @@ const ProjectLogsModal: React.FC<Props> = ({ isOpen, onClose, projectName, logs,
|
|||
<div key={idx} className="border border-base-300 overflow-hidden">
|
||||
<div className="bg-base-200 p-2 font-semibold flex justify-between items-center">
|
||||
<span className="text-[12px]">
|
||||
🕒 Session {idx + 1} — {session.totalSessionTime}
|
||||
🕒 Session {idx + 1} — {getTimeDifference(
|
||||
new Date(session.from).toISOString(),
|
||||
new Date(session.to).toISOString()
|
||||
).formatted}
|
||||
|
||||
</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
{new Date(session.from).toLocaleTimeString()} -{" "}
|
||||
|
|
@ -69,10 +89,10 @@ const ProjectLogsModal: React.FC<Props> = ({ isOpen, onClose, projectName, logs,
|
|||
<tr key={tabIdx} className="text-[10px]">
|
||||
<td>{tabIdx + 1}</td>
|
||||
<td className="truncate max-w-[150px]" title={tab.title}>
|
||||
{tab.title} <br/>
|
||||
<span className="text-slate-500">{tab.appName}</span>
|
||||
{tab.title} <br />
|
||||
<span className="text-slate-500">{tab.appName}</span>
|
||||
</td>
|
||||
<td><span className="text-green-500">{new Date(tab.from).toLocaleTimeString()}</span> <br/><span className="text-red-600">{new Date(tab.to).toLocaleTimeString()}</span></td>
|
||||
<td><span className="text-green-500">{new Date(tab.from).toLocaleTimeString()}</span> <br /><span className="text-red-600">{new Date(tab.to).toLocaleTimeString()}</span></td>
|
||||
<td>{tab.durationFormatted}</td>
|
||||
</tr>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -52,10 +52,11 @@ const Timesheets: React.FC = () => {
|
|||
|
||||
const { windowLogs } = useSelector((state: RootState) => state.window);
|
||||
|
||||
|
||||
console.log(" Timesheet in component:", windowLogs);
|
||||
const [projectsData, setProjectsData] = useState<Project[]>([]);
|
||||
const [logs, setLogs] = useState([]);
|
||||
const [selectedProject, setSelectedProject] = useState<string | null>(null);
|
||||
// const [selectedProject, setSelectedProject] = useState<string | null>(null);
|
||||
const [selectedProjectName, setSelectedProjectName] = useState<string>("");
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
|
@ -63,9 +64,13 @@ const Timesheets: React.FC = () => {
|
|||
const [lastRefreshed, setLastRefreshed] = useState<Date | null>(null);
|
||||
const [initialLoading, setInitialLoading] = useState(true);
|
||||
const [tick, setTick] = useState(0);
|
||||
const { selectedProject } =
|
||||
useSelector((state: any) => state.projects);
|
||||
|
||||
const activeProjects = projectsData.filter(p => p.status === "ACTIVE");
|
||||
|
||||
console.log("selectedProject", selectedProject.projectId)
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
|
|
@ -84,7 +89,6 @@ const Timesheets: React.FC = () => {
|
|||
};
|
||||
|
||||
|
||||
|
||||
const fetchProjects = async () => {
|
||||
setInitialLoading(true);
|
||||
try {
|
||||
|
|
@ -120,7 +124,7 @@ const Timesheets: React.FC = () => {
|
|||
try {
|
||||
setModalOpen(true);
|
||||
setLoading(true);
|
||||
setSelectedProject(projectID);
|
||||
// setSelectedProject(projectID);
|
||||
setSelectedProjectName(projectName);
|
||||
|
||||
console.log("projectID", projectID)
|
||||
|
|
@ -154,10 +158,6 @@ const Timesheets: React.FC = () => {
|
|||
return parts.slice(0, 2).join(" ");
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Skeleton loader rows
|
||||
const renderSkeletonRows = () => {
|
||||
return Array.from({ length: 5 }).map((_, index) => (
|
||||
|
|
@ -225,13 +225,13 @@ const Timesheets: React.FC = () => {
|
|||
<td>{formatWorkTime(entry.totalWorkTime)}</td>
|
||||
<td>
|
||||
<button
|
||||
disabled={formatWorkTime(entry.totalWorkTime) === "0h 0m"}
|
||||
disabled={(formatWorkTime(entry.totalWorkTime) === "0h 0m" || (isTracking && entry.projectId === selectedProject.projectId))}
|
||||
onClick={() =>
|
||||
handleViewLogs(entry.projectId, entry.projectName)
|
||||
}
|
||||
className="btn btn-ghost btn-xs"
|
||||
>
|
||||
{formatWorkTime(entry.totalWorkTime) === "0h 0m" ? (
|
||||
{(formatWorkTime(entry.totalWorkTime) === "0h 0m" || (isTracking && entry.projectId === selectedProject.projectId)) ? (
|
||||
<EyeOff size={16} />
|
||||
) : (
|
||||
<Eye size={16} />
|
||||
|
|
|
|||
|
|
@ -1,54 +1,201 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Shield, ShieldAlert, RefreshCw, Wifi, Power } from "lucide-react";
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const VpnGuard: React.FC<Props> = ({ children }) => {
|
||||
const [checking, setChecking] = useState(true);
|
||||
const [vpnConnected, setVpnConnected] = useState(true);
|
||||
|
||||
// const checkVPN = async () => {
|
||||
// try {
|
||||
// const res = await window.electron.checkVpnConnection();
|
||||
// setVpnConnected(res?.success);
|
||||
// } catch {
|
||||
// setVpnConnected(false);
|
||||
// } finally {
|
||||
// setChecking(false);
|
||||
// }
|
||||
// };
|
||||
const VPN_API = "http://10.0.0.1:3000";
|
||||
const DEFAULT_API = "https://portal-sf.docva.com"
|
||||
|
||||
const [checking, setChecking] = useState(true);
|
||||
const [vpnConnected, setVpnConnected] = useState(true);
|
||||
const [isRetrying, setIsRetrying] = useState(false);
|
||||
|
||||
const checkVPN = async () => {
|
||||
try {
|
||||
setChecking(true);
|
||||
|
||||
const res = await window.electron?.checkRegion();
|
||||
console.log("VpnGuard", res);
|
||||
if (res.status === 200) {
|
||||
setVpnConnected(true);
|
||||
} else {
|
||||
setVpnConnected(false);
|
||||
return;
|
||||
}
|
||||
// setVpnConnected(res?.success === true);
|
||||
} catch (error) {
|
||||
console.error("VPN check failed:", error);
|
||||
setVpnConnected(false);
|
||||
} finally {
|
||||
setChecking(false);
|
||||
setIsRetrying(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
checkVPN();
|
||||
}, []);
|
||||
|
||||
const connectVPN = async () => {
|
||||
setChecking(true);
|
||||
try {
|
||||
const res = await window.electron.connectVPN();
|
||||
console.log("connectVPN result → ", res);
|
||||
|
||||
// Only proceed if VPN actually connected
|
||||
if (res === "Windows VPN connected silently") {
|
||||
|
||||
const vpnRes = await window.electron.updateBaseURL(VPN_API);
|
||||
console.log("vpnRes", vpnRes);
|
||||
|
||||
if (vpnRes) {
|
||||
// Run VPN check again AFTER URL changes
|
||||
await checkVPN();
|
||||
} else {
|
||||
console.warn("Base URL updated but health check failed");
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
const vpnRes = await window.electron.updateBaseURL(VPN_API);
|
||||
console.log("vpnRes", vpnRes);
|
||||
|
||||
if (vpnRes) {
|
||||
// Run VPN check again AFTER URL changes
|
||||
await checkVPN();
|
||||
} else {
|
||||
console.warn("Base URL updated but health check failed");
|
||||
}
|
||||
} finally {
|
||||
setChecking(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const disconnectVPN = async () => {
|
||||
setChecking(true);
|
||||
try {
|
||||
const res = await window.electron.disconnectVPN();;
|
||||
console.log("disconnectVPN →", res);
|
||||
|
||||
// Restore original base URL
|
||||
const baseRes = await window.electron.updateBaseURL(DEFAULT_API);
|
||||
console.log("baseRes disconnect →", baseRes);
|
||||
|
||||
setVpnConnected(true);
|
||||
|
||||
// await checkVPN();
|
||||
} catch (error) {
|
||||
console.log("Disconnect Error:", error);
|
||||
} finally {
|
||||
setChecking(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Checking state
|
||||
if (checking && !isRetrying) {
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col justify-center items-center bg-base-200">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="w-24 h-24 border-4 border-primary opacity-20 rounded-full animate-ping"></div>
|
||||
</div>
|
||||
<div className="relative bg-base-100 rounded-full p-6 shadow-xl">
|
||||
<Shield className="w-12 h-12 text-primary animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 text-center">
|
||||
<h2 className="text-2xl font-semibold mb-2">
|
||||
Verifying Connection
|
||||
</h2>
|
||||
<p className="text-base-content opacity-70">Checking VPN status...</p>
|
||||
<div className="flex justify-center mt-4 gap-1">
|
||||
<span className="loading loading-dots loading-md text-primary"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// VPN not connected state
|
||||
if (!vpnConnected) {
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col justify-center items-center bg-base-200 p-4">
|
||||
<div className="max-w-md w-full">
|
||||
<div className="card bg-base-100 shadow-2xl">
|
||||
<div className="card-body items-center text-center">
|
||||
|
||||
{/* Icon */}
|
||||
<div className="relative mb-4">
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="w-20 h-20 border-4 border-error opacity-20 rounded-full animate-pulse"></div>
|
||||
</div>
|
||||
<div className="relative bg-error bg-opacity-10 rounded-full p-4">
|
||||
<ShieldAlert className="w-12 h-12" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 className="card-title text-3xl mb-3">VPN Required</h2>
|
||||
<p className="mb-6 opacity-80">
|
||||
Please connect to your company VPN to access this application.
|
||||
</p>
|
||||
|
||||
{/* Connect VPN */}
|
||||
<button
|
||||
className="btn btn-primary btn-wide gap-2"
|
||||
onClick={connectVPN}
|
||||
disabled={isRetrying || vpnConnected} // Disable if retrying OR already connected
|
||||
>
|
||||
{isRetrying ? (
|
||||
<>
|
||||
<span className="loading loading-spinner loading-sm"></span>
|
||||
Checking Connection...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<RefreshCw className="w-5 h-5" />
|
||||
Connect VPN
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Disconnect VPN (Disabled when NOT connected) */}
|
||||
{/* <button
|
||||
className="btn btn-error btn-wide gap-2 mt-3"
|
||||
onClick={disconnectVPN}
|
||||
// disabled={!vpnConnected || checking} // Disable if not connected
|
||||
>
|
||||
<Power className="w-4 h-4" />
|
||||
Disconnect VPN
|
||||
</button> */}
|
||||
|
||||
<div className="divider"></div>
|
||||
<p className="text-xs opacity-60">
|
||||
Need help? Contact IT support at{" "}
|
||||
<a href="mailto:support@docva.com" className="link link-primary">
|
||||
support@docva.com
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// checkVPN();
|
||||
}, []);
|
||||
|
||||
if (checking) {
|
||||
return (
|
||||
<div className="min-h-screen flex justify-center items-center">
|
||||
<span className="loading loading-spinner loading-lg"></span>
|
||||
<span className="ml-3">Checking VPN...</span>
|
||||
</div>
|
||||
<>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (!vpnConnected) {
|
||||
return (
|
||||
<div className="min-h-screen flex justify-center items-center text-center">
|
||||
<div className="card shadow-xl bg-base-100 p-6">
|
||||
<h2 className="text-xl font-bold text-red-600">VPN Required</h2>
|
||||
<p className="mt-2">Please connect to your company VPN to continue.</p>
|
||||
|
||||
<button className="btn btn-primary mt-4"
|
||||
onClick={() => window.location.reload()}>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default VpnGuard;
|
||||
export default VpnGuard;
|
||||
|
|
@ -4,6 +4,7 @@ import windowReducer from "./windowSlice";
|
|||
import projectsReducer from "./projectSlice";
|
||||
import userReducer from "./userSlice";
|
||||
import profileReducer from "./profileSlice";
|
||||
import logsReducer from './logsSlice';
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
|
|
@ -11,6 +12,7 @@ export const store = configureStore({
|
|||
projects: projectsReducer,
|
||||
user: userReducer,
|
||||
profile: profileReducer,
|
||||
logs: logsReducer,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,240 @@
|
|||
// store/logsSlice.ts
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
export interface LogEntry {
|
||||
id: string; // Unique ID for each log entry
|
||||
title: string;
|
||||
appName: string;
|
||||
url: string | null;
|
||||
from: string; // UTC ISO string with .000 milliseconds
|
||||
to: string; // UTC ISO string with .000 milliseconds
|
||||
totalTime: number; // milliseconds
|
||||
}
|
||||
|
||||
export interface ProjectLogs {
|
||||
projectId: string;
|
||||
logId: string | null;
|
||||
logs: LogEntry[]; // All collected logs during tracking
|
||||
trackingStartTime: string | null;
|
||||
lastWindowTitle: string | null; // Track last window title
|
||||
lastWindowApp: string | null; // Track last window app
|
||||
}
|
||||
|
||||
interface LogsState {
|
||||
logsByProject: Record<string, ProjectLogs>;
|
||||
}
|
||||
|
||||
const initialState: LogsState = {
|
||||
logsByProject: {},
|
||||
};
|
||||
|
||||
// Helper function to get timestamp with .000 milliseconds
|
||||
const getTimestampWithZeroMs = () => {
|
||||
const now = new Date();
|
||||
// Format: YYYY-MM-DDTHH:mm:ss.000Z
|
||||
const isoString = now.toISOString();
|
||||
// Replace milliseconds with .000
|
||||
return isoString.replace(/\.\d{3}Z$/, '.000Z');
|
||||
};
|
||||
|
||||
// Helper function to format any date string to have .000 milliseconds
|
||||
const formatToZeroMs = (dateString: string): string => {
|
||||
if (dateString.endsWith('.000Z')) {
|
||||
return dateString; // Already formatted
|
||||
}
|
||||
const date = new Date(dateString);
|
||||
const isoString = date.toISOString();
|
||||
return isoString.replace(/\.\d{3}Z$/, '.000Z');
|
||||
};
|
||||
|
||||
const logsSlice = createSlice({
|
||||
name: "logs",
|
||||
initialState,
|
||||
reducers: {
|
||||
// Initialize logs for a project
|
||||
initializeProjectLogs: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
projectId: string;
|
||||
logId: string;
|
||||
}>
|
||||
) => {
|
||||
const { projectId, logId } = action.payload;
|
||||
const nowUTC = getTimestampWithZeroMs();
|
||||
|
||||
// Always create fresh logs for new tracking session
|
||||
state.logsByProject[projectId] = {
|
||||
projectId,
|
||||
logId,
|
||||
logs: [], // Start with empty logs
|
||||
trackingStartTime: nowUTC,
|
||||
lastWindowTitle: null,
|
||||
lastWindowApp: null,
|
||||
};
|
||||
|
||||
console.log(`📝 Initialized logs for project ${projectId} with logId: ${logId}`);
|
||||
},
|
||||
|
||||
// Add or update window log
|
||||
updateWindowLog: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
projectId: string;
|
||||
title: string;
|
||||
appName: string;
|
||||
url?: string | null;
|
||||
}>
|
||||
) => {
|
||||
const { projectId, title, appName, url = null } = action.payload;
|
||||
const projectLogs = state.logsByProject[projectId];
|
||||
|
||||
if (!projectLogs) {
|
||||
console.warn(`Cannot update log: Project ${projectId} not initialized`);
|
||||
return;
|
||||
}
|
||||
|
||||
const nowUTC = getTimestampWithZeroMs();
|
||||
const isSameWindow =
|
||||
projectLogs.lastWindowTitle === title &&
|
||||
projectLogs.lastWindowApp === appName;
|
||||
|
||||
// If same window as before, update the last log's end time
|
||||
if (isSameWindow && projectLogs.logs.length > 0) {
|
||||
const lastLog = projectLogs.logs[projectLogs.logs.length - 1];
|
||||
// Format the existing from time to ensure it has .000 milliseconds
|
||||
lastLog.from = formatToZeroMs(lastLog.from);
|
||||
lastLog.to = nowUTC;
|
||||
|
||||
// Calculate total time in milliseconds
|
||||
const startTime = new Date(lastLog.from).getTime();
|
||||
const endTime = new Date(lastLog.to).getTime();
|
||||
lastLog.totalTime = endTime - startTime;
|
||||
|
||||
// console.log(`⏱️ Updated existing log: ${title} (${lastLog.totalTime}ms)`);
|
||||
}
|
||||
// If new window or first window
|
||||
else {
|
||||
// If there was a previous window, ensure it's properly ended
|
||||
if (projectLogs.lastWindowTitle && projectLogs.logs.length > 0) {
|
||||
const lastLog = projectLogs.logs[projectLogs.logs.length - 1];
|
||||
// Format the existing from time to ensure it has .000 milliseconds
|
||||
lastLog.from = formatToZeroMs(lastLog.from);
|
||||
lastLog.to = nowUTC;
|
||||
|
||||
// Calculate total time in milliseconds
|
||||
const startTime = new Date(lastLog.from).getTime();
|
||||
const endTime = new Date(lastLog.to).getTime();
|
||||
lastLog.totalTime = endTime - startTime;
|
||||
|
||||
console.log(
|
||||
`✅ Finalized previous window: ${lastLog.title} (${lastLog.totalTime}ms)`
|
||||
);
|
||||
}
|
||||
|
||||
// Create new log entry with .000 milliseconds
|
||||
const newLog: LogEntry = {
|
||||
id: `log_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
title,
|
||||
appName,
|
||||
url,
|
||||
from: nowUTC,
|
||||
to: nowUTC, // Will be updated continuously
|
||||
totalTime: 0, // Starts at 0 (in milliseconds)
|
||||
};
|
||||
|
||||
projectLogs.logs.push(newLog);
|
||||
console.log(`📝 Started new window: ${title} on ${appName}`);
|
||||
}
|
||||
|
||||
// Update last window tracking
|
||||
projectLogs.lastWindowTitle = title;
|
||||
projectLogs.lastWindowApp = appName;
|
||||
},
|
||||
|
||||
// Clear project logs
|
||||
clearProjectLogs: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
projectId: string;
|
||||
}>
|
||||
) => {
|
||||
const { projectId } = action.payload;
|
||||
delete state.logsByProject[projectId];
|
||||
console.log(`🧹 Cleared logs for project ${projectId}`);
|
||||
},
|
||||
|
||||
// Clear all logs
|
||||
clearAllLogs: (state) => {
|
||||
state.logsByProject = {};
|
||||
console.log("🧹 Cleared all logs");
|
||||
},
|
||||
|
||||
// Debug: Get logs state
|
||||
debugLogsState: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
projectId: string;
|
||||
}>
|
||||
) => {
|
||||
const { projectId } = action.payload;
|
||||
const projectLogs = state.logsByProject[projectId];
|
||||
|
||||
if (!projectLogs) {
|
||||
console.log(`📋 No logs found for project ${projectId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`📋 Logs State for ${projectId}:`, {
|
||||
totalLogs: projectLogs.logs.length,
|
||||
lastWindow: projectLogs.lastWindowTitle,
|
||||
logs: projectLogs.logs.map((log) => ({
|
||||
title: log.title.substring(0, 40) + (log.title.length > 40 ? "..." : ""),
|
||||
appName: log.appName,
|
||||
from: log.from,
|
||||
to: log.to,
|
||||
durationMs: log.totalTime,
|
||||
durationSec: Math.round(log.totalTime / 1000),
|
||||
})),
|
||||
});
|
||||
},
|
||||
|
||||
// Optional: Add a reducer to fix all timestamps in existing logs
|
||||
fixAllTimestamps: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
projectId: string;
|
||||
}>
|
||||
) => {
|
||||
const { projectId } = action.payload;
|
||||
const projectLogs = state.logsByProject[projectId];
|
||||
|
||||
if (!projectLogs) {
|
||||
console.warn(`Cannot fix timestamps: Project ${projectId} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fix all timestamps in existing logs
|
||||
projectLogs.logs.forEach(log => {
|
||||
log.from = formatToZeroMs(log.from);
|
||||
log.to = formatToZeroMs(log.to);
|
||||
// Recalculate totalTime based on formatted timestamps
|
||||
const startTime = new Date(log.from).getTime();
|
||||
const endTime = new Date(log.to).getTime();
|
||||
log.totalTime = endTime - startTime;
|
||||
});
|
||||
|
||||
console.log(`🔧 Fixed timestamps for project ${projectId}`);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
initializeProjectLogs,
|
||||
updateWindowLog,
|
||||
clearProjectLogs,
|
||||
clearAllLogs,
|
||||
debugLogsState,
|
||||
fixAllTimestamps,
|
||||
} = logsSlice.actions;
|
||||
|
||||
export default logsSlice.reducer;
|
||||
|
|
@ -133,6 +133,33 @@ interface PayslipResponse {
|
|||
}
|
||||
|
||||
|
||||
interface RegionResponse {
|
||||
success: boolean;
|
||||
status: number;
|
||||
data?: any;
|
||||
retry?: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
|
||||
interface TrackingLog {
|
||||
title: string;
|
||||
appName: string;
|
||||
url: string;
|
||||
from: string;
|
||||
to: string;
|
||||
totalTime: number;
|
||||
}
|
||||
|
||||
interface TrackingPayload {
|
||||
vaId: string;
|
||||
projectId: string;
|
||||
logId: string;
|
||||
logs: TrackingLog[];
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface Window {
|
||||
electron: {
|
||||
login: (credentials: { email: string; password: string }) => Promise<any>;
|
||||
|
|
@ -158,18 +185,12 @@ interface Window {
|
|||
}) => Promise<any>;
|
||||
stopProjectTracking: (data: {
|
||||
vaId: string;
|
||||
logId: string,
|
||||
projectId: string;
|
||||
type: string;
|
||||
time: string
|
||||
}) => Promise<any>;
|
||||
trackingTabsOpeningClosing: (data: {
|
||||
id?: string;
|
||||
logId: string;
|
||||
projectId: string;
|
||||
title: string;
|
||||
appName: string;
|
||||
}) => Promise<any>;
|
||||
|
||||
trackingTabsOpeningClosing: (data: TrackingPayload) => Promise<any>;
|
||||
getProjectLogs: (
|
||||
projectId: string,
|
||||
fromDate?: string,
|
||||
|
|
@ -242,8 +263,14 @@ interface Window {
|
|||
data?: any;
|
||||
}>;
|
||||
|
||||
checkRegion: () => Promise<RegionResponse>;
|
||||
connectVPN: () => Promise<string>;
|
||||
disconnectVPN: () => Promise<void>;
|
||||
getBaseURL: () => Promise<string>;
|
||||
updateBaseURL: (url: string) => Promise<{
|
||||
updated: boolean;
|
||||
baseURL: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
// vite.config.ts
|
||||
import { defineConfig } from "file:///C:/Users/user/OneDrive/Desktop/electron-main/node_modules/vite/dist/node/index.js";
|
||||
import react from "file:///C:/Users/user/OneDrive/Desktop/electron-main/node_modules/@vitejs/plugin-react/dist/index.js";
|
||||
import tailwindcss from "file:///C:/Users/user/OneDrive/Desktop/electron-main/node_modules/@tailwindcss/vite/dist/index.mjs";
|
||||
var vite_config_default = defineConfig({
|
||||
plugins: [react(), tailwindcss()],
|
||||
base: "./",
|
||||
build: {
|
||||
outDir: "dist-react"
|
||||
},
|
||||
server: {
|
||||
port: 5123,
|
||||
strictPort: true
|
||||
}
|
||||
});
|
||||
export {
|
||||
vite_config_default as default
|
||||
};
|
||||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJDOlxcXFxVc2Vyc1xcXFx1c2VyXFxcXE9uZURyaXZlXFxcXERlc2t0b3BcXFxcZWxlY3Ryb24tbWFpblwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiQzpcXFxcVXNlcnNcXFxcdXNlclxcXFxPbmVEcml2ZVxcXFxEZXNrdG9wXFxcXGVsZWN0cm9uLW1haW5cXFxcdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL0M6L1VzZXJzL3VzZXIvT25lRHJpdmUvRGVza3RvcC9lbGVjdHJvbi1tYWluL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSAndml0ZSc7XHJcbmltcG9ydCByZWFjdCBmcm9tICdAdml0ZWpzL3BsdWdpbi1yZWFjdCc7XHJcbmltcG9ydCB0YWlsd2luZGNzcyBmcm9tICdAdGFpbHdpbmRjc3Mvdml0ZSdcclxuXHJcbi8vIGh0dHBzOi8vdml0ZWpzLmRldi9jb25maWcvXHJcbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XHJcbiAgcGx1Z2luczogW3JlYWN0KCksdGFpbHdpbmRjc3MoKV0sXHJcbiAgYmFzZTogJy4vJyxcclxuICBidWlsZDoge1xyXG4gICAgb3V0RGlyOiAnZGlzdC1yZWFjdCcsXHJcbiAgfSxcclxuICBzZXJ2ZXI6IHtcclxuICAgIHBvcnQ6IDUxMjMsXHJcbiAgICBzdHJpY3RQb3J0OiB0cnVlLFxyXG4gIH0sXHJcbn0pO1xyXG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQWtVLFNBQVMsb0JBQW9CO0FBQy9WLE9BQU8sV0FBVztBQUNsQixPQUFPLGlCQUFpQjtBQUd4QixJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUMxQixTQUFTLENBQUMsTUFBTSxHQUFFLFlBQVksQ0FBQztBQUFBLEVBQy9CLE1BQU07QUFBQSxFQUNOLE9BQU87QUFBQSxJQUNMLFFBQVE7QUFBQSxFQUNWO0FBQUEsRUFDQSxRQUFRO0FBQUEsSUFDTixNQUFNO0FBQUEsSUFDTixZQUFZO0FBQUEsRUFDZDtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg==
|
||||
Loading…
Reference in New Issue