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.
- goosebit/__init__.py +32 -3
- goosebit/api/v1/devices/device/routes.py +10 -4
- goosebit/api/v1/devices/responses.py +0 -7
- goosebit/api/v1/devices/routes.py +19 -3
- goosebit/api/v1/rollouts/responses.py +2 -7
- goosebit/api/v1/rollouts/routes.py +7 -3
- goosebit/api/v1/software/responses.py +0 -7
- goosebit/api/v1/software/routes.py +24 -11
- goosebit/auth/__init__.py +12 -8
- goosebit/db/__init__.py +12 -1
- goosebit/db/migrations/models/1_20241109151811_update.py +11 -0
- goosebit/db/models.py +19 -4
- goosebit/realtime/logs.py +1 -1
- goosebit/schema/devices.py +42 -38
- goosebit/schema/rollouts.py +21 -18
- goosebit/schema/software.py +24 -19
- goosebit/settings/schema.py +2 -0
- goosebit/ui/bff/common/__init__.py +0 -0
- goosebit/ui/bff/common/requests.py +44 -0
- goosebit/ui/bff/common/responses.py +16 -0
- goosebit/ui/bff/common/util.py +32 -0
- goosebit/ui/bff/devices/responses.py +15 -19
- goosebit/ui/bff/devices/routes.py +61 -7
- goosebit/ui/bff/rollouts/responses.py +15 -19
- goosebit/ui/bff/rollouts/routes.py +8 -6
- goosebit/ui/bff/routes.py +4 -2
- goosebit/ui/bff/software/responses.py +29 -19
- goosebit/ui/bff/software/routes.py +29 -16
- goosebit/ui/nav.py +1 -1
- goosebit/ui/routes.py +10 -19
- goosebit/ui/static/js/devices.js +188 -94
- goosebit/ui/static/js/rollouts.js +20 -13
- goosebit/ui/static/js/software.js +5 -11
- goosebit/ui/static/js/util.js +43 -14
- goosebit/ui/templates/devices.html.jinja +77 -49
- goosebit/ui/templates/nav.html.jinja +35 -4
- goosebit/ui/templates/rollouts.html.jinja +23 -23
- goosebit/updater/controller/v1/routes.py +33 -23
- goosebit/updater/controller/v1/schema.py +4 -4
- goosebit/updater/manager.py +28 -52
- goosebit/updater/routes.py +6 -2
- goosebit/updates/__init__.py +14 -21
- goosebit/updates/swdesc.py +36 -15
- {goosebit-0.2.3.dist-info → goosebit-0.2.5.dist-info}/METADATA +23 -7
- {goosebit-0.2.3.dist-info → goosebit-0.2.5.dist-info}/RECORD +48 -44
- {goosebit-0.2.3.dist-info → goosebit-0.2.5.dist-info}/WHEEL +1 -1
- goosebit-0.2.5.dist-info/entry_points.txt +3 -0
- goosebit/ui/static/js/index.js +0 -155
- goosebit/ui/templates/index.html.jinja +0 -25
- {goosebit-0.2.3.dist-info → goosebit-0.2.5.dist-info}/LICENSE +0 -0
goosebit/updater/routes.py
CHANGED
@@ -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
|
-
|
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(
|
goosebit/updates/__init__.py
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
import
|
2
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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:
|
goosebit/updates/swdesc.py
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
import hashlib
|
2
2
|
import logging
|
3
|
-
|
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
|
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
|
-
|
63
|
-
swdesc_attrs["
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
+
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.
|
24
|
-
Requires-Dist: opentelemetry-exporter-prometheus (>=0.
|
25
|
-
Requires-Dist: opentelemetry-instrumentation-fastapi (>=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
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
|
+
[](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
|
-
|
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=
|
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=
|
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=
|
19
|
-
goosebit/api/v1/devices/routes.py,sha256=
|
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=
|
25
|
-
goosebit/api/v1/rollouts/routes.py,sha256=
|
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=
|
30
|
-
goosebit/api/v1/software/routes.py,sha256=
|
31
|
-
goosebit/auth/__init__.py,sha256=
|
32
|
-
goosebit/db/__init__.py,sha256=
|
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=
|
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=
|
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=
|
41
|
-
goosebit/schema/rollouts.py,sha256=
|
42
|
-
goosebit/schema/software.py,sha256=
|
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=
|
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=
|
51
|
-
goosebit/ui/bff/devices/routes.py,sha256=
|
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=
|
56
|
-
goosebit/ui/bff/rollouts/routes.py,sha256=
|
57
|
-
goosebit/ui/bff/routes.py,sha256=
|
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=
|
60
|
-
goosebit/ui/bff/software/routes.py,sha256=
|
61
|
-
goosebit/ui/nav.py,sha256=
|
62
|
-
goosebit/ui/routes.py,sha256=
|
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=
|
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=
|
71
|
-
goosebit/ui/static/js/software.js,sha256=
|
72
|
-
goosebit/ui/static/js/util.js,sha256=
|
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=
|
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=
|
80
|
-
goosebit/ui/templates/rollouts.html.jinja,sha256=
|
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=
|
87
|
-
goosebit/updater/controller/v1/schema.py,sha256=
|
88
|
-
goosebit/updater/manager.py,sha256=
|
89
|
-
goosebit/updater/routes.py,sha256=
|
90
|
-
goosebit/updates/__init__.py,sha256=
|
91
|
-
goosebit/updates/swdesc.py,sha256=
|
92
|
-
goosebit-0.2.
|
93
|
-
goosebit-0.2.
|
94
|
-
goosebit-0.2.
|
95
|
-
goosebit-0.2.
|
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,,
|
goosebit/ui/static/js/index.js
DELETED
@@ -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 %}
|
File without changes
|