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