goosebit 0.2.5__py3-none-any.whl → 0.2.7__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 (88) hide show
  1. goosebit/__init__.py +41 -7
  2. goosebit/api/telemetry/metrics.py +1 -5
  3. goosebit/api/v1/devices/device/responses.py +1 -0
  4. goosebit/api/v1/devices/device/routes.py +8 -8
  5. goosebit/api/v1/devices/requests.py +20 -0
  6. goosebit/api/v1/devices/routes.py +68 -8
  7. goosebit/api/v1/download/routes.py +14 -3
  8. goosebit/api/v1/rollouts/routes.py +5 -4
  9. goosebit/api/v1/routes.py +2 -1
  10. goosebit/api/v1/settings/routes.py +14 -0
  11. goosebit/api/v1/settings/users/__init__.py +1 -0
  12. goosebit/api/v1/settings/users/requests.py +16 -0
  13. goosebit/api/v1/settings/users/responses.py +7 -0
  14. goosebit/api/v1/settings/users/routes.py +56 -0
  15. goosebit/api/v1/software/routes.py +18 -14
  16. goosebit/auth/__init__.py +49 -13
  17. goosebit/auth/permissions.py +80 -0
  18. goosebit/db/config.py +57 -1
  19. goosebit/db/migrations/models/2_20241121113728_update.py +11 -0
  20. goosebit/db/migrations/models/3_20241121140210_update.py +11 -0
  21. goosebit/db/migrations/models/4_20250324110331_update.py +16 -0
  22. goosebit/db/migrations/models/4_20250402085235_rename_uuid_to_id.py +11 -0
  23. goosebit/db/migrations/models/5_20250619090242_null_feed.py +83 -0
  24. goosebit/db/models.py +19 -8
  25. goosebit/db/pg_ssl_context.py +51 -0
  26. goosebit/device_manager.py +262 -0
  27. goosebit/plugins/__init__.py +32 -0
  28. goosebit/schema/devices.py +8 -5
  29. goosebit/schema/plugins.py +67 -0
  30. goosebit/schema/updates.py +15 -0
  31. goosebit/schema/users.py +9 -0
  32. goosebit/settings/__init__.py +0 -3
  33. goosebit/settings/schema.py +60 -14
  34. goosebit/storage/__init__.py +62 -0
  35. goosebit/storage/base.py +14 -0
  36. goosebit/storage/filesystem.py +111 -0
  37. goosebit/storage/s3.py +104 -0
  38. goosebit/ui/bff/common/columns.py +50 -0
  39. goosebit/ui/bff/common/responses.py +1 -0
  40. goosebit/ui/bff/devices/device/__init__.py +1 -0
  41. goosebit/ui/bff/devices/device/routes.py +17 -0
  42. goosebit/ui/bff/devices/requests.py +1 -0
  43. goosebit/ui/bff/devices/routes.py +49 -46
  44. goosebit/ui/bff/download/routes.py +14 -3
  45. goosebit/ui/bff/rollouts/routes.py +32 -4
  46. goosebit/ui/bff/routes.py +2 -1
  47. goosebit/ui/bff/settings/__init__.py +1 -0
  48. goosebit/ui/bff/settings/routes.py +20 -0
  49. goosebit/ui/bff/settings/users/__init__.py +1 -0
  50. goosebit/ui/bff/settings/users/responses.py +33 -0
  51. goosebit/ui/bff/settings/users/routes.py +80 -0
  52. goosebit/ui/bff/software/routes.py +40 -12
  53. goosebit/ui/nav.py +12 -2
  54. goosebit/ui/routes.py +66 -13
  55. goosebit/ui/static/js/devices.js +32 -24
  56. goosebit/ui/static/js/login.js +21 -5
  57. goosebit/ui/static/js/logs.js +7 -22
  58. goosebit/ui/static/js/rollouts.js +31 -30
  59. goosebit/ui/static/js/settings.js +322 -0
  60. goosebit/ui/static/js/setup.js +28 -0
  61. goosebit/ui/static/js/software.js +127 -121
  62. goosebit/ui/static/js/util.js +25 -4
  63. goosebit/ui/templates/__init__.py +10 -1
  64. goosebit/ui/templates/login.html.jinja +5 -0
  65. goosebit/ui/templates/nav.html.jinja +13 -5
  66. goosebit/ui/templates/rollouts.html.jinja +4 -22
  67. goosebit/ui/templates/settings.html.jinja +88 -0
  68. goosebit/ui/templates/setup.html.jinja +71 -0
  69. goosebit/ui/templates/software.html.jinja +0 -11
  70. goosebit/updater/controller/v1/routes.py +119 -77
  71. goosebit/updater/routes.py +83 -8
  72. goosebit/updates/__init__.py +24 -31
  73. goosebit/updates/swdesc.py +15 -8
  74. goosebit/users/__init__.py +63 -0
  75. goosebit/util/__init__.py +0 -0
  76. goosebit/util/path.py +42 -0
  77. goosebit/util/version.py +92 -0
  78. goosebit-0.2.7.dist-info/METADATA +280 -0
  79. goosebit-0.2.7.dist-info/RECORD +133 -0
  80. {goosebit-0.2.5.dist-info → goosebit-0.2.7.dist-info}/WHEEL +1 -1
  81. goosebit/realtime/logs.py +0 -42
  82. goosebit/realtime/routes.py +0 -13
  83. goosebit/updater/manager.py +0 -325
  84. goosebit-0.2.5.dist-info/METADATA +0 -189
  85. goosebit-0.2.5.dist-info/RECORD +0 -99
  86. /goosebit/{realtime → api/v1/settings}/__init__.py +0 -0
  87. {goosebit-0.2.5.dist-info → goosebit-0.2.7.dist-info}/LICENSE +0 -0
  88. {goosebit-0.2.5.dist-info → goosebit-0.2.7.dist-info}/entry_points.txt +0 -0
