goosebit 0.2.3__py3-none-any.whl → 0.2.5__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 (50) hide show
  1. goosebit/__init__.py +32 -3
  2. goosebit/api/v1/devices/device/routes.py +10 -4
  3. goosebit/api/v1/devices/responses.py +0 -7
  4. goosebit/api/v1/devices/routes.py +19 -3
  5. goosebit/api/v1/rollouts/responses.py +2 -7
  6. goosebit/api/v1/rollouts/routes.py +7 -3
  7. goosebit/api/v1/software/responses.py +0 -7
  8. goosebit/api/v1/software/routes.py +24 -11
  9. goosebit/auth/__init__.py +12 -8
  10. goosebit/db/__init__.py +12 -1
  11. goosebit/db/migrations/models/1_20241109151811_update.py +11 -0
  12. goosebit/db/models.py +19 -4
  13. goosebit/realtime/logs.py +1 -1
  14. goosebit/schema/devices.py +42 -38
  15. goosebit/schema/rollouts.py +21 -18
  16. goosebit/schema/software.py +24 -19
  17. goosebit/settings/schema.py +2 -0
  18. goosebit/ui/bff/common/__init__.py +0 -0
  19. goosebit/ui/bff/common/requests.py +44 -0
  20. goosebit/ui/bff/common/responses.py +16 -0
  21. goosebit/ui/bff/common/util.py +32 -0
  22. goosebit/ui/bff/devices/responses.py +15 -19
  23. goosebit/ui/bff/devices/routes.py +61 -7
  24. goosebit/ui/bff/rollouts/responses.py +15 -19
  25. goosebit/ui/bff/rollouts/routes.py +8 -6
  26. goosebit/ui/bff/routes.py +4 -2
  27. goosebit/ui/bff/software/responses.py +29 -19
  28. goosebit/ui/bff/software/routes.py +29 -16
  29. goosebit/ui/nav.py +1 -1
  30. goosebit/ui/routes.py +10 -19
  31. goosebit/ui/static/js/devices.js +188 -94
  32. goosebit/ui/static/js/rollouts.js +20 -13
  33. goosebit/ui/static/js/software.js +5 -11
  34. goosebit/ui/static/js/util.js +43 -14
  35. goosebit/ui/templates/devices.html.jinja +77 -49
  36. goosebit/ui/templates/nav.html.jinja +35 -4
  37. goosebit/ui/templates/rollouts.html.jinja +23 -23
  38. goosebit/updater/controller/v1/routes.py +33 -23
  39. goosebit/updater/controller/v1/schema.py +4 -4
  40. goosebit/updater/manager.py +28 -52
  41. goosebit/updater/routes.py +6 -2
  42. goosebit/updates/__init__.py +14 -21
  43. goosebit/updates/swdesc.py +36 -15
  44. {goosebit-0.2.3.dist-info → goosebit-0.2.5.dist-info}/METADATA +23 -7
  45. {goosebit-0.2.3.dist-info → goosebit-0.2.5.dist-info}/RECORD +48 -44
  46. {goosebit-0.2.3.dist-info → goosebit-0.2.5.dist-info}/WHEEL +1 -1
  47. goosebit-0.2.5.dist-info/entry_points.txt +3 -0
  48. goosebit/ui/static/js/index.js +0 -155
  49. goosebit/ui/templates/index.html.jinja +0 -25
  50. {goosebit-0.2.3.dist-info → goosebit-0.2.5.dist-info}/LICENSE +0 -0
@@ -3,14 +3,18 @@ import time
3
3
  from fastapi import APIRouter, Depends
4
4
  from fastapi.requests import Request
5
5
 
6
+ from goosebit.settings import config
7
+
6
8
  from . import controller
7
9
  from .manager import get_update_manager
8
10
 
9
11
 
10
12
  async def log_last_connection(request: Request, dev_id: str):
11
- host = request.client.host
12
13
  updater = await get_update_manager(dev_id)
13
- await updater.update_last_connection(round(time.time()), host)
14
+ if config.track_device_ip:
15
+ await updater.update_last_connection(round(time.time()), request.client.host)
16
+ else:
17
+ await updater.update_last_connection(round(time.time()))
14
18
 
