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.
Files changed (52) hide show
  1. goosebit/__init__.py +5 -2
  2. goosebit/api/__init__.py +1 -1
  3. goosebit/api/devices.py +59 -39
  4. goosebit/api/download.py +28 -14
  5. goosebit/api/firmware.py +40 -34
  6. goosebit/api/helper.py +30 -0
  7. goosebit/api/rollouts.py +64 -13
  8. goosebit/api/routes.py +14 -7
  9. goosebit/auth/__init__.py +14 -6
  10. goosebit/db.py +5 -0
  11. goosebit/models.py +110 -10
  12. goosebit/permissions.py +26 -20
  13. goosebit/realtime/__init__.py +1 -1
  14. goosebit/realtime/logs.py +3 -6
  15. goosebit/settings.py +4 -6
  16. goosebit/telemetry/__init__.py +28 -0
  17. goosebit/telemetry/prometheus.py +10 -0
  18. goosebit/ui/__init__.py +1 -1
  19. goosebit/ui/routes.py +33 -40
  20. goosebit/ui/static/js/devices.js +187 -250
  21. goosebit/ui/static/js/firmware.js +229 -92
  22. goosebit/ui/static/js/index.js +79 -90
  23. goosebit/ui/static/js/logs.js +14 -11
  24. goosebit/ui/static/js/rollouts.js +169 -27
  25. goosebit/ui/static/js/util.js +66 -0
  26. goosebit/ui/templates/devices.html +75 -51
  27. goosebit/ui/templates/firmware.html +149 -35
  28. goosebit/ui/templates/index.html +9 -26
  29. goosebit/ui/templates/login.html +58 -27
  30. goosebit/ui/templates/logs.html +15 -5
  31. goosebit/ui/templates/nav.html +77 -26
  32. goosebit/ui/templates/rollouts.html +62 -39
  33. goosebit/updater/__init__.py +1 -1
  34. goosebit/updater/controller/__init__.py +1 -1
  35. goosebit/updater/controller/v1/__init__.py +1 -1
  36. goosebit/updater/controller/v1/routes.py +53 -35
  37. goosebit/updater/manager.py +205 -103
  38. goosebit/updater/routes.py +4 -7
  39. goosebit/updates/__init__.py +70 -0
  40. goosebit/updates/swdesc.py +83 -0
  41. {goosebit-0.1.1.dist-info → goosebit-0.1.2.dist-info}/METADATA +53 -3
  42. goosebit-0.1.2.dist-info/RECORD +51 -0
  43. goosebit/updater/download/__init__.py +0 -1
  44. goosebit/updater/download/routes.py +0 -6
  45. goosebit/updater/download/v1/__init__.py +0 -1
  46. goosebit/updater/download/v1/routes.py +0 -13
  47. goosebit/updater/misc.py +0 -57
  48. goosebit/updates/artifacts.py +0 -89
  49. goosebit/updates/version.py +0 -38
  50. goosebit-0.1.1.dist-info/RECORD +0 -53
  51. {goosebit-0.1.1.dist-info → goosebit-0.1.2.dist-info}/LICENSE +0 -0
  52. {goosebit-0.1.1.dist-info → goosebit-0.1.2.dist-info}/WHEEL +0 -0