@@ -1,325 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import asyncio
4
- import re
5
- from abc import ABC, abstractmethod
6
- from contextlib import asynccontextmanager
7
- from datetime import datetime
8
- from enum import StrEnum
9
- from typing import Callable
10
-
11
- from aiocache import cached, caches
12
-
13
- from goosebit.db.models import (
14
- Device,
15
- Hardware,
16
- Rollout,
17
- Software,
18
- UpdateModeEnum,
19
- UpdateStateEnum,
20
- )
21
- from goosebit.settings import config
22
-
23
- caches.set_config(
24
- {
25
- "default": {
26
- "cache": "aiocache.SimpleMemoryCache",
27
- "serializer": {"class": "aiocache.serializers.PickleSerializer"},
28
- "ttl": 600,
29
- },
30
- }
31
- )
32
-
33
-
34
- class HandlingType(StrEnum):
35
- SKIP = "skip"
36
- ATTEMPT = "attempt"
37
- FORCED = "forced"
38
-
39
-
40
- class UpdateManager(ABC):
41
- device_log_subscriptions: dict[str, list[Callable]] = {}
42
- device_poll_time: dict[str, str] = {}
43
-
44
- def __init__(self, dev_id: str):
45
- self.dev_id = dev_id
46
-
47
- async def get_device(self) -> Device | None:
48
- return None
49
-
50
- async def update_force_update(self, force_update: bool) -> None:
51
- return
52
-
53
- async def update_sw_version(self, version: str) -> None:
54
- return
55
-
56
- async def update_hardware(self, hardware: Hardware) -> None:
57
- return
58
-
59
- async def update_device_state(self, state: UpdateStateEnum) -> None:
60
- return
61
-
62
- async def update_last_connection(self, last_seen: int, last_ip: str | None = None) -> None:
63
- return
64
-
65
- async def update_update(self, update_mode: UpdateModeEnum, software: Software | None):
66
- return
67
-
68
- async def update_name(self, name: str):
69
- return
70
-
71
- async def update_feed(self, feed: str):
72
- return
73
-
74
- async def update_config_data(self, **kwargs):
75
- return
76
-
77
- async def deployment_action_success(self):
78
- return
79
-
80
- async def clear_log(self) -> None:
81
- return
82
-
83
- async def get_rollout(self) -> Rollout | None:
84
- return None
85
-
86
- @asynccontextmanager
87
- async def subscribe_log(self, callback: Callable):
88
- device = await self.get_device()
89
- # do not modify, breaks when combined
90
- subscribers = self.log_subscribers
91
- subscribers.append(callback)
92
- self.log_subscribers = subscribers
93
-
94
- if device is not None:
95
- await callback(device.last_log)
96
- try:
97
- yield
98
- except asyncio.CancelledError:
99
- pass
100
- finally:
101
- # do not modify, breaks when combined
102
- subscribers = self.log_subscribers
103
- subscribers.remove(callback)
104
- self.log_subscribers = subscribers
105
-
106
- @property
107
- def poll_seconds(self):
108
- time_obj = datetime.strptime(self.poll_time, "%H:%M:%S")
109
- return time_obj.hour * 3600 + time_obj.minute * 60 + time_obj.second
110
-
111
- @property
112
- def log_subscribers(self):
113
- return UpdateManager.device_log_subscriptions.get(self.dev_id, [])
114
-
115
- @log_subscribers.setter
116
- def log_subscribers(self, value: list):
117
- UpdateManager.device_log_subscriptions[self.dev_id] = value
118
-
119
- @property
120
- def poll_time(self):
121
- return UpdateManager.device_poll_time.get(self.dev_id, config.poll_time_default)
122
-
123
- @poll_time.setter
124
- def poll_time(self, value: str):
125
- if not value == config.poll_time_default:
126
- UpdateManager.device_poll_time[self.dev_id] = value
127
- return
128
- if self.dev_id in UpdateManager.device_poll_time:
129
- del UpdateManager.device_poll_time[self.dev_id]
130
-
131
- async def publish_log(self, log_data: str | None):
132
- for cb in self.log_subscribers:
133
- await cb(log_data)
134
-
135
- @abstractmethod
136
- async def get_update(self) -> tuple[HandlingType, Software | None]: ...
137
-
138
- @abstractmethod
139
- async def update_log(self, log_data: str) -> None: ...
140
-
141
-
142
- class DeviceUpdateManager(UpdateManager):
143
- hardware_default = None
144
-
145
- @cached(key_builder=lambda fn, self: self.dev_id, alias="default")
146
- async def get_device(self) -> Device:
147
- hardware = DeviceUpdateManager.hardware_default
148
- if hardware is None:
149
- hardware = (await Hardware.get_or_create(model="default", revision="default"))[0]
150
- DeviceUpdateManager.hardware_default = hardware
151
-
152
- return (await Device.get_or_create(uuid=self.dev_id, defaults={"hardware": hardware}))[0]
153
-
154
- async def save_device(self, device: Device, update_fields: list[str]):
155
- await device.save(update_fields=update_fields)
156
-
157
- # only update cache after a successful database save
158
- result = await caches.get("default").set(self.dev_id, device, ttl=600)
159
- assert result, "device being cached"
160
-
161
- async def update_force_update(self, force_update: bool) -> None:
162
- device = await self.get_device()
163
- device.force_update = force_update
164
- await self.save_device(device, update_fields=["force_update"])
165
-
166
- async def update_sw_version(self, version: str) -> None:
167
- device = await self.get_device()
168
- device.sw_version = version
169
- await self.save_device(device, update_fields=["sw_version"])
170
-
171
- async def update_hardware(self, hardware: Hardware) -> None:
172
- device = await self.get_device()
173
- device.hardware = hardware
174
- await self.save_device(device, update_fields=["hardware"])
175
-
176
- async def update_device_state(self, state: UpdateStateEnum) -> None:
177
- device = await self.get_device()
178
- device.last_state = state
179
- await self.save_device(device, update_fields=["last_state"])
180
-
181
- async def update_last_connection(self, last_seen: int, last_ip: str | None = None) -> None:
182
- device = await self.get_device()
183
- device.last_seen = last_seen
184
- if last_ip is None:
185
- await self.save_device(device, update_fields=["last_seen"])
186
- elif ":" in last_ip:
187
- device.last_ipv6 = last_ip
188
- await self.save_device(device, update_fields=["last_seen", "last_ipv6"])
189
- else:
190
- device.last_ip = last_ip
191
- await self.save_device(device, update_fields=["last_seen", "last_ip"])
192
-
193
- async def update_update(self, update_mode: UpdateModeEnum, software: Software | None):
194
- device = await self.get_device()
195
- device.assigned_software = software
196
- device.update_mode = update_mode
197
- await self.save_device(device, update_fields=["assigned_software_id", "update_mode"])
198
-
199
- async def update_name(self, name: str):
200
- device = await self.get_device()
201
- device.name = name
202
- await self.save_device(device, update_fields=["name"])
203
-
204
- async def update_feed(self, feed: str):
205
- device = await self.get_device()
206
- device.feed = feed
207
- await self.save_device(device, update_fields=["feed"])
208
-
209
- async def update_config_data(self, **kwargs):
210
- model = kwargs.get("hw_boardname") or "default"
211
- revision = kwargs.get("hw_revision") or "default"
212
- sw_version = kwargs.get("sw_version")
213
-
214
- hardware = (await Hardware.get_or_create(model=model, revision=revision))[0]
215
- device = await self.get_device()
216
- modified = False
217
-
218
- if device.hardware != hardware:
219
- device.hardware = hardware
220
- modified = True
221
-
222
- if device.last_state == UpdateStateEnum.UNKNOWN:
223
- device.last_state = UpdateStateEnum.REGISTERED
224
- modified = True
225
-
226
- if device.sw_version != sw_version:
227
- device.sw_version = sw_version
228
- modified = True
229
-
230
- if modified:
231
- await self.save_device(device, update_fields=["hardware_id", "last_state", "sw_version"])
232
-
233
- async def deployment_action_success(self):
234
- device = await self.get_device()
235
- device.progress = 100
236
- await self.save_device(device, update_fields=["progress"])
237
-
238
- async def get_rollout(self) -> Rollout | None:
239
- device = await self.get_device()
240
-
241
- if device.update_mode == UpdateModeEnum.ROLLOUT:
242
- return (
243
- await Rollout.filter(
244
- feed=device.feed,
245
- paused=False,
246
- software__compatibility__devices__uuid=device.uuid,
247
- )
248
- .order_by("-created_at")
249
- .first()
250
- .prefetch_related("software")
251
- )
252
-
253
- return None
254
-
255
- async def _get_software(self) -> Software | None:
256
- device = await self.get_device()
257
-
258
- if device.update_mode == UpdateModeEnum.ROLLOUT:
259
- rollout = await self.get_rollout()
260
- if not rollout or rollout.paused:
261
- return None
262
- await rollout.fetch_related("software")
263
- return rollout.software
264
- if device.update_mode == UpdateModeEnum.ASSIGNED:
265
- await device.fetch_related("assigned_software")
266
- return device.assigned_software
267
-
268
- if device.update_mode == UpdateModeEnum.LATEST:
269
- return await Software.latest(device)
270
-
271
- assert device.update_mode == UpdateModeEnum.PINNED
272
- return None
273
-
274
- async def get_update(self) -> tuple[HandlingType, Software | None]:
275
- device = await self.get_device()
276
- software = await self._get_software()
277
-
278
- if software is None:
279
- handling_type = HandlingType.SKIP
280
-
281
- elif software.version == device.sw_version and not device.force_update:
282
- handling_type = HandlingType.SKIP
283
-
284
- elif device.last_state == UpdateStateEnum.ERROR and not device.force_update:
285
- handling_type = HandlingType.SKIP
286
-
287
- else:
288
- handling_type = HandlingType.FORCED
289
-
290
- return handling_type, software
291
-
292
- async def update_log(self, log_data: str) -> None:
293
- if log_data is None:
294
- return
295
- device = await self.get_device()
296
-
297
- if device.last_log is None:
298
- device.last_log = ""
299
-
300
- # SWUpdate-specific log parsing to report progress
301
- matches = re.findall(r"Downloaded (\d+)%", log_data)
302
- if matches:
303
- device.progress = matches[-1]
304
-
305
- device.last_log += f"{log_data}\n"
306
- await self.publish_log(f"{log_data}\n")
307
-
308
- await self.save_device(device, update_fields=["progress", "last_log"])
309
-
310
- async def clear_log(self) -> None:
311
- device = await self.get_device()
312
- device.last_log = ""
313
- await self.save_device(device, update_fields=["last_log"])
314
- await self.publish_log(None)
315
-
316
-
317
- async def get_update_manager(dev_id: str) -> UpdateManager:
318
- return DeviceUpdateManager(dev_id)
319
-
320
-
321
- async def delete_devices(ids: list[str]):
322
- await Device.filter(uuid__in=ids).delete()
323
- for dev_id in ids:
324
- result = await caches.get("default").delete(dev_id)
325
- assert result == 1, "device has been cached"
@@ -1,189 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: goosebit
3
- Version: 0.2.5
4
- Summary:
5
- Author: Upstream Data
6
- Author-email: brett@upstreamdata.ca
7
- Requires-Python: >=3.11,<4.0
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Programming Language :: Python :: 3.11
10
- Classifier: Programming Language :: Python :: 3.12
11
- Classifier: Programming Language :: Python :: 3.13
12
- Provides-Extra: postgresql
13
- Requires-Dist: aerich (>=0.7.2,<0.8.0)
14
- Requires-Dist: aiocache (>=0.12.2,<0.13.0)
15
- Requires-Dist: argon2-cffi (>=23.1.0,<24.0.0)
16
- Requires-Dist: asyncpg (>=0.29.0,<0.30.0) ; extra == "postgresql"
17
- Requires-Dist: fastapi[uvicorn] (>=0.111.0,<0.112.0)
18
- Requires-Dist: httpx (>=0.27.0,<0.28.0)
19
- Requires-Dist: itsdangerous (>=2.2.0,<3.0.0)
20
- Requires-Dist: jinja2 (>=3.1.4,<4.0.0)
21
- Requires-Dist: joserfc (>=1.0.0,<2.0.0)
22
- Requires-Dist: libconf (>=2.0.1,<3.0.0)
23
- Requires-Dist: opentelemetry-distro (>=0.49b1,<0.50)
24
- Requires-Dist: opentelemetry-exporter-prometheus (>=0.49b1,<0.50)
25
- Requires-Dist: opentelemetry-instrumentation-fastapi (>=0.49b1,<0.50)
26
- Requires-Dist: pydantic-settings (>=2.4.0,<3.0.0)
27
- Requires-Dist: python-multipart (>=0.0.9,<0.0.10)
28
- Requires-Dist: semver (>=3.0.2,<4.0.0)
29
- Requires-Dist: tortoise-orm (>=0.21.4,<0.22.0)
30
- Requires-Dist: websockets (>=12.0,<13.0)
31
- Description-Content-Type: text/markdown
32
-
33
- # gooseBit
34
-
35
- <img src="docs/img/goosebit-logo.png" style="width: 100px; height: 100px; display: block;">
36
-
37
- [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/UpstreamDataInc/goosebit/badge)](https://scorecard.dev/viewer/?uri=github.com/UpstreamDataInc/goosebit)
38
-
39
- ---
40
-
41
- A simplistic, opinionated remote update server implementing hawkBit™'s [DDI API](https://eclipse.dev/hawkbit/apis/ddi_api/).
42
-
43
- ## Quick Start
44
-
45
- ### Installation
46
-
47
- 1. Install dependencies using [Poetry](https://python-poetry.org/):
48
-
49
- ```bash
50
- poetry install
51
- ```
52
-
53
- 2. Create the database:
54
-
55
- ```bash
56
- poetry run aerich upgrade
57
- ```
58
-
59
- 3. Launch gooseBit:
60
- ```bash
61
- python main.py
62
- ```
63
-
64
- ### Initial Configuration
65
-
66
- Before running gooseBit for the first time, update the default credentials in `settings.yaml`. The default login for testing purposes is:
67
-
68
- - **Username:** `admin@goosebit.local`
69
- - **Password:** `admin`
70
-
71
- ## Assumptions
72
-
73
- - Devices use [SWUpdate](https://swupdate.org) for managing software updates.
74
-
75
- ## Features
76
-
77
- ### Device Registry
78
-
79
- When a device connects to gooseBit for the first time, it is automatically added to the device registry. The server will then request the device's configuration data, including:
80
-
81
- - `hw_model` and `hw_revision`: Used to match compatible software.
82
- - `sw_version`: Indicates the currently installed software version.
83
-
84
- The registry tracks each device's status, including the last online timestamp, installed software version, update state, and more.
85
-
86
- ### Software Repository
87
-
88
- Software packages (`*.swu` files) can be hosted directly on the gooseBit server or on an external server. gooseBit parses the software metadata to determine compatibility with specific hardware models and revisions.
89
-
90
- ### Device Update Modes
91
-
92
- Devices can be configured with different update modes. The default mode is `Rollout`.
93
-
94
- #### 1. Manual Update to Specified Software
95
-
96
- Assign specific software to a device manually. Once installed, no further updates will be triggered.
97
-
98
- #### 2. Automatic Update to Latest Software
99
-
100
- Automatically updates the device to the latest compatible software, based on the reported `hw_model` and `hw_revision`. Note: versions are interpreted as [SemVer](https://semver.org) versions.
101
-
102
- #### 3. Software Rollout
103
-
104
- Rollouts target all devices with a specified "feed" value, ensuring that the assigned software is installed on all matching devices. Rollouts also track success and error rates, with future plans for automatic aborts. If multiple rollouts exist for the same feed, the most recent rollout takes precedence.
105
-
106
- ### Pause Updates
107
-
108
- Devices can be pinned to their current software version, preventing any updates from being applied.
109
-
110
- ### Real-time Update Logs
111
-
112
- While updates are in progress, gooseBit captures real-time logs, which are accessible through the device repository.
113
-
114
- ## Development
115
-
116
- ### Database
117
-
118
- Create or upgrade database
119
-
120
- ```bash
121
- poetry run aerich upgrade
122
- ```
123
-
124
- After a model change create the migration
125
-
126
- ```bash
127
- poetry run aerich migrate
128
- ```
129
-
130
- To seed some sample data (attention: drops all current data) use
131
-
132
- ```bash
133
- poetry run generate-sample-data
134
- ```
135
-
136
- ### Code formatting and linting
137
-
138
- Code is formatted using different tools
139
-
140
- - black and isort for `*.py`
141
- - biomejs for `*.js`, `*.json`
142
- - prettier for `*.html`, `*.md`, `*.yml`, `*.yaml`
143
-
144
- Code is linted using different tools as well
145
-
146
- - flake8 for `*.py`
147
- - biomejs for `*.js`
148
-
149
- Best to have pre-commit install git hooks that run all those tools before a commit:
150
-
151
- ```bash
152
- poetry run pre-commit install
153
- ```
154
-
155
- To manually apply the hooks to all files use:
156
-
157
- ```bash
158
- poetry run pre-commit run --all-files
159
- ```
160
-
161
- ### Testing
162
-
163
- Tests are implemented using pytest. To run all tests
164
-
165
- ```bash
166
- poetry run pytest
167
- ```
168
-
169
- ### Structure
170
-
171
- The structure of gooseBit is as follows:
172
-
173
- - `api`: Files for the API.
174
- - `ui`: Files for the UI.
175
- - `bff`: Backend for frontend API.
176
- - `static`: Static files.
177
- - `templates`: Jinja2 formatted templates.
178
- - `nav`: Navbar handler.
179
- - `updater`: DDI API handler and device update manager.
180
- - `updates`: SWUpdate file parsing.
181
- - `realtime`: Realtime API functionality with websockets.
182
- - `auth`: Authentication functions and permission handling.
183
- - `models`: Database models.
184
- - `db`: Database config and initialization.
185
- - `schema`: Pydantic models used for API type hinting.
186
- - `settings`: Settings loader and handler.
187
- - `telemetry`: Telemetry data handlers.
188
- - `routes`: Routes for a giving endpoint, including the router.
189
-
@@ -1,99 +0,0 @@
1
- goosebit/__init__.py,sha256=qHY0WDT5n5PdX6D6tv2bAsBwBfMw-bcjEjKotIQDAzg,4158
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=rxdu7A2_NRcPV1ofXntrqE1elH9H2GuKE5kO07xQtXM,1173
17
- goosebit/api/v1/devices/requests.py,sha256=zx9eWXQanqBnmy-XboA_lrzInfx9ozOB4vG2jRTwnJA,131
18
- goosebit/api/v1/devices/responses.py,sha256=NmaxpXKJ_YsQ92mvoTXG8HYB39_HLFmZaHo_uvXFmhg,185
19
- goosebit/api/v1/devices/routes.py,sha256=pcQx5U-KJXnGjbwheINOtdWtdcJOz3mvnRVcefAph10,1683
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=yKljabI1wQB6MXNXrF4aIi5csKRCo0A96RqTW6OwAjE,311
25
- goosebit/api/v1/rollouts/routes.py,sha256=afUQTjzrpLbHA68CEpMDMZ93C6-jp8JLrdjUEYUFm7o,1927
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=l41-JWqyJmmEslXkx4NEJsGOqMrAB__nBUZS3E_9le0,192
30
- goosebit/api/v1/software/routes.py,sha256=r8lSPg1eQajPF6990DwnbJpDsuzW53rC5ktLuG3zyjA,3209
31
- goosebit/auth/__init__.py,sha256=VWrDFMfYfQpGV6Pc4xkb2zbUiQATpcsv5PbTiXQ9FoU,4703
32
- goosebit/db/__init__.py,sha256=ktnV4302iWzzTr-3oHXv3ud-23qTcAJ4tPLAXyTZZJw,462
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/migrations/models/1_20241109151811_update.py,sha256=cPbX-8Ga7HYKHQkESR5FXNlS-s-bsx4bsLrXvcfVIkQ,309
36
- goosebit/db/models.py,sha256=4gi-FsNJFLiAVNxV61Eb4qU8_Qn3zDNQmc2ngqDt_uU,5150
37
- goosebit/realtime/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
38
- goosebit/realtime/logs.py,sha256=sjGg2n7H-4j4i02Kbq_YlECLFp33mmOmaoAZXeE8J9Y,1225
39
- goosebit/realtime/routes.py,sha256=yLHrA-BHHU5cpUNvxAPVxWC3KcB9aUrV31HT_th24Ls,265
40
- goosebit/schema/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
- goosebit/schema/devices.py,sha256=mUmPLLEF506eKAxVPuUv5echqeHrkLIEuhNCHxL5y0Y,2659
42
- goosebit/schema/rollouts.py,sha256=Kp--XRC39SDs1bKf59q8fBHzitoW88ZMN1GVlbCQotQ,882
43
- goosebit/schema/software.py,sha256=W02rA0guQ7zeeVMTdwxt6EjCnTdf-9JewVJRqdN_GK0,951
44
- goosebit/settings/__init__.py,sha256=UkJa_G_VJckNf16Em0zz8sXgEi5pzxHFvZ96oa9MC3k,360
45
- goosebit/settings/const.py,sha256=hOpeA9pGmbU4V4mqcfhpHV7cmEjf2GIenmzCSz2Jpj8,779
46
- goosebit/settings/schema.py,sha256=ffDhMsyW6_TGIReJRiw3hisKUretdf1PnISTJ0yBe0o,2577
47
- goosebit/ui/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
48
- goosebit/ui/bff/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
49
- goosebit/ui/bff/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
- goosebit/ui/bff/common/requests.py,sha256=Db8AohkRfsjUbx3pbdKVva7ZkLhAfBiDwAA221OXd5M,1183
51
- goosebit/ui/bff/common/responses.py,sha256=3LLIrBhsjdELnZ4kFc5-QDDYwdn7OvcD_2CObiJ6NME,303
52
- goosebit/ui/bff/common/util.py,sha256=_hz89EFLAL1UvbIvJJJSF80PL4T1mz1Vk7354wHBzOE,1144
53
- goosebit/ui/bff/devices/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
54
- goosebit/ui/bff/devices/requests.py,sha256=YbTUsyVLQZaUnn3dJ6AQe0mZl_H5aQC6QKCRxhCjGJc,286
55
- goosebit/ui/bff/devices/responses.py,sha256=4279xeu-soqgyOc4ncKZpai6juBQ3dl096p2LgtIu2s,1208
56
- goosebit/ui/bff/devices/routes.py,sha256=9WoGBheOkOT5-UDlQ0rtK-MJF4MNPuHxz3mBxdVhbWU,5260
57
- goosebit/ui/bff/download/__init__.py,sha256=kzt5TBA-bJmDZFXYPohggbMOW7GhklyflaRKZNC0yDY,42
58
- goosebit/ui/bff/download/routes.py,sha256=wdeXA-dGO4D9FxaUP6OnUSW4_2NZhi_VgIxMcSRL2x4,675
59
- goosebit/ui/bff/rollouts/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
60
- goosebit/ui/bff/rollouts/responses.py,sha256=gFEzWkFUBIka98lrA0ivdXBiTqaU5AugfJm2F3p33uk,1180
61
- goosebit/ui/bff/rollouts/routes.py,sha256=ZO-mkDzTgzssBtIbhOqSxrPgZYMVPsMW4EXw3f8YA1o,1553
62
- goosebit/ui/bff/routes.py,sha256=wx7so5Kfleop7ZdTEnMFSQ6P_Eck8iX6TnAqyetd3wM,428
63
- goosebit/ui/bff/software/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
64
- goosebit/ui/bff/software/responses.py,sha256=e7zMP8q2uWgKRy1q5Jp0HTNx-lggljOy2BFQRJglDSo,1920
65
- goosebit/ui/bff/software/routes.py,sha256=xwLsI6wTJzu_-eWAV1f8DlEWw67i22LK72D-uYLRMu0,3220
66
- goosebit/ui/nav.py,sha256=l-MCY2AN5v6jDCSDZoka3qjQ2_24SjLqUi1dYtxCjIM,378
67
- goosebit/ui/routes.py,sha256=J6Nkh1yShO810IRU3vjFlZCwYOy_qgwPCiOhHP3szbw,2015
68
- goosebit/ui/static/__init__.py,sha256=AsZiM3h9chQ_YaBbAD6TVFf0244Q9DetkDMl70q8swQ,135
69
- goosebit/ui/static/favicon.ico,sha256=MKAqEjW7F1-j2dmW4s08y4U0dFteIlU7rpLKakU0Lxk,4286
70
- goosebit/ui/static/favicon.svg,sha256=8zvM7o2Ob3YmTEV6Rj7rCTgSajvY6wIaAQAAjWwlDsw,8733
71
- goosebit/ui/static/js/devices.js,sha256=zseDgA1PnnreNajQRx7F38GuS_BjQugVtQLNPUhI3xg,13635
72
- goosebit/ui/static/js/login.js,sha256=9qlXki3udm5CmAO8UXUJe94Kc_-SJekdKjfH9TzJhgk,579
73
- goosebit/ui/static/js/logs.js,sha256=DgOWLKUvK-mM-oYRA0JJTIhtiCiegb5qCJ7pE9H2I3k,863
74
- goosebit/ui/static/js/rollouts.js,sha256=bswZSdPvDGSctpxyw2qhnuslTPugXDqkIMVBMYiY-Gc,7484
75
- goosebit/ui/static/js/software.js,sha256=3B9F6mLkvKhl0K1YJn4qkw5Wj4u_V9gabbz3ix84tDI,8728
76
- goosebit/ui/static/js/util.js,sha256=GlF2eVL6iGUWEbBgxzmLX4r3x5bLYh1YiBN7okX9dVE,4593
77
- goosebit/ui/static/svg/goosebit-logo.svg,sha256=8zvM7o2Ob3YmTEV6Rj7rCTgSajvY6wIaAQAAjWwlDsw,8733
78
- goosebit/ui/templates/__init__.py,sha256=xHCio6TnuNnYKLOZ3VhvbxyJPZ2ddzTSle_RmiuLrrA,378
79
- goosebit/ui/templates/devices.html.jinja,sha256=G2t3pKIe0e7LDkm9H0YwDddSvyXY4PaSPM11Rl9d1BY,5773
80
- goosebit/ui/templates/login.html.jinja,sha256=eQX1ypZ5iJ2L4v4lw8VB7GKluUjUYmpoUyUy8jY98Og,3050
81
- goosebit/ui/templates/logs.html.jinja,sha256=JLHogVHWcP_Y321dd-gHexNqix-PFfGVpEKbNmzsMU8,1310
82
- goosebit/ui/templates/nav.html.jinja,sha256=cdAM-9RZG_taugisf9Sh-UcRT3jLHAqNGbf9r7mk8ow,5725
83
- goosebit/ui/templates/rollouts.html.jinja,sha256=s9kIxRYO8OypqIQH8tQUwZJDDjsex89piuV4kh1eqwE,4728
84
- goosebit/ui/templates/software.html.jinja,sha256=CTIeNkPMJMvtDVWbcKl_BjvtAFV3bskpCcNg-lCJIZM,7551
85
- goosebit/updater/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
86
- goosebit/updater/controller/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
87
- goosebit/updater/controller/routes.py,sha256=8CnLb-kDuO-yFeWdu4apIyctCf9OzvJ011Az3QDGemU,123
88
- goosebit/updater/controller/v1/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
89
- goosebit/updater/controller/v1/routes.py,sha256=lJFx3gi01iI7ep_omPg5pI4TSVRa13kqfFSeXKuGIpY,7273
90
- goosebit/updater/controller/v1/schema.py,sha256=NwSyPx3A38iFabfOfzaVtxPozJQNacikP5WOhxMHqdg,1228
91
- goosebit/updater/manager.py,sha256=b_OqH5TBHicd1cZNzvkwlgpjXTa5N4BOe6oqiNXgYLI,10692
92
- goosebit/updater/routes.py,sha256=BYH8qdSUO99MQo09IQOkPNnmNZS74O5IZqIKcuNkNkY,647
93
- goosebit/updates/__init__.py,sha256=RIq3m7x0-ByWjU21llKZSnj4a0Hg27iSNMfO1G4Kjwc,4284
94
- goosebit/updates/swdesc.py,sha256=f2GxYwDvxgNsCTV_3RBlQ5oOWfB9cpNZuQBMMuZyZn8,3154
95
- goosebit-0.2.5.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
96
- goosebit-0.2.5.dist-info/METADATA,sha256=nHUxHj5Qy3KK8K542dcMbzTxSZ3wtTErq-OCGejBqz8,5801
97
- goosebit-0.2.5.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
98
- goosebit-0.2.5.dist-info/entry_points.txt,sha256=5p3wNB9_WEljksEBgZmOxO0DrBVhRxP20JD9JJ_lpb4,57
99
- goosebit-0.2.5.dist-info/RECORD,,
File without changes