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.
- goosebit/__init__.py +50 -19
- goosebit/__main__.py +7 -0
- goosebit/api/responses.py +5 -0
- goosebit/api/routes.py +5 -15
- goosebit/api/telemetry/__init__.py +1 -0
- goosebit/{telemetry/__init__.py → api/telemetry/metrics.py} +9 -3
- goosebit/api/telemetry/prometheus/__init__.py +2 -0
- goosebit/api/telemetry/prometheus/readers.py +3 -0
- goosebit/api/telemetry/prometheus/routes.py +18 -0
- goosebit/api/telemetry/routes.py +9 -0
- goosebit/api/v1/__init__.py +1 -0
- goosebit/api/v1/devices/__init__.py +1 -0
- goosebit/api/v1/devices/device/__init__.py +1 -0
- goosebit/api/v1/devices/device/responses.py +13 -0
- goosebit/api/v1/devices/device/routes.py +27 -0
- goosebit/api/v1/devices/requests.py +7 -0
- goosebit/api/v1/devices/responses.py +16 -0
- goosebit/api/v1/devices/routes.py +35 -0
- goosebit/api/v1/download/__init__.py +1 -0
- goosebit/api/v1/download/routes.py +22 -0
- goosebit/api/v1/rollouts/__init__.py +1 -0
- goosebit/api/v1/rollouts/requests.py +16 -0
- goosebit/api/v1/rollouts/responses.py +19 -0
- goosebit/api/v1/rollouts/routes.py +50 -0
- goosebit/api/v1/routes.py +9 -0
- goosebit/api/v1/software/__init__.py +1 -0
- goosebit/api/v1/software/requests.py +5 -0
- goosebit/api/v1/software/responses.py +16 -0
- goosebit/api/v1/software/routes.py +77 -0
- goosebit/auth/__init__.py +101 -101
- goosebit/db/__init__.py +11 -0
- goosebit/db/config.py +10 -0
- goosebit/db/migrations/models/0_20240830054046_init.py +136 -0
- goosebit/{models.py → db/models.py} +17 -10
- goosebit/realtime/logs.py +4 -3
- goosebit/realtime/routes.py +2 -2
- goosebit/schema/__init__.py +0 -0
- goosebit/schema/devices.py +73 -0
- goosebit/schema/rollouts.py +31 -0
- goosebit/schema/software.py +37 -0
- goosebit/settings/__init__.py +17 -0
- goosebit/settings/const.py +21 -0
- goosebit/settings/schema.py +86 -0
- goosebit/ui/bff/__init__.py +1 -0
- goosebit/ui/bff/devices/__init__.py +1 -0
- goosebit/ui/bff/devices/requests.py +12 -0
- goosebit/ui/bff/devices/responses.py +39 -0
- goosebit/ui/bff/devices/routes.py +72 -0
- goosebit/ui/bff/download/__init__.py +1 -0
- goosebit/ui/bff/download/routes.py +22 -0
- goosebit/ui/bff/rollouts/__init__.py +1 -0
- goosebit/ui/bff/rollouts/responses.py +37 -0
- goosebit/ui/bff/rollouts/routes.py +52 -0
- goosebit/ui/bff/routes.py +11 -0
- goosebit/ui/bff/software/__init__.py +1 -0
- goosebit/ui/bff/software/responses.py +37 -0
- goosebit/ui/bff/software/routes.py +83 -0
- goosebit/ui/nav.py +16 -0
- goosebit/ui/routes.py +29 -66
- goosebit/ui/static/favicon.ico +0 -0
- goosebit/ui/static/favicon.svg +1 -1
- goosebit/ui/static/js/devices.js +47 -71
- goosebit/ui/static/js/index.js +4 -9
- goosebit/ui/static/js/login.js +23 -0
- goosebit/ui/static/js/logs.js +1 -1
- goosebit/ui/static/js/rollouts.js +33 -19
- goosebit/ui/static/js/{firmware.js → software.js} +87 -86
- goosebit/ui/static/js/util.js +60 -6
- goosebit/ui/static/svg/goosebit-logo.svg +1 -1
- goosebit/ui/templates/__init__.py +9 -1
- goosebit/ui/templates/devices.html.jinja +75 -0
- goosebit/ui/templates/index.html.jinja +25 -0
- goosebit/ui/templates/login.html.jinja +57 -0
- goosebit/ui/templates/logs.html.jinja +31 -0
- goosebit/ui/templates/nav.html.jinja +84 -0
- goosebit/ui/templates/rollouts.html.jinja +93 -0
- goosebit/ui/templates/software.html.jinja +139 -0
- goosebit/updater/controller/v1/routes.py +101 -96
- goosebit/updater/controller/v1/schema.py +56 -0
- goosebit/updater/manager.py +65 -65
- goosebit/updater/routes.py +3 -11
- goosebit/updates/__init__.py +91 -32
- goosebit/updates/swdesc.py +2 -7
- goosebit-0.2.1.dist-info/METADATA +173 -0
- goosebit-0.2.1.dist-info/RECORD +95 -0
- goosebit/api/devices.py +0 -136
- goosebit/api/download.py +0 -34
- goosebit/api/firmware.py +0 -57
- goosebit/api/helper.py +0 -30
- goosebit/api/rollouts.py +0 -87
- goosebit/db.py +0 -37
- goosebit/permissions.py +0 -75
- goosebit/settings.py +0 -64
- goosebit/telemetry/prometheus.py +0 -10
- goosebit/ui/templates/devices.html +0 -115
- goosebit/ui/templates/firmware.html +0 -163
- goosebit/ui/templates/index.html +0 -23
- goosebit/ui/templates/login.html +0 -65
- goosebit/ui/templates/logs.html +0 -36
- goosebit/ui/templates/nav.html +0 -117
- goosebit/ui/templates/rollouts.html +0 -76
- goosebit-0.1.2.dist-info/METADATA +0 -123
- goosebit-0.1.2.dist-info/RECORD +0 -51
- {goosebit-0.1.2.dist-info → goosebit-0.2.1.dist-info}/LICENSE +0 -0
- {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
|
goosebit/telemetry/prometheus.py
DELETED
@@ -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")
|