goosebit 0.1.2__py3-none-any.whl → 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. goosebit/__init__.py +50 -19
  2. goosebit/__main__.py +7 -0
  3. goosebit/api/responses.py +5 -0
  4. goosebit/api/routes.py +5 -15
  5. goosebit/api/telemetry/__init__.py +1 -0
  6. goosebit/{telemetry/__init__.py → api/telemetry/metrics.py} +9 -3
  7. goosebit/api/telemetry/prometheus/__init__.py +2 -0
  8. goosebit/api/telemetry/prometheus/readers.py +3 -0
  9. goosebit/api/telemetry/prometheus/routes.py +18 -0
  10. goosebit/api/telemetry/routes.py +9 -0
  11. goosebit/api/v1/__init__.py +1 -0
  12. goosebit/api/v1/devices/__init__.py +1 -0
  13. goosebit/api/v1/devices/device/__init__.py +1 -0
  14. goosebit/api/v1/devices/device/responses.py +13 -0
  15. goosebit/api/v1/devices/device/routes.py +27 -0
  16. goosebit/api/v1/devices/requests.py +7 -0
  17. goosebit/api/v1/devices/responses.py +16 -0
  18. goosebit/api/v1/devices/routes.py +35 -0
  19. goosebit/api/v1/download/__init__.py +1 -0
  20. goosebit/api/v1/download/routes.py +22 -0
  21. goosebit/api/v1/rollouts/__init__.py +1 -0
  22. goosebit/api/v1/rollouts/requests.py +16 -0
  23. goosebit/api/v1/rollouts/responses.py +19 -0
  24. goosebit/api/v1/rollouts/routes.py +50 -0
  25. goosebit/api/v1/routes.py +9 -0
  26. goosebit/api/v1/software/__init__.py +1 -0
  27. goosebit/api/v1/software/requests.py +5 -0
  28. goosebit/api/v1/software/responses.py +16 -0
  29. goosebit/api/v1/software/routes.py +77 -0
  30. goosebit/auth/__init__.py +101 -101
  31. goosebit/db/__init__.py +11 -0
  32. goosebit/db/config.py +10 -0
  33. goosebit/db/migrations/models/0_20240830054046_init.py +136 -0
  34. goosebit/{models.py → db/models.py} +17 -10
  35. goosebit/realtime/logs.py +4 -3
  36. goosebit/realtime/routes.py +2 -2
  37. goosebit/schema/__init__.py +0 -0
  38. goosebit/schema/devices.py +73 -0
  39. goosebit/schema/rollouts.py +31 -0
  40. goosebit/schema/software.py +37 -0
  41. goosebit/settings/__init__.py +17 -0
  42. goosebit/settings/const.py +21 -0
  43. goosebit/settings/schema.py +86 -0
  44. goosebit/ui/bff/__init__.py +1 -0
  45. goosebit/ui/bff/devices/__init__.py +1 -0
  46. goosebit/ui/bff/devices/requests.py +12 -0
  47. goosebit/ui/bff/devices/responses.py +39 -0
  48. goosebit/ui/bff/devices/routes.py +72 -0
  49. goosebit/ui/bff/download/__init__.py +1 -0
  50. goosebit/ui/bff/download/routes.py +22 -0
  51. goosebit/ui/bff/rollouts/__init__.py +1 -0
  52. goosebit/ui/bff/rollouts/responses.py +37 -0
  53. goosebit/ui/bff/rollouts/routes.py +52 -0
  54. goosebit/ui/bff/routes.py +11 -0
  55. goosebit/ui/bff/software/__init__.py +1 -0
  56. goosebit/ui/bff/software/responses.py +37 -0
  57. goosebit/ui/bff/software/routes.py +83 -0
  58. goosebit/ui/nav.py +16 -0
  59. goosebit/ui/routes.py +29 -66
  60. goosebit/ui/static/favicon.ico +0 -0
  61. goosebit/ui/static/favicon.svg +1 -1
  62. goosebit/ui/static/js/devices.js +47 -71
  63. goosebit/ui/static/js/index.js +4 -9
  64. goosebit/ui/static/js/login.js +23 -0
  65. goosebit/ui/static/js/logs.js +1 -1
  66. goosebit/ui/static/js/rollouts.js +33 -19
  67. goosebit/ui/static/js/{firmware.js → software.js} +87 -86
  68. goosebit/ui/static/js/util.js +60 -6
  69. goosebit/ui/static/svg/goosebit-logo.svg +1 -1
  70. goosebit/ui/templates/__init__.py +9 -1
  71. goosebit/ui/templates/devices.html.jinja +75 -0
  72. goosebit/ui/templates/index.html.jinja +25 -0
  73. goosebit/ui/templates/login.html.jinja +57 -0
  74. goosebit/ui/templates/logs.html.jinja +31 -0
  75. goosebit/ui/templates/nav.html.jinja +84 -0
  76. goosebit/ui/templates/rollouts.html.jinja +93 -0
  77. goosebit/ui/templates/software.html.jinja +139 -0
  78. goosebit/updater/controller/v1/routes.py +101 -96
  79. goosebit/updater/controller/v1/schema.py +56 -0
  80. goosebit/updater/manager.py +65 -65
  81. goosebit/updater/routes.py +3 -11
  82. goosebit/updates/__init__.py +91 -32
  83. goosebit/updates/swdesc.py +2 -7
  84. goosebit-0.2.1.dist-info/METADATA +173 -0
  85. goosebit-0.2.1.dist-info/RECORD +95 -0
  86. goosebit/api/devices.py +0 -136
  87. goosebit/api/download.py +0 -34
  88. goosebit/api/firmware.py +0 -57
  89. goosebit/api/helper.py +0 -30
  90. goosebit/api/rollouts.py +0 -87
  91. goosebit/db.py +0 -37
  92. goosebit/permissions.py +0 -75
  93. goosebit/settings.py +0 -64
  94. goosebit/telemetry/prometheus.py +0 -10
  95. goosebit/ui/templates/devices.html +0 -115
  96. goosebit/ui/templates/firmware.html +0 -163
  97. goosebit/ui/templates/index.html +0 -23
  98. goosebit/ui/templates/login.html +0 -65
  99. goosebit/ui/templates/logs.html +0 -36
  100. goosebit/ui/templates/nav.html +0 -117
  101. goosebit/ui/templates/rollouts.html +0 -76
  102. goosebit-0.1.2.dist-info/METADATA +0 -123
  103. goosebit-0.1.2.dist-info/RECORD +0 -51
  104. {goosebit-0.1.2.dist-info → goosebit-0.2.1.dist-info}/LICENSE +0 -0
  105. {goosebit-0.1.2.dist-info → goosebit-0.2.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,95 @@
1
+ goosebit/__init__.py,sha256=vhOcvSYVWRttpjBDeAVftUHZPVaLExvqFEV9LU69vgc,3039
2
+ goosebit/__main__.py,sha256=ezSLOobcrbnBspFOSbyjuATEdk8B3aAj9cgEu647ujk,161
3
+ goosebit/api/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
4
+ goosebit/api/responses.py,sha256=HXLpFgL-iv5Ts6LKa3AMt1XGV1GbDE5GzjK66_3Gz5o,84
5
+ goosebit/api/routes.py,sha256=KOA1r8Ripl9nhZV4gZpuQrRi0fG9I3VEqcAAMeBGqBk,272
6
+ goosebit/api/telemetry/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
7
+ goosebit/api/telemetry/metrics.py,sha256=aJ2viqBcxU41wJ0tDwPXj0Dsv6YEHaWv7EBDqkUilGI,791
8
+ goosebit/api/telemetry/prometheus/__init__.py,sha256=nvVtx3DsqBhoXE4Y4L6k7nYoQTl1VV0afyVW2YHLQGs,83
9
+ goosebit/api/telemetry/prometheus/readers.py,sha256=TKD7LT-lYS53xdt-tLMxqyWAeya62299cJtok9Q2MSE,104
10
+ goosebit/api/telemetry/prometheus/routes.py,sha256=91TNk_pbxi5BfCLo_OVojubL9kxFuL84dJ55RmsZ0eE,676
11
+ goosebit/api/telemetry/routes.py,sha256=nuv9bClyo_P8TAkcp3huqxZoUn-ATlRAMnl0OXbWNpw,217
12
+ goosebit/api/v1/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
13
+ goosebit/api/v1/devices/__init__.py,sha256=kzt5TBA-bJmDZFXYPohggbMOW7GhklyflaRKZNC0yDY,42
14
+ goosebit/api/v1/devices/device/__init__.py,sha256=kzt5TBA-bJmDZFXYPohggbMOW7GhklyflaRKZNC0yDY,42
15
+ goosebit/api/v1/devices/device/responses.py,sha256=THIPMzc6mwl7GmIXuqAeLLesFHfssvPrMJeHe8tV4mA,222
16
+ goosebit/api/v1/devices/device/routes.py,sha256=Q9v7NzTFGdwQQmSRjGsFXl9qDwilVB1ieG9isXLaXgs,957
17
+ goosebit/api/v1/devices/requests.py,sha256=zx9eWXQanqBnmy-XboA_lrzInfx9ozOB4vG2jRTwnJA,131
18
+ goosebit/api/v1/devices/responses.py,sha256=m1S0DfseNhlJ0mlw8J5msmXrdftUC0oZbwciy8eNfvY,402
19
+ goosebit/api/v1/devices/routes.py,sha256=D6w6vq0XkRGMaE-QHHLNPPi5kiZ8JufdHFTyar1XH8s,1051
20
+ goosebit/api/v1/download/__init__.py,sha256=kzt5TBA-bJmDZFXYPohggbMOW7GhklyflaRKZNC0yDY,42
21
+ goosebit/api/v1/download/routes.py,sha256=wdeXA-dGO4D9FxaUP6OnUSW4_2NZhi_VgIxMcSRL2x4,675
22
+ goosebit/api/v1/rollouts/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
23
+ goosebit/api/v1/rollouts/requests.py,sha256=EF1VUwaK-f-2SSKxl2klKrMGfBkBo72iWKtw3cAzzW0,257
24
+ goosebit/api/v1/rollouts/responses.py,sha256=grPcETWUSJrmVyQZs70jYhFjACsL-I_gPbg806BsauA,482
25
+ goosebit/api/v1/rollouts/routes.py,sha256=wZki0Cz959gXYjQFjByU259TElubfhRs0RupYy8Xpl0,1683
26
+ goosebit/api/v1/routes.py,sha256=g9vL0VOuFSdNG3FnK3y_03trwv4X2OLN9ustJeCgc58,272
27
+ goosebit/api/v1/software/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
28
+ goosebit/api/v1/software/requests.py,sha256=visd81BMeWDKJADcC10NBJDs7Z3xQC-5nod9z_WHiPg,101
29
+ goosebit/api/v1/software/responses.py,sha256=pxhhqdjSkKOpF8KfJZuiRer0o4L3ydvb81RarEyAUlI,418
30
+ goosebit/api/v1/software/routes.py,sha256=s89cBMB_IM4XR7CtftK9a6tPoTHoR6B9Et8KtuakfU0,2612
31
+ goosebit/auth/__init__.py,sha256=REU94_KD2YmYYLNZdVhCIkNq8J7DOcVKyi_Grh5V2dc,4558
32
+ goosebit/db/__init__.py,sha256=B8yfzkcjrb-qH-C6O_Uix4Y1ttcyJSxMBKwIFwNNo08,202
33
+ goosebit/db/config.py,sha256=YCtF52lQ5bEVtKsrqkSzQpbKmCbD79O8Tz0RE4thV3c,220
34
+ goosebit/db/migrations/models/0_20240830054046_init.py,sha256=EBig7b0-AOWt_AE10rQf_0ZrserjKTm0r53wYInK5Co,5526
35
+ goosebit/db/models.py,sha256=P-mdryY9wbxGQkU3Dvy90fTgcKsksYJWEab3Vu9U2t4,4389
36
+ goosebit/realtime/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
37
+ goosebit/realtime/logs.py,sha256=JJ6bfVKzux9n3mXaUFoQhH2krGCpW7U1VzaloNmiORY,1223
38
+ goosebit/realtime/routes.py,sha256=yLHrA-BHHU5cpUNvxAPVxWC3KcB9aUrV31HT_th24Ls,265
39
+ goosebit/schema/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
+ goosebit/schema/devices.py,sha256=jaZSP0yA_ef7mbbHvgQSeoO5MWehnNiHuTA_HnoYerc,2469
41
+ goosebit/schema/rollouts.py,sha256=_43CtAMX1j6AhCRQkSBkoukvhRzGeOYQgkVxCx3wt4g,788
42
+ goosebit/schema/software.py,sha256=xxwe6ix-dNa3Nt3FL4_mGSr45yXHzCxesjADREDsrWI,884
43
+ goosebit/settings/__init__.py,sha256=UkJa_G_VJckNf16Em0zz8sXgEi5pzxHFvZ96oa9MC3k,360
44
+ goosebit/settings/const.py,sha256=hOpeA9pGmbU4V4mqcfhpHV7cmEjf2GIenmzCSz2Jpj8,779
45
+ goosebit/settings/schema.py,sha256=T6MntOkM6Pbf98dQHZoEVUADwKpeJqCrFDc40ufCiG4,2543
46
+ goosebit/ui/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
47
+ goosebit/ui/bff/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
48
+ goosebit/ui/bff/devices/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
49
+ goosebit/ui/bff/devices/requests.py,sha256=YbTUsyVLQZaUnn3dJ6AQe0mZl_H5aQC6QKCRxhCjGJc,286
50
+ goosebit/ui/bff/devices/responses.py,sha256=ljXPzDX6CzWYguMOVYs1qBrlVyA0TqGn4kmxnkIgcAE,1431
51
+ goosebit/ui/bff/devices/routes.py,sha256=H7ftSFz2d4BFNK7RV6a-m6m5eo6xv86xsDZweBj9k18,2680
52
+ goosebit/ui/bff/download/__init__.py,sha256=kzt5TBA-bJmDZFXYPohggbMOW7GhklyflaRKZNC0yDY,42
53
+ goosebit/ui/bff/download/routes.py,sha256=wdeXA-dGO4D9FxaUP6OnUSW4_2NZhi_VgIxMcSRL2x4,675
54
+ goosebit/ui/bff/rollouts/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
55
+ goosebit/ui/bff/rollouts/responses.py,sha256=wdQiMdgg6qY5pvu1aB3QsqZ1aQiOmBqFHk_ayoT9ByI,1403
56
+ goosebit/ui/bff/rollouts/routes.py,sha256=Krr7ds_BRgezO-SUUBjXgxKpmWjijArJOLMkQLKLdGo,1408
57
+ goosebit/ui/bff/routes.py,sha256=dq0fHXBiJfERzvUhvR4KVYCK3Ogf0Gb9wMSpzQanDac,323
58
+ goosebit/ui/bff/software/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
59
+ goosebit/ui/bff/software/responses.py,sha256=B85qsf5SgV4iuMvH5z3kv4IW0UDn4VkyPqg9z79BvNs,1404
60
+ goosebit/ui/bff/software/routes.py,sha256=0BphAUN1wOGyBWnBuJufyje803lCRl10GWJTQWgt4BE,2610
61
+ goosebit/ui/nav.py,sha256=ZdNGTST3bnRRT7P4vsS9LwlzB82H6SRshqSnQC35i_E,371
62
+ goosebit/ui/routes.py,sha256=cc62_rEDjbniw0Fnsu7sZ13CNUuJ3z9z7F46urSUuBk,2151
63
+ goosebit/ui/static/__init__.py,sha256=AsZiM3h9chQ_YaBbAD6TVFf0244Q9DetkDMl70q8swQ,135
64
+ goosebit/ui/static/favicon.ico,sha256=MKAqEjW7F1-j2dmW4s08y4U0dFteIlU7rpLKakU0Lxk,4286
65
+ goosebit/ui/static/favicon.svg,sha256=8zvM7o2Ob3YmTEV6Rj7rCTgSajvY6wIaAQAAjWwlDsw,8733
66
+ goosebit/ui/static/js/devices.js,sha256=ZRAS1C_UzgQeB_MI8WjUGGaCPSUMOPPhgL-hns8B24I,11052
67
+ goosebit/ui/static/js/index.js,sha256=ldsPX0PpGB1vkYEfMY05tYEgG6iCif3tx60aaAc3URY,5515
68
+ goosebit/ui/static/js/login.js,sha256=9qlXki3udm5CmAO8UXUJe94Kc_-SJekdKjfH9TzJhgk,579
69
+ goosebit/ui/static/js/logs.js,sha256=DgOWLKUvK-mM-oYRA0JJTIhtiCiegb5qCJ7pE9H2I3k,863
70
+ goosebit/ui/static/js/rollouts.js,sha256=KQ_BaJn11VkLNBkIK7I_GLGpsPTjGg0ISEMfRszJtKQ,7174
71
+ goosebit/ui/static/js/software.js,sha256=H9P4RjizC2EhL6Otjv4-iLnESdi3IvQYhWFvM4ddjaQ,8907
72
+ goosebit/ui/static/js/util.js,sha256=D1EfZGCXlfmyDCXwae0XbHIsPckEbDNsqkCnrD_5D-o,3706
73
+ goosebit/ui/static/svg/goosebit-logo.svg,sha256=8zvM7o2Ob3YmTEV6Rj7rCTgSajvY6wIaAQAAjWwlDsw,8733
74
+ goosebit/ui/templates/__init__.py,sha256=xHCio6TnuNnYKLOZ3VhvbxyJPZ2ddzTSle_RmiuLrrA,378
75
+ goosebit/ui/templates/devices.html.jinja,sha256=JROU5CInzjXlaiH2yxvCS1cepLXzU2O7TmiXUXJ2jes,3523
76
+ goosebit/ui/templates/index.html.jinja,sha256=SDROkbhSRX6-pdET-yikfnrcfNKZR5zPhY2ByrSdAy4,907
77
+ goosebit/ui/templates/login.html.jinja,sha256=eQX1ypZ5iJ2L4v4lw8VB7GKluUjUYmpoUyUy8jY98Og,3050
78
+ goosebit/ui/templates/logs.html.jinja,sha256=JLHogVHWcP_Y321dd-gHexNqix-PFfGVpEKbNmzsMU8,1310
79
+ goosebit/ui/templates/nav.html.jinja,sha256=cKS8SGxI_zP5oSElQ_dosmuvmf7sC9L0dHY2sZi1H-g,4241
80
+ goosebit/ui/templates/rollouts.html.jinja,sha256=5FKm6EoBS-2Sk5L4cfWRxuoYZ-NT7ehff6Q_NapWp9k,4515
81
+ goosebit/ui/templates/software.html.jinja,sha256=CTIeNkPMJMvtDVWbcKl_BjvtAFV3bskpCcNg-lCJIZM,7551
82
+ goosebit/updater/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
83
+ goosebit/updater/controller/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
84
+ goosebit/updater/controller/routes.py,sha256=8CnLb-kDuO-yFeWdu4apIyctCf9OzvJ011Az3QDGemU,123
85
+ goosebit/updater/controller/v1/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
86
+ goosebit/updater/controller/v1/routes.py,sha256=cuRIS-K_Pf3ufiSq1e5Uur7eR06jbLO4mQ-Tl9UZy-E,7110
87
+ goosebit/updater/controller/v1/schema.py,sha256=VomKoJYRWsbpkChWrq3SrjSVhPtkz6mrUcMrer94Tks,1200
88
+ goosebit/updater/manager.py,sha256=WVqNQwr0ymgBcoBkyuGA_b_rKz6j9rAjn1fee-g1WRQ,11478
89
+ goosebit/updater/routes.py,sha256=GfjbSdFYaw28B8b7hByOPN8BMIbaGvbz4CbG1jQCmP8,515
90
+ goosebit/updates/__init__.py,sha256=MjoFNaMbCYJRNeXtg90AdFy5ott41Og1q01-D0XMgmc,4185
91
+ goosebit/updates/swdesc.py,sha256=APfUMUkP8Tz2H1Ajk9FQeJfjJWARxoMGPSnJQiU0Xzc,2419
92
+ goosebit-0.2.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
93
+ goosebit-0.2.1.dist-info/METADATA,sha256=V0osZhzcKX-lRPclYg1khVjMcPAfQDUeTotsCxFNO3E,5420
94
+ goosebit-0.2.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
95
+ goosebit-0.2.1.dist-info/RECORD,,
goosebit/api/devices.py DELETED
@@ -1,136 +0,0 @@
1
- import time
2
- from typing import Any
3
-
4
- from fastapi import APIRouter, Security
5
- from fastapi.requests import Request
6
- from pydantic import BaseModel
7
- from tortoise.expressions import Q
8
-
9
- from goosebit.api.helper import filter_data
10
- from goosebit.auth import validate_user_permissions
11
- from goosebit.models import Device, Firmware, UpdateModeEnum, UpdateStateEnum
12
- from goosebit.permissions import Permissions
13
- from goosebit.updater.manager import delete_devices, get_update_manager
14
-
15
- router = APIRouter(prefix="/devices")
16
-
17
-
18
- @router.get(
19
- "/all",
20
- dependencies=[Security(validate_user_permissions, scopes=[Permissions.HOME.READ])],
21
- )
22
- async def devices_get_all(request: Request) -> dict[str, int | list[Any] | Any]:
23
- query = Device.all().prefetch_related("assigned_firmware", "hardware")
24
-
25
- def search_filter(search_value):
26
- return (
27
- Q(uuid__icontains=search_value)
28
- | Q(name__icontains=search_value)
29
- | Q(feed__icontains=search_value)
30
- | Q(flavor__icontains=search_value)
31
- | Q(update_mode__icontains=UpdateModeEnum.from_str(search_value))
32
- | Q(last_state__icontains=UpdateStateEnum.from_str(search_value))
33
- )
34
-
35
- async def parse(device: Device) -> dict:
36
- manager = await get_update_manager(device.uuid)
37
- _, target_firmware = await manager.get_update()
38
- last_seen = device.last_seen
39
- if last_seen is not None:
40
- last_seen = round(time.time() - device.last_seen)
41
- return {
42
- "uuid": device.uuid,
43
- "name": device.name,
44
- "fw_installed_version": device.fw_version,
45
- "fw_target_version": (target_firmware.version if target_firmware is not None else None),
46
- "fw_assigned": (device.assigned_firmware.id if device.assigned_firmware is not None else None),
47
- "hw_model": device.hardware.model,
48
- "hw_revision": device.hardware.revision,
49
- "feed": device.feed,
50
- "flavor": device.flavor,
51
- "progress": device.progress,
52
- "state": str(device.last_state),
53
- "update_mode": str(device.update_mode),
54
- "force_update": device.force_update,
55
- "last_ip": device.last_ip,
56
- "last_seen": last_seen,
57
- "online": (last_seen < manager.poll_seconds if last_seen is not None else None),
58
- }
59
-
60
- total_records = await Device.all().count()
61
- return await filter_data(request, query, search_filter, parse, total_records)
62
-
63
-
64
- class UpdateDevicesModel(BaseModel):
65
- devices: list[str]
66
- firmware: str | None = None
67
- name: str | None = None
68
- pinned: bool | None = None
69
- feed: str | None = None
70
- flavor: str | None = None
71
-
72
-
73
- @router.post(
74
- "/update",
75
- dependencies=[Security(validate_user_permissions, scopes=[Permissions.DEVICE.WRITE])],
76
- )
77
- async def devices_update(_: Request, config: UpdateDevicesModel) -> dict:
78
- for uuid in config.devices:
79
- updater = await get_update_manager(uuid)
80
- if config.firmware is not None:
81
- if config.firmware == "rollout":
82
- await updater.update_update(UpdateModeEnum.ROLLOUT, None)
83
- elif config.firmware == "latest":
84
- await updater.update_update(UpdateModeEnum.LATEST, None)
85
- else:
86
- firmware = await Firmware.get_or_none(id=config.firmware)
87
- await updater.update_update(UpdateModeEnum.ASSIGNED, firmware)
88
- if config.pinned is not None:
89
- await updater.update_update(UpdateModeEnum.PINNED, None)
90
- if config.name is not None:
91
- await updater.update_name(config.name)
92
- if config.feed is not None:
93
- await updater.update_feed(config.feed)
94
- if config.flavor is not None:
95
- await updater.update_flavor(config.flavor)
96
- return {"success": True}
97
-
98
-
99
- class ForceUpdateModel(BaseModel):
100
- devices: list[str]
101
-
102
-
103
- @router.post(
104
- "/force_update",
105
- dependencies=[Security(validate_user_permissions, scopes=[Permissions.DEVICE.WRITE])],
106
- )
107
- async def devices_force_update(_: Request, config: ForceUpdateModel) -> dict:
108
- for uuid in config.devices:
109
- updater = await get_update_manager(uuid)
110
- await updater.update_force_update(True)
111
- return {"success": True}
112
-
113
-
114
- @router.get(
115
- "/logs/{dev_id}",
116
- dependencies=[Security(validate_user_permissions, scopes=[Permissions.HOME.READ])],
117
- )
118
- async def device_logs(_: Request, dev_id: str) -> str:
119
- updater = await get_update_manager(dev_id)
120
- device = await updater.get_device()
121
- if device.last_log is not None:
122
- return device.last_log
123
- return "No logs found."
124
-
125
-
126
- class DeleteModel(BaseModel):
127
- devices: list[str]
128
-
129
-
130
- @router.post(
131
- "/delete",
132
- dependencies=[Security(validate_user_permissions, scopes=[Permissions.DEVICE.DELETE])],
133
- )
134
- async def devices_delete(_: Request, config: DeleteModel) -> dict:
135
- await delete_devices(config.devices)
136
- return {"success": True}
goosebit/api/download.py DELETED
@@ -1,34 +0,0 @@
1
- from fastapi import APIRouter, HTTPException
2
- from fastapi.requests import Request
3
- from fastapi.responses import FileResponse, RedirectResponse
4
- from starlette.responses import Response
5
-
6
- from goosebit.models import Firmware
7
-
8
- router = APIRouter(prefix="/download")
9
-
10
-
11
- @router.head("/{file_id}")
12
- async def download_file_head(_: Request, file_id: int):
13
- firmware = await Firmware.get_or_none(id=file_id)
14
- if firmware is None:
15
- raise HTTPException(404)
16
-
17
- response = Response()
18
- response.headers["Content-Length"] = str(firmware.size)
19
- return response
20
-
21
-
22
- @router.get("/{file_id}")
23
- async def download_file(_: Request, file_id: int):
24
- firmware = await Firmware.get_or_none(id=file_id)
25
- if firmware is None:
26
- raise HTTPException(404)
27
- if firmware.local:
28
- return FileResponse(
29
- firmware.path,
30
- media_type="application/octet-stream",
31
- filename=firmware.path.name,
32
- )
33
- else:
34
- return RedirectResponse(url=firmware.uri)
goosebit/api/firmware.py DELETED
@@ -1,57 +0,0 @@
1
- from typing import Any
2
-
3
- from fastapi import APIRouter, Body, Security
4
- from fastapi.requests import Request
5
- from tortoise.expressions import Q
6
-
7
- from goosebit.api.helper import filter_data
8
- from goosebit.auth import validate_user_permissions
9
- from goosebit.models import Firmware
10
- from goosebit.permissions import Permissions
11
-
12
- router = APIRouter(prefix="/firmware")
13
-
14
-
15
- @router.get(
16
- "/all",
17
- dependencies=[Security(validate_user_permissions, scopes=[Permissions.FIRMWARE.READ])],
18
- )
19
- async def firmware_get_all(
20
- request: Request,
21
- ) -> dict[str, int | list[dict[str, list[Any] | Any]]]:
22
- query = Firmware.all()
23
-
24
- def search_filter(search_value):
25
- return Q(uri__icontains=search_value) | Q(version__icontains=search_value)
26
-
27
- async def parse(f):
28
- return {
29
- "id": f.id,
30
- "name": f.path.name,
31
- "size": f.size,
32
- "hash": f.hash,
33
- "version": f.version,
34
- "compatibility": list(await f.compatibility.all().values()),
35
- }
36
-
37
- total_records = await Firmware.all().count()
38
- return await filter_data(request, query, search_filter, parse, total_records)
39
-
40
-
41
- @router.post(
42
- "/delete",
43
- dependencies=[Security(validate_user_permissions, scopes=[Permissions.FIRMWARE.DELETE])],
44
- )
45
- async def firmware_delete(_: Request, files: list[int] = Body()) -> dict:
46
- success = False
47
- for f_id in files:
48
- firmware = await Firmware.get_or_none(id=f_id)
49
- if firmware is None:
50
- continue
51
- if firmware.local:
52
- path = firmware.path
53
- if path.exists():
54
- path.unlink()
55
- await firmware.delete()
56
- success = True
57
- return {"success": success}
goosebit/api/helper.py DELETED
@@ -1,30 +0,0 @@
1
- import asyncio
2
-
3
-
4
- async def filter_data(request, query, search_filter, parse, total_records):
5
- params = request.query_params
6
-
7
- draw = int(params.get("draw", 1))
8
- start = int(params.get("start", 0))
9
- length = int(params.get("length", 10))
10
- search_value = params.get("search[value]", None)
11
- order_column_index = params.get("order[0][column]", None)
12
- order_column = params.get(f"columns[{order_column_index}][data]", None)
13
- order_dir = params.get("order[0][dir]", None)
14
-
15
- if search_value:
16
- query = query.filter(search_filter(search_value))
17
-
18
- if order_column:
19
- query = query.order_by(f"{"-" if order_dir == "desc" else ""}{order_column}")
20
-
21
- filtered_records = await query.count()
22
- rollouts = await query.offset(start).limit(length).all()
23
- data = list(await asyncio.gather(*[parse(r) for r in rollouts]))
24
-
25
- return {
26
- "draw": draw,
27
- "recordsTotal": total_records,
28
- "recordsFiltered": filtered_records,
29
- "data": data,
30
- }
goosebit/api/rollouts.py DELETED
@@ -1,87 +0,0 @@
1
- from fastapi import APIRouter, Security
2
- from fastapi.requests import Request
3
- from pydantic import BaseModel
4
- from tortoise.expressions import Q
5
-
6
- from goosebit.api.helper import filter_data
7
- from goosebit.auth import validate_user_permissions
8
- from goosebit.models import Rollout
9
- from goosebit.permissions import Permissions
10
-
11
- router = APIRouter(prefix="/rollouts")
12
-
13
-
14
- @router.get(
15
- "/all",
16
- dependencies=[Security(validate_user_permissions, scopes=[Permissions.ROLLOUT.READ])],
17
- )
18
- async def rollouts_get_all(request: Request) -> dict[str, int | list[dict]]:
19
- query = Rollout.all().prefetch_related("firmware")
20
-
21
- def search_filter(search_value):
22
- return Q(name__icontains=search_value) | Q(feed__icontains=search_value) | Q(flavor__icontains=search_value)
23
-
24
- async def parse(rollout: Rollout) -> dict:
25
- return {
26
- "id": rollout.id,
27
- "created_at": int(rollout.created_at.timestamp() * 1000),
28
- "name": rollout.name,
29
- "feed": rollout.feed,
30
- "flavor": rollout.flavor,
31
- "fw_file": rollout.firmware.path.name,
32
- "fw_version": rollout.firmware.version,
33
- "paused": rollout.paused,
34
- "success_count": rollout.success_count,
35
- "failure_count": rollout.failure_count,
36
- }
37
-
38
- total_records = await Rollout.all().count()
39
- return await filter_data(request, query, search_filter, parse, total_records)
40
-
41
-
42
- class CreateRolloutsModel(BaseModel):
43
- name: str
44
- feed: str
45
- flavor: str
46
- firmware_id: int
47
-
48
-
49
- @router.post(
50
- "/",
51
- dependencies=[Security(validate_user_permissions, scopes=[Permissions.ROLLOUT.WRITE])],
52
- )
53
- async def rollouts_create(_: Request, rollout: CreateRolloutsModel) -> dict:
54
- rollout = await Rollout.create(
55
- name=rollout.name,
56
- feed=rollout.feed,
57
- flavor=rollout.flavor,
58
- firmware_id=rollout.firmware_id,
59
- )
60
- return {"success": True, "id": rollout.id}
61
-
62
-
63
- class UpdateRolloutsModel(BaseModel):
64
- ids: list[int]
65
- paused: bool
66
-
67
-
68
- @router.post(
69
- "/update",
70
- dependencies=[Security(validate_user_permissions, scopes=[Permissions.ROLLOUT.WRITE])],
71
- )
72
- async def rollouts_update(_: Request, rollouts: UpdateRolloutsModel) -> dict:
73
- await Rollout.filter(id__in=rollouts.ids).update(paused=rollouts.paused)
74
- return {"success": True}
75
-
76
-
77
- class DeleteRolloutsModel(BaseModel):
78
- ids: list[int]
79
-
80
-
81
- @router.post(
82
- "/delete",
83
- dependencies=[Security(validate_user_permissions, scopes=[Permissions.ROLLOUT.DELETE])],
84
- )
85
- async def rollouts_delete(_: Request, rollouts: DeleteRolloutsModel) -> dict:
86
- await Rollout.filter(id__in=rollouts.ids).delete()
87
- return {"success": True}
goosebit/db.py DELETED
@@ -1,37 +0,0 @@
1
- from aerich import Command
2
- from tortoise import Tortoise, run_async
3
-
4
- from goosebit.models import Firmware
5
- from goosebit.settings import DB_MIGRATIONS_LOC, DB_URI
6
-
7
- TORTOISE_CONF = {
8
- "connections": {"default": DB_URI},
9
- "apps": {
10
- "models": {
11
- "models": ["goosebit.models", "aerich.models"],
12
- },
13
- },
14
- }
15
-
16
-
17
- async def init():
18
- command = Command(tortoise_config=TORTOISE_CONF, location=DB_MIGRATIONS_LOC)
19
- await Tortoise.init(config=TORTOISE_CONF)
20
- if not DB_MIGRATIONS_LOC.exists():
21
- await command.init_db(safe=True)
22
- await command.init()
23
- await command.migrate()
24
- await command.upgrade(run_in_transaction=True)
25
- await Tortoise.generate_schemas(safe=True)
26
- for firmware in await Firmware.all():
27
- if firmware.local and not firmware.path.exists():
28
- # delete it
29
- await firmware.delete()
30
-
31
-
32
- async def close():
33
- await Tortoise.close_connections()
34
-
35
-
36
- if __name__ == "__main__":
37
- run_async(init())
goosebit/permissions.py DELETED
@@ -1,75 +0,0 @@
1
- from enum import Enum
2
- from typing import TypeVar, cast
3
-
4
- T = TypeVar("T", bound="PermissionsBase")
5
-
6
-
7
- class PermissionsBase(str, Enum):
8
- @classmethod
9
- def full(cls) -> set[T]:
10
- all_items = set[T]()
11
- for permission in cls:
12
- all_items.add(cast(T, permission))
13
- return all_items
14
-
15
- def __str__(self):
16
- return self.value
17
-
18
-
19
- class FirmwarePermissions(PermissionsBase):
20
- READ = "firmware.read"
21
- WRITE = "firmware.write"
22
- DELETE = "firmware.delete"
23
-
24
-
25
- class DevicePermissions(PermissionsBase):
26
- READ = "device.read"
27
- WRITE = "device.write"
28
- DELETE = "device.delete"
29
-
30
-
31
- class RolloutPermissions(PermissionsBase):
32
- READ = "rollout.read"
33
- WRITE = "rollout.write"
34
- DELETE = "rollout.delete"
35
-
36
-
37
- class HomePermissions(PermissionsBase):
38
- READ = "home.read"
39
-
40
-
41
- class Permissions:
42
- HOME = HomePermissions
43
- FIRMWARE = FirmwarePermissions
44
- DEVICE = DevicePermissions
45
- ROLLOUT = RolloutPermissions
46
-
47
- @classmethod
48
- def full(cls) -> set[T]:
49
- all_items = set()
50
- for item in [cls.HOME, cls.FIRMWARE, cls.DEVICE, cls.ROLLOUT]:
51
- all_items.update(item.full())
52
- return all_items
53
-
54
- @classmethod
55
- def from_str(cls, permission: str) -> set[T]:
56
- if permission == "*":
57
- return cls.full()
58
- area, action = permission.upper().split(".")
59
- if area == "FIRMWARE":
60
- return {FirmwarePermissions[action]}
61
- if area == "DEVICE":
62
- return {DevicePermissions[action]}
63
- if area == "ROLLOUT":
64
- return {RolloutPermissions[action]}
65
- if area == "HOME":
66
- return {HomePermissions[action]}
67
-
68
-
69
- ADMIN = Permissions.full()
70
- MONITORING = [
71
- *Permissions.HOME.full(),
72
- *Permissions.FIRMWARE.full(),
73
- *Permissions.DEVICE.full(),
74
- ]
75
- READONLY = [Permissions.HOME.READ]
goosebit/settings.py DELETED
@@ -1,64 +0,0 @@
1
- import secrets
2
- from dataclasses import dataclass
3
- from pathlib import Path
4
-
5
- import yaml
6
- from argon2 import PasswordHasher
7
- from joserfc.rfc7518.oct_key import OctKey
8
-
9
- from goosebit.permissions import Permissions
10
-
11
- BASE_DIR = Path(__file__).resolve().parent.parent
12
- TOKEN_SWU_DIR = BASE_DIR.joinpath("swugen")
13
- SWUPDATE_FILES_DIR = BASE_DIR.joinpath("swupdate")
14
- UPDATES_DIR = BASE_DIR.joinpath("updates")
15
- DB_MIGRATIONS_LOC = BASE_DIR.joinpath("migrations")
16
-
17
- SECRET = OctKey.import_key(secrets.token_hex(16))
18
- PWD_CXT = PasswordHasher()
19
-
20
- with open(BASE_DIR.joinpath("settings.yaml"), "r") as f:
21
- config = yaml.safe_load(f.read())
22
-
23
- LOGGING = config.get("logging", {})
24
-
25
- TENANT = config.get("tenant", "DEFAULT")
26
-
27
- POLL_TIME = config.get("poll_time_default", "00:01:00")
28
- POLL_TIME_UPDATING = config.get("poll_time_updating", "00:00:05")
29
- POLL_TIME_REGISTRATION = config.get("poll_time_registration", "00:00:10")
30
-
31
- DB_LOC = BASE_DIR.joinpath(config.get("db_location", "db.sqlite3"))
32
- DB_URI = f"sqlite:///{DB_LOC}"
33
-
34
-
35
- @dataclass
36
- class User:
37
- username: str
38
- hashed_pwd: str
39
- permissions: set
40
-
41
- def get_json_permissions(self):
42
- return [str(p) for p in self.permissions]
43
-
44
-
45
- users: dict[str, User] = {}
46
-
47
-
48
- def add_user(u: User):
49
- users[u.username] = u
50
-
51
-
52
- for user in config.get("users", []):
53
- permissions = set()
54
- for p in user["permissions"]:
55
- permissions.update(Permissions.from_str(p))
56
- add_user(
57
- User(
58
- username=user["email"],
59
- hashed_pwd=PWD_CXT.hash(user["password"]),
60
- permissions=permissions,
61
- )
62
- )
63
-
64
- USERS = users
@@ -1,10 +0,0 @@
1
- from opentelemetry.exporter.prometheus import PrometheusMetricReader
2
- from prometheus_client import start_http_server
3
-
4
- from goosebit import settings
5
-
6
- PROMETHEUS_PORT = settings.config.get("metrics", {}).get("prometheus", {}).get("port", 9090)
7
-
8
- # separate file to enable it as a feature later.
9
- reader = PrometheusMetricReader()
10
- start_http_server(port=PROMETHEUS_PORT, addr="0.0.0.0")