goosebit 0.1.1__py3-none-any.whl → 0.1.2__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 +5 -2
- goosebit/api/__init__.py +1 -1
- goosebit/api/devices.py +59 -39
- goosebit/api/download.py +28 -14
- goosebit/api/firmware.py +40 -34
- goosebit/api/helper.py +30 -0
- goosebit/api/rollouts.py +64 -13
- goosebit/api/routes.py +14 -7
- goosebit/auth/__init__.py +14 -6
- goosebit/db.py +5 -0
- goosebit/models.py +110 -10
- goosebit/permissions.py +26 -20
- goosebit/realtime/__init__.py +1 -1
- goosebit/realtime/logs.py +3 -6
- goosebit/settings.py +4 -6
- goosebit/telemetry/__init__.py +28 -0
- goosebit/telemetry/prometheus.py +10 -0
- goosebit/ui/__init__.py +1 -1
- goosebit/ui/routes.py +33 -40
- goosebit/ui/static/js/devices.js +187 -250
- goosebit/ui/static/js/firmware.js +229 -92
- goosebit/ui/static/js/index.js +79 -90
- goosebit/ui/static/js/logs.js +14 -11
- goosebit/ui/static/js/rollouts.js +169 -27
- goosebit/ui/static/js/util.js +66 -0
- goosebit/ui/templates/devices.html +75 -51
- goosebit/ui/templates/firmware.html +149 -35
- goosebit/ui/templates/index.html +9 -26
- goosebit/ui/templates/login.html +58 -27
- goosebit/ui/templates/logs.html +15 -5
- goosebit/ui/templates/nav.html +77 -26
- goosebit/ui/templates/rollouts.html +62 -39
- goosebit/updater/__init__.py +1 -1
- goosebit/updater/controller/__init__.py +1 -1
- goosebit/updater/controller/v1/__init__.py +1 -1
- goosebit/updater/controller/v1/routes.py +53 -35
- goosebit/updater/manager.py +205 -103
- goosebit/updater/routes.py +4 -7
- goosebit/updates/__init__.py +70 -0
- goosebit/updates/swdesc.py +83 -0
- {goosebit-0.1.1.dist-info → goosebit-0.1.2.dist-info}/METADATA +53 -3
- goosebit-0.1.2.dist-info/RECORD +51 -0
- goosebit/updater/download/__init__.py +0 -1
- goosebit/updater/download/routes.py +0 -6
- goosebit/updater/download/v1/__init__.py +0 -1
- goosebit/updater/download/v1/routes.py +0 -13
- goosebit/updater/misc.py +0 -57
- goosebit/updates/artifacts.py +0 -89
- goosebit/updates/version.py +0 -38
- goosebit-0.1.1.dist-info/RECORD +0 -53
- {goosebit-0.1.1.dist-info → goosebit-0.1.2.dist-info}/LICENSE +0 -0
- {goosebit-0.1.1.dist-info → goosebit-0.1.2.dist-info}/WHEEL +0 -0
@@ -1,140 +1,277 @@
|
|
1
1
|
const CHUNK_SIZE = 10 * 1024 * 1024; // 10 MB chunk size
|
2
|
-
const
|
3
|
-
const
|
4
|
-
const
|
5
|
-
const
|
2
|
+
const uploadForm = document.getElementById("upload-form");
|
3
|
+
const uploadFileInput = document.getElementById("file-upload");
|
4
|
+
const uploadFileSubmit = document.getElementById("file-upload-submit");
|
5
|
+
const uploadProgressBar = document.getElementById("upload-progress");
|
6
6
|
|
7
|
-
|
7
|
+
let dataTable;
|
8
|
+
|
9
|
+
uploadForm.addEventListener("submit", async (e) => {
|
8
10
|
e.preventDefault();
|
9
|
-
sendFileChunks(
|
11
|
+
await sendFileChunks(uploadFileInput.files[0]);
|
10
12
|
});
|
11
13
|
|
12
|
-
|
14
|
+
async function sendFileChunks(file) {
|
13
15
|
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
|
14
16
|
let start = 0;
|
15
17
|
let uploadedChunks = 0;
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
+
uploadFileSubmit.disabled = true;
|
20
|
+
uploadFileSubmit.classList.add("d-none");
|
21
|
+
uploadFileInput.disabled = true;
|
22
|
+
uploadProgressBar.parentElement.classList.remove("d-none");
|
19
23
|
|
20
24
|
for (let i = 0; i < totalChunks; i++) {
|
21
25
|
const end = Math.min(start + CHUNK_SIZE, file.size);
|
22
26
|
const chunk = file.slice(start, end);
|
23
27
|
const formData = new FormData();
|
24
|
-
formData.append(
|
25
|
-
formData.append(
|
26
|
-
if (i
|
27
|
-
formData.append(
|
28
|
-
} else
|
29
|
-
formData.append(
|
28
|
+
formData.append("chunk", chunk);
|
29
|
+
formData.append("filename", file.name);
|
30
|
+
if (i === 0) {
|
31
|
+
formData.append("init", "true");
|
32
|
+
} else {
|
33
|
+
formData.append("init", "false");
|
30
34
|
}
|
31
35
|
|
32
|
-
if (i
|
33
|
-
formData.append(
|
36
|
+
if (i === totalChunks - 1) {
|
37
|
+
formData.append("done", "true");
|
34
38
|
} else {
|
35
|
-
formData.append(
|
39
|
+
formData.append("done", "false");
|
36
40
|
}
|
37
41
|
|
38
|
-
const response = await fetch("/ui/upload", {
|
39
|
-
method:
|
42
|
+
const response = await fetch("/ui/upload/local", {
|
43
|
+
method: "POST",
|
40
44
|
body: formData,
|
41
45
|
});
|
42
46
|
|
43
47
|
if (response.ok) {
|
44
48
|
uploadedChunks++;
|
45
49
|
const progress = (uploadedChunks / totalChunks) * 100;
|
46
|
-
|
47
|
-
|
50
|
+
uploadProgressBar.style.width = `${progress}%`;
|
51
|
+
uploadProgressBar.innerHTML = `${Math.round(progress)}%`;
|
48
52
|
} else {
|
49
53
|
if (response.status === 400) {
|
50
|
-
result = await response.json()
|
51
|
-
alerts = document.getElementById("upload-alerts");
|
54
|
+
const result = await response.json();
|
55
|
+
const alerts = document.getElementById("upload-alerts");
|
52
56
|
alerts.innerHTML = `<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
53
|
-
${result
|
57
|
+
${result.detail}
|
54
58
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
55
|
-
</div
|
59
|
+
</div>`;
|
56
60
|
}
|
57
61
|
}
|
58
62
|
|
59
63
|
start = end;
|
60
64
|
}
|
61
65
|
|
62
|
-
window.setTimeout(
|
63
|
-
resetProgress()
|
64
|
-
}, 1000)
|
65
|
-
};
|
66
|
-
|
67
|
-
function resetProgress() {
|
68
|
-
fileInput.disabled = false;
|
69
|
-
fileSubmit.disabled = false;
|
70
|
-
progressBar.style.width = `0%`;
|
71
|
-
progressBar.innerHTML = `0%`;
|
72
|
-
updateFirmwareList();
|
66
|
+
window.setTimeout(() => {
|
67
|
+
resetProgress();
|
68
|
+
}, 1000);
|
73
69
|
}
|
74
70
|
|
75
|
-
document.
|
76
|
-
|
71
|
+
const urlForm = document.getElementById("url-form");
|
72
|
+
const urlFileInput = document.getElementById("file-url");
|
73
|
+
const urlFileSubmit = document.getElementById("url-submit");
|
74
|
+
|
75
|
+
urlForm.addEventListener("submit", async (e) => {
|
76
|
+
e.preventDefault();
|
77
|
+
await sendFileUrl(urlFileInput.value);
|
77
78
|
});
|
78
79
|
|
80
|
+
async function sendFileUrl(url) {
|
81
|
+
const formData = new FormData();
|
82
|
+
formData.append("url", url);
|
79
83
|
|
80
|
-
|
81
|
-
|
84
|
+
const response = await fetch("/ui/upload/remote", {
|
85
|
+
method: "POST",
|
86
|
+
body: formData,
|
87
|
+
});
|
82
88
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
89
|
+
if (!response.ok) {
|
90
|
+
if (response.status === 400) {
|
91
|
+
const result = await response.json();
|
92
|
+
const alerts = document.getElementById("url-alerts");
|
93
|
+
alerts.innerHTML = `<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
94
|
+
${result.detail}
|
95
|
+
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
96
|
+
</div>`;
|
87
97
|
}
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
function updateFirmwareList() {
|
102
|
+
dataTable.ajax.reload(null, false);
|
103
|
+
}
|
104
|
+
|
105
|
+
function resetProgress() {
|
106
|
+
uploadFileInput.disabled = false;
|
107
|
+
uploadFileSubmit.disabled = false;
|
108
|
+
uploadFileSubmit.classList.remove("d-none");
|
109
|
+
urlFileInput.disabled = false;
|
110
|
+
urlFileSubmit.disabled = false;
|
111
|
+
uploadProgressBar.style.width = "0%";
|
112
|
+
uploadProgressBar.innerHTML = "0%";
|
113
|
+
uploadProgressBar.parentElement.classList.add("d-none");
|
114
|
+
|
115
|
+
updateFirmwareList();
|
116
|
+
}
|
117
|
+
|
118
|
+
document.addEventListener("DOMContentLoaded", () => {
|
119
|
+
dataTable = new DataTable("#firmware-table", {
|
120
|
+
responsive: true,
|
121
|
+
paging: true,
|
122
|
+
processing: false,
|
123
|
+
serverSide: true,
|
124
|
+
order: [2, "desc"],
|
125
|
+
scrollCollapse: true,
|
126
|
+
scroller: true,
|
127
|
+
scrollY: "60vh",
|
128
|
+
stateSave: true,
|
129
|
+
stateLoadParams: (settings, data) => {
|
130
|
+
// if save state is older than last breaking code change...
|
131
|
+
if (data.time <= 1722415428000) {
|
132
|
+
// ... delete it
|
133
|
+
for (const key of Object.keys(data)) {
|
134
|
+
delete data[key];
|
135
|
+
}
|
136
|
+
}
|
137
|
+
},
|
138
|
+
ajax: {
|
139
|
+
url: "/api/firmware/all",
|
140
|
+
contentType: "application/json",
|
141
|
+
},
|
142
|
+
initComplete: () => {
|
143
|
+
updateBtnState();
|
144
|
+
},
|
145
|
+
columnDefs: [
|
146
|
+
{
|
147
|
+
targets: "_all",
|
148
|
+
searchable: false,
|
149
|
+
orderable: false,
|
150
|
+
render: (data) => data || "-",
|
151
|
+
},
|
152
|
+
],
|
153
|
+
columns: [
|
154
|
+
{ data: "id", visible: false },
|
155
|
+
{ data: "name", searchable: true },
|
156
|
+
{ data: "version", searchable: true, orderable: true },
|
157
|
+
{
|
158
|
+
data: "size",
|
159
|
+
render: (data, type) => {
|
160
|
+
if (type === "display" || type === "filter") {
|
161
|
+
return `${(data / 1024 / 1024).toFixed(2)}MB`;
|
162
|
+
}
|
163
|
+
return data;
|
164
|
+
},
|
165
|
+
},
|
166
|
+
],
|
167
|
+
select: true,
|
168
|
+
rowId: "id",
|
169
|
+
layout: {
|
170
|
+
bottom1Start: {
|
171
|
+
buttons: [
|
172
|
+
{
|
173
|
+
text: '<i class="bi bi-cloud-download" ></i>',
|
174
|
+
action: (e, dt) => {
|
175
|
+
const selectedFirmware = dt
|
176
|
+
.rows({ selected: true })
|
177
|
+
.data()
|
178
|
+
.toArray()
|
179
|
+
.map((d) => d.id);
|
180
|
+
downloadFirmware(selectedFirmware[0]);
|
181
|
+
},
|
182
|
+
className: "buttons-download",
|
183
|
+
titleAttr: "Download Firmware",
|
184
|
+
},
|
185
|
+
{
|
186
|
+
text: '<i class="bi bi-trash" ></i>',
|
187
|
+
action: async (e, dt) => {
|
188
|
+
const selectedFirmware = dt
|
189
|
+
.rows({ selected: true })
|
190
|
+
.data()
|
191
|
+
.toArray()
|
192
|
+
.map((d) => d.id);
|
193
|
+
await deleteFirmware(selectedFirmware);
|
194
|
+
},
|
195
|
+
className: "buttons-delete",
|
196
|
+
titleAttr: "Delete Firmware",
|
197
|
+
},
|
198
|
+
],
|
199
|
+
},
|
200
|
+
},
|
201
|
+
});
|
202
|
+
|
203
|
+
dataTable
|
204
|
+
.on("select", () => {
|
205
|
+
updateBtnState();
|
206
|
+
})
|
207
|
+
.on("deselect", () => {
|
208
|
+
updateBtnState();
|
118
209
|
});
|
119
|
-
|
120
|
-
|
121
|
-
|
210
|
+
|
211
|
+
// Compatibility tooltip
|
212
|
+
$(() => {
|
213
|
+
$('[data-toggle="tooltip"]').tooltip();
|
122
214
|
});
|
215
|
+
|
216
|
+
$("#firmware-table tbody")
|
217
|
+
.on("mouseenter", "tr", function () {
|
218
|
+
const rowData = dataTable.row(this).data();
|
219
|
+
const compat = rowData.compatibility;
|
220
|
+
let tooltipText = "";
|
221
|
+
if (compat) {
|
222
|
+
const result = compat.reduce((acc, { model, revision }) => {
|
223
|
+
if (!acc[model]) {
|
224
|
+
acc[model] = [];
|
225
|
+
}
|
226
|
+
acc[model].push(revision);
|
227
|
+
return acc;
|
228
|
+
}, {});
|
229
|
+
|
230
|
+
tooltipText = Object.entries(result)
|
231
|
+
.map(([model, revision]) => `<b>${model}</b> [${revision.join(", ")}]`)
|
232
|
+
.join(", ");
|
233
|
+
}
|
234
|
+
|
235
|
+
// Initialize Bootstrap tooltip
|
236
|
+
$(this)
|
237
|
+
.attr("title", tooltipText)
|
238
|
+
.tooltip({
|
239
|
+
placement: "top",
|
240
|
+
trigger: "hover",
|
241
|
+
container: "body",
|
242
|
+
html: true,
|
243
|
+
})
|
244
|
+
.tooltip("show");
|
245
|
+
})
|
246
|
+
.on("mouseleave", "tr", function () {
|
247
|
+
$(this).tooltip("dispose");
|
248
|
+
});
|
249
|
+
|
250
|
+
updateFirmwareList();
|
251
|
+
});
|
252
|
+
|
253
|
+
function updateBtnState() {
|
254
|
+
if (dataTable.rows({ selected: true }).any()) {
|
255
|
+
document.querySelector("button.buttons-delete").classList.remove("disabled");
|
256
|
+
} else {
|
257
|
+
document.querySelector("button.buttons-delete").classList.add("disabled");
|
258
|
+
}
|
259
|
+
if (dataTable.rows({ selected: true }).count() === 1) {
|
260
|
+
document.querySelector("button.buttons-download").classList.remove("disabled");
|
261
|
+
} else {
|
262
|
+
document.querySelector("button.buttons-download").classList.add("disabled");
|
263
|
+
}
|
123
264
|
}
|
124
265
|
|
125
|
-
function deleteFirmware(
|
126
|
-
|
127
|
-
|
128
|
-
body: file,
|
129
|
-
})
|
130
|
-
.then(response => {
|
131
|
-
if (!response.ok) {
|
132
|
-
throw new Error('Failed to delete firmware.');
|
133
|
-
}
|
266
|
+
async function deleteFirmware(files) {
|
267
|
+
try {
|
268
|
+
await post("/api/firmware/delete", files);
|
134
269
|
updateFirmwareList();
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
270
|
+
} catch (error) {
|
271
|
+
console.error("Deleting firmwares failed:", error);
|
272
|
+
}
|
273
|
+
}
|
274
|
+
|
275
|
+
function downloadFirmware(file) {
|
276
|
+
window.location.href = `/api/download/${file}`;
|
140
277
|
}
|
goosebit/ui/static/js/index.js
CHANGED
@@ -1,64 +1,80 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
let dataTable;
|
2
|
+
|
3
|
+
document.addEventListener("DOMContentLoaded", () => {
|
4
|
+
dataTable = new DataTable("#device-table", {
|
3
5
|
responsive: true,
|
4
|
-
paging:
|
6
|
+
paging: true,
|
7
|
+
processing: false,
|
8
|
+
serverSide: true,
|
5
9
|
scrollCollapse: true,
|
6
10
|
scroller: true,
|
7
11
|
scrollY: "65vh",
|
8
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
|
+
},
|
9
22
|
ajax: {
|
10
23
|
url: "/api/devices/all",
|
11
|
-
|
24
|
+
contentType: "application/json",
|
12
25
|
},
|
13
|
-
initComplete:
|
26
|
+
initComplete: () => {
|
14
27
|
updateBtnState();
|
15
28
|
},
|
16
29
|
columnDefs: [
|
30
|
+
{
|
31
|
+
targets: [0, 2],
|
32
|
+
searchable: true,
|
33
|
+
orderable: true,
|
34
|
+
},
|
17
35
|
{
|
18
36
|
targets: "_all",
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
}
|
37
|
+
searchable: false,
|
38
|
+
orderable: false,
|
39
|
+
render: (data) => data || "-",
|
40
|
+
},
|
23
41
|
],
|
24
42
|
columns: [
|
25
|
-
{ data:
|
43
|
+
{ data: "name" },
|
26
44
|
{
|
27
|
-
data:
|
28
|
-
render:
|
29
|
-
if (
|
30
|
-
|
31
|
-
color = data ? "success" : "danger"
|
45
|
+
data: "online",
|
46
|
+
render: (data, type) => {
|
47
|
+
if (type === "display" || type === "filter") {
|
48
|
+
const color = data ? "success" : "danger";
|
32
49
|
return `
|
33
50
|
<div class="text-${color}">
|
34
51
|
●
|
35
52
|
</div>
|
36
|
-
|
53
|
+
`;
|
37
54
|
}
|
38
55
|
return data;
|
39
|
-
}
|
56
|
+
},
|
40
57
|
},
|
41
|
-
{ data:
|
42
|
-
{ data:
|
58
|
+
{ data: "uuid" },
|
59
|
+
{ data: "fw_installed_version" },
|
43
60
|
{
|
44
|
-
data:
|
45
|
-
render:
|
46
|
-
if (
|
47
|
-
return
|
61
|
+
data: "progress",
|
62
|
+
render: (data, type) => {
|
63
|
+
if (type === "display" || type === "filter") {
|
64
|
+
return data ? `${data}%` : "-";
|
48
65
|
}
|
49
66
|
return data;
|
50
|
-
}
|
51
|
-
|
67
|
+
},
|
52
68
|
},
|
53
|
-
{ data:
|
69
|
+
{ data: "last_ip" },
|
54
70
|
{
|
55
|
-
data:
|
56
|
-
render:
|
57
|
-
if (
|
71
|
+
data: "last_seen",
|
72
|
+
render: (data, type) => {
|
73
|
+
if (type === "display" || type === "filter") {
|
58
74
|
return secondsToRecentDate(data);
|
59
75
|
}
|
60
76
|
return data;
|
61
|
-
}
|
77
|
+
},
|
62
78
|
},
|
63
79
|
],
|
64
80
|
select: true,
|
@@ -69,103 +85,76 @@ document.addEventListener("DOMContentLoaded", function() {
|
|
69
85
|
{
|
70
86
|
text: '<i class="bi bi-check-all"></i>',
|
71
87
|
extend: "selectAll",
|
72
|
-
titleAttr:
|
88
|
+
titleAttr: "Select All",
|
73
89
|
},
|
74
90
|
{
|
75
91
|
text: '<i class="bi bi-x"></i>',
|
76
92
|
extend: "selectNone",
|
77
|
-
titleAttr:
|
93
|
+
titleAttr: "Clear Selection",
|
78
94
|
},
|
79
95
|
{
|
80
96
|
text: '<i class="bi bi-file-earmark-arrow-down"></i>',
|
81
|
-
action:
|
82
|
-
selectedDevices = dt.rows(
|
97
|
+
action: (e, dt) => {
|
98
|
+
const selectedDevices = dt.rows({ selected: true }).data().toArray();
|
83
99
|
downloadLogins(selectedDevices);
|
84
100
|
},
|
85
101
|
className: "buttons-export-login",
|
86
|
-
titleAttr:
|
102
|
+
titleAttr: "Export Login",
|
87
103
|
},
|
88
104
|
{
|
89
105
|
text: '<i class="bi bi-file-text"></i>',
|
90
|
-
action:
|
91
|
-
selectedDevice = dt.rows(
|
92
|
-
window.location.href = `/ui/logs/${selectedDevice
|
106
|
+
action: (e, dt) => {
|
107
|
+
const selectedDevice = dt.rows({ selected: true }).data().toArray()[0];
|
108
|
+
window.location.href = `/ui/logs/${selectedDevice.uuid}`;
|
93
109
|
},
|
94
110
|
className: "buttons-logs",
|
95
|
-
titleAttr:
|
111
|
+
titleAttr: "View Log",
|
96
112
|
},
|
97
|
-
]
|
98
|
-
}
|
99
|
-
}
|
113
|
+
],
|
114
|
+
},
|
115
|
+
},
|
100
116
|
});
|
101
117
|
|
102
|
-
dataTable
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
118
|
+
dataTable
|
119
|
+
.on("select", () => {
|
120
|
+
updateBtnState();
|
121
|
+
})
|
122
|
+
.on("deselect", () => {
|
123
|
+
updateBtnState();
|
124
|
+
});
|
107
125
|
|
108
|
-
setInterval(
|
126
|
+
setInterval(() => {
|
109
127
|
dataTable.ajax.reload(null, false);
|
110
128
|
}, TABLE_UPDATE_TIME);
|
111
129
|
});
|
112
130
|
|
113
131
|
function updateBtnState() {
|
114
|
-
dataTable
|
115
|
-
|
116
|
-
document.querySelector(
|
117
|
-
document.querySelector('button.buttons-export-login').classList.remove('disabled');
|
132
|
+
if (dataTable.rows({ selected: true }).any()) {
|
133
|
+
document.querySelector("button.buttons-select-none").classList.remove("disabled");
|
134
|
+
document.querySelector("button.buttons-export-login").classList.remove("disabled");
|
118
135
|
} else {
|
119
|
-
document.querySelector(
|
120
|
-
document.querySelector(
|
136
|
+
document.querySelector("button.buttons-select-none").classList.add("disabled");
|
137
|
+
document.querySelector("button.buttons-export-login").classList.add("disabled");
|
121
138
|
}
|
122
|
-
if (dataTable.rows(
|
123
|
-
document.querySelector(
|
139
|
+
if (dataTable.rows({ selected: true }).count() === 1) {
|
140
|
+
document.querySelector("button.buttons-logs").classList.remove("disabled");
|
124
141
|
} else {
|
125
|
-
document.querySelector(
|
126
|
-
}
|
127
|
-
|
128
|
-
|
129
|
-
if(dataTable.rows( {selected:true} ).ids().toArray().length === dataTable.rows().ids().toArray().length){
|
130
|
-
document.querySelector('button.buttons-select-all').classList.add('disabled');
|
131
|
-
} else {
|
132
|
-
document.querySelector('button.buttons-select-all').classList.remove('disabled');
|
142
|
+
document.querySelector("button.buttons-logs").classList.add("disabled");
|
133
143
|
}
|
134
144
|
}
|
135
145
|
|
136
146
|
function downloadLogins(devices) {
|
137
|
-
|
138
|
-
return [dev
|
147
|
+
const deviceLogins = devices.map((dev) => {
|
148
|
+
return [dev.name, `https://${dev.uuid}-access.loadsync.io`, dev.uuid];
|
139
149
|
});
|
140
150
|
deviceLogins.unshift(["Building", "Access Link", "Serial Number/Wifi SSID", "Login/Wifi Password"]);
|
141
151
|
|
142
|
-
|
143
|
-
|
144
|
-
|
152
|
+
const csvContent = `data:text/csv;charset=utf-8,${deviceLogins.map((e) => e.join(",")).join("\n")}`;
|
153
|
+
const encodedUri = encodeURI(csvContent);
|
154
|
+
const link = document.createElement("a");
|
145
155
|
link.setAttribute("href", encodedUri);
|
146
156
|
link.setAttribute("download", "LoadsyncLogins-Export.csv");
|
147
157
|
document.body.appendChild(link);
|
148
158
|
|
149
159
|
link.click();
|
150
160
|
}
|
151
|
-
|
152
|
-
function secondsToRecentDate(t) {
|
153
|
-
if (t == null) {
|
154
|
-
return null
|
155
|
-
}
|
156
|
-
t = Number(t);
|
157
|
-
var d = Math.floor(t / 86400)
|
158
|
-
var h = Math.floor(t % 86400 / 3600);
|
159
|
-
var m = Math.floor(t % 86400 % 3600 / 60);
|
160
|
-
var s = Math.floor(t % 86400 % 3600 % 60);
|
161
|
-
|
162
|
-
if (d > 0) {
|
163
|
-
return d + (d == 1 ? " day" : " days");
|
164
|
-
} else if (h > 0) {
|
165
|
-
return h + (h == 1 ? " hour" : " hours");
|
166
|
-
} else if (m > 0) {
|
167
|
-
return m + (m == 1 ? " minute" : " minutes");
|
168
|
-
} else {
|
169
|
-
return s + (s == 1 ? " second" : " seconds");
|
170
|
-
}
|
171
|
-
}
|
goosebit/ui/static/js/logs.js
CHANGED
@@ -1,22 +1,25 @@
|
|
1
|
-
document.addEventListener("DOMContentLoaded",
|
2
|
-
|
1
|
+
document.addEventListener("DOMContentLoaded", () => {
|
2
|
+
const logs_ws = create_ws(`/realtime/logs/${device}`);
|
3
3
|
|
4
|
-
logs_ws.addEventListener(
|
5
|
-
res = JSON.parse(event.data);
|
4
|
+
logs_ws.addEventListener("message", (event) => {
|
5
|
+
const res = JSON.parse(event.data);
|
6
6
|
|
7
|
-
const logElem = document.getElementById(
|
8
|
-
if (res
|
9
|
-
logElem.textContent = ""
|
7
|
+
const logElem = document.getElementById("device-log");
|
8
|
+
if (res.clear) {
|
9
|
+
logElem.textContent = "";
|
10
10
|
}
|
11
|
-
logElem.textContent += res
|
11
|
+
logElem.textContent += res.log;
|
12
12
|
|
13
|
-
const progressElem = document.getElementById(
|
13
|
+
const progressElem = document.getElementById("install-progress");
|
14
14
|
progressElem.style.width = `${res.progress}%`;
|
15
15
|
progressElem.innerHTML = `${res.progress}%`;
|
16
16
|
});
|
17
17
|
});
|
18
18
|
|
19
19
|
function create_ws(s) {
|
20
|
-
|
21
|
-
|
20
|
+
const l = window.location;
|
21
|
+
const protocol = l.protocol === "https:" ? "wss://" : "ws://";
|
22
|
+
const port = l.port !== "80" || l.port !== "443" ? l.port : "";
|
23
|
+
const url = `${protocol}${l.hostname}${port}${s}`;
|
24
|
+
return new WebSocket(url);
|
22
25
|
}
|