15
19
 
16
20
  router = APIRouter(
@@ -1,8 +1,9 @@
1
- import shutil
2
- from pathlib import Path
1
+ from __future__ import annotations
2
+
3
3
  from urllib.parse import unquote, urlparse
4
4
  from urllib.request import url2pathname
5
5
 
6
+ from anyio import Path
6
7
  from fastapi import HTTPException
7
8
  from fastapi.requests import Request
8
9
  from tortoise.expressions import Q
@@ -10,6 +11,7 @@ from tortoise.expressions import Q
10
11
  from goosebit.db.models import Hardware, Software
11
12
  from goosebit.updater.manager import UpdateManager
12
13
 
14
+ from ..settings import config
13
15
  from . import swdesc
14
16
 
15
17
 
@@ -18,6 +20,8 @@ async def create_software_update(uri: str, temp_file: Path | None) -> Software:
18
20
 
19
21
  # parse swu header into update_info
20
22
  if parsed_uri.scheme == "file":
23
+ if temp_file is None:
24
+ raise HTTPException(500, "Temporary file missing, cannot parse file information")
21
25
  try:
22
26
  update_info = await swdesc.parse_file(temp_file)
23
27
  except Exception:
@@ -28,7 +32,6 @@ async def create_software_update(uri: str, temp_file: Path | None) -> Software:
28
32
  update_info = await swdesc.parse_remote(uri)
29
33
  except Exception:
30
34
  raise HTTPException(422, "Software swu header cannot be parsed")
31
-
32
35
  else:
33
36
  raise HTTPException(422, "Software URI protocol unknown")
34
37
 
@@ -42,10 +45,14 @@ async def create_software_update(uri: str, temp_file: Path | None) -> Software:
42
45
 
43
46
  # for local file: rename temp file to final name
44
47
  if parsed_uri.scheme == "file":
45
- path = _unique_path(parsed_uri)
46
- path.parent.mkdir(parents=True, exist_ok=True)
47
- shutil.copy(temp_file, path)
48
- uri = path.absolute().as_uri()
48
+ if temp_file is None:
49
+ raise HTTPException(500, "Temporary file missing, cannot parse file information")
50
+ filename = Path(url2pathname(unquote(parsed_uri.path))).name
51
+ path = Path(config.artifacts_dir).joinpath(update_info["hash"], filename)
52
+ await path.parent.mkdir(parents=True, exist_ok=True)
53
+ await temp_file.replace(path)
54
+ absolute = await path.absolute()
55
+ uri = absolute.as_uri()
49
56
 
50
57
  # create software
51
58
  software = await Software.create(
@@ -85,20 +92,6 @@ async def _is_software_colliding(update_info):
85
92
  return is_colliding
86
93
 
87
94
 
88
- def _unique_path(uri):
89
- path = Path(url2pathname(unquote(uri.path)))
90
- if not path.exists():
91
- return path
92
-
93
- counter = 1
94
- new_path = path.with_name(f"{path.stem}-{counter}{path.suffix}")
95
- while new_path.exists():
96
- counter += 1
97
- new_path = path.with_name(f"{path.stem}-{counter}{path.suffix}")
98
-
99
- return new_path
100
-
101
-
102
95
  async def generate_chunk(request: Request, updater: UpdateManager) -> list:
103
96
  _, software = await updater.get_update()
104
97
  if software is None:
@@ -1,12 +1,15 @@
1
1
  import hashlib
2
2
  import logging
3
- from pathlib import Path
3
+ import random
4
+ import string
4
5
  from typing import Any
5
6
 
6
- import aiofiles
7
7
  import httpx
8
8
  import libconf
9
9
  import semver
10
+ from anyio import AsyncFile, Path, open_file
11
+
12
+ from goosebit.settings import config
10
13
 
11
14
  logger = logging.getLogger(__name__)
12
15
 
@@ -20,8 +23,8 @@ def _append_compatibility(boardname, value, compatibility):
20
23
  def parse_descriptor(swdesc: libconf.AttrDict[Any, Any | None]):
21
24
  swdesc_attrs = {}
22
25
  try:
23
- swdesc_attrs["version"] = semver.Version.parse(swdesc["software"]["version"])
24
- compatibility = []
26
+ swdesc_attrs["version"] = semver.Version.parse(swdesc["software"]["version"], optional_minor_and_patch=True)
27
+ compatibility: list[dict[str, str]] = []
25
28
  _append_compatibility("default", swdesc["software"], compatibility)
26
29
 
27
30
  for key in swdesc["software"]:
@@ -41,7 +44,7 @@ def parse_descriptor(swdesc: libconf.AttrDict[Any, Any | None]):
41
44
 
42
45
 
43
46
  async def parse_file(file: Path):
44
- async with aiofiles.open(file, "r+b") as f:
47
+ async with await open_file(file, "r+b") as f:
45
48
  # get file size
46
49
  size = int((await f.read(110))[54:62], 16)
47
50
  filename = b""
@@ -59,20 +62,38 @@ async def parse_file(file: Path):
59
62
  swdesc = libconf.loads((await f.read(size)).decode("utf-8"))
60
63
 
61
64
  swdesc_attrs = parse_descriptor(swdesc)
62
- swdesc_attrs["size"] = file.stat().st_size
63
- swdesc_attrs["hash"] = _sha1_hash_file(file)
65
+ stat = await file.stat()
66
+ swdesc_attrs["size"] = stat.st_size
67
+ swdesc_attrs["hash"] = await _sha1_hash_file(f)
64
68
  return swdesc_attrs
65
69
 
66
70
 
67
71
  async def parse_remote(url: str):
68
72
  async with httpx.AsyncClient() as c:
69
73
  file = await c.get(url)
70
- async with aiofiles.tempfile.NamedTemporaryFile("w+b") as f:
71
- await f.write(file.content)
72
- return await parse_file(Path(f.name))
73
-
74
-
75
- def _sha1_hash_file(file_path: Path):
76
- with file_path.open("rb") as f:
77
- sha1_hash = hashlib.file_digest(f, "sha1")
74
+ artifacts_dir = Path(config.artifacts_dir)
75
+ tmp_file_path = artifacts_dir.joinpath("tmp", ("".join(random.choices(string.ascii_lowercase, k=12)) + ".tmp"))
76
+ await tmp_file_path.parent.mkdir(parents=True, exist_ok=True)
77
+ try:
78
+ async with await open_file(tmp_file_path, "w+b") as f:
79
+ await f.write(file.content)
80
+ file_data = await parse_file(Path(str(f.name)))
81
+ finally:
82
+ await tmp_file_path.unlink()
83
+ return file_data
84
+
85
+
86
+ async def _sha1_hash_file(fileobj: AsyncFile):
87
+ last = await fileobj.tell()
88
+ await fileobj.seek(0)
89
+ sha1_hash = hashlib.sha1()
90
+ buf = bytearray(2**18)
91
+ view = memoryview(buf)
92
+ while True:
93
+ size = await fileobj.readinto(buf)
94
+ if size == 0:
95
+ break
96
+ sha1_hash.update(view[:size])
97
+
98
+ await fileobj.seek(last)
78
99
  return sha1_hash.hexdigest()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: goosebit
3
- Version: 0.2.3
3
+ Version: 0.2.5
4
4
  Summary:
5
5
  Author: Upstream Data
6
6
  Author-email: brett@upstreamdata.ca
@@ -8,10 +8,10 @@ Requires-Python: >=3.11,<4.0
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3.11
10
10
  Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
11
12
  Provides-Extra: postgresql
12
13
  Requires-Dist: aerich (>=0.7.2,<0.8.0)
13
14
  Requires-Dist: aiocache (>=0.12.2,<0.13.0)
14
- Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
15
15
  Requires-Dist: argon2-cffi (>=23.1.0,<24.0.0)
16
16
  Requires-Dist: asyncpg (>=0.29.0,<0.30.0) ; extra == "postgresql"
17
17
  Requires-Dist: fastapi[uvicorn] (>=0.111.0,<0.112.0)
@@ -20,9 +20,9 @@ Requires-Dist: itsdangerous (>=2.2.0,<3.0.0)
20
20
  Requires-Dist: jinja2 (>=3.1.4,<4.0.0)
21
21
  Requires-Dist: joserfc (>=1.0.0,<2.0.0)
22
22
  Requires-Dist: libconf (>=2.0.1,<3.0.0)
23
- Requires-Dist: opentelemetry-distro (>=0.47b0,<0.48)
24
- Requires-Dist: opentelemetry-exporter-prometheus (>=0.47b0,<0.48)
25
- Requires-Dist: opentelemetry-instrumentation-fastapi (>=0.47b0,<0.48)
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
26
  Requires-Dist: pydantic-settings (>=2.4.0,<3.0.0)
27
27
  Requires-Dist: python-multipart (>=0.0.9,<0.0.10)
28
28
  Requires-Dist: semver (>=3.0.2,<4.0.0)
@@ -34,6 +34,8 @@ Description-Content-Type: text/markdown
34
34
 
35
35
  <img src="docs/img/goosebit-logo.png" style="width: 100px; height: 100px; display: block;">
36
36
 
37
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/UpstreamDataInc/goosebit/badge)](https://scorecard.dev/viewer/?uri=github.com/UpstreamDataInc/goosebit)
38
+
37
39
  ---
38
40
 
39
41
  A simplistic, opinionated remote update server implementing hawkBit™'s [DDI API](https://eclipse.dev/hawkbit/apis/ddi_api/).
@@ -43,10 +45,18 @@ A simplistic, opinionated remote update server implementing hawkBit™'s [DDI AP
43
45
  ### Installation
44
46
 
45
47
  1. Install dependencies using [Poetry](https://python-poetry.org/):
48
+
46
49
  ```bash
47
50
  poetry install
48
51
  ```
49
- 2. Launch gooseBit:
52
+
53
+ 2. Create the database:
54
+
55
+ ```bash
56
+ poetry run aerich upgrade
57
+ ```
58
+
59
+ 3. Launch gooseBit:
50
60
  ```bash
51
61
  python main.py
52
62
  ```
@@ -117,6 +127,12 @@ After a model change create the migration
117
127
  poetry run aerich migrate
118
128
  ```
119
129
 
130
+ To seed some sample data (attention: drops all current data) use
131
+
132
+ ```bash
133
+ poetry run generate-sample-data
134
+ ```
135
+
120
136
  ### Code formatting and linting
121
137
 
122
138
  Code is formatted using different tools
@@ -139,7 +155,7 @@ poetry run pre-commit install
139
155
  To manually apply the hooks to all files use:
140
156
 
141
157
  ```bash
142
- pre-commit run --all-files
158
+ poetry run pre-commit run --all-files
143
159
  ```
144
160
 
145
161
  ### Testing
@@ -1,4 +1,4 @@
1
- goosebit/__init__.py,sha256=vhOcvSYVWRttpjBDeAVftUHZPVaLExvqFEV9LU69vgc,3039
1
+ goosebit/__init__.py,sha256=qHY0WDT5n5PdX6D6tv2bAsBwBfMw-bcjEjKotIQDAzg,4158
2
2
  goosebit/__main__.py,sha256=ezSLOobcrbnBspFOSbyjuATEdk8B3aAj9cgEu647ujk,161
3
3
  goosebit/api/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
4
4
  goosebit/api/responses.py,sha256=HXLpFgL-iv5Ts6LKa3AMt1XGV1GbDE5GzjK66_3Gz5o,84
@@ -13,83 +13,87 @@ goosebit/api/v1/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,4
13
13
  goosebit/api/v1/devices/__init__.py,sha256=kzt5TBA-bJmDZFXYPohggbMOW7GhklyflaRKZNC0yDY,42
14
14
  goosebit/api/v1/devices/device/__init__.py,sha256=kzt5TBA-bJmDZFXYPohggbMOW7GhklyflaRKZNC0yDY,42
15
15
  goosebit/api/v1/devices/device/responses.py,sha256=THIPMzc6mwl7GmIXuqAeLLesFHfssvPrMJeHe8tV4mA,222
16
- goosebit/api/v1/devices/device/routes.py,sha256=Q9v7NzTFGdwQQmSRjGsFXl9qDwilVB1ieG9isXLaXgs,957
16
+ goosebit/api/v1/devices/device/routes.py,sha256=rxdu7A2_NRcPV1ofXntrqE1elH9H2GuKE5kO07xQtXM,1173
17
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
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
20
  goosebit/api/v1/download/__init__.py,sha256=kzt5TBA-bJmDZFXYPohggbMOW7GhklyflaRKZNC0yDY,42
21
21
  goosebit/api/v1/download/routes.py,sha256=wdeXA-dGO4D9FxaUP6OnUSW4_2NZhi_VgIxMcSRL2x4,675
22
22
  goosebit/api/v1/rollouts/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
23
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
24
+ goosebit/api/v1/rollouts/responses.py,sha256=yKljabI1wQB6MXNXrF4aIi5csKRCo0A96RqTW6OwAjE,311
25
+ goosebit/api/v1/rollouts/routes.py,sha256=afUQTjzrpLbHA68CEpMDMZ93C6-jp8JLrdjUEYUFm7o,1927
26
26
  goosebit/api/v1/routes.py,sha256=g9vL0VOuFSdNG3FnK3y_03trwv4X2OLN9ustJeCgc58,272
27
27
  goosebit/api/v1/software/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
28
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
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
33
  goosebit/db/config.py,sha256=YCtF52lQ5bEVtKsrqkSzQpbKmCbD79O8Tz0RE4thV3c,220
34
34
  goosebit/db/migrations/models/0_20240830054046_init.py,sha256=EBig7b0-AOWt_AE10rQf_0ZrserjKTm0r53wYInK5Co,5526
35
- goosebit/db/models.py,sha256=P-mdryY9wbxGQkU3Dvy90fTgcKsksYJWEab3Vu9U2t4,4389
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
36
37
  goosebit/realtime/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
37
- goosebit/realtime/logs.py,sha256=JJ6bfVKzux9n3mXaUFoQhH2krGCpW7U1VzaloNmiORY,1223
38
+ goosebit/realtime/logs.py,sha256=sjGg2n7H-4j4i02Kbq_YlECLFp33mmOmaoAZXeE8J9Y,1225
38
39
  goosebit/realtime/routes.py,sha256=yLHrA-BHHU5cpUNvxAPVxWC3KcB9aUrV31HT_th24Ls,265
39
40
  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
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
43
44
  goosebit/settings/__init__.py,sha256=UkJa_G_VJckNf16Em0zz8sXgEi5pzxHFvZ96oa9MC3k,360
44
45
  goosebit/settings/const.py,sha256=hOpeA9pGmbU4V4mqcfhpHV7cmEjf2GIenmzCSz2Jpj8,779
45
- goosebit/settings/schema.py,sha256=T6MntOkM6Pbf98dQHZoEVUADwKpeJqCrFDc40ufCiG4,2543
46
+ goosebit/settings/schema.py,sha256=ffDhMsyW6_TGIReJRiw3hisKUretdf1PnISTJ0yBe0o,2577
46
47
  goosebit/ui/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
47
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
48
53
  goosebit/ui/bff/devices/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
49
54
  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
55
+ goosebit/ui/bff/devices/responses.py,sha256=4279xeu-soqgyOc4ncKZpai6juBQ3dl096p2LgtIu2s,1208
56
+ goosebit/ui/bff/devices/routes.py,sha256=9WoGBheOkOT5-UDlQ0rtK-MJF4MNPuHxz3mBxdVhbWU,5260
52
57
  goosebit/ui/bff/download/__init__.py,sha256=kzt5TBA-bJmDZFXYPohggbMOW7GhklyflaRKZNC0yDY,42
53
58
  goosebit/ui/bff/download/routes.py,sha256=wdeXA-dGO4D9FxaUP6OnUSW4_2NZhi_VgIxMcSRL2x4,675
54
59
  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
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
58
63
  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
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
63
68
  goosebit/ui/static/__init__.py,sha256=AsZiM3h9chQ_YaBbAD6TVFf0244Q9DetkDMl70q8swQ,135
64
69
  goosebit/ui/static/favicon.ico,sha256=MKAqEjW7F1-j2dmW4s08y4U0dFteIlU7rpLKakU0Lxk,4286
65
70
  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
71
+ goosebit/ui/static/js/devices.js,sha256=zseDgA1PnnreNajQRx7F38GuS_BjQugVtQLNPUhI3xg,13635
68
72
  goosebit/ui/static/js/login.js,sha256=9qlXki3udm5CmAO8UXUJe94Kc_-SJekdKjfH9TzJhgk,579
69
73
  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
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
73
77
  goosebit/ui/static/svg/goosebit-logo.svg,sha256=8zvM7o2Ob3YmTEV6Rj7rCTgSajvY6wIaAQAAjWwlDsw,8733
74
78
  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
79
+ goosebit/ui/templates/devices.html.jinja,sha256=G2t3pKIe0e7LDkm9H0YwDddSvyXY4PaSPM11Rl9d1BY,5773
77
80
  goosebit/ui/templates/login.html.jinja,sha256=eQX1ypZ5iJ2L4v4lw8VB7GKluUjUYmpoUyUy8jY98Og,3050
78
81
  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
82
+ goosebit/ui/templates/nav.html.jinja,sha256=cdAM-9RZG_taugisf9Sh-UcRT3jLHAqNGbf9r7mk8ow,5725
83
+ goosebit/ui/templates/rollouts.html.jinja,sha256=s9kIxRYO8OypqIQH8tQUwZJDDjsex89piuV4kh1eqwE,4728
81
84
  goosebit/ui/templates/software.html.jinja,sha256=CTIeNkPMJMvtDVWbcKl_BjvtAFV3bskpCcNg-lCJIZM,7551
82
85
  goosebit/updater/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
83
86
  goosebit/updater/controller/__init__.py,sha256=4RRIzqC6KbCESIC9Vc6G4iAa28IWQH4KROTGd2S4AoI,41
84
87
  goosebit/updater/controller/routes.py,sha256=8CnLb-kDuO-yFeWdu4apIyctCf9OzvJ011Az3QDGemU,123
85
88
  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.3.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
93
- goosebit-0.2.3.dist-info/METADATA,sha256=dlawimz01PevrQ4HtDDvaIhkhkIf2PPs348HH1hbHco,5420
94
- goosebit-0.2.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
95
- goosebit-0.2.3.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 1.9.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ generate-sample-data=sample_data:main
3
+
@@ -1,155 +0,0 @@
1
- let dataTable;
2
-
3
- document.addEventListener("DOMContentLoaded", () => {
4
- dataTable = new DataTable("#device-table", {
5
- responsive: true,
6
- paging: true,
7
- processing: false,
8
- serverSide: true,
9
- scrollCollapse: true,
10
- scroller: true,
11
- scrollY: "65vh",
12
- stateSave: true,
13
- stateLoadParams: (settings, data) => {
14
- // if save state is older than last breaking code change...
15
- if (data.time <= 1722434386000) {
16
- // ... delete it
17
- for (const key of Object.keys(data)) {
18
- delete data[key];
19
- }
20
- }
21
- },
22
- ajax: {
23
- url: "/ui/bff/devices",
24
- contentType: "application/json",
25
- },
26
- initComplete: () => {
27
- updateBtnState();
28
- },
29
- columnDefs: [
30
- {
31
- targets: "_all",
32
- searchable: false,
33
- orderable: false,
34
- render: (data) => data || "-",
35
- },
36
- ],
37
- columns: [
38
- { data: "name", searchable: true, orderable: true },
39
- {
40
- data: "online",
41
- render: (data, type) => {
42
- if (type === "display" || type === "filter") {
43
- const color = data ? "success" : "danger";
44
- return `
45
- <div class="text-${color}">
46
-
47
- </div>
48
- `;
49
- }
50
- return data;
51
- },
52
- },
53
- { data: "uuid", searchable: true, orderable: true },
54
- { data: "sw_version", searchable: true, orderable: true },
55
- {
56
- data: "progress",
57
- render: (data, type) => {
58
- if (type === "display" || type === "filter") {
59
- return data ? `${data}%` : "-";
60
- }
61
- return data;
62
- },
63
- },
64
- { data: "last_ip" },
65
- {
66
- data: "last_seen",
67
- render: (data, type) => {
68
- if (type === "display" || type === "filter") {
69
- return secondsToRecentDate(data);
70
- }
71
- return data;
72
- },
73
- },
74
- ],
75
- select: true,
76
- rowId: "uuid",
77
- layout: {
78
- top1Start: {
79
- buttons: [
80
- {
81
- text: '<i class="bi bi-check-all"></i>',
82
- extend: "selectAll",
83
- titleAttr: "Select All",
84
- },
85
- {
86
- text: '<i class="bi bi-x"></i>',
87
- extend: "selectNone",
88
- titleAttr: "Clear Selection",
89
- },
90
- {
91
- text: '<i class="bi bi-file-earmark-arrow-down"></i>',
92
- action: (e, dt) => {
93
- const selectedDevices = dt.rows({ selected: true }).data().toArray();
94
- downloadLogins(selectedDevices);
95
- },
96
- className: "buttons-export-login",
97
- titleAttr: "Export Login",
98
- },
99
- {
100
- text: '<i class="bi bi-file-text"></i>',
101
- action: (e, dt) => {
102
- const selectedDevice = dt.rows({ selected: true }).data().toArray()[0];
103
- window.location.href = `/ui/logs/${selectedDevice.uuid}`;
104
- },
105
- className: "buttons-logs",
106
- titleAttr: "View Log",
107
- },
108
- ],
109
- },
110
- },
111
- });
112
-
113
- dataTable
114
- .on("select", () => {
115
- updateBtnState();
116
- })
117
- .on("deselect", () => {
118
- updateBtnState();
119
- });
120
-
121
- setInterval(() => {
122
- dataTable.ajax.reload(null, false);
123
- }, TABLE_UPDATE_TIME);
124
- });
125
-
126
- function updateBtnState() {
127
- if (dataTable.rows({ selected: true }).any()) {
128
- document.querySelector("button.buttons-select-none").classList.remove("disabled");
129
- document.querySelector("button.buttons-export-login").classList.remove("disabled");
130
- } else {
131
- document.querySelector("button.buttons-select-none").classList.add("disabled");
132
- document.querySelector("button.buttons-export-login").classList.add("disabled");
133
- }
134
- if (dataTable.rows({ selected: true }).count() === 1) {
135
- document.querySelector("button.buttons-logs").classList.remove("disabled");
136
- } else {
137
- document.querySelector("button.buttons-logs").classList.add("disabled");
138
- }
139
- }
140
-
141
- function downloadLogins(devices) {
142
- const deviceLogins = devices.map((dev) => {
143
- return [dev.name, `https://${dev.uuid}-access.loadsync.io`, dev.uuid];
144
- });
145
- deviceLogins.unshift(["Building", "Access Link", "Serial Number/Wifi SSID", "Login/Wifi Password"]);
146
-
147
- const csvContent = `data:text/csv;charset=utf-8,${deviceLogins.map((e) => e.join(",")).join("\n")}`;
148
- const encodedUri = encodeURI(csvContent);
149
- const link = document.createElement("a");
150
- link.setAttribute("href", encodedUri);
151
- link.setAttribute("download", "LoadsyncLogins-Export.csv");
152
- document.body.appendChild(link);
153
-
154
- link.click();
155
- }
@@ -1,25 +0,0 @@
1
- {% extends "nav.html.jinja" %}
2
- {% block content %}
3
- <div class="container-fluid">
4
- <div class="row p-2 d-flex justify-content-center">
5
- <div class="col">
6
- <table id="device-table" class="table table-hover">
7
- <thead>
8
- <tr>
9
- <th>Name</th>
10
- <th>Up</th>
11
- <th>UUID</th>
12
- <th>Software</th>
13
- <th>Progress</th>
14
- <th>Last IP</th>
15
- <th>Last Seen</th>
16
- </tr>
17
- </thead>
18
- <tbody id="devices-list">
19
- </tbody>
20
- </table>
21
- </div>
22
- </div>
23
- </div>
24
- <script src="{{ url_for('static', path='js/index.js') }}"></script>
25
- {% endblock content %}