goosebit 0.2.3__py3-none-any.whl → 0.2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- goosebit/__init__.py +32 -3
- goosebit/api/v1/devices/device/routes.py +10 -4
- goosebit/api/v1/devices/responses.py +0 -7
- goosebit/api/v1/devices/routes.py +19 -3
- goosebit/api/v1/rollouts/responses.py +2 -7
- goosebit/api/v1/rollouts/routes.py +7 -3
- goosebit/api/v1/software/responses.py +0 -7
- goosebit/api/v1/software/routes.py +24 -11
- goosebit/auth/__init__.py +12 -8
- goosebit/db/__init__.py +12 -1
- goosebit/db/migrations/models/1_20241109151811_update.py +11 -0
- goosebit/db/models.py +19 -4
- goosebit/realtime/logs.py +1 -1
- goosebit/schema/devices.py +42 -38
- goosebit/schema/rollouts.py +21 -18
- goosebit/schema/software.py +24 -19
- goosebit/settings/schema.py +2 -0
- goosebit/ui/bff/common/__init__.py +0 -0
- goosebit/ui/bff/common/requests.py +44 -0
- goosebit/ui/bff/common/responses.py +16 -0
- goosebit/ui/bff/common/util.py +32 -0
- goosebit/ui/bff/devices/responses.py +15 -19
- goosebit/ui/bff/devices/routes.py +61 -7
- goosebit/ui/bff/rollouts/responses.py +15 -19
- goosebit/ui/bff/rollouts/routes.py +8 -6
- goosebit/ui/bff/routes.py +4 -2
- goosebit/ui/bff/software/responses.py +29 -19
- goosebit/ui/bff/software/routes.py +29 -16
- goosebit/ui/nav.py +1 -1
- goosebit/ui/routes.py +10 -19
- goosebit/ui/static/js/devices.js +188 -94
- goosebit/ui/static/js/rollouts.js +20 -13
- goosebit/ui/static/js/software.js +5 -11
- goosebit/ui/static/js/util.js +43 -14
- goosebit/ui/templates/devices.html.jinja +77 -49
- goosebit/ui/templates/nav.html.jinja +35 -4
- goosebit/ui/templates/rollouts.html.jinja +23 -23
- goosebit/updater/controller/v1/routes.py +33 -23
- goosebit/updater/controller/v1/schema.py +4 -4
- goosebit/updater/manager.py +28 -52
- goosebit/updater/routes.py +6 -2
- goosebit/updates/__init__.py +14 -21
- goosebit/updates/swdesc.py +36 -15
- {goosebit-0.2.3.dist-info → goosebit-0.2.5.dist-info}/METADATA +23 -7
- {goosebit-0.2.3.dist-info → goosebit-0.2.5.dist-info}/RECORD +48 -44
- {goosebit-0.2.3.dist-info → goosebit-0.2.5.dist-info}/WHEEL +1 -1
- goosebit-0.2.5.dist-info/entry_points.txt +3 -0
- goosebit/ui/static/js/index.js +0 -155
- goosebit/ui/templates/index.html.jinja +0 -25
- {goosebit-0.2.3.dist-info → goosebit-0.2.5.dist-info}/LICENSE +0 -0
goosebit/ui/static/js/devices.js
CHANGED
@@ -1,29 +1,70 @@
|
|
1
1
|
let dataTable;
|
2
2
|
|
3
|
+
const renderFunctions = {
|
4
|
+
online: (data, type) => {
|
5
|
+
if (type === "display" || type === "filter") {
|
6
|
+
const color = data ? "success" : "danger";
|
7
|
+
return `
|
8
|
+
<div class="text-${color}">
|
9
|
+
●
|
10
|
+
</div>
|
11
|
+
`;
|
12
|
+
}
|
13
|
+
return data;
|
14
|
+
},
|
15
|
+
force_update: (data, type) => {
|
16
|
+
if (type === "display" || type === "filter") {
|
17
|
+
const color = data ? "success" : "muted";
|
18
|
+
return `
|
19
|
+
<div class="text-${color}">
|
20
|
+
●
|
21
|
+
</div>
|
22
|
+
`;
|
23
|
+
}
|
24
|
+
return data;
|
25
|
+
},
|
26
|
+
progress: (data, type) => {
|
27
|
+
if (type === "display" || type === "filter") {
|
28
|
+
return data ? `${data}%` : "-";
|
29
|
+
}
|
30
|
+
return data;
|
31
|
+
},
|
32
|
+
last_seen: (data, type) => {
|
33
|
+
if (type === "display" || type === "filter") {
|
34
|
+
return secondsToRecentDate(data);
|
35
|
+
}
|
36
|
+
return data;
|
37
|
+
},
|
38
|
+
};
|
39
|
+
|
3
40
|
document.addEventListener("DOMContentLoaded", async () => {
|
41
|
+
const columnConfig = await get_request("/ui/bff/devices/columns");
|
42
|
+
for (const col in columnConfig.columns) {
|
43
|
+
const colDesc = columnConfig.columns[col];
|
44
|
+
const colName = colDesc.data;
|
45
|
+
if (renderFunctions[colName]) {
|
46
|
+
columnConfig.columns[col].render = renderFunctions[colName];
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
4
50
|
dataTable = new DataTable("#device-table", {
|
5
51
|
responsive: true,
|
6
52
|
paging: true,
|
7
53
|
processing: false,
|
8
54
|
serverSide: true,
|
9
|
-
order:
|
55
|
+
order: { name: "uuid", dir: "asc" },
|
10
56
|
scrollCollapse: true,
|
11
57
|
scroller: true,
|
12
58
|
scrollY: "65vh",
|
13
59
|
stateSave: true,
|
14
|
-
stateLoadParams: (settings, data) => {
|
15
|
-
// if save state is older than last breaking code change...
|
16
|
-
if (data.time <= 1722434189000) {
|
17
|
-
// ... delete it
|
18
|
-
for (const key of Object.keys(data)) {
|
19
|
-
delete data[key];
|
20
|
-
}
|
21
|
-
}
|
22
|
-
},
|
23
60
|
select: true,
|
24
61
|
rowId: "uuid",
|
25
62
|
ajax: {
|
26
|
-
url: "/ui/bff/devices
|
63
|
+
url: "/ui/bff/devices",
|
64
|
+
data: (data) => {
|
65
|
+
// biome-ignore lint/performance/noDelete: really has to be deleted
|
66
|
+
delete data.columns;
|
67
|
+
},
|
27
68
|
contentType: "application/json",
|
28
69
|
},
|
29
70
|
initComplete: () => {
|
@@ -37,64 +78,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
37
78
|
render: (data) => data || "-",
|
38
79
|
},
|
39
80
|
],
|
40
|
-
columns:
|
41
|
-
{
|
42
|
-
data: "online",
|
43
|
-
render: (data, type) => {
|
44
|
-
if (type === "display" || type === "filter") {
|
45
|
-
const color = data ? "success" : "danger";
|
46
|
-
return `
|
47
|
-
<div class="text-${color}">
|
48
|
-
●
|
49
|
-
</div>
|
50
|
-
`;
|
51
|
-
}
|
52
|
-
return data;
|
53
|
-
},
|
54
|
-
},
|
55
|
-
{ data: "uuid", searchable: true, orderable: true },
|
56
|
-
{ data: "name", searchable: true, orderable: true },
|
57
|
-
{ data: "hw_model" },
|
58
|
-
{ data: "hw_revision" },
|
59
|
-
{ data: "feed", searchable: true, orderable: true },
|
60
|
-
{ data: "sw_version", searchable: true, orderable: true },
|
61
|
-
{ data: "sw_target_version" },
|
62
|
-
{ data: "update_mode", searchable: true, orderable: true },
|
63
|
-
{ data: "last_state", searchable: true, orderable: true },
|
64
|
-
{
|
65
|
-
data: "force_update",
|
66
|
-
render: (data, type) => {
|
67
|
-
if (type === "display" || type === "filter") {
|
68
|
-
const color = data ? "success" : "muted";
|
69
|
-
return `
|
70
|
-
<div class="text-${color}">
|
71
|
-
●
|
72
|
-
</div>
|
73
|
-
`;
|
74
|
-
}
|
75
|
-
return data;
|
76
|
-
},
|
77
|
-
},
|
78
|
-
{
|
79
|
-
data: "progress",
|
80
|
-
render: (data, type) => {
|
81
|
-
if (type === "display" || type === "filter") {
|
82
|
-
return data ? `${data}%` : "-";
|
83
|
-
}
|
84
|
-
return data;
|
85
|
-
},
|
86
|
-
},
|
87
|
-
{ data: "last_ip" },
|
88
|
-
{
|
89
|
-
data: "last_seen",
|
90
|
-
render: (data, type) => {
|
91
|
-
if (type === "display" || type === "filter") {
|
92
|
-
return secondsToRecentDate(data);
|
93
|
-
}
|
94
|
-
return data;
|
95
|
-
},
|
96
|
-
},
|
97
|
-
],
|
81
|
+
columns: columnConfig.columns,
|
98
82
|
layout: {
|
99
83
|
top1Start: {
|
100
84
|
buttons: [
|
@@ -124,20 +108,11 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
124
108
|
{
|
125
109
|
text: '<i class="bi bi-pen" ></i>',
|
126
110
|
action: () => {
|
127
|
-
const
|
128
|
-
|
111
|
+
const selectedDevices = dataTable.rows({ selected: true }).data().toArray();
|
112
|
+
const selectedDevice = selectedDevices[0];
|
113
|
+
updateSoftwareSelection(selectedDevices);
|
114
|
+
$("#device-name").val(selectedDevice.name);
|
129
115
|
$("#device-selected-feed").val(selectedDevice.feed);
|
130
|
-
|
131
|
-
let selectedValue;
|
132
|
-
if (selectedDevice.update_mode === "Rollout") {
|
133
|
-
selectedValue = "rollout";
|
134
|
-
} else if (selectedDevice.update_mode === "Latest") {
|
135
|
-
selectedValue = "latest";
|
136
|
-
} else {
|
137
|
-
selectedValue = selectedDevice.sw_assigned;
|
138
|
-
}
|
139
|
-
$("#selected-sw").val(selectedValue);
|
140
|
-
|
141
116
|
new bootstrap.Modal("#device-config-modal").show();
|
142
117
|
},
|
143
118
|
className: "buttons-config",
|
@@ -199,22 +174,89 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
199
174
|
dataTable.ajax.reload(null, false);
|
200
175
|
}, TABLE_UPDATE_TIME);
|
201
176
|
|
202
|
-
await updateSoftwareSelection(
|
177
|
+
await updateSoftwareSelection();
|
203
178
|
|
204
|
-
//
|
205
|
-
const
|
206
|
-
|
179
|
+
// Name update form submit
|
180
|
+
const nameForm = document.getElementById("device-name-form");
|
181
|
+
nameForm.addEventListener(
|
207
182
|
"submit",
|
208
183
|
async (event) => {
|
209
|
-
if (
|
184
|
+
if (nameForm.checkValidity() === false) {
|
210
185
|
event.preventDefault();
|
211
186
|
event.stopPropagation();
|
212
|
-
|
187
|
+
nameForm.classList.add("was-validated");
|
213
188
|
} else {
|
214
189
|
event.preventDefault();
|
215
|
-
await
|
216
|
-
|
217
|
-
|
190
|
+
await updateDeviceName();
|
191
|
+
nameForm.classList.remove("was-validated");
|
192
|
+
nameForm.reset();
|
193
|
+
const modal = bootstrap.Modal.getInstance(document.getElementById("device-config-modal"));
|
194
|
+
modal.hide();
|
195
|
+
}
|
196
|
+
},
|
197
|
+
false,
|
198
|
+
);
|
199
|
+
|
200
|
+
// Rollout form submit
|
201
|
+
const rolloutForm = document.getElementById("device-software-rollout-form");
|
202
|
+
rolloutForm.addEventListener(
|
203
|
+
"submit",
|
204
|
+
async (event) => {
|
205
|
+
if (rolloutForm.checkValidity() === false) {
|
206
|
+
event.preventDefault();
|
207
|
+
event.stopPropagation();
|
208
|
+
rolloutForm.classList.add("was-validated");
|
209
|
+
} else {
|
210
|
+
event.preventDefault();
|
211
|
+
await updateDeviceRollout();
|
212
|
+
rolloutForm.classList.remove("was-validated");
|
213
|
+
rolloutForm.reset();
|
214
|
+
const modal = bootstrap.Modal.getInstance(document.getElementById("device-config-modal"));
|
215
|
+
modal.hide();
|
216
|
+
}
|
217
|
+
},
|
218
|
+
false,
|
219
|
+
);
|
220
|
+
|
221
|
+
// Manual software form submit
|
222
|
+
const manualSoftwareForm = document.getElementById("device-software-manual-form");
|
223
|
+
manualSoftwareForm.addEventListener(
|
224
|
+
"submit",
|
225
|
+
async (event) => {
|
226
|
+
if (manualSoftwareForm.checkValidity() === false) {
|
227
|
+
event.preventDefault();
|
228
|
+
event.stopPropagation();
|
229
|
+
manualSoftwareForm.classList.add("was-validated");
|
230
|
+
if (document.getElementById("selected-sw").value === "") {
|
231
|
+
document.getElementById("selected-sw").parentElement.classList.add("is-invalid");
|
232
|
+
}
|
233
|
+
} else {
|
234
|
+
event.preventDefault();
|
235
|
+
await updateDeviceManualSoftware();
|
236
|
+
manualSoftwareForm.classList.remove("was-validated");
|
237
|
+
document.getElementById("selected-sw").parentElement.classList.remove("is-invalid");
|
238
|
+
manualSoftwareForm.reset();
|
239
|
+
const modal = bootstrap.Modal.getInstance(document.getElementById("device-config-modal"));
|
240
|
+
modal.hide();
|
241
|
+
}
|
242
|
+
},
|
243
|
+
false,
|
244
|
+
);
|
245
|
+
|
246
|
+
// Latest software form submit
|
247
|
+
const latestSoftwareForm = document.getElementById("device-software-latest-form");
|
248
|
+
latestSoftwareForm.addEventListener(
|
249
|
+
"submit",
|
250
|
+
async (event) => {
|
251
|
+
if (latestSoftwareForm.checkValidity() === false) {
|
252
|
+
event.preventDefault();
|
253
|
+
event.stopPropagation();
|
254
|
+
latestSoftwareForm.classList.add("was-validated");
|
255
|
+
} else {
|
256
|
+
event.preventDefault();
|
257
|
+
await updateDeviceLatest();
|
258
|
+
latestSoftwareForm.classList.remove("was-validated");
|
259
|
+
latestSoftwareForm.reset();
|
218
260
|
const modal = bootstrap.Modal.getInstance(document.getElementById("device-config-modal"));
|
219
261
|
modal.hide();
|
220
262
|
}
|
@@ -244,18 +286,70 @@ function updateBtnState() {
|
|
244
286
|
}
|
245
287
|
}
|
246
288
|
|
247
|
-
async function
|
289
|
+
async function updateDeviceName() {
|
290
|
+
const devices = dataTable
|
291
|
+
.rows({ selected: true })
|
292
|
+
.data()
|
293
|
+
.toArray()
|
294
|
+
.map((d) => d.uuid);
|
295
|
+
const name = document.getElementById("device-name").value;
|
296
|
+
|
297
|
+
try {
|
298
|
+
await patch_request("/ui/bff/devices", { devices, name });
|
299
|
+
} catch (error) {
|
300
|
+
console.error("Update device config failed:", error);
|
301
|
+
}
|
302
|
+
|
303
|
+
setTimeout(updateDeviceList, 50);
|
304
|
+
}
|
305
|
+
|
306
|
+
async function updateDeviceRollout() {
|
248
307
|
const devices = dataTable
|
249
308
|
.rows({ selected: true })
|
250
309
|
.data()
|
251
310
|
.toArray()
|
252
311
|
.map((d) => d.uuid);
|
253
|
-
const name = document.getElementById("device-selected-name").value;
|
254
312
|
const feed = document.getElementById("device-selected-feed").value;
|
313
|
+
const software = "rollout";
|
314
|
+
|
315
|
+
try {
|
316
|
+
await patch_request("/ui/bff/devices", { devices, feed, software });
|
317
|
+
} catch (error) {
|
318
|
+
console.error("Update device config failed:", error);
|
319
|
+
}
|
320
|
+
|
321
|
+
setTimeout(updateDeviceList, 50);
|
322
|
+
}
|
323
|
+
|
324
|
+
async function updateDeviceManualSoftware() {
|
325
|
+
const devices = dataTable
|
326
|
+
.rows({ selected: true })
|
327
|
+
.data()
|
328
|
+
.toArray()
|
329
|
+
.map((d) => d.uuid);
|
330
|
+
const feed = null;
|
255
331
|
const software = document.getElementById("selected-sw").value;
|
256
332
|
|
257
333
|
try {
|
258
|
-
await patch_request("/ui/bff/devices", { devices,
|
334
|
+
await patch_request("/ui/bff/devices", { devices, feed, software });
|
335
|
+
} catch (error) {
|
336
|
+
console.error("Update device config failed:", error);
|
337
|
+
}
|
338
|
+
|
339
|
+
setTimeout(updateDeviceList, 50);
|
340
|
+
}
|
341
|
+
|
342
|
+
async function updateDeviceLatest() {
|
343
|
+
const devices = dataTable
|
344
|
+
.rows({ selected: true })
|
345
|
+
.data()
|
346
|
+
.toArray()
|
347
|
+
.map((d) => d.uuid);
|
348
|
+
const feed = null;
|
349
|
+
const software = "latest";
|
350
|
+
|
351
|
+
try {
|
352
|
+
await patch_request("/ui/bff/devices", { devices, feed, software });
|
259
353
|
} catch (error) {
|
260
354
|
console.error("Update device config failed:", error);
|
261
355
|
}
|
@@ -6,24 +6,22 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
6
6
|
paging: true,
|
7
7
|
processing: true,
|
8
8
|
serverSide: true,
|
9
|
-
order:
|
9
|
+
order: {
|
10
|
+
name: "created_at",
|
11
|
+
dir: "desc",
|
12
|
+
},
|
10
13
|
scrollCollapse: true,
|
11
14
|
scroller: true,
|
12
15
|
scrollY: "65vh",
|
13
16
|
stateSave: true,
|
14
|
-
stateLoadParams: (settings, data) => {
|
15
|
-
// if save state is older than last breaking code change...
|
16
|
-
if (data.time <= 1722413708000) {
|
17
|
-
// ... delete it
|
18
|
-
for (const key of Object.keys(data)) {
|
19
|
-
delete data[key];
|
20
|
-
}
|
21
|
-
}
|
22
|
-
},
|
23
17
|
select: true,
|
24
18
|
rowId: "id",
|
25
19
|
ajax: {
|
26
20
|
url: "/ui/bff/rollouts",
|
21
|
+
data: (data) => {
|
22
|
+
// biome-ignore lint/performance/noDelete: really has to be deleted
|
23
|
+
delete data.columns;
|
24
|
+
},
|
27
25
|
contentType: "application/json",
|
28
26
|
},
|
29
27
|
initComplete: () => {
|
@@ -38,9 +36,14 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
38
36
|
],
|
39
37
|
columns: [
|
40
38
|
{ data: "id", visible: false },
|
41
|
-
{
|
42
|
-
|
43
|
-
|
39
|
+
{
|
40
|
+
data: "created_at",
|
41
|
+
name: "created_at",
|
42
|
+
orderable: true,
|
43
|
+
render: (data) => new Date(data).toLocaleString(),
|
44
|
+
},
|
45
|
+
{ data: "name", name: "name", searchable: true, orderable: true },
|
46
|
+
{ data: "feed", name: "feed", searchable: true, orderable: true },
|
44
47
|
{ data: "sw_file" },
|
45
48
|
{ data: "sw_version" },
|
46
49
|
{
|
@@ -136,6 +139,9 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
136
139
|
"submit",
|
137
140
|
(event) => {
|
138
141
|
if (form.checkValidity() === false) {
|
142
|
+
if (document.getElementById("selected-sw").value === "") {
|
143
|
+
document.getElementById("selected-sw").parentElement.classList.add("is-invalid");
|
144
|
+
}
|
139
145
|
event.preventDefault();
|
140
146
|
event.stopPropagation();
|
141
147
|
form.classList.add("was-validated");
|
@@ -143,6 +149,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
143
149
|
event.preventDefault();
|
144
150
|
createRollout();
|
145
151
|
form.classList.remove("was-validated");
|
152
|
+
document.getElementById("selected-sw").parentElement.classList.remove("is-invalid");
|
146
153
|
form.reset();
|
147
154
|
const modal = bootstrap.Modal.getInstance(document.getElementById("rollout-create-modal"));
|
148
155
|
modal.hide();
|
@@ -172,22 +172,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
172
172
|
paging: true,
|
173
173
|
processing: false,
|
174
174
|
serverSide: true,
|
175
|
-
order: [2, "desc"],
|
176
175
|
scrollCollapse: true,
|
177
176
|
scroller: true,
|
178
177
|
scrollY: "60vh",
|
179
178
|
stateSave: true,
|
180
|
-
stateLoadParams: (settings, data) => {
|
181
|
-
// if save state is older than last breaking code change...
|
182
|
-
if (data.time <= 1722415428000) {
|
183
|
-
// ... delete it
|
184
|
-
for (const key of Object.keys(data)) {
|
185
|
-
delete data[key];
|
186
|
-
}
|
187
|
-
}
|
188
|
-
},
|
189
179
|
ajax: {
|
190
180
|
url: "/ui/bff/software",
|
181
|
+
data: (data) => {
|
182
|
+
// biome-ignore lint/performance/noDelete: really has to be deleted
|
183
|
+
delete data.columns;
|
184
|
+
},
|
191
185
|
contentType: "application/json",
|
192
186
|
},
|
193
187
|
initComplete: () => {
|
@@ -204,7 +198,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
204
198
|
columns: [
|
205
199
|
{ data: "id", visible: false },
|
206
200
|
{ data: "name" },
|
207
|
-
{ data: "version", searchable: true, orderable: true },
|
201
|
+
{ data: "version", name: "version", searchable: true, orderable: true },
|
208
202
|
{
|
209
203
|
data: "compatibility",
|
210
204
|
render: (data) => {
|
goosebit/ui/static/js/util.js
CHANGED
@@ -20,27 +20,22 @@ function secondsToRecentDate(t) {
|
|
20
20
|
return s + (s === 1 ? " second" : " seconds");
|
21
21
|
}
|
22
22
|
|
23
|
-
async function updateSoftwareSelection(
|
23
|
+
async function updateSoftwareSelection(devices = null) {
|
24
24
|
try {
|
25
|
-
const
|
25
|
+
const url = new URL("/ui/bff/software?order[0][dir]=desc&order[0][name]=version", window.location.origin);
|
26
|
+
if (devices != null) {
|
27
|
+
for (const device of devices) {
|
28
|
+
url.searchParams.append("uuids", device.uuid);
|
29
|
+
}
|
30
|
+
}
|
31
|
+
const response = await fetch(url.toString());
|
26
32
|
if (!response.ok) {
|
27
33
|
console.error("Retrieving software list failed.");
|
28
34
|
return;
|
29
35
|
}
|
30
36
|
const data = (await response.json()).data;
|
31
37
|
const selectElem = document.getElementById("selected-sw");
|
32
|
-
|
33
|
-
if (addSpecialMode) {
|
34
|
-
let optionElem = document.createElement("option");
|
35
|
-
optionElem.value = "rollout";
|
36
|
-
optionElem.textContent = "Rollout";
|
37
|
-
selectElem.appendChild(optionElem);
|
38
|
-
|
39
|
-
optionElem = document.createElement("option");
|
40
|
-
optionElem.value = "latest";
|
41
|
-
optionElem.textContent = "Latest";
|
42
|
-
selectElem.appendChild(optionElem);
|
43
|
-
}
|
38
|
+
selectElem.innerHTML = "";
|
44
39
|
|
45
40
|
for (const item of data) {
|
46
41
|
const optionElem = document.createElement("option");
|
@@ -50,11 +45,45 @@ async function updateSoftwareSelection(addSpecialMode = false) {
|
|
50
45
|
optionElem.textContent = `${item.version} (${models})`;
|
51
46
|
selectElem.appendChild(optionElem);
|
52
47
|
}
|
48
|
+
$("#selected-sw").selectpicker("destroy");
|
49
|
+
if (data.length === 0) {
|
50
|
+
selectElem.title = "No valid software found for selected device";
|
51
|
+
if (devices != null) {
|
52
|
+
if (devices.length > 1) {
|
53
|
+
selectElem.title += "s";
|
54
|
+
}
|
55
|
+
}
|
56
|
+
selectElem.disabled = true;
|
57
|
+
} else {
|
58
|
+
selectElem.disabled = false;
|
59
|
+
selectElem.title = "Select Software";
|
60
|
+
}
|
61
|
+
$("#selected-sw").selectpicker();
|
53
62
|
} catch (error) {
|
54
63
|
console.error("Failed to fetch device data:", error);
|
55
64
|
}
|
56
65
|
}
|
57
66
|
|
67
|
+
async function get_request(url) {
|
68
|
+
const response = await fetch(url, {
|
69
|
+
method: "GET",
|
70
|
+
});
|
71
|
+
|
72
|
+
const result = await response.json();
|
73
|
+
if (!response.ok) {
|
74
|
+
if (result.detail) {
|
75
|
+
Swal.fire({
|
76
|
+
title: "Warning",
|
77
|
+
text: result.detail,
|
78
|
+
icon: "warning",
|
79
|
+
confirmButtonText: "Understood",
|
80
|
+
});
|
81
|
+
}
|
82
|
+
|
83
|
+
throw new Error(`POST ${url} failed for ${JSON.stringify(object)}`);
|
84
|
+
}
|
85
|
+
return result;
|
86
|
+
}
|
58
87
|
async function post_request(url, object) {
|
59
88
|
const response = await fetch(url, {
|
60
89
|
method: "POST",
|