goosebit 0.2.4__py3-none-any.whl → 0.2.6__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 +56 -6
- goosebit/api/telemetry/metrics.py +1 -5
- goosebit/api/v1/devices/device/responses.py +1 -0
- goosebit/api/v1/devices/device/routes.py +8 -8
- goosebit/api/v1/devices/requests.py +20 -0
- goosebit/api/v1/devices/routes.py +83 -8
- goosebit/api/v1/download/routes.py +14 -3
- goosebit/api/v1/rollouts/routes.py +5 -4
- goosebit/api/v1/routes.py +2 -1
- goosebit/api/v1/settings/routes.py +14 -0
- goosebit/api/v1/settings/users/__init__.py +1 -0
- goosebit/api/v1/settings/users/requests.py +16 -0
- goosebit/api/v1/settings/users/responses.py +7 -0
- goosebit/api/v1/settings/users/routes.py +56 -0
- goosebit/api/v1/software/routes.py +18 -14
- goosebit/auth/__init__.py +54 -14
- goosebit/auth/permissions.py +80 -0
- goosebit/db/config.py +57 -1
- goosebit/db/migrations/models/1_20241109151811_update.py +11 -0
- goosebit/db/migrations/models/2_20241121113728_update.py +11 -0
- goosebit/db/migrations/models/3_20241121140210_update.py +11 -0
- goosebit/db/migrations/models/4_20250324110331_update.py +16 -0
- goosebit/db/migrations/models/4_20250402085235_rename_uuid_to_id.py +11 -0
- goosebit/db/migrations/models/5_20250619090242_null_feed.py +83 -0
- goosebit/db/models.py +22 -7
- goosebit/db/pg_ssl_context.py +51 -0
- goosebit/device_manager.py +262 -0
- goosebit/plugins/__init__.py +32 -0
- goosebit/schema/devices.py +9 -6
- goosebit/schema/plugins.py +67 -0
- goosebit/schema/updates.py +15 -0
- goosebit/schema/users.py +9 -0
- goosebit/settings/__init__.py +0 -3
- goosebit/settings/schema.py +62 -14
- goosebit/storage/__init__.py +62 -0
- goosebit/storage/base.py +14 -0
- goosebit/storage/filesystem.py +111 -0
- goosebit/storage/s3.py +104 -0
- goosebit/ui/bff/common/columns.py +50 -0
- goosebit/ui/bff/common/requests.py +3 -15
- goosebit/ui/bff/common/responses.py +17 -0
- goosebit/ui/bff/devices/device/__init__.py +1 -0
- goosebit/ui/bff/devices/device/routes.py +17 -0
- goosebit/ui/bff/devices/requests.py +1 -0
- goosebit/ui/bff/devices/responses.py +6 -2
- goosebit/ui/bff/devices/routes.py +71 -17
- goosebit/ui/bff/download/routes.py +14 -3
- goosebit/ui/bff/rollouts/responses.py +6 -2
- goosebit/ui/bff/rollouts/routes.py +32 -4
- goosebit/ui/bff/routes.py +6 -3
- goosebit/ui/bff/settings/__init__.py +1 -0
- goosebit/ui/bff/settings/routes.py +20 -0
- goosebit/ui/bff/settings/users/__init__.py +1 -0
- goosebit/ui/bff/settings/users/responses.py +33 -0
- goosebit/ui/bff/settings/users/routes.py +80 -0
- goosebit/ui/bff/software/responses.py +19 -9
- goosebit/ui/bff/software/routes.py +40 -12
- goosebit/ui/nav.py +12 -2
- goosebit/ui/routes.py +70 -26
- goosebit/ui/static/js/devices.js +72 -80
- goosebit/ui/static/js/login.js +21 -5
- goosebit/ui/static/js/logs.js +7 -22
- goosebit/ui/static/js/rollouts.js +39 -35
- goosebit/ui/static/js/settings.js +322 -0
- goosebit/ui/static/js/setup.js +28 -0
- goosebit/ui/static/js/software.js +127 -127
- goosebit/ui/static/js/util.js +45 -4
- goosebit/ui/templates/__init__.py +10 -1
- goosebit/ui/templates/devices.html.jinja +0 -20
- goosebit/ui/templates/login.html.jinja +5 -0
- goosebit/ui/templates/nav.html.jinja +26 -7
- goosebit/ui/templates/rollouts.html.jinja +4 -22
- goosebit/ui/templates/settings.html.jinja +88 -0
- goosebit/ui/templates/setup.html.jinja +71 -0
- goosebit/ui/templates/software.html.jinja +0 -11
- goosebit/updater/controller/v1/routes.py +120 -72
- goosebit/updater/routes.py +86 -7
- goosebit/updates/__init__.py +24 -31
- goosebit/updates/swdesc.py +15 -8
- goosebit/users/__init__.py +63 -0
- goosebit/util/__init__.py +0 -0
- goosebit/util/path.py +42 -0
- goosebit/util/version.py +92 -0
- goosebit-0.2.6.dist-info/METADATA +280 -0
- goosebit-0.2.6.dist-info/RECORD +133 -0
- {goosebit-0.2.4.dist-info → goosebit-0.2.6.dist-info}/WHEEL +1 -1
- goosebit-0.2.6.dist-info/entry_points.txt +3 -0
- goosebit/realtime/logs.py +0 -42
- goosebit/realtime/routes.py +0 -13
- goosebit/ui/static/js/index.js +0 -155
- goosebit/ui/templates/index.html.jinja +0 -25
- goosebit/updater/manager.py +0 -357
- goosebit-0.2.4.dist-info/METADATA +0 -181
- goosebit-0.2.4.dist-info/RECORD +0 -98
- /goosebit/{realtime → api/v1/settings}/__init__.py +0 -0
- {goosebit-0.2.4.dist-info → goosebit-0.2.6.dist-info}/LICENSE +0 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
from fastapi import APIRouter, Security
|
2
|
+
|
3
|
+
from goosebit.api.v1.settings import routes
|
4
|
+
from goosebit.auth import validate_user_permissions
|
5
|
+
from goosebit.auth.permissions import GOOSEBIT_PERMISSIONS
|
6
|
+
|
7
|
+
from . import users
|
8
|
+
|
9
|
+
router = APIRouter(prefix="/settings")
|
10
|
+
|
11
|
+
router.include_router(users.router)
|
12
|
+
|
13
|
+
router.add_api_route(
|
14
|
+
"/permissions",
|
15
|
+
routes.settings_permissions_get,
|
16
|
+
methods=["GET"],
|
17
|
+
dependencies=[Security(validate_user_permissions, scopes=[GOOSEBIT_PERMISSIONS["settings"]["users"]["read"]()])],
|
18
|
+
name="bff_settings_permissions_get",
|
19
|
+
response_model_exclude_none=True,
|
20
|
+
)
|
@@ -0,0 +1 @@
|
|
1
|
+
from .routes import router # noqa: F401
|
@@ -0,0 +1,33 @@
|
|
1
|
+
from typing import Callable
|
2
|
+
|
3
|
+
from pydantic import BaseModel, Field
|
4
|
+
from tortoise.queryset import QuerySet
|
5
|
+
|
6
|
+
from goosebit.schema.users import UserSchema
|
7
|
+
from goosebit.ui.bff.common.requests import DataTableRequest
|
8
|
+
|
9
|
+
|
10
|
+
class BFFSettingsUsersResponse(BaseModel):
|
11
|
+
data: list[UserSchema]
|
12
|
+
draw: int
|
13
|
+
records_total: int = Field(serialization_alias="recordsTotal")
|
14
|
+
records_filtered: int = Field(serialization_alias="recordsFiltered")
|
15
|
+
|
16
|
+
@classmethod
|
17
|
+
async def convert(cls, dt_query: DataTableRequest, query: QuerySet, search_filter: Callable):
|
18
|
+
total_records = await query.count()
|
19
|
+
if dt_query.search.value:
|
20
|
+
query = query.filter(search_filter(dt_query.search.value))
|
21
|
+
|
22
|
+
filtered_records = await query.count()
|
23
|
+
|
24
|
+
if dt_query.order_query:
|
25
|
+
query = query.order_by(dt_query.order_query)
|
26
|
+
|
27
|
+
if dt_query.length is not None:
|
28
|
+
query = query.limit(dt_query.length)
|
29
|
+
|
30
|
+
rollouts = await query.offset(dt_query.start).all()
|
31
|
+
data = [UserSchema.model_validate(r) for r in rollouts]
|
32
|
+
|
33
|
+
return cls(data=data, draw=dt_query.draw, records_total=total_records, records_filtered=filtered_records)
|
@@ -0,0 +1,80 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Annotated
|
4
|
+
|
5
|
+
from fastapi import APIRouter, Depends, Security
|
6
|
+
from tortoise.expressions import Q
|
7
|
+
|
8
|
+
from goosebit.api.v1.settings.users import routes
|
9
|
+
from goosebit.auth import validate_user_permissions
|
10
|
+
from goosebit.auth.permissions import GOOSEBIT_PERMISSIONS
|
11
|
+
from goosebit.db.models import User
|
12
|
+
from goosebit.ui.bff.common.columns import SettingsUsersColumns
|
13
|
+
from goosebit.ui.bff.common.requests import DataTableRequest
|
14
|
+
from goosebit.ui.bff.common.responses import DTColumns
|
15
|
+
from goosebit.ui.bff.common.util import parse_datatables_query
|
16
|
+
from goosebit.ui.bff.settings.users.responses import BFFSettingsUsersResponse
|
17
|
+
|
18
|
+
router = APIRouter(prefix="/users")
|
19
|
+
|
20
|
+
|
21
|
+
@router.get(
|
22
|
+
"",
|
23
|
+
dependencies=[Security(validate_user_permissions, scopes=[GOOSEBIT_PERMISSIONS["settings"]["users"]["read"]()])],
|
24
|
+
)
|
25
|
+
async def settings_users_get(
|
26
|
+
dt_query: Annotated[DataTableRequest, Depends(parse_datatables_query)],
|
27
|
+
) -> BFFSettingsUsersResponse:
|
28
|
+
filters: list[Q] = []
|
29
|
+
|
30
|
+
def search_filter(search_value):
|
31
|
+
base_filter = Q(Q(username__icontains=search_value), join_type="OR")
|
32
|
+
return Q(base_filter, *filters, join_type="AND")
|
33
|
+
|
34
|
+
query = User.all()
|
35
|
+
|
36
|
+
return await BFFSettingsUsersResponse.convert(dt_query, query, search_filter)
|
37
|
+
|
38
|
+
|
39
|
+
router.add_api_route(
|
40
|
+
"",
|
41
|
+
routes.settings_users_put,
|
42
|
+
methods=["POST"],
|
43
|
+
dependencies=[Security(validate_user_permissions, scopes=[GOOSEBIT_PERMISSIONS["settings"]["users"]["write"]()])],
|
44
|
+
name="bff_settings_users_put",
|
45
|
+
)
|
46
|
+
|
47
|
+
router.add_api_route(
|
48
|
+
"",
|
49
|
+
routes.settings_users_delete,
|
50
|
+
methods=["DELETE"],
|
51
|
+
dependencies=[Security(validate_user_permissions, scopes=[GOOSEBIT_PERMISSIONS["settings"]["users"]["delete"]()])],
|
52
|
+
name="bff_settings_users_delete",
|
53
|
+
)
|
54
|
+
|
55
|
+
router.add_api_route(
|
56
|
+
"",
|
57
|
+
routes.settings_users_patch,
|
58
|
+
methods=["PATCH"],
|
59
|
+
dependencies=[Security(validate_user_permissions, scopes=[GOOSEBIT_PERMISSIONS["settings"]["users"]["write"]()])],
|
60
|
+
name="bff_settings_users_patch",
|
61
|
+
)
|
62
|
+
|
63
|
+
|
64
|
+
@router.get(
|
65
|
+
"/columns",
|
66
|
+
dependencies=[Security(validate_user_permissions, scopes=[GOOSEBIT_PERMISSIONS["settings"]["users"]["read"]()])],
|
67
|
+
response_model_exclude_none=True,
|
68
|
+
)
|
69
|
+
async def settings_users_get_columns() -> DTColumns:
|
70
|
+
columns = list(
|
71
|
+
filter(
|
72
|
+
None,
|
73
|
+
[
|
74
|
+
SettingsUsersColumns.username,
|
75
|
+
SettingsUsersColumns.enabled,
|
76
|
+
SettingsUsersColumns.permissions,
|
77
|
+
],
|
78
|
+
)
|
79
|
+
)
|
80
|
+
return DTColumns(columns=columns)
|
@@ -5,7 +5,7 @@ from tortoise.expressions import Q
|
|
5
5
|
from tortoise.queryset import QuerySet
|
6
6
|
|
7
7
|
from goosebit.schema.software import SoftwareSchema
|
8
|
-
from goosebit.ui.bff.common.requests import DataTableRequest
|
8
|
+
from goosebit.ui.bff.common.requests import DataTableOrderDirection, DataTableRequest
|
9
9
|
|
10
10
|
|
11
11
|
class BFFSoftwareResponse(BaseModel):
|
@@ -21,17 +21,27 @@ class BFFSoftwareResponse(BaseModel):
|
|
21
21
|
if dt_query.search.value:
|
22
22
|
query = query.filter(search_filter(dt_query.search.value))
|
23
23
|
|
24
|
-
if dt_query.order_query:
|
25
|
-
query = query.order_by(dt_query.order_query)
|
26
|
-
|
27
24
|
filtered_records = await query.count()
|
28
25
|
|
29
|
-
|
26
|
+
if len(dt_query.order) > 0 and dt_query.order[0].name == "version":
|
27
|
+
# ordering cannot be delegated to database as semantic versioning sorting is not supported
|
28
|
+
software = await query.all()
|
29
|
+
reverse = dt_query.order[0].dir == DataTableOrderDirection.DESCENDING
|
30
|
+
software.sort(key=lambda s: s.parsed_version, reverse=reverse)
|
31
|
+
|
32
|
+
# in-memory paging
|
33
|
+
if dt_query.length is None:
|
34
|
+
software = software[dt_query.start :]
|
35
|
+
else:
|
36
|
+
software = software[dt_query.start : dt_query.start + dt_query.length]
|
37
|
+
|
38
|
+
else:
|
39
|
+
# if no ordering is specified, database-side paging can be used
|
40
|
+
if dt_query.length is not None:
|
41
|
+
query = query.limit(dt_query.length)
|
30
42
|
|
31
|
-
|
32
|
-
query = query.limit(dt_query.length)
|
43
|
+
software = await query.offset(dt_query.start).all()
|
33
44
|
|
34
|
-
|
35
|
-
data = [SoftwareSchema.model_validate(d) for d in devices]
|
45
|
+
data = [SoftwareSchema.model_validate(s) for s in software]
|
36
46
|
|
37
47
|
return cls(data=data, draw=dt_query.draw, records_total=total_records, records_filtered=filtered_records)
|
@@ -9,12 +9,16 @@ from tortoise.expressions import Q
|
|
9
9
|
|
10
10
|
from goosebit.api.v1.software import routes
|
11
11
|
from goosebit.auth import validate_user_permissions
|
12
|
+
from goosebit.auth.permissions import GOOSEBIT_PERMISSIONS
|
12
13
|
from goosebit.db.models import Hardware, Rollout, Software
|
13
|
-
from goosebit.
|
14
|
+
from goosebit.storage import storage
|
14
15
|
from goosebit.ui.bff.common.requests import DataTableRequest
|
15
16
|
from goosebit.ui.bff.common.util import parse_datatables_query
|
16
17
|
from goosebit.updates import create_software_update
|
18
|
+
from goosebit.util.path import validate_filename
|
17
19
|
|
20
|
+
from ..common.columns import SoftwareColumns
|
21
|
+
from ..common.responses import DTColumns
|
18
22
|
from .responses import BFFSoftwareResponse
|
19
23
|
|
20
24
|
router = APIRouter(prefix="/software")
|
@@ -22,11 +26,11 @@ router = APIRouter(prefix="/software")
|
|
22
26
|
|
23
27
|
@router.get(
|
24
28
|
"",
|
25
|
-
dependencies=[Security(validate_user_permissions, scopes=["software
|
29
|
+
dependencies=[Security(validate_user_permissions, scopes=[GOOSEBIT_PERMISSIONS["software"]["read"]()])],
|
26
30
|
)
|
27
31
|
async def software_get(
|
28
32
|
dt_query: Annotated[DataTableRequest, Depends(parse_datatables_query)],
|
29
|
-
|
33
|
+
ids: list[str] = Query(default=None),
|
30
34
|
) -> BFFSoftwareResponse:
|
31
35
|
filters: list[Q] = []
|
32
36
|
|
@@ -36,8 +40,8 @@ async def software_get(
|
|
36
40
|
|
37
41
|
query = Software.all().prefetch_related("compatibility")
|
38
42
|
|
39
|
-
if
|
40
|
-
hardware = await Hardware.filter(
|
43
|
+
if ids:
|
44
|
+
hardware = await Hardware.filter(devices__id__in=ids).distinct()
|
41
45
|
filters.append(Q(*[Q(compatibility__id=c.id) for c in hardware], join_type="AND"))
|
42
46
|
|
43
47
|
return await BFFSoftwareResponse.convert(dt_query, query, search_filter, Q(*filters))
|
@@ -47,14 +51,14 @@ router.add_api_route(
|
|
47
51
|
"",
|
48
52
|
routes.software_delete,
|
49
53
|
methods=["DELETE"],
|
50
|
-
dependencies=[Security(validate_user_permissions, scopes=["software
|
54
|
+
dependencies=[Security(validate_user_permissions, scopes=[GOOSEBIT_PERMISSIONS["software"]["delete"]()])],
|
51
55
|
name="bff_software_delete",
|
52
56
|
)
|
53
57
|
|
54
58
|
|
55
59
|
@router.post(
|
56
60
|
"",
|
57
|
-
dependencies=[Security(validate_user_permissions, scopes=["software
|
61
|
+
dependencies=[Security(validate_user_permissions, scopes=[GOOSEBIT_PERMISSIONS["software"]["write"]()])],
|
58
62
|
)
|
59
63
|
async def post_update(
|
60
64
|
request: Request,
|
@@ -77,11 +81,14 @@ async def post_update(
|
|
77
81
|
await create_software_update(url, None)
|
78
82
|
else:
|
79
83
|
# local file
|
80
|
-
|
81
|
-
file = artifacts_dir.joinpath(filename)
|
82
|
-
await artifacts_dir.mkdir(parents=True, exist_ok=True)
|
84
|
+
temp_dir = Path(storage.get_temp_dir())
|
83
85
|
|
84
|
-
|
86
|
+
try:
|
87
|
+
file_path = await validate_filename(filename, temp_dir)
|
88
|
+
except ValueError as e:
|
89
|
+
raise HTTPException(400, f"Invalid filename: {e}")
|
90
|
+
|
91
|
+
temp_file = file_path.with_suffix(".tmp")
|
85
92
|
if init:
|
86
93
|
await temp_file.unlink(missing_ok=True)
|
87
94
|
|
@@ -90,7 +97,28 @@ async def post_update(
|
|
90
97
|
|
91
98
|
if done:
|
92
99
|
try:
|
93
|
-
absolute = await
|
100
|
+
absolute = await file_path.absolute()
|
94
101
|
await create_software_update(absolute.as_uri(), temp_file)
|
95
102
|
finally:
|
96
103
|
await temp_file.unlink(missing_ok=True)
|
104
|
+
|
105
|
+
|
106
|
+
@router.get(
|
107
|
+
"/columns",
|
108
|
+
dependencies=[Security(validate_user_permissions, scopes=[GOOSEBIT_PERMISSIONS["software"]["read"]()])],
|
109
|
+
response_model_exclude_none=True,
|
110
|
+
)
|
111
|
+
async def devices_get_columns() -> DTColumns:
|
112
|
+
columns = list(
|
113
|
+
filter(
|
114
|
+
None,
|
115
|
+
[
|
116
|
+
SoftwareColumns.id,
|
117
|
+
SoftwareColumns.name,
|
118
|
+
SoftwareColumns.version,
|
119
|
+
SoftwareColumns.compatibility,
|
120
|
+
SoftwareColumns.size,
|
121
|
+
],
|
122
|
+
)
|
123
|
+
)
|
124
|
+
return DTColumns(columns=columns)
|
goosebit/ui/nav.py
CHANGED
@@ -1,10 +1,20 @@
|
|
1
|
+
from pydantic import BaseModel
|
2
|
+
|
3
|
+
|
4
|
+
class NavigationItem(BaseModel):
|
5
|
+
function: str
|
6
|
+
text: str
|
7
|
+
permissions: list[str]
|
8
|
+
show: bool
|
9
|
+
|
10
|
+
|
1
11
|
class Navigation:
|
2
12
|
def __init__(self):
|
3
13
|
self.items = []
|
4
14
|
|
5
|
-
def route(self, text: str, permissions: str | None = None):
|
15
|
+
def route(self, text: str, permissions: list[str] | None = None, show: bool = True):
|
6
16
|
def decorator(func):
|
7
|
-
self.items.append(
|
17
|
+
self.items.append(NavigationItem(function=func.__name__, text=text, permissions=permissions, show=show))
|
8
18
|
return func
|
9
19
|
|
10
20
|
return decorator
|
goosebit/ui/routes.py
CHANGED
@@ -1,64 +1,108 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
import logging
|
2
|
+
|
3
|
+
from fastapi import APIRouter, Depends, HTTPException, Security
|
4
|
+
from fastapi.requests import HTTPConnection, Request
|
3
5
|
from fastapi.responses import RedirectResponse
|
4
|
-
from fastapi.security import
|
6
|
+
from fastapi.security import SecurityScopes
|
5
7
|
|
6
|
-
from goosebit.auth import
|
8
|
+
from goosebit.auth import (
|
9
|
+
check_permissions,
|
10
|
+
get_current_user,
|
11
|
+
redirect_if_unauthenticated,
|
12
|
+
)
|
13
|
+
from goosebit.auth.permissions import GOOSEBIT_PERMISSIONS
|
7
14
|
from goosebit.ui.nav import nav
|
8
15
|
|
16
|
+
from ..db.models import User
|
9
17
|
from . import bff
|
10
18
|
from .templates import templates
|
11
19
|
|
12
|
-
|
13
|
-
|
14
|
-
router = APIRouter(prefix="/ui", dependencies=[Depends(redirect_if_unauthenticated)], include_in_schema=False)
|
20
|
+
router = APIRouter(prefix="/ui", include_in_schema=False)
|
15
21
|
router.include_router(bff.router)
|
16
22
|
|
17
|
-
|
18
|
-
|
23
|
+
logger = logging.getLogger(__name__)
|
24
|
+
|
25
|
+
|
26
|
+
def validate_user_permissions_with_nav_redirect(
|
27
|
+
connection: HTTPConnection,
|
28
|
+
security: SecurityScopes,
|
29
|
+
user: User = Depends(get_current_user),
|
30
|
+
):
|
31
|
+
if not check_permissions(security.scopes, user.permissions):
|
32
|
+
logger.warning(f"{user.username} does not have sufficient permissions")
|
33
|
+
for item in nav.items:
|
34
|
+
if check_permissions(item.permissions, user.permissions):
|
35
|
+
raise HTTPException(
|
36
|
+
status_code=302,
|
37
|
+
headers={"location": str(connection.url_for(item.function))},
|
38
|
+
)
|
39
|
+
raise HTTPException(
|
40
|
+
status_code=403,
|
41
|
+
detail="Not enough permissions",
|
42
|
+
headers={"WWW-Authenticate": "Bearer"},
|
43
|
+
)
|
44
|
+
return connection
|
45
|
+
|
46
|
+
|
47
|
+
@router.get("", dependencies=[Depends(redirect_if_unauthenticated)])
|
19
48
|
async def ui_root(request: Request):
|
20
|
-
return RedirectResponse(request.url_for("
|
21
|
-
|
22
|
-
|
23
|
-
@router.get(
|
24
|
-
"/home",
|
25
|
-
dependencies=[Security(validate_user_permissions, scopes=["home.read"])],
|
26
|
-
)
|
27
|
-
@nav.route("Home", permissions="home.read")
|
28
|
-
async def home_ui(request: Request):
|
29
|
-
return templates.TemplateResponse(request, "index.html.jinja", context={"title": "Home"})
|
49
|
+
return RedirectResponse(request.url_for("devices_ui"))
|
30
50
|
|
31
51
|
|
32
52
|
@router.get(
|
33
53
|
"/devices",
|
34
|
-
dependencies=[
|
54
|
+
dependencies=[
|
55
|
+
Depends(redirect_if_unauthenticated),
|
56
|
+
Security(validate_user_permissions_with_nav_redirect, scopes=[GOOSEBIT_PERMISSIONS["device"]["read"]()]),
|
57
|
+
],
|
35
58
|
)
|
36
|
-
@nav.route("Devices", permissions="device
|
59
|
+
@nav.route("Devices", permissions=[GOOSEBIT_PERMISSIONS["device"]["read"]()])
|
37
60
|
async def devices_ui(request: Request):
|
38
61
|
return templates.TemplateResponse(request, "devices.html.jinja", context={"title": "Devices"})
|
39
62
|
|
40
63
|
|
41
64
|
@router.get(
|
42
65
|
"/software",
|
43
|
-
dependencies=[
|
66
|
+
dependencies=[
|
67
|
+
Depends(redirect_if_unauthenticated),
|
68
|
+
Security(validate_user_permissions_with_nav_redirect, scopes=[GOOSEBIT_PERMISSIONS["software"]["read"]()]),
|
69
|
+
],
|
44
70
|
)
|
45
|
-
@nav.route("Software", permissions="software
|
71
|
+
@nav.route("Software", permissions=[GOOSEBIT_PERMISSIONS["software"]["read"]()])
|
46
72
|
async def software_ui(request: Request):
|
47
73
|
return templates.TemplateResponse(request, "software.html.jinja", context={"title": "Software"})
|
48
74
|
|
49
75
|
|
50
76
|
@router.get(
|
51
77
|
"/rollouts",
|
52
|
-
dependencies=[
|
78
|
+
dependencies=[
|
79
|
+
Depends(redirect_if_unauthenticated),
|
80
|
+
Security(validate_user_permissions_with_nav_redirect, scopes=[GOOSEBIT_PERMISSIONS["rollout"]["read"]()]),
|
81
|
+
],
|
53
82
|
)
|
54
|
-
@nav.route("Rollouts", permissions="rollout
|
83
|
+
@nav.route("Rollouts", permissions=[GOOSEBIT_PERMISSIONS["rollout"]["read"]()])
|
55
84
|
async def rollouts_ui(request: Request):
|
56
85
|
return templates.TemplateResponse(request, "rollouts.html.jinja", context={"title": "Rollouts"})
|
57
86
|
|
58
87
|
|
59
88
|
@router.get(
|
60
89
|
"/logs/{dev_id}",
|
61
|
-
dependencies=[
|
90
|
+
dependencies=[
|
91
|
+
Depends(redirect_if_unauthenticated),
|
92
|
+
Security(validate_user_permissions_with_nav_redirect, scopes=[GOOSEBIT_PERMISSIONS["device"]["read"]()]),
|
93
|
+
],
|
62
94
|
)
|
63
95
|
async def logs_ui(request: Request, dev_id: str):
|
64
96
|
return templates.TemplateResponse(request, "logs.html.jinja", context={"title": "Log", "device": dev_id})
|
97
|
+
|
98
|
+
|
99
|
+
@router.get(
|
100
|
+
"/settings",
|
101
|
+
dependencies=[
|
102
|
+
Depends(redirect_if_unauthenticated),
|
103
|
+
Security(validate_user_permissions_with_nav_redirect, scopes=[GOOSEBIT_PERMISSIONS["settings"]()]),
|
104
|
+
],
|
105
|
+
)
|
106
|
+
@nav.route("Settings", permissions=[GOOSEBIT_PERMISSIONS["settings"]()], show=False)
|
107
|
+
async def settings_ui(request: Request):
|
108
|
+
return templates.TemplateResponse(request, "settings.html.jinja", context={"title": "Settings"})
|