@@ -1,84 +1,105 @@
1
- document.addEventListener("DOMContentLoaded", function() {
2
- var dataTable = new DataTable("#device-table", {
1
+ let dataTable;
2
+
3
+ document.addEventListener("DOMContentLoaded", async () => {
4
+ dataTable = new DataTable("#device-table", {
3
5
  responsive: true,
4
- paging: false,
6
+ paging: true,
7
+ processing: false,
8
+ serverSide: true,
9
+ order: [],
5
10
  scrollCollapse: true,
6
11
  scroller: true,
7
12
  scrollY: "65vh",
8
13
  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
+ },
9
23
  select: true,
10
24
  rowId: "uuid",
11
25
  ajax: {
12
26
  url: "/api/devices/all",
13
- dataSrc: "",
27
+ contentType: "application/json",
14
28
  },
15
- initComplete:function(){
29
+ initComplete: () => {
16
30
  updateBtnState();
17
31
  },
18
32
  columnDefs: [
33
+ {
34
+ targets: [1, 2, 3, 4, 5, 6, 9, 10],
35
+ searchable: true,
36
+ orderable: true,
37
+ },
19
38
  {
20
39
  targets: "_all",
21
- render: function(data, type, row) {
22
- return data || "❓";
23
- },
24
- }
40
+ searchable: false,
41
+ orderable: false,
42
+ render: (data) => data || "-",
43
+ },
25
44
  ],
26
45
  columns: [
27
- { data: 'name' },
28
46
  {
29
- data: 'online',
30
- render: function(data, type, row) {
31
- if ( type === 'display' || type === 'filter' ) {
32
- color = data ? "success" : "danger"
47
+ data: "online",
48
+ render: (data, type) => {
49
+ if (type === "display" || type === "filter") {
50
+ const color = data ? "success" : "danger";
33
51
  return `
34
52
  <div class="text-${color}">
35
53
 
36
54
  </div>
37
- `
55
+ `;
38
56
  }
39
57
  return data;
40
- }
58
+ },
41
59
  },
42
- { data: 'uuid' },
43
- { data: 'hw_model' },
44
- { data: 'hw_revision' },
45
- { data: 'fw' },
60
+ { data: "uuid" },
61
+ { data: "name" },
62
+ { data: "hw_model" },
63
+ { data: "hw_revision" },
64
+ { data: "feed" },
65
+ { data: "flavor" },
66
+ { data: "fw_installed_version" },
67
+ { data: "fw_target_version" },
68
+ { data: "update_mode" },
69
+ { data: "state" },
46
70
  {
47
- data: 'force_update',
48
- render: function(data, type, row) {
49
- if ( type === 'display' || type === 'filter' ) {
50
- color = data ? "success" : "danger"
71
+ data: "force_update",
72
+ render: (data, type) => {
73
+ if (type === "display" || type === "filter") {
74
+ const color = data ? "success" : "muted";
51
75
  return `
52
76
  <div class="text-${color}">
53
77
 
54
78
  </div>
55
- `
79
+ `;
56
80
  }
57
81
  return data;
58
- }
82
+ },
59
83
  },
60
- { data: 'fw_file' },
61
84
  {
62
- data: 'progress',
63
- render: function(data, type, row) {
64
- if ( type === 'display' || type === 'filter' ) {
65
- return (data || "❓") + "%";
85
+ data: "progress",
86
+ render: (data, type) => {
87
+ if (type === "display" || type === "filter") {
88
+ return data ? `${data}%` : "-";
66
89
  }
67
90
  return data;
68
- }
69
-
91
+ },
70
92
  },
71
- { data: 'last_ip' },
93
+ { data: "last_ip" },
72
94
  {
73
- data: 'last_seen',
74
- render: function(data, type, row) {
75
- if ( type === 'display' || type === 'filter' ) {
95
+ data: "last_seen",
96
+ render: (data, type) => {
97
+ if (type === "display" || type === "filter") {
76
98
  return secondsToRecentDate(data);
77
99
  }
78
100
  return data;
79
- }
101
+ },
80
102
  },
81
- { data: 'state' },
82
103
  ],
83
104
  layout: {
84
105
  top1Start: {
@@ -86,275 +107,212 @@ document.addEventListener("DOMContentLoaded", function() {
86
107
  {
87
108
  text: '<i class="bi bi-check-all"></i>',
88
109
  extend: "selectAll",
89
- titleAttr: 'Select All'
110
+ titleAttr: "Select All",
90
111
  },
91
112
  {
92
113
  text: '<i class="bi bi-x"></i>',
93
114
  extend: "selectNone",
94
- titleAttr: 'Clear Selection'
115
+ titleAttr: "Clear Selection",
95
116
  },
96
117
  {
97
118
  text: '<i class="bi bi-file-text"></i>',
98
- action: function (e, dt, node, config) {
99
- selectedDevice = dataTable.rows( {selected:true} ).data().toArray()[0];
100
- window.location.href = `/ui/logs/${selectedDevice["uuid"]}`;
119
+ action: () => {
120
+ const selectedDevice = dataTable.rows({ selected: true }).data().toArray()[0];
121
+ window.location.href = `/ui/logs/${selectedDevice.uuid}`;
101
122
  },
102
123
  className: "buttons-logs",
103
- titleAttr: 'View Log'
124
+ titleAttr: "View Log",
104
125
  },
105
- ]
126
+ ],
106
127
  },
107
128
  bottom1Start: {
108
129
  buttons: [
109
130
  {
110
131
  text: '<i class="bi bi-pen" ></i>',
111
- action: function (e, dt, node, config) {
132
+ action: (e, dt) => {
112
133
  const input = document.getElementById("device-selected-name");
113
- const selectedDeviceName = dt.rows( {selected:true} ).data().toArray().map(d => d["name"])[0];
114
- input.value = selectedDeviceName;
134
+ input.value = dt
135
+ .rows({ selected: true })
136
+ .data()
137
+ .toArray()
138
+ .map((d) => d.name)[0];
115
139
 
116
- new bootstrap.Modal('#device-rename-modal').show();
140
+ new bootstrap.Modal("#device-rename-modal").show();
117
141
  },
118
142
  className: "buttons-rename",
119
- titleAttr: 'Rename Devices'
143
+ titleAttr: "Rename Devices",
120
144
  },
121
145
  {
122
146
  text: '<i class="bi bi-gear" ></i>',
123
- action: function (e, dt, node, config) {
124
- new bootstrap.Modal('#device-config-modal').show();
147
+ action: () => {
148
+ const selectedDevice = dataTable.rows({ selected: true }).data().toArray()[0];
149
+ $("#rollout-selected-feed").val(selectedDevice.feed);
150
+ $("#rollout-selected-flavor").val(selectedDevice.flavor);
151
+
152
+ let selectedValue;
153
+ if (selectedDevice.update_mode === "Rollout") {
154
+ selectedValue = "rollout";
155
+ } else if (selectedDevice.update_mode === "Latest") {
156
+ selectedValue = "latest";
157
+ } else {
158
+ selectedValue = selectedDevice.fw_assigned;
159
+ }
160
+ $("#selected-fw").val(selectedValue);
161
+
162
+ new bootstrap.Modal("#device-config-modal").show();
125
163
  },
126
164
  className: "buttons-config",
127
- titleAttr: 'Configure Devices'
165
+ titleAttr: "Configure Devices",
128
166
  },
129
167
  {
130
168
  text: '<i class="bi bi-trash" ></i>',
131
- action: function (e, dt, node, config) {
132
- selectedDevices = dt.rows( {selected:true} ).data().toArray().map(d => d["uuid"]);
169
+ action: (e, dt) => {
170
+ const selectedDevices = dt
171
+ .rows({ selected: true })
172
+ .data()
173
+ .toArray()
174
+ .map((d) => d.uuid);
133
175
  deleteDevices(selectedDevices);
134
176
  },
135
177
  className: "buttons-delete",
136
- titleAttr: 'Delete Devices'
178
+ titleAttr: "Delete Devices",
137
179
  },
138
180
  {
139
181
  text: '<i class="bi bi-box-arrow-in-up-right"></i>',
140
- action: function (e, dt, node, config) {
141
- selectedDevices = dataTable.rows( {selected:true} ).data().toArray().map(d => d["uuid"]);
182
+ action: () => {
183
+ const selectedDevices = dataTable
184
+ .rows({ selected: true })
185
+ .data()
186
+ .toArray()
187
+ .map((d) => d.uuid);
142
188
  forceUpdateDevices(selectedDevices);
143
189
  },
144
190
  className: "buttons-force-update",
145
- titleAttr: 'Force Update'
191
+ titleAttr: "Force Update",
146
192
  },
147
193
  {
148
194
  text: '<i class="bi bi-pin-angle"></i>',
149
- action: function (e, dt, node, config) {
150
- selectedDevices = dataTable.rows( {selected:true} ).data().toArray().map(d => d["uuid"]);
195
+ action: () => {
196
+ const selectedDevices = dataTable
197
+ .rows({ selected: true })
198
+ .data()
199
+ .toArray()
200
+ .map((d) => d.uuid);
151
201
  pinDevices(selectedDevices);
152
202
  },
153
203
  className: "buttons-pin",
154
- titleAttr: 'Pin Version'
204
+ titleAttr: "Pin Version",
155
205
  },
156
- ]
157
- }
206
+ ],
207
+ },
158
208
  },
159
209
  });
160
210
 
161
- dataTable.on('click', 'button.edit-name', function (e) {
162
- let data = dataTable.row(e.target.closest('tr')).data();
163
- uuid = data["uuid"];
164
- updateDeviceName(uuid);
211
+ dataTable.on("click", "button.edit-name", (e) => {
212
+ const data = dataTable.row(e.target.closest("tr")).data();
213
+ updateDeviceName(data.uuid);
165
214
  });
166
215
 
167
- dataTable.on( 'select', function ( e, dt, type, indexes ) {
168
- updateBtnState();
169
- } ).on( 'deselect', function ( e, dt, type, indexes ) {
170
- updateBtnState();
171
- } );
216
+ dataTable
217
+ .on("select", () => {
218
+ updateBtnState();
219
+ })
220
+ .on("deselect", () => {
221
+ updateBtnState();
222
+ });
172
223
 
173
- setInterval(function () {
224
+ setInterval(() => {
174
225
  dataTable.ajax.reload(null, false);
175
226
  }, TABLE_UPDATE_TIME);
176
227
 
177
- updateFirmwareSelection();
228
+ await updateFirmwareSelection(true);
178
229
  });
179
230
 
180
-
181
231
  function updateBtnState() {
182
- dataTable = $("#device-table").DataTable();
183
- if (dataTable.rows( {selected:true} ).any()){
184
- document.querySelector('button.buttons-select-none').classList.remove('disabled');
185
- document.querySelector('button.buttons-config').classList.remove('disabled');
186
- document.querySelector('button.buttons-force-update').classList.remove('disabled');
187
- document.querySelector('button.buttons-delete').classList.remove('disabled');
188
- document.querySelector('button.buttons-pin').classList.remove('disabled');
189
- } else {
190
- document.querySelector('button.buttons-select-none').classList.add('disabled');
191
- document.querySelector('button.buttons-config').classList.add('disabled');
192
- document.querySelector('button.buttons-force-update').classList.add('disabled');
193
- document.querySelector('button.buttons-delete').classList.add('disabled');
194
- document.querySelector('button.buttons-pin').classList.add('disabled');
195
- }
196
- if (dataTable.rows( {selected:true} ).count() == 1){
197
- document.querySelector('button.buttons-logs').classList.remove('disabled');
198
- document.querySelector('button.buttons-rename').classList.remove('disabled');
232
+ if (dataTable.rows({ selected: true }).any()) {
233
+ document.querySelector("button.buttons-select-none").classList.remove("disabled");
234
+ document.querySelector("button.buttons-config").classList.remove("disabled");
235
+ document.querySelector("button.buttons-force-update").classList.remove("disabled");
236
+ document.querySelector("button.buttons-delete").classList.remove("disabled");
237
+ document.querySelector("button.buttons-pin").classList.remove("disabled");
199
238
  } else {
200
- document.querySelector('button.buttons-logs').classList.add('disabled');
201
- document.querySelector('button.buttons-rename').classList.add('disabled');
239
+ document.querySelector("button.buttons-select-none").classList.add("disabled");
240
+ document.querySelector("button.buttons-config").classList.add("disabled");
241
+ document.querySelector("button.buttons-force-update").classList.add("disabled");
242
+ document.querySelector("button.buttons-delete").classList.add("disabled");
243
+ document.querySelector("button.buttons-pin").classList.add("disabled");
202
244
  }
203
-
204
-
205
- if(dataTable.rows( {selected:true} ).ids().toArray().length === dataTable.rows().ids().toArray().length){
206
- document.querySelector('button.buttons-select-all').classList.add('disabled');
245
+ if (dataTable.rows({ selected: true }).count() === 1) {
246
+ document.querySelector("button.buttons-logs").classList.remove("disabled");
247
+ document.querySelector("button.buttons-rename").classList.remove("disabled");
207
248
  } else {
208
- document.querySelector('button.buttons-select-all').classList.remove('disabled');
249
+ document.querySelector("button.buttons-logs").classList.add("disabled");
250
+ document.querySelector("button.buttons-rename").classList.add("disabled");
209
251
  }
210
252
  }
211
253
 
212
- function updateFirmwareSelection() {
213
- const url = '/api/firmware/all';
214
-
215
- fetch(url)
216
- .then(response => {
217
- if (!response.ok) {
218
- throw new Error('Request failed');
219
- }
220
- return response.json();
221
- })
222
- .then(data => {
223
- selectElem = document.getElementById("device-selected-fw");
254
+ async function updateDeviceConfig() {
255
+ const devices = dataTable
256
+ .rows({ selected: true })
257
+ .data()
258
+ .toArray()
259
+ .map((d) => d.uuid);
260
+ const firmware = document.getElementById("selected-fw").value;
261
+ const feed = document.getElementById("rollout-selected-feed").value;
262
+ const flavor = document.getElementById("rollout-selected-flavor").value;
224
263
 
225
- optionElem = document.createElement("option");
226
- optionElem.value = "none";
227
- optionElem.textContent = "none";
228
- selectElem.appendChild(optionElem);
229
-
230
- optionElem = document.createElement("option");
231
- optionElem.value = "latest";
232
- optionElem.textContent = "latest";
233
- selectElem.appendChild(optionElem);
234
-
235
- data.forEach(item => {
236
- optionElem = document.createElement("option");
237
- optionElem.value = item["name"];
238
- optionElem.textContent = item["name"];
239
- selectElem.appendChild(optionElem);
240
- });
241
- })
242
- .catch(error => {
243
- console.error('Failed to fetch device data:', error);
244
- });
245
- }
246
-
247
- function updateDeviceConfig() {
248
- selectedDevices = dataTable.rows( {selected:true} ).data().toArray().map(d => d["uuid"]);
249
- selectedFirmware = document.getElementById("device-selected-fw").value;
250
-
251
- fetch('/api/devices/update', {
252
- method: 'POST',
253
- headers: {
254
- 'Content-Type': 'application/json'
255
- },
256
- body: JSON.stringify({
257
- 'devices': selectedDevices,
258
- 'firmware': selectedFirmware
259
- })
260
- }).then(response => {
261
- if (!response.ok) {
262
- throw new Error('Failed to update devices.');
263
- }
264
- return response.json();
265
- }).catch(error => {
266
- console.error('Error:', error);
267
- });
264
+ try {
265
+ await post("/api/devices/update", { devices, firmware, feed, flavor });
266
+ } catch (error) {
267
+ console.error("Update device config failed:", error);
268
+ }
268
269
 
269
270
  setTimeout(updateDeviceList, 50);
270
271
  }
271
272
 
272
- function updateDeviceName() {
273
- selectedDevices = dataTable.rows( {selected:true} ).data().toArray().map(d => d["uuid"]);
274
- name = document.getElementById("device-selected-name").value;
273
+ async function updateDeviceName() {
274
+ const devices = dataTable
275
+ .rows({ selected: true })
276
+ .data()
277
+ .toArray()
278
+ .map((d) => d.uuid);
279
+ const name = document.getElementById("device-selected-name").value;
275
280
 
276
- fetch('/api/devices/update', {
277
- method: 'POST',
278
- headers: {
279
- 'Content-Type': 'application/json'
280
- },
281
- body: JSON.stringify({
282
- 'devices': selectedDevices,
283
- 'name': name
284
- })
285
- }).then(response => {
286
- if (!response.ok) {
287
- throw new Error('Failed to update devices.');
288
- }
289
- return response.json();
290
- }).catch(error => {
291
- console.error('Error:', error);
292
- });
281
+ try {
282
+ await post("/api/devices/update", { devices, name });
283
+ } catch (error) {
284
+ console.error("Update device name failed:", error);
285
+ }
293
286
 
294
287
  setTimeout(updateDeviceList, 50);
295
288
  }
296
289
 
297
-
298
- function forceUpdateDevices(devices) {
299
- fetch('/api/devices/force_update', {
300
- method: 'POST',
301
- headers: {
302
- 'Content-Type': 'application/json'
303
- },
304
- body: JSON.stringify({
305
- 'devices': devices,
306
- })
307
- }).then(response => {
308
- if (!response.ok) {
309
- throw new Error('Failed to force device update.');
310
- }
311
- return response.json();
312
- }).catch(error => {
313
- console.error('Error:', error);
314
- });
290
+ async function forceUpdateDevices(devices) {
291
+ try {
292
+ await post("/api/devices/force_update", { devices });
293
+ } catch (error) {
294
+ console.error("Update force update state failed:", error);
295
+ }
315
296
 
316
297
  setTimeout(updateDeviceList, 50);
317
298
  }
318
299
 
319
- function deleteDevices(devices) {
320
- fetch('/api/devices/delete', {
321
- method: 'POST',
322
- headers: {
323
- 'Content-Type': 'application/json'
324
- },
325
- body: JSON.stringify({
326
- 'devices': devices,
327
- })
328
- }).then(response => {
329
- if (!response.ok) {
330
- throw new Error('Failed to delete devices.');
331
- }
332
- return response.json();
333
- }).catch(error => {
334
- console.error('Error:', error);
335
- });
300
+ async function deleteDevices(devices) {
301
+ try {
302
+ await post("/api/devices/delete", { devices });
303
+ } catch (error) {
304
+ console.error("Delete device failed:", error);
305
+ }
336
306
 
337
307
  setTimeout(updateDeviceList, 50);
338
308
  }
339
309
 
340
- function pinDevices(devices) {
341
- fetch('/api/devices/update', {
342
- method: 'POST',
343
- headers: {
344
- 'Content-Type': 'application/json'
345
- },
346
- body: JSON.stringify({
347
- 'devices': devices,
348
- 'firmware': "pinned"
349
- })
350
- }).then(response => {
351
- if (!response.ok) {
352
- throw new Error('Failed to update devices.');
353
- }
354
- return response.json();
355
- }).catch(error => {
356
- console.error('Error:', error);
357
- });
310
+ async function pinDevices(devices) {
311
+ try {
312
+ await post("/api/devices/update", { devices, pinned: true });
313
+ } catch (error) {
314
+ console.error("Error:", error);
315
+ }
358
316
 
359
317
  setTimeout(updateDeviceList, 50);
360
318
  }
@@ -362,24 +320,3 @@ function pinDevices(devices) {
362
320
  function updateDeviceList() {
363
321
  dataTable.ajax.reload();
364
322
  }
365
-
366
- function secondsToRecentDate(t) {
367
- if (t == null) {
368
- return null
369
- }
370
- t = Number(t);
371
- var d = Math.floor(t / 86400)
372
- var h = Math.floor(t % 86400 / 3600);
373
- var m = Math.floor(t % 86400 % 3600 / 60);
374
- var s = Math.floor(t % 86400 % 3600 % 60);
375
-
376
- if (d > 0) {
377
- return d + (d == 1 ? " day" : " days");
378
- } else if (h > 0) {
379
- return h + (h == 1 ? " hour" : " hours");
380
- } else if (m > 0) {
381
- return m + (m == 1 ? " minute" : " minutes");
382
- } else {
383
- return s + (s == 1 ? " second" : " seconds");
384
- }
385
- }