goosebit 0.1.0__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 +62 -0
- goosebit/api/__init__.py +1 -0
- goosebit/api/devices.py +112 -0
- goosebit/api/download.py +20 -0
- goosebit/api/firmware.py +64 -0
- goosebit/api/routes.py +11 -0
- goosebit/auth/__init__.py +123 -0
- goosebit/db.py +32 -0
- goosebit/models.py +21 -0
- goosebit/permissions.py +55 -0
- goosebit/realtime/__init__.py +1 -0
- goosebit/realtime/logs.py +43 -0
- goosebit/realtime/routes.py +13 -0
- goosebit/settings.py +55 -0
- goosebit/ui/__init__.py +1 -0
- goosebit/ui/routes.py +104 -0
- goosebit/ui/static/__init__.py +5 -0
- goosebit/ui/static/favicon.ico +0 -0
- goosebit/ui/static/favicon.svg +1 -0
- goosebit/ui/static/js/devices.js +370 -0
- goosebit/ui/static/js/firmware.js +131 -0
- goosebit/ui/static/js/index.js +161 -0
- goosebit/ui/static/js/logs.js +18 -0
- goosebit/ui/static/svg/goosebit-logo.svg +1 -0
- goosebit/ui/templates/__init__.py +5 -0
- goosebit/ui/templates/devices.html +82 -0
- goosebit/ui/templates/firmware.html +47 -0
- goosebit/ui/templates/index.html +37 -0
- goosebit/ui/templates/login.html +34 -0
- goosebit/ui/templates/logs.html +21 -0
- goosebit/ui/templates/nav.html +64 -0
- goosebit/updater/__init__.py +1 -0
- goosebit/updater/controller/__init__.py +1 -0
- goosebit/updater/controller/routes.py +6 -0
- goosebit/updater/controller/v1/__init__.py +1 -0
- goosebit/updater/controller/v1/routes.py +92 -0
- goosebit/updater/download/__init__.py +1 -0
- goosebit/updater/download/routes.py +6 -0
- goosebit/updater/download/v1/__init__.py +1 -0
- goosebit/updater/download/v1/routes.py +26 -0
- goosebit/updater/manager.py +206 -0
- goosebit/updater/misc.py +69 -0
- goosebit/updater/routes.py +30 -0
- goosebit/updater/updates.py +93 -0
- goosebit-0.1.0.dist-info/LICENSE +201 -0
- goosebit-0.1.0.dist-info/METADATA +37 -0
- goosebit-0.1.0.dist-info/RECORD +48 -0
- goosebit-0.1.0.dist-info/WHEEL +4 -0
goosebit/ui/routes.py
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
import aiofiles
|
2
|
+
from fastapi import APIRouter, Depends, Form, Security, UploadFile
|
3
|
+
from fastapi.requests import Request
|
4
|
+
from fastapi.responses import RedirectResponse
|
5
|
+
from fastapi.security import OAuth2PasswordBearer
|
6
|
+
|
7
|
+
from goosebit.auth import authenticate_session, validate_user_permissions
|
8
|
+
from goosebit.permissions import Permissions
|
9
|
+
from goosebit.settings import UPDATES_DIR
|
10
|
+
from goosebit.ui.templates import templates
|
11
|
+
|
12
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login")
|
13
|
+
|
14
|
+
router = APIRouter(
|
15
|
+
prefix="/ui", dependencies=[Depends(authenticate_session)], include_in_schema=False
|
16
|
+
)
|
17
|
+
|
18
|
+
|
19
|
+
@router.get("/")
|
20
|
+
async def ui_root(request: Request):
|
21
|
+
return RedirectResponse(request.url_for("home_ui"))
|
22
|
+
|
23
|
+
|
24
|
+
@router.get(
|
25
|
+
"/firmware",
|
26
|
+
dependencies=[
|
27
|
+
Security(validate_user_permissions, scopes=[Permissions.FIRMWARE.READ])
|
28
|
+
],
|
29
|
+
)
|
30
|
+
async def firmware_ui(request: Request):
|
31
|
+
return templates.TemplateResponse(
|
32
|
+
"firmware.html", context={"request": request, "title": "Firmware"}
|
33
|
+
)
|
34
|
+
|
35
|
+
|
36
|
+
@router.post(
|
37
|
+
"/upload",
|
38
|
+
dependencies=[
|
39
|
+
Security(validate_user_permissions, scopes=[Permissions.FIRMWARE.WRITE])
|
40
|
+
],
|
41
|
+
)
|
42
|
+
async def upload_update(
|
43
|
+
request: Request,
|
44
|
+
chunk: UploadFile = Form(...),
|
45
|
+
init: bool = Form(...),
|
46
|
+
done: bool = Form(...),
|
47
|
+
filename: str = Form(...),
|
48
|
+
):
|
49
|
+
file = UPDATES_DIR.joinpath(filename)
|
50
|
+
tmpfile = file.with_suffix(".tmp")
|
51
|
+
contents = await chunk.read()
|
52
|
+
if init:
|
53
|
+
file.unlink(missing_ok=True)
|
54
|
+
|
55
|
+
async with aiofiles.open(tmpfile, mode="ab") as f:
|
56
|
+
await f.write(contents)
|
57
|
+
if done:
|
58
|
+
tmpfile.replace(file)
|
59
|
+
|
60
|
+
|
61
|
+
@router.get(
|
62
|
+
"/home",
|
63
|
+
dependencies=[Security(validate_user_permissions, scopes=[Permissions.HOME.READ])],
|
64
|
+
)
|
65
|
+
async def home_ui(request: Request):
|
66
|
+
return templates.TemplateResponse(
|
67
|
+
"index.html", context={"request": request, "title": "Home"}
|
68
|
+
)
|
69
|
+
|
70
|
+
|
71
|
+
@router.get(
|
72
|
+
"/devices",
|
73
|
+
dependencies=[
|
74
|
+
Security(validate_user_permissions, scopes=[Permissions.DEVICE.READ])
|
75
|
+
],
|
76
|
+
)
|
77
|
+
async def devices_ui(request: Request):
|
78
|
+
return templates.TemplateResponse(
|
79
|
+
"devices.html", context={"request": request, "title": "Devices"}
|
80
|
+
)
|
81
|
+
|
82
|
+
|
83
|
+
@router.get(
|
84
|
+
"/logs/{dev_id}",
|
85
|
+
dependencies=[
|
86
|
+
Security(validate_user_permissions, scopes=[Permissions.DEVICE.READ])
|
87
|
+
],
|
88
|
+
)
|
89
|
+
async def logs_ui(request: Request, dev_id: str):
|
90
|
+
return templates.TemplateResponse(
|
91
|
+
"logs.html", context={"request": request, "title": "Log", "device": dev_id}
|
92
|
+
)
|
93
|
+
|
94
|
+
|
95
|
+
@router.get(
|
96
|
+
"/tunnels",
|
97
|
+
dependencies=[
|
98
|
+
Security(validate_user_permissions, scopes=[Permissions.TUNNEL.READ])
|
99
|
+
],
|
100
|
+
)
|
101
|
+
async def tunnels_ui(request: Request):
|
102
|
+
return templates.TemplateResponse(
|
103
|
+
"tunnels.html", context={"request": request, "title": "Tunnels"}
|
104
|
+
)
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
<svg width="160mm" height="160mm" version="1.1" viewBox="0 0 160 160" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-32.316 -71.486)"><g transform="translate(3.1433 .77798)" stroke="#000"><path d="m112.72 77.662a3.072 3.072 0 0 0-3.0722 3.0722 3.072 3.072 0 0 0 0.89762 2.1699v13.542h-43.886c-7.6869 0-14.133 5.0881-16.192 12.091h-12.416a3.072 3.072 0 0 0-2.159-0.89762 3.072 3.072 0 0 0-3.0722 3.0722 3.072 3.072 0 0 0 3.0722 3.0722 3.072 3.072 0 0 0 2.1699-0.89762h11.731c-0.0039 0.15084-0.02274 0.29776-0.02274 0.44958v7.3003h-11.719a3.072 3.072 0 0 0-2.159-0.89762 3.072 3.072 0 0 0-3.0722 3.0722 3.072 3.072 0 0 0 3.0722 3.0722 3.072 3.072 0 0 0 2.1699-0.89762h11.708v7.7499h-11.719a3.072 3.072 0 0 0-2.159-0.89762 3.072 3.072 0 0 0-3.0722 3.0722 3.072 3.072 0 0 0 3.0722 3.0722 3.072 3.072 0 0 0 2.1699-0.89762h11.708v7.7499h-11.719a3.072 3.072 0 0 0-2.159-0.89762 3.072 3.072 0 0 0-3.0722 3.0722 3.072 3.072 0 0 0 3.0722 3.0722 3.072 3.072 0 0 0 2.1699-0.89762h11.708v7.7499h-11.719a3.072 3.072 0 0 0-2.159-0.89762 3.072 3.072 0 0 0-3.0722 3.0722 3.072 3.072 0 0 0 3.0722 3.0722 3.072 3.072 0 0 0 2.1699-0.89762h11.708v7.7499h-11.719a3.072 3.072 0 0 0-2.159-0.89762 3.072 3.072 0 0 0-3.0722 3.0722 3.072 3.072 0 0 0 3.0722 3.0722 3.072 3.072 0 0 0 2.1699-0.89761h11.708v7.7494h-11.719a3.072 3.072 0 0 0-2.159-0.89762 3.072 3.072 0 0 0-3.0722 3.0722 3.072 3.072 0 0 0 3.0722 3.0722 3.072 3.072 0 0 0 2.1699-0.89762h11.708v7.3008c0 0.1517 0.0188 0.29835 0.02274 0.44907h-11.742a3.072 3.072 0 0 0-2.159-0.89762 3.072 3.072 0 0 0-3.0722 3.0722 3.072 3.072 0 0 0 3.0722 3.0722 3.072 3.072 0 0 0 2.1699-0.89762h12.405c1.6399 5.5765 6.0655 9.9236 11.683 11.468v15.152a3.072 3.072 0 0 0-0.89762 2.159 3.072 3.072 0 0 0 3.0722 3.0722 3.072 3.072 0 0 0 3.0722-3.0722 3.072 3.072 0 0 0-0.89762-2.1699v-14.527c0.05398 5e-4 0.10609 8e-3 0.1602 8e-3h7.5897v14.529a3.072 3.072 0 0 0-0.89762 2.159 3.072 3.072 0 0 0 3.0722 3.0722 3.072 3.072 0 0 0 3.0722-3.0722 3.072 3.072 0 0 0-0.89762-2.1699v-14.518h7.7494v14.529a3.072 3.072 0 0 0-0.89762 2.159 3.072 3.072 0 0 0 3.0722 3.0722 3.072 3.072 0 0 0 3.0722-3.0722 3.072 3.072 0 0 0-0.89762-2.1699v-14.518h7.7499v14.529a3.072 3.072 0 0 0-0.89762 2.159 3.072 3.072 0 0 0 3.0722 3.0722 3.072 3.072 0 0 0 3.0722-3.0722 3.072 3.072 0 0 0-0.89762-2.1699v-14.518h7.7499v14.529a3.072 3.072 0 0 0-0.89762 2.159 3.072 3.072 0 0 0 3.0722 3.0722 3.072 3.072 0 0 0 3.0722-3.0722 3.072 3.072 0 0 0-0.89762-2.1699v-14.518h7.7499v14.529a3.072 3.072 0 0 0-0.89762 2.159 3.072 3.072 0 0 0 3.0722 3.0722 3.072 3.072 0 0 0 3.0722-3.0722 3.072 3.072 0 0 0-0.89762-2.1699v-14.518h7.7499v14.529a3.072 3.072 0 0 0-0.89762 2.159 3.072 3.072 0 0 0 3.0722 3.0722 3.072 3.072 0 0 0 3.0722-3.0722 3.072 3.072 0 0 0-0.89762-2.1699v-14.518h7.5892c0.0543 0 0.10656-8e-3 0.16071-8e-3v14.538a3.072 3.072 0 0 0-0.89762 2.159 3.072 3.072 0 0 0 3.0722 3.0722 3.072 3.072 0 0 0 3.0722-3.0722 3.072 3.072 0 0 0-0.89762-2.1699v-15.141c5.6174-1.5445 10.043-5.8916 11.682-11.468h12.992a3.072 3.072 0 0 0 2.159 0.89762 3.072 3.072 0 0 0 3.0722-3.0722 3.072 3.072 0 0 0-3.0722-3.0722 3.072 3.072 0 0 0-2.1699 0.89762h-12.307c4e-3 -0.15072 0.0227-0.29737 0.0227-0.44907v-7.3008h12.295a3.072 3.072 0 0 0 2.159 0.89762 3.072 3.072 0 0 0 3.0722-3.0722 3.072 3.072 0 0 0-3.0722-3.0722 3.072 3.072 0 0 0-2.1699 0.89762h-12.284v-7.7494h12.295a3.072 3.072 0 0 0 2.159 0.89761 3.072 3.072 0 0 0 3.0722-3.0722 3.072 3.072 0 0 0-3.0722-3.0722 3.072 3.072 0 0 0-2.1699 0.89762h-12.284v-7.7499h12.295a3.072 3.072 0 0 0 2.159 0.89762 3.072 3.072 0 0 0 3.0722-3.0722 3.072 3.072 0 0 0-3.0722-3.0722 3.072 3.072 0 0 0-2.1699 0.89762h-12.284v-43.597c0-7.791-5.2299-14.3-12.38-16.266v-14.176a3.072 3.072 0 0 0 0.89762-2.159 3.072 3.072 0 0 0-3.0722-3.0722 3.072 3.072 0 0 0-3.0722 3.0722 3.072 3.072 0 0 0 0.89762 2.1699v13.551c-0.0542-5.05e-4 -0.10644-0.0083-0.16071-0.0083h-7.5892v-13.553a3.072 3.072 0 0 0 0.89762-2.159 3.072 3.072 0 0 0-3.0722-3.0722 3.072 3.072 0 0 0-3.0722 3.0722 3.072 3.072 0 0 0 0.89762 2.1699v13.542h-7.7499v-13.553a3.072 3.072 0 0 0 0.89762-2.159 3.072 3.072 0 0 0-3.0722-3.0722 3.072 3.072 0 0 0-3.0722 3.0722 3.072 3.072 0 0 0 0.89762 2.1699v13.542h-7.7499v-13.553a3.072 3.072 0 0 0 0.89762-2.159 3.072 3.072 0 0 0-3.0722-3.0722z" fill="#fff"/><g><path d="m83.315 71.988c-7.7785-0.10611-12.693 4.0778-12.693 4.0778s-4.8045 3.244-6.8197 5.6885c-2.0152 2.4446-3.9998 3.3161-3.9998 3.3161l-10.92 4.4292s-4.3039 2.004-4.6829 6.8874c-0.37903 4.8834 8.6052 6.8151 8.6052 6.8151l10.779 1.2867c-3.797 2.1848-6.349 6.2714-6.349 10.984v74.649c0 7.0224 5.6533 12.676 12.676 12.676h74.649c7.0224 0 12.676-5.6533 12.676-12.676v-28.141c19.08-22.042 22.782-33.839 22.782-33.839s2.3205-7.1538 4.8922-15.174c2.5717-8.0199-3.6974-6.8585-3.6974-6.8585l-24.252 6.7293c-1.207-5.7485-6.2825-10.041-12.4-10.041h-33.177c-1.9266-19.83-13.78-25.894-13.78-25.894-5.3521-3.6273-10.213-4.8599-14.287-4.9155z" fill="#121e2c"/><path d="m69.443 107.99c-3.9647 0-7.1567 3.1915-7.1567 7.1562v40.219c0.96019-8.4807 4.4328-18.052 9.6733-24.824 9.8714-12.755 9.3931-19.262 5.7521-22.552zm42.644 0c0.24496 10.488-4.3418 20.083-4.3418 20.083l44.46-13.835c-0.44487-3.5314-3.4372-6.2477-7.0931-6.2477zm-49.801 61.069v21.844c0 3.9647 3.192 7.1562 7.1567 7.1562h18.112c-8.3075-3.693-21.415-12.054-25.268-29z" fill="#29befe"/><path d="m178.96 112.14-69.703 20.837s-18.894 12.226-6.2239 35.914c0.31428-4e-3 39.979 0.0384 39.979 0.0384s5.3864-3.9688 9.3648-9.8406l-40.695-0.25269s-2.0754 0.0793-2.0075-2.3201c0.068-2.3994 1.2177-2.4825 1.2177-2.4825l44.832 0.25605s3.3352-2.6554 8.4851-11.307l-46.34-0.0399s-1.8324-0.34051-1.7768-2.6268c0.0556-2.2863 2.6305-2.585 2.6305-2.585l45.957-0.17048s2.2148 0.37311 4.94-2.6224c2.7252-2.9954 9.801-21.321 9.801-21.321s0.63122-1.6448-0.46214-1.4781zm-72.26 60.929 33.354-0.22045s-4.43 6.7245-14.346 7.0264c-14.503 0.44144-19.008-6.8059-19.008-6.8059zm-39.296-87.393 7.3033 15.24s22.892 2.1374 4.4586 29.071c-18.433 26.934-7.9015 44.329-7.9015 44.329s10.309 21.075 36.028 23.294l35.722 0.45927s7.6669 5e-3 8.9801-7.2795l-0.0255-22.663s-10.703 16.455-23.723 17.408c-9.6849 0.70923-14.934-1.1038-17.672-2.5611-2.2513-1.1981-30.046-14.686-11.338-50.201 16.735-31.77-0.73572-47.166-0.73572-47.166l-13.986 3.0599s0.67535 2.9676-1.8951 4.436c-1.9916 1.1377-5.4854 0.11131-6.6183-1.8831-1.1522-2.0285-0.22903-5.5118 1.8024-6.7625 2.7445-1.6898 5.1812-0.15486 5.1812-0.15486l12.185-2.4294s-15.541-10.99-27.765 3.8033zm2.126 14.889s-16.877-2.0953-19.634-2.9542c-2.7568-0.85892-0.37994-2.5393-0.37994-2.5393s10.175-2.8442 14.302-6.5923z" fill="#fff"/></g></g></g></svg>
|
@@ -0,0 +1,370 @@
|
|
1
|
+
document.addEventListener("DOMContentLoaded", function() {
|
2
|
+
var dataTable = new DataTable("#device-table", {
|
3
|
+
responsive: true,
|
4
|
+
paging: false,
|
5
|
+
scrollCollapse: true,
|
6
|
+
scroller: true,
|
7
|
+
scrollY: "65vh",
|
8
|
+
stateSave: true,
|
9
|
+
select: true,
|
10
|
+
rowId: "uuid",
|
11
|
+
ajax: {
|
12
|
+
url: "/api/devices/all",
|
13
|
+
dataSrc: "",
|
14
|
+
},
|
15
|
+
initComplete:function(){
|
16
|
+
updateBtnState();
|
17
|
+
},
|
18
|
+
columnDefs: [
|
19
|
+
{
|
20
|
+
targets: "_all",
|
21
|
+
render: function(data, type, row) {
|
22
|
+
return data || "❓";
|
23
|
+
},
|
24
|
+
}
|
25
|
+
],
|
26
|
+
columns: [
|
27
|
+
{ data: 'name' },
|
28
|
+
{
|
29
|
+
data: 'online',
|
30
|
+
render: function(data, type, row) {
|
31
|
+
if ( type === 'display' || type === 'filter' ) {
|
32
|
+
color = data ? "success" : "danger"
|
33
|
+
return `
|
34
|
+
<div class="text-${color}">
|
35
|
+
●
|
36
|
+
</div>
|
37
|
+
`
|
38
|
+
}
|
39
|
+
return data;
|
40
|
+
}
|
41
|
+
},
|
42
|
+
{ data: 'uuid' },
|
43
|
+
{ data: 'fw' },
|
44
|
+
{
|
45
|
+
data: 'force_update',
|
46
|
+
render: function(data, type, row) {
|
47
|
+
if ( type === 'display' || type === 'filter' ) {
|
48
|
+
color = data ? "success" : "danger"
|
49
|
+
return `
|
50
|
+
<div class="text-${color}">
|
51
|
+
●
|
52
|
+
</div>
|
53
|
+
`
|
54
|
+
}
|
55
|
+
return data;
|
56
|
+
}
|
57
|
+
},
|
58
|
+
{ data: 'fw_file' },
|
59
|
+
{ data: 'last_ip' },
|
60
|
+
{
|
61
|
+
data: 'last_seen',
|
62
|
+
render: function(data, type, row) {
|
63
|
+
if ( type === 'display' || type === 'filter' ) {
|
64
|
+
return secondsToRecentDate(data);
|
65
|
+
}
|
66
|
+
return data;
|
67
|
+
}
|
68
|
+
},
|
69
|
+
{ data: 'state' },
|
70
|
+
],
|
71
|
+
layout: {
|
72
|
+
top1Start: {
|
73
|
+
buttons: [
|
74
|
+
{
|
75
|
+
text: '<i class="bi bi-check-all"></i>',
|
76
|
+
extend: "selectAll",
|
77
|
+
titleAttr: 'Select All'
|
78
|
+
},
|
79
|
+
{
|
80
|
+
text: '<i class="bi bi-x"></i>',
|
81
|
+
extend: "selectNone",
|
82
|
+
titleAttr: 'Clear Selection'
|
83
|
+
},
|
84
|
+
{
|
85
|
+
text: '<i class="bi bi-file-text"></i>',
|
86
|
+
action: function (e, dt, node, config) {
|
87
|
+
selectedDevice = dataTable.rows( {selected:true} ).data().toArray()[0];
|
88
|
+
window.location.href = `/ui/logs/${selectedDevice["uuid"]}`;
|
89
|
+
},
|
90
|
+
className: "buttons-logs",
|
91
|
+
titleAttr: 'View Log'
|
92
|
+
},
|
93
|
+
]
|
94
|
+
},
|
95
|
+
bottom1Start: {
|
96
|
+
buttons: [
|
97
|
+
{
|
98
|
+
text: '<i class="bi bi-pen" ></i>',
|
99
|
+
action: function (e, dt, node, config) {
|
100
|
+
const input = document.getElementById("device-selected-name");
|
101
|
+
const selectedDeviceName = dt.rows( {selected:true} ).data().toArray().map(d => d["name"])[0];
|
102
|
+
input.value = selectedDeviceName;
|
103
|
+
|
104
|
+
new bootstrap.Modal('#device-rename-modal').show();
|
105
|
+
},
|
106
|
+
className: "buttons-rename",
|
107
|
+
titleAttr: 'Rename Devices'
|
108
|
+
},
|
109
|
+
{
|
110
|
+
text: '<i class="bi bi-gear" ></i>',
|
111
|
+
action: function (e, dt, node, config) {
|
112
|
+
new bootstrap.Modal('#device-config-modal').show();
|
113
|
+
},
|
114
|
+
className: "buttons-config",
|
115
|
+
titleAttr: 'Configure Devices'
|
116
|
+
},
|
117
|
+
{
|
118
|
+
text: '<i class="bi bi-trash" ></i>',
|
119
|
+
action: function (e, dt, node, config) {
|
120
|
+
selectedDevices = dt.rows( {selected:true} ).data().toArray().map(d => d["uuid"]);
|
121
|
+
deleteDevices(selectedDevices);
|
122
|
+
},
|
123
|
+
className: "buttons-delete",
|
124
|
+
titleAttr: 'Delete Devices'
|
125
|
+
},
|
126
|
+
{
|
127
|
+
text: '<i class="bi bi-box-arrow-in-up-right"></i>',
|
128
|
+
action: function (e, dt, node, config) {
|
129
|
+
selectedDevices = dataTable.rows( {selected:true} ).data().toArray().map(d => d["uuid"]);
|
130
|
+
forceUpdateDevices(selectedDevices);
|
131
|
+
},
|
132
|
+
className: "buttons-force-update",
|
133
|
+
titleAttr: 'Force Update'
|
134
|
+
},
|
135
|
+
{
|
136
|
+
text: '<i class="bi bi-pin-angle"></i>',
|
137
|
+
action: function (e, dt, node, config) {
|
138
|
+
selectedDevices = dataTable.rows( {selected:true} ).data().toArray().map(d => d["uuid"]);
|
139
|
+
pinDevices(selectedDevices);
|
140
|
+
},
|
141
|
+
className: "buttons-pin",
|
142
|
+
titleAttr: 'Pin Version'
|
143
|
+
},
|
144
|
+
]
|
145
|
+
}
|
146
|
+
},
|
147
|
+
});
|
148
|
+
|
149
|
+
dataTable.on('click', 'button.edit-name', function (e) {
|
150
|
+
let data = dataTable.row(e.target.closest('tr')).data();
|
151
|
+
uuid = data["uuid"];
|
152
|
+
updateDeviceName(uuid);
|
153
|
+
});
|
154
|
+
|
155
|
+
dataTable.on( 'select', function ( e, dt, type, indexes ) {
|
156
|
+
updateBtnState();
|
157
|
+
} ).on( 'deselect', function ( e, dt, type, indexes ) {
|
158
|
+
updateBtnState();
|
159
|
+
} );
|
160
|
+
|
161
|
+
setInterval(function () {
|
162
|
+
dataTable.ajax.reload(null, false);
|
163
|
+
}, TABLE_UPDATE_TIME);
|
164
|
+
|
165
|
+
updateFirmwareSelection();
|
166
|
+
});
|
167
|
+
|
168
|
+
|
169
|
+
function updateBtnState() {
|
170
|
+
dataTable = $("#device-table").DataTable();
|
171
|
+
if (dataTable.rows( {selected:true} ).any()){
|
172
|
+
document.querySelector('button.buttons-select-none').classList.remove('disabled');
|
173
|
+
document.querySelector('button.buttons-config').classList.remove('disabled');
|
174
|
+
document.querySelector('button.buttons-force-update').classList.remove('disabled');
|
175
|
+
document.querySelector('button.buttons-delete').classList.remove('disabled');
|
176
|
+
document.querySelector('button.buttons-pin').classList.remove('disabled');
|
177
|
+
} else {
|
178
|
+
document.querySelector('button.buttons-select-none').classList.add('disabled');
|
179
|
+
document.querySelector('button.buttons-config').classList.add('disabled');
|
180
|
+
document.querySelector('button.buttons-force-update').classList.add('disabled');
|
181
|
+
document.querySelector('button.buttons-delete').classList.add('disabled');
|
182
|
+
document.querySelector('button.buttons-pin').classList.add('disabled');
|
183
|
+
}
|
184
|
+
if (dataTable.rows( {selected:true} ).count() == 1){
|
185
|
+
document.querySelector('button.buttons-logs').classList.remove('disabled');
|
186
|
+
document.querySelector('button.buttons-rename').classList.remove('disabled');
|
187
|
+
} else {
|
188
|
+
document.querySelector('button.buttons-logs').classList.add('disabled');
|
189
|
+
document.querySelector('button.buttons-rename').classList.add('disabled');
|
190
|
+
}
|
191
|
+
|
192
|
+
|
193
|
+
if(dataTable.rows( {selected:true} ).ids().toArray().length === dataTable.rows().ids().toArray().length){
|
194
|
+
document.querySelector('button.buttons-select-all').classList.add('disabled');
|
195
|
+
} else {
|
196
|
+
document.querySelector('button.buttons-select-all').classList.remove('disabled');
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
function updateFirmwareSelection() {
|
201
|
+
const url = '/api/firmware/all';
|
202
|
+
|
203
|
+
fetch(url)
|
204
|
+
.then(response => {
|
205
|
+
if (!response.ok) {
|
206
|
+
throw new Error('Request failed');
|
207
|
+
}
|
208
|
+
return response.json();
|
209
|
+
})
|
210
|
+
.then(data => {
|
211
|
+
selectElem = document.getElementById("device-selected-fw");
|
212
|
+
|
213
|
+
optionElem = document.createElement("option");
|
214
|
+
optionElem.value = "latest";
|
215
|
+
optionElem.textContent = "latest";
|
216
|
+
|
217
|
+
selectElem.appendChild(optionElem);
|
218
|
+
|
219
|
+
data.forEach(item => {
|
220
|
+
optionElem = document.createElement("option");
|
221
|
+
optionElem.value = item["name"];
|
222
|
+
optionElem.textContent = item["name"];
|
223
|
+
|
224
|
+
selectElem.appendChild(optionElem);
|
225
|
+
});
|
226
|
+
})
|
227
|
+
.catch(error => {
|
228
|
+
console.error('Failed to fetch device data:', error);
|
229
|
+
});
|
230
|
+
}
|
231
|
+
|
232
|
+
function updateDeviceConfig() {
|
233
|
+
selectedDevices = dataTable.rows( {selected:true} ).data().toArray().map(d => d["uuid"]);
|
234
|
+
selectedFirmware = document.getElementById("device-selected-fw").value;
|
235
|
+
|
236
|
+
fetch('/api/devices/update', {
|
237
|
+
method: 'POST',
|
238
|
+
headers: {
|
239
|
+
'Content-Type': 'application/json'
|
240
|
+
},
|
241
|
+
body: JSON.stringify({
|
242
|
+
'devices': selectedDevices,
|
243
|
+
'firmware': selectedFirmware
|
244
|
+
})
|
245
|
+
}).then(response => {
|
246
|
+
if (!response.ok) {
|
247
|
+
throw new Error('Failed to update devices.');
|
248
|
+
}
|
249
|
+
return response.json();
|
250
|
+
}).catch(error => {
|
251
|
+
console.error('Error:', error);
|
252
|
+
});
|
253
|
+
|
254
|
+
setTimeout(updateDeviceList, 50);
|
255
|
+
}
|
256
|
+
|
257
|
+
function updateDeviceName() {
|
258
|
+
selectedDevices = dataTable.rows( {selected:true} ).data().toArray().map(d => d["uuid"]);
|
259
|
+
name = document.getElementById("device-selected-name").value;
|
260
|
+
|
261
|
+
fetch('/api/devices/update', {
|
262
|
+
method: 'POST',
|
263
|
+
headers: {
|
264
|
+
'Content-Type': 'application/json'
|
265
|
+
},
|
266
|
+
body: JSON.stringify({
|
267
|
+
'devices': selectedDevices,
|
268
|
+
'name': name
|
269
|
+
})
|
270
|
+
}).then(response => {
|
271
|
+
if (!response.ok) {
|
272
|
+
throw new Error('Failed to update devices.');
|
273
|
+
}
|
274
|
+
return response.json();
|
275
|
+
}).catch(error => {
|
276
|
+
console.error('Error:', error);
|
277
|
+
});
|
278
|
+
|
279
|
+
setTimeout(updateDeviceList, 50);
|
280
|
+
}
|
281
|
+
|
282
|
+
|
283
|
+
function forceUpdateDevices(devices) {
|
284
|
+
fetch('/api/devices/force_update', {
|
285
|
+
method: 'POST',
|
286
|
+
headers: {
|
287
|
+
'Content-Type': 'application/json'
|
288
|
+
},
|
289
|
+
body: JSON.stringify({
|
290
|
+
'devices': devices,
|
291
|
+
})
|
292
|
+
}).then(response => {
|
293
|
+
if (!response.ok) {
|
294
|
+
throw new Error('Failed to force device update.');
|
295
|
+
}
|
296
|
+
return response.json();
|
297
|
+
}).catch(error => {
|
298
|
+
console.error('Error:', error);
|
299
|
+
});
|
300
|
+
|
301
|
+
setTimeout(updateDeviceList, 50);
|
302
|
+
}
|
303
|
+
|
304
|
+
function deleteDevices(devices) {
|
305
|
+
fetch('/api/devices/delete', {
|
306
|
+
method: 'POST',
|
307
|
+
headers: {
|
308
|
+
'Content-Type': 'application/json'
|
309
|
+
},
|
310
|
+
body: JSON.stringify({
|
311
|
+
'devices': devices,
|
312
|
+
})
|
313
|
+
}).then(response => {
|
314
|
+
if (!response.ok) {
|
315
|
+
throw new Error('Failed to delete devices.');
|
316
|
+
}
|
317
|
+
return response.json();
|
318
|
+
}).catch(error => {
|
319
|
+
console.error('Error:', error);
|
320
|
+
});
|
321
|
+
|
322
|
+
setTimeout(updateDeviceList, 50);
|
323
|
+
}
|
324
|
+
|
325
|
+
function pinDevices(devices) {
|
326
|
+
fetch('/api/devices/update', {
|
327
|
+
method: 'POST',
|
328
|
+
headers: {
|
329
|
+
'Content-Type': 'application/json'
|
330
|
+
},
|
331
|
+
body: JSON.stringify({
|
332
|
+
'devices': devices,
|
333
|
+
'firmware': "pinned"
|
334
|
+
})
|
335
|
+
}).then(response => {
|
336
|
+
if (!response.ok) {
|
337
|
+
throw new Error('Failed to update devices.');
|
338
|
+
}
|
339
|
+
return response.json();
|
340
|
+
}).catch(error => {
|
341
|
+
console.error('Error:', error);
|
342
|
+
});
|
343
|
+
|
344
|
+
setTimeout(updateDeviceList, 50);
|
345
|
+
}
|
346
|
+
|
347
|
+
function updateDeviceList() {
|
348
|
+
dataTable.ajax.reload();
|
349
|
+
}
|
350
|
+
|
351
|
+
function secondsToRecentDate(t) {
|
352
|
+
if (t == null) {
|
353
|
+
return null
|
354
|
+
}
|
355
|
+
t = Number(t);
|
356
|
+
var d = Math.floor(t / 86400)
|
357
|
+
var h = Math.floor(t % 86400 / 3600);
|
358
|
+
var m = Math.floor(t % 86400 % 3600 / 60);
|
359
|
+
var s = Math.floor(t % 86400 % 3600 % 60);
|
360
|
+
|
361
|
+
if (d > 0) {
|
362
|
+
return d + (d == 1 ? " day" : " days");
|
363
|
+
} else if (h > 0) {
|
364
|
+
return h + (h == 1 ? " hour" : " hours");
|
365
|
+
} else if (m > 0) {
|
366
|
+
return m + (m == 1 ? " minute" : " minutes");
|
367
|
+
} else {
|
368
|
+
return s + (s == 1 ? " second" : " seconds");
|
369
|
+
}
|
370
|
+
}
|
@@ -0,0 +1,131 @@
|
|
1
|
+
const CHUNK_SIZE = 10 * 1024 * 1024; // 10 MB chunk size
|
2
|
+
const form = document.getElementById('upload-form');
|
3
|
+
const fileInput = document.getElementById('file-upload');
|
4
|
+
const fileSubmit = document.getElementById('file-upload-submit');
|
5
|
+
const progressBar = document.getElementById('upload-progress');
|
6
|
+
|
7
|
+
form.addEventListener('submit', e => {
|
8
|
+
e.preventDefault();
|
9
|
+
sendFileChunks(fileInput.files[0])
|
10
|
+
});
|
11
|
+
|
12
|
+
const sendFileChunks = async (file) => {
|
13
|
+
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
|
14
|
+
let start = 0;
|
15
|
+
let uploadedChunks = 0;
|
16
|
+
|
17
|
+
fileSubmit.disabled = true
|
18
|
+
fileInput.disabled = true
|
19
|
+
|
20
|
+
for (let i = 0; i < totalChunks; i++) {
|
21
|
+
const end = Math.min(start + CHUNK_SIZE, file.size);
|
22
|
+
const chunk = file.slice(start, end);
|
23
|
+
const formData = new FormData();
|
24
|
+
formData.append('chunk', chunk);
|
25
|
+
formData.append('filename', file.name);
|
26
|
+
if (i == 0) {
|
27
|
+
formData.append('init', true);
|
28
|
+
} else {
|
29
|
+
formData.append('init', false);
|
30
|
+
}
|
31
|
+
|
32
|
+
if (i == totalChunks - 1) {
|
33
|
+
formData.append('done', true);
|
34
|
+
} else {
|
35
|
+
formData.append('done', false);
|
36
|
+
}
|
37
|
+
|
38
|
+
const response = await fetch("/ui/upload", {
|
39
|
+
method: 'POST',
|
40
|
+
body: formData,
|
41
|
+
});
|
42
|
+
|
43
|
+
if (response.ok) {
|
44
|
+
uploadedChunks++;
|
45
|
+
const progress = (uploadedChunks / totalChunks) * 100;
|
46
|
+
progressBar.style.width = `${progress}%`;
|
47
|
+
progressBar.innerHTML = `${Math.round(progress)}%`;
|
48
|
+
}
|
49
|
+
|
50
|
+
start = end;
|
51
|
+
}
|
52
|
+
|
53
|
+
window.setTimeout(function () {
|
54
|
+
resetProgress()
|
55
|
+
}, 1000)
|
56
|
+
};
|
57
|
+
|
58
|
+
function resetProgress() {
|
59
|
+
fileInput.disabled = false;
|
60
|
+
fileSubmit.disabled = false;
|
61
|
+
progressBar.style.width = `0%`;
|
62
|
+
progressBar.innerHTML = `0%`;
|
63
|
+
updateFirmwareList();
|
64
|
+
}
|
65
|
+
|
66
|
+
document.addEventListener("DOMContentLoaded", function() {
|
67
|
+
updateFirmwareList();
|
68
|
+
});
|
69
|
+
|
70
|
+
|
71
|
+
function updateFirmwareList() {
|
72
|
+
const url = '/api/firmware/all';
|
73
|
+
|
74
|
+
fetch(url)
|
75
|
+
.then(response => {
|
76
|
+
if (!response.ok) {
|
77
|
+
throw new Error('Request failed');
|
78
|
+
}
|
79
|
+
return response.json();
|
80
|
+
})
|
81
|
+
.then(data => {
|
82
|
+
const list = document.getElementById('firmware-list');
|
83
|
+
list.innerHTML = "";
|
84
|
+
|
85
|
+
data.forEach(item => {
|
86
|
+
const listItem = document.createElement('li');
|
87
|
+
listItem.textContent = item["version"];
|
88
|
+
listItem.classList = ["list-group-item d-flex justify-content-between align-items-center"];
|
89
|
+
|
90
|
+
const btnGroup = document.createElement("div")
|
91
|
+
btnGroup.classList = "btn-group"
|
92
|
+
btnGroup.role = "group"
|
93
|
+
|
94
|
+
const deleteBtn = document.createElement('button');
|
95
|
+
deleteBtn.innerHTML = "<i class='bi bi-trash'></i>";
|
96
|
+
deleteBtn.classList = ["btn btn-danger"];
|
97
|
+
deleteBtn.onclick = function() {deleteFirmware(item["name"])};
|
98
|
+
|
99
|
+
const downloadBtn = document.createElement('button');
|
100
|
+
downloadBtn.innerHTML = "<i class='bi bi-cloud-download'></i>";
|
101
|
+
downloadBtn.classList = ["btn btn-primary"];
|
102
|
+
downloadBtn.onclick = function() {window.location.href = `/api/download/${item["name"]}`};
|
103
|
+
|
104
|
+
btnGroup.appendChild(deleteBtn);
|
105
|
+
btnGroup.appendChild(downloadBtn);
|
106
|
+
|
107
|
+
listItem.appendChild(btnGroup);
|
108
|
+
list.appendChild(listItem);
|
109
|
+
});
|
110
|
+
})
|
111
|
+
.catch(error => {
|
112
|
+
console.error('Failed to fetch firmware data:', error);
|
113
|
+
});
|
114
|
+
}
|
115
|
+
|
116
|
+
function deleteFirmware(file) {
|
117
|
+
fetch('/api/firmware/delete', {
|
118
|
+
method: 'POST',
|
119
|
+
body: file,
|
120
|
+
})
|
121
|
+
.then(response => {
|
122
|
+
if (!response.ok) {
|
123
|
+
throw new Error('Failed to delete firmware.');
|
124
|
+
}
|
125
|
+
updateFirmwareList();
|
126
|
+
return response.json();
|
127
|
+
})
|
128
|
+
.catch(error => {
|
129
|
+
console.error('Error:', error);
|
130
|
+
});
|
131
|
+
}
|