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
@@ -0,0 +1,161 @@
|
|
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
|
+
ajax: {
|
10
|
+
url: "/api/devices/all",
|
11
|
+
dataSrc: "",
|
12
|
+
},
|
13
|
+
initComplete:function(){
|
14
|
+
updateBtnState();
|
15
|
+
},
|
16
|
+
columnDefs: [
|
17
|
+
{
|
18
|
+
targets: "_all",
|
19
|
+
render: function(data, type, row) {
|
20
|
+
return data || "❓";
|
21
|
+
},
|
22
|
+
}
|
23
|
+
],
|
24
|
+
columns: [
|
25
|
+
{ data: 'name' },
|
26
|
+
{
|
27
|
+
data: 'online',
|
28
|
+
render: function(data, type, row) {
|
29
|
+
if ( type === 'display' || type === 'filter' ) {
|
30
|
+
online = data ? "Online" : "Offline"
|
31
|
+
color = data ? "success" : "danger"
|
32
|
+
return `
|
33
|
+
<div class="text-${color}">
|
34
|
+
●
|
35
|
+
</div>
|
36
|
+
`
|
37
|
+
}
|
38
|
+
return data;
|
39
|
+
}
|
40
|
+
},
|
41
|
+
{ data: 'uuid' },
|
42
|
+
{ data: 'fw' },
|
43
|
+
{ data: 'last_ip' },
|
44
|
+
{
|
45
|
+
data: 'last_seen',
|
46
|
+
render: function(data, type, row) {
|
47
|
+
if ( type === 'display' || type === 'filter' ) {
|
48
|
+
return secondsToRecentDate(data);
|
49
|
+
}
|
50
|
+
return data;
|
51
|
+
}
|
52
|
+
},
|
53
|
+
],
|
54
|
+
select: true,
|
55
|
+
rowId: "uuid",
|
56
|
+
layout: {
|
57
|
+
top1Start: {
|
58
|
+
buttons: [
|
59
|
+
{
|
60
|
+
text: '<i class="bi bi-check-all"></i>',
|
61
|
+
extend: "selectAll",
|
62
|
+
titleAttr: 'Select All'
|
63
|
+
},
|
64
|
+
{
|
65
|
+
text: '<i class="bi bi-x"></i>',
|
66
|
+
extend: "selectNone",
|
67
|
+
titleAttr: 'Clear Selection'
|
68
|
+
},
|
69
|
+
{
|
70
|
+
text: '<i class="bi bi-file-earmark-arrow-down"></i>',
|
71
|
+
action: function (e, dt, node, config) {
|
72
|
+
selectedDevices = dt.rows( {selected:true} ).data().toArray();
|
73
|
+
downloadLogins(selectedDevices);
|
74
|
+
},
|
75
|
+
className: "buttons-export-login",
|
76
|
+
titleAttr: 'Export Login'
|
77
|
+
},
|
78
|
+
{
|
79
|
+
text: '<i class="bi bi-file-text"></i>',
|
80
|
+
action: function (e, dt, node, config) {
|
81
|
+
selectedDevice = dt.rows( {selected:true} ).data().toArray()[0];
|
82
|
+
window.location.href = `/ui/logs/${selectedDevice["uuid"]}`;
|
83
|
+
},
|
84
|
+
className: "buttons-logs",
|
85
|
+
titleAttr: 'View Log'
|
86
|
+
},
|
87
|
+
]
|
88
|
+
}
|
89
|
+
}
|
90
|
+
});
|
91
|
+
|
92
|
+
dataTable.on( 'select', function ( e, dt, type, indexes ) {
|
93
|
+
updateBtnState();
|
94
|
+
} ).on( 'deselect', function ( e, dt, type, indexes ) {
|
95
|
+
updateBtnState();
|
96
|
+
} );
|
97
|
+
|
98
|
+
setInterval(function () {
|
99
|
+
dataTable.ajax.reload(null, false);
|
100
|
+
}, TABLE_UPDATE_TIME);
|
101
|
+
});
|
102
|
+
|
103
|
+
function updateBtnState() {
|
104
|
+
dataTable = $("#device-table").DataTable();
|
105
|
+
if (dataTable.rows( {selected:true} ).any()){
|
106
|
+
document.querySelector('button.buttons-select-none').classList.remove('disabled');
|
107
|
+
document.querySelector('button.buttons-export-login').classList.remove('disabled');
|
108
|
+
} else {
|
109
|
+
document.querySelector('button.buttons-select-none').classList.add('disabled');
|
110
|
+
document.querySelector('button.buttons-export-login').classList.add('disabled');
|
111
|
+
}
|
112
|
+
if (dataTable.rows( {selected:true} ).count() == 1){
|
113
|
+
document.querySelector('button.buttons-logs').classList.remove('disabled');
|
114
|
+
} else {
|
115
|
+
document.querySelector('button.buttons-logs').classList.add('disabled');
|
116
|
+
}
|
117
|
+
|
118
|
+
|
119
|
+
if(dataTable.rows( {selected:true} ).ids().toArray().length === dataTable.rows().ids().toArray().length){
|
120
|
+
document.querySelector('button.buttons-select-all').classList.add('disabled');
|
121
|
+
} else {
|
122
|
+
document.querySelector('button.buttons-select-all').classList.remove('disabled');
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
function downloadLogins(devices) {
|
127
|
+
let deviceLogins = devices.map(dev => {
|
128
|
+
return [dev["name"], `https://${dev["uuid"]}-access.loadsync.io`, dev["uuid"], dev["web_pwd"]];
|
129
|
+
});
|
130
|
+
deviceLogins.unshift(["Building", "Access Link", "Serial Number/Wifi SSID", "Login/Wifi Password"]);
|
131
|
+
|
132
|
+
let csvContent = "data:text/csv;charset=utf-8," + deviceLogins.map(e => e.join(",")).join("\n");
|
133
|
+
var encodedUri = encodeURI(csvContent);
|
134
|
+
var link = document.createElement("a");
|
135
|
+
link.setAttribute("href", encodedUri);
|
136
|
+
link.setAttribute("download", "LoadsyncLogins-Export.csv");
|
137
|
+
document.body.appendChild(link);
|
138
|
+
|
139
|
+
link.click();
|
140
|
+
}
|
141
|
+
|
142
|
+
function secondsToRecentDate(t) {
|
143
|
+
if (t == null) {
|
144
|
+
return null
|
145
|
+
}
|
146
|
+
t = Number(t);
|
147
|
+
var d = Math.floor(t / 86400)
|
148
|
+
var h = Math.floor(t % 86400 / 3600);
|
149
|
+
var m = Math.floor(t % 86400 % 3600 / 60);
|
150
|
+
var s = Math.floor(t % 86400 % 3600 % 60);
|
151
|
+
|
152
|
+
if (d > 0) {
|
153
|
+
return d + (d == 1 ? " day" : " days");
|
154
|
+
} else if (h > 0) {
|
155
|
+
return h + (h == 1 ? " hour" : " hours");
|
156
|
+
} else if (m > 0) {
|
157
|
+
return m + (m == 1 ? " minute" : " minutes");
|
158
|
+
} else {
|
159
|
+
return s + (s == 1 ? " second" : " seconds");
|
160
|
+
}
|
161
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
document.addEventListener("DOMContentLoaded", function() {
|
2
|
+
var logs_ws = create_ws(`/realtime/logs/${device}`);
|
3
|
+
|
4
|
+
logs_ws.addEventListener('message', (event) => {
|
5
|
+
res = JSON.parse(event.data);
|
6
|
+
|
7
|
+
const logElem = document.getElementById('device-log');
|
8
|
+
if (res["clear"]) {
|
9
|
+
logElem.textContent = ""
|
10
|
+
}
|
11
|
+
logElem.textContent += res["log"];
|
12
|
+
});
|
13
|
+
});
|
14
|
+
|
15
|
+
function create_ws(s) {
|
16
|
+
var l = window.location;
|
17
|
+
return new WebSocket(((l.protocol === "https:") ? "wss://" : "ws://") + l.hostname + (((l.port != 80) && (l.port != 443)) ? ":" + l.port : "") + s);
|
18
|
+
}
|
@@ -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,82 @@
|
|
1
|
+
{% extends "nav.html" %}
|
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>
|
10
|
+
Name
|
11
|
+
</th>
|
12
|
+
<th>
|
13
|
+
Up
|
14
|
+
</th>
|
15
|
+
<th>
|
16
|
+
UUID
|
17
|
+
</th>
|
18
|
+
<th>
|
19
|
+
Firmware
|
20
|
+
</th>
|
21
|
+
<th>
|
22
|
+
Force Update
|
23
|
+
</th>
|
24
|
+
<th>
|
25
|
+
Update File
|
26
|
+
</th>
|
27
|
+
<th>
|
28
|
+
Last IP
|
29
|
+
</th>
|
30
|
+
<th>
|
31
|
+
Last Seen
|
32
|
+
</th>
|
33
|
+
<th>
|
34
|
+
State
|
35
|
+
</th>
|
36
|
+
</tr>
|
37
|
+
</thead>
|
38
|
+
<tbody id="devices-list">
|
39
|
+
|
40
|
+
</tbody>
|
41
|
+
</table>
|
42
|
+
</div>
|
43
|
+
</div>
|
44
|
+
</div>
|
45
|
+
<div class="modal" id="device-config-modal">
|
46
|
+
<div class="modal-dialog">
|
47
|
+
<div class="modal-content">
|
48
|
+
<div class="modal-header">
|
49
|
+
<h5 class="modal-title">Configure Devices</h5>
|
50
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
51
|
+
</div>
|
52
|
+
<div class="modal-body">
|
53
|
+
<select class="form-select" id="device-selected-fw">
|
54
|
+
|
55
|
+
</select>
|
56
|
+
</div>
|
57
|
+
<div class="modal-footer">
|
58
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
59
|
+
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" onclick="updateDeviceConfig()">Save changes</button>
|
60
|
+
</div>
|
61
|
+
</div>
|
62
|
+
</div>
|
63
|
+
</div>
|
64
|
+
<div class="modal" id="device-rename-modal">
|
65
|
+
<div class="modal-dialog">
|
66
|
+
<div class="modal-content">
|
67
|
+
<div class="modal-header">
|
68
|
+
<h5 class="modal-title">Rename Device</h5>
|
69
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
70
|
+
</div>
|
71
|
+
<div class="modal-body">
|
72
|
+
<input id="device-selected-name" class="form-control" placeholder="Name"/>
|
73
|
+
</div>
|
74
|
+
<div class="modal-footer">
|
75
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
76
|
+
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" onclick="updateDeviceName()">Save changes</button>
|
77
|
+
</div>
|
78
|
+
</div>
|
79
|
+
</div>
|
80
|
+
</div>
|
81
|
+
<script src="{{ url_for('static', path='js/devices.js') }}"></script>
|
82
|
+
{% endblock content %}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
{% extends "nav.html" %}
|
2
|
+
{% block content %}
|
3
|
+
<div class="container-fluid">
|
4
|
+
<div class="row p-2 pt-4 g-4 d-flex justify-content-center">
|
5
|
+
{% if "firmware.write" in request.user.permissions %}
|
6
|
+
<div class="col col-12 col-lg-6">
|
7
|
+
<div class="card border rounded">
|
8
|
+
<div class="card-header">
|
9
|
+
<h3>Upload SWUpdate File</h3>
|
10
|
+
</div>
|
11
|
+
<div class="card-body">
|
12
|
+
<form id="upload-form">
|
13
|
+
<div class="row g-3 row-cols-1">
|
14
|
+
<div class="col">
|
15
|
+
<input class="form-control" type="file" accept=".swu" id="file-upload" name="file" />
|
16
|
+
</div>
|
17
|
+
<div class="col">
|
18
|
+
<input class="btn btn-primary w-100" id="file-upload-submit" type="submit" value="Upload" />
|
19
|
+
</div>
|
20
|
+
<h3>Upload Progress</h3>
|
21
|
+
<div class="col">
|
22
|
+
<div class="progress w-100" style=" height: 40px;" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
|
23
|
+
<div class="progress-bar progress-bar-striped progress-bar-animated" id="upload-progress" style="width: 0%;">0%</div>
|
24
|
+
</div>
|
25
|
+
</div>
|
26
|
+
</div>
|
27
|
+
</form>
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
{% endif %}
|
32
|
+
<div class="col-12 col-lg-6">
|
33
|
+
<div class="card border rounded">
|
34
|
+
<div class="card-header">
|
35
|
+
<h3>Firmware</h3>
|
36
|
+
</div>
|
37
|
+
<div class="card-body p-3">
|
38
|
+
<ul class="list-group" id="firmware-list">
|
39
|
+
|
40
|
+
</ul>
|
41
|
+
</div>
|
42
|
+
</div>
|
43
|
+
</div>
|
44
|
+
</div>
|
45
|
+
</div>
|
46
|
+
<script src="{{ url_for('static', path='js/firmware.js') }}"></script>
|
47
|
+
{% endblock content %}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
{% extends "nav.html" %}
|
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>
|
10
|
+
Name
|
11
|
+
</th>
|
12
|
+
<th>
|
13
|
+
Up
|
14
|
+
</th>
|
15
|
+
<th>
|
16
|
+
UUID
|
17
|
+
</th>
|
18
|
+
<th>
|
19
|
+
Firmware
|
20
|
+
</th>
|
21
|
+
<th>
|
22
|
+
Last IP
|
23
|
+
</th>
|
24
|
+
<th>
|
25
|
+
Last Seen
|
26
|
+
</th>
|
27
|
+
</tr>
|
28
|
+
</thead>
|
29
|
+
<tbody id="devices-list">
|
30
|
+
|
31
|
+
</tbody>
|
32
|
+
</table>
|
33
|
+
</div>
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
<script src="{{ url_for('static', path='js/index.js') }}"></script>
|
37
|
+
{% endblock content %}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
6
|
+
<title>Login</title>
|
7
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
9
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
10
|
+
<link rel="icon" href="{{ url_for('static', path='favicon.svg') }}">
|
11
|
+
</head>
|
12
|
+
<body data-bs-theme="dark">
|
13
|
+
<div class="container py-5 h-100">
|
14
|
+
<div class="row d-flex justify-content-center align-items-center h-100">
|
15
|
+
<div class="col-12 col-md-8 col-lg-6 col-xl-5">
|
16
|
+
<div class="mb-md-5 mt-md-4 pb-5">
|
17
|
+
<h2 class="fw-bold mb-3 text-uppercase">Login</h2>
|
18
|
+
<form method="post">
|
19
|
+
<div class="form-outline form-white mb-4">
|
20
|
+
<input type="email" id="username" name="username" placeholder="Email" class="form-control form-control-lg" />
|
21
|
+
</div>
|
22
|
+
|
23
|
+
<div class="form-outline form-white mb-4">
|
24
|
+
<input type="password" id="password" name="password" placeholder="Password" class="form-control form-control-lg" />
|
25
|
+
</div>
|
26
|
+
|
27
|
+
<button class="btn btn-outline-light btn-lg px-5 w-100" type="submit">Login</button>
|
28
|
+
</form>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
</body>
|
34
|
+
</html>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
{% extends "nav.html" %}
|
2
|
+
{% block content %}
|
3
|
+
<div class="container-fluid">
|
4
|
+
<div class="row p-2 d-flex justify-content-center">
|
5
|
+
<div class="col col-12 col-lg-9">
|
6
|
+
<div class="card border rounded">
|
7
|
+
<div class="card-header">
|
8
|
+
<h3>Logs - {{ device }}</h3>
|
9
|
+
</div>
|
10
|
+
<div class="card-body">
|
11
|
+
<pre id="device-log"></pre>
|
12
|
+
</div>
|
13
|
+
</div>
|
14
|
+
</div>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
<script>
|
18
|
+
device = "{{ device }}"
|
19
|
+
</script>
|
20
|
+
<script src="{{ url_for('static', path='js/logs.js') }}"></script>
|
21
|
+
{% endblock content %}
|
@@ -0,0 +1,64 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<script>
|
5
|
+
const PERMISSIONS = {{request.user.get_json_permissions() | safe}};
|
6
|
+
</script>
|
7
|
+
<meta charset="utf-8">
|
8
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
9
|
+
<title>{{title}}</title>
|
10
|
+
<!--bootstrap-->
|
11
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
12
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
13
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
14
|
+
<!--data tables-->
|
15
|
+
<link href="https://cdn.datatables.net/v/bs5/jq-3.7.0/dt-2.0.1/b-3.0.0/r-3.0.0/sl-2.0.0/datatables.min.css" rel="stylesheet">
|
16
|
+
<script src="https://cdn.datatables.net/v/bs5/jq-3.7.0/dt-2.0.1/b-3.0.0/r-3.0.0/sl-2.0.0/datatables.min.js"></script>
|
17
|
+
<!--favicon-->
|
18
|
+
<link rel="icon" href="{{ url_for('static', path='favicon.svg') }}">
|
19
|
+
<!--data tables alignment fix-->
|
20
|
+
<style>
|
21
|
+
th.dt-type-numeric {
|
22
|
+
text-align: left!important;
|
23
|
+
}
|
24
|
+
td.dt-type-numeric {
|
25
|
+
text-align: left!important;
|
26
|
+
}
|
27
|
+
</style>
|
28
|
+
<script>
|
29
|
+
const TABLE_UPDATE_TIME = 3000;
|
30
|
+
</script>
|
31
|
+
</head>
|
32
|
+
<body data-bs-theme="dark">
|
33
|
+
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
34
|
+
<div class="container-fluid">
|
35
|
+
<a class="navbar-brand" href="/ui/home">
|
36
|
+
<img src="{{ request.url_for('static', path='svg/goosebit-logo.svg') }}" class="me-2" style="height:30px; width: 30px;"/>
|
37
|
+
GooseBit
|
38
|
+
</a>
|
39
|
+
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
|
40
|
+
<span class="navbar-toggler-icon"></span>
|
41
|
+
</button>
|
42
|
+
<div class="collapse navbar-collapse" id="navbar">
|
43
|
+
<div class="navbar-nav">
|
44
|
+
{% if "home.read" in request.user.permissions %}
|
45
|
+
<a class="nav-link{% if request.url.path.endswith('home') %} active{% endif %}" href="/ui/home">Home</a>
|
46
|
+
{% endif %}
|
47
|
+
{% if "firmware.read" in request.user.permissions %}
|
48
|
+
<a class="nav-link{% if request.url.path.endswith('firmware') %} active{% endif %}" href="/ui/firmware">Firmware</a>
|
49
|
+
{% endif %}
|
50
|
+
{% if "devices.read" in request.user.permissions %}
|
51
|
+
<a class="nav-link{% if request.url.path.endswith('devices') %} active{% endif %}" href="/ui/devices">Devices</a>
|
52
|
+
{% endif %}
|
53
|
+
|
54
|
+
</div>
|
55
|
+
<div class="navbar-nav d-flex flex-fill justify-content-end">
|
56
|
+
<a class="nav-link" href="/logout">Logout<i class="bi bi-box-arrow-right ps-2"></i></a>
|
57
|
+
</div>
|
58
|
+
</div>
|
59
|
+
</div>
|
60
|
+
</nav>
|
61
|
+
{% block content %}
|
62
|
+
{% endblock content %}
|
63
|
+
</body>
|
64
|
+
</html>
|
@@ -0,0 +1 @@
|
|
1
|
+
from .routes import router
|
@@ -0,0 +1 @@
|
|
1
|
+
from .routes import router
|
@@ -0,0 +1 @@
|
|
1
|
+
from .routes import router
|
@@ -0,0 +1,92 @@
|
|
1
|
+
import json
|
2
|
+
|
3
|
+
from fastapi import APIRouter, Depends
|
4
|
+
from fastapi.requests import Request
|
5
|
+
|
6
|
+
from goosebit.updater.manager import UpdateManager, get_update_manager
|
7
|
+
|
8
|
+
# v1 is hardware revision
|
9
|
+
router = APIRouter(prefix="/v1")
|
10
|
+
|
11
|
+
|
12
|
+
@router.get("/{dev_id}")
|
13
|
+
async def polling(
|
14
|
+
request: Request,
|
15
|
+
tenant: str,
|
16
|
+
dev_id: str,
|
17
|
+
updater: UpdateManager = Depends(get_update_manager),
|
18
|
+
):
|
19
|
+
return {
|
20
|
+
"config": {"polling": {"sleep": updater.poll_time}},
|
21
|
+
"_links": {
|
22
|
+
"deploymentBase": {
|
23
|
+
"href": str(
|
24
|
+
request.url_for(
|
25
|
+
"deployment_base", tenant=tenant, dev_id=dev_id, action_id=1
|
26
|
+
)
|
27
|
+
)
|
28
|
+
},
|
29
|
+
},
|
30
|
+
}
|
31
|
+
|
32
|
+
|
33
|
+
@router.put("/{dev_id}/configData")
|
34
|
+
async def config_data(
|
35
|
+
request: Request,
|
36
|
+
dev_id: str,
|
37
|
+
updater: UpdateManager = Depends(get_update_manager),
|
38
|
+
):
|
39
|
+
data = await request.json()
|
40
|
+
# TODO: make standard schema to deal with this
|
41
|
+
print(data)
|
42
|
+
return {"success": True, "message": "Updated swupdate data."}
|
43
|
+
|
44
|
+
|
45
|
+
@router.get("/{dev_id}/deploymentBase/{action_id}")
|
46
|
+
async def deployment_base(
|
47
|
+
request: Request,
|
48
|
+
tenant: str,
|
49
|
+
dev_id: str,
|
50
|
+
action_id: int,
|
51
|
+
updater: UpdateManager = Depends(get_update_manager),
|
52
|
+
):
|
53
|
+
artifact = await updater.get_update_file()
|
54
|
+
update = await updater.get_update_mode()
|
55
|
+
await updater.save()
|
56
|
+
|
57
|
+
return {
|
58
|
+
"id": f"{action_id}",
|
59
|
+
"deployment": {
|
60
|
+
"download": update,
|
61
|
+
"update": update,
|
62
|
+
"chunks": artifact.generate_chunk(request, tenant=tenant, dev_id=dev_id),
|
63
|
+
},
|
64
|
+
}
|
65
|
+
|
66
|
+
|
67
|
+
@router.post("/{dev_id}/deploymentBase/{action_id}/feedback")
|
68
|
+
async def deployment_feedback(
|
69
|
+
request: Request,
|
70
|
+
tenant: str,
|
71
|
+
dev_id: str,
|
72
|
+
action_id: int,
|
73
|
+
updater: UpdateManager = Depends(get_update_manager),
|
74
|
+
):
|
75
|
+
try:
|
76
|
+
data = await request.json()
|
77
|
+
except json.JSONDecodeError:
|
78
|
+
return
|
79
|
+
try:
|
80
|
+
state = data["status"]["result"]["finished"]
|
81
|
+
await updater.update_device_state(state)
|
82
|
+
except KeyError:
|
83
|
+
pass
|
84
|
+
|
85
|
+
try:
|
86
|
+
log = data["status"]["details"]
|
87
|
+
await updater.update_log("\n".join(log))
|
88
|
+
except KeyError:
|
89
|
+
pass
|
90
|
+
|
91
|
+
await updater.save()
|
92
|
+
return {"id": str(action_id)}
|
@@ -0,0 +1 @@
|
|
1
|
+
from .routes import router
|
@@ -0,0 +1 @@
|
|
1
|
+
from .routes import router
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from fastapi import APIRouter, Depends
|
2
|
+
from fastapi.requests import Request
|
3
|
+
from fastapi.responses import FileResponse
|
4
|
+
|
5
|
+
from goosebit.settings import TOKEN_SWU_DIR, UPDATES_DIR
|
6
|
+
from goosebit.updater.manager import UpdateManager, get_update_manager
|
7
|
+
|
8
|
+
router = APIRouter(prefix="/v1")
|
9
|
+
|
10
|
+
|
11
|
+
@router.get("/{dev_id}/{file}")
|
12
|
+
async def download_file(request: Request, tenant: str, dev_id: str, file: str):
|
13
|
+
filename = UPDATES_DIR.joinpath(file)
|
14
|
+
return FileResponse(filename, media_type="application/octet-stream")
|
15
|
+
|
16
|
+
|
17
|
+
@router.get("/{dev_id}/cfd_conf/{file}")
|
18
|
+
async def download_cfd_conf(
|
19
|
+
request: Request,
|
20
|
+
tenant: str,
|
21
|
+
dev_id: str,
|
22
|
+
file: str,
|
23
|
+
updater: UpdateManager = Depends(get_update_manager),
|
24
|
+
):
|
25
|
+
filename = TOKEN_SWU_DIR.joinpath(dev_id, file)
|
26
|
+
return FileResponse(filename, media_type="application/octet-stream")
|