goosebit 0.2.4__py3-none-any.whl → 0.2.6__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 +56 -6
- goosebit/api/telemetry/metrics.py +1 -5
- goosebit/api/v1/devices/device/responses.py +1 -0
- goosebit/api/v1/devices/device/routes.py +8 -8
- goosebit/api/v1/devices/requests.py +20 -0
- goosebit/api/v1/devices/routes.py +83 -8
- goosebit/api/v1/download/routes.py +14 -3
- goosebit/api/v1/rollouts/routes.py +5 -4
- goosebit/api/v1/routes.py +2 -1
- goosebit/api/v1/settings/routes.py +14 -0
- goosebit/api/v1/settings/users/__init__.py +1 -0
- goosebit/api/v1/settings/users/requests.py +16 -0
- goosebit/api/v1/settings/users/responses.py +7 -0
- goosebit/api/v1/settings/users/routes.py +56 -0
- goosebit/api/v1/software/routes.py +18 -14
- goosebit/auth/__init__.py +54 -14
- goosebit/auth/permissions.py +80 -0
- goosebit/db/config.py +57 -1
- goosebit/db/migrations/models/1_20241109151811_update.py +11 -0
- goosebit/db/migrations/models/2_20241121113728_update.py +11 -0
- goosebit/db/migrations/models/3_20241121140210_update.py +11 -0
- goosebit/db/migrations/models/4_20250324110331_update.py +16 -0
- goosebit/db/migrations/models/4_20250402085235_rename_uuid_to_id.py +11 -0
- goosebit/db/migrations/models/5_20250619090242_null_feed.py +83 -0
- goosebit/db/models.py +22 -7
- goosebit/db/pg_ssl_context.py +51 -0
- goosebit/device_manager.py +262 -0
- goosebit/plugins/__init__.py +32 -0
- goosebit/schema/devices.py +9 -6
- goosebit/schema/plugins.py +67 -0
- goosebit/schema/updates.py +15 -0
- goosebit/schema/users.py +9 -0
- goosebit/settings/__init__.py +0 -3
- goosebit/settings/schema.py +62 -14
- goosebit/storage/__init__.py +62 -0
- goosebit/storage/base.py +14 -0
- goosebit/storage/filesystem.py +111 -0
- goosebit/storage/s3.py +104 -0
- goosebit/ui/bff/common/columns.py +50 -0
- goosebit/ui/bff/common/requests.py +3 -15
- goosebit/ui/bff/common/responses.py +17 -0
- goosebit/ui/bff/devices/device/__init__.py +1 -0
- goosebit/ui/bff/devices/device/routes.py +17 -0
- goosebit/ui/bff/devices/requests.py +1 -0
- goosebit/ui/bff/devices/responses.py +6 -2
- goosebit/ui/bff/devices/routes.py +71 -17
- goosebit/ui/bff/download/routes.py +14 -3
- goosebit/ui/bff/rollouts/responses.py +6 -2
- goosebit/ui/bff/rollouts/routes.py +32 -4
- goosebit/ui/bff/routes.py +6 -3
- goosebit/ui/bff/settings/__init__.py +1 -0
- goosebit/ui/bff/settings/routes.py +20 -0
- goosebit/ui/bff/settings/users/__init__.py +1 -0
- goosebit/ui/bff/settings/users/responses.py +33 -0
- goosebit/ui/bff/settings/users/routes.py +80 -0
- goosebit/ui/bff/software/responses.py +19 -9
- goosebit/ui/bff/software/routes.py +40 -12
- goosebit/ui/nav.py +12 -2
- goosebit/ui/routes.py +70 -26
- goosebit/ui/static/js/devices.js +72 -80
- goosebit/ui/static/js/login.js +21 -5
- goosebit/ui/static/js/logs.js +7 -22
- goosebit/ui/static/js/rollouts.js +39 -35
- goosebit/ui/static/js/settings.js +322 -0
- goosebit/ui/static/js/setup.js +28 -0
- goosebit/ui/static/js/software.js +127 -127
- goosebit/ui/static/js/util.js +45 -4
- goosebit/ui/templates/__init__.py +10 -1
- goosebit/ui/templates/devices.html.jinja +0 -20
- goosebit/ui/templates/login.html.jinja +5 -0
- goosebit/ui/templates/nav.html.jinja +26 -7
- goosebit/ui/templates/rollouts.html.jinja +4 -22
- goosebit/ui/templates/settings.html.jinja +88 -0
- goosebit/ui/templates/setup.html.jinja +71 -0
- goosebit/ui/templates/software.html.jinja +0 -11
- goosebit/updater/controller/v1/routes.py +120 -72
- goosebit/updater/routes.py +86 -7
- goosebit/updates/__init__.py +24 -31
- goosebit/updates/swdesc.py +15 -8
- goosebit/users/__init__.py +63 -0
- goosebit/util/__init__.py +0 -0
- goosebit/util/path.py +42 -0
- goosebit/util/version.py +92 -0
- goosebit-0.2.6.dist-info/METADATA +280 -0
- goosebit-0.2.6.dist-info/RECORD +133 -0
- {goosebit-0.2.4.dist-info → goosebit-0.2.6.dist-info}/WHEEL +1 -1
- goosebit-0.2.6.dist-info/entry_points.txt +3 -0
- goosebit/realtime/logs.py +0 -42
- goosebit/realtime/routes.py +0 -13
- goosebit/ui/static/js/index.js +0 -155
- goosebit/ui/templates/index.html.jinja +0 -25
- goosebit/updater/manager.py +0 -357
- goosebit-0.2.4.dist-info/METADATA +0 -181
- goosebit-0.2.4.dist-info/RECORD +0 -98
- /goosebit/{realtime → api/v1/settings}/__init__.py +0 -0
- {goosebit-0.2.4.dist-info → goosebit-0.2.6.dist-info}/LICENSE +0 -0
goosebit/ui/static/js/devices.js
CHANGED
@@ -1,29 +1,62 @@
|
|
1
1
|
let dataTable;
|
2
2
|
|
3
|
+
const renderFunctions = {
|
4
|
+
force_update: (data, type) => {
|
5
|
+
if (type === "display") {
|
6
|
+
const color = data ? "success" : "muted";
|
7
|
+
return `
|
8
|
+
<div class="text-${color}">
|
9
|
+
●
|
10
|
+
</div>
|
11
|
+
`;
|
12
|
+
}
|
13
|
+
return data;
|
14
|
+
},
|
15
|
+
progress: (data, type) => {
|
16
|
+
if (type === "display" || type === "filter") {
|
17
|
+
return data ? `${data}%` : "-";
|
18
|
+
}
|
19
|
+
return data;
|
20
|
+
},
|
21
|
+
polling: (data, type) => {
|
22
|
+
return data ? "on time" : "overdue";
|
23
|
+
},
|
24
|
+
last_seen: (data, type) => {
|
25
|
+
if (type === "display" || type === "filter") {
|
26
|
+
return secondsToRecentDate(data);
|
27
|
+
}
|
28
|
+
return data;
|
29
|
+
},
|
30
|
+
};
|
31
|
+
|
3
32
|
document.addEventListener("DOMContentLoaded", async () => {
|
33
|
+
const columnConfig = await get_request("/ui/bff/devices/columns");
|
34
|
+
for (const col in columnConfig.columns) {
|
35
|
+
const colDesc = columnConfig.columns[col];
|
36
|
+
const colName = colDesc.data;
|
37
|
+
if (renderFunctions[colName]) {
|
38
|
+
columnConfig.columns[col].render = renderFunctions[colName];
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
4
42
|
dataTable = new DataTable("#device-table", {
|
5
43
|
responsive: true,
|
6
44
|
paging: true,
|
7
45
|
processing: false,
|
8
46
|
serverSide: true,
|
9
|
-
order:
|
47
|
+
order: { name: "id", dir: "asc" },
|
10
48
|
scrollCollapse: true,
|
11
49
|
scroller: true,
|
12
50
|
scrollY: "65vh",
|
13
51
|
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
52
|
select: true,
|
24
|
-
rowId: "
|
53
|
+
rowId: "id",
|
25
54
|
ajax: {
|
26
|
-
url: "/ui/bff/devices
|
55
|
+
url: "/ui/bff/devices",
|
56
|
+
data: (data) => {
|
57
|
+
// biome-ignore lint/performance/noDelete: really has to be deleted
|
58
|
+
delete data.columns;
|
59
|
+
},
|
27
60
|
contentType: "application/json",
|
28
61
|
},
|
29
62
|
initComplete: () => {
|
@@ -37,64 +70,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
37
70
|
render: (data) => data || "-",
|
38
71
|
},
|
39
72
|
],
|
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
|
-
],
|
73
|
+
columns: columnConfig.columns,
|
98
74
|
layout: {
|
99
75
|
top1Start: {
|
100
76
|
buttons: [
|
@@ -112,7 +88,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
112
88
|
text: '<i class="bi bi-file-text"></i>',
|
113
89
|
action: () => {
|
114
90
|
const selectedDevice = dataTable.rows({ selected: true }).data().toArray()[0];
|
115
|
-
window.location.href = `/ui/logs/${selectedDevice.
|
91
|
+
window.location.href = `/ui/logs/${selectedDevice.id}`;
|
116
92
|
},
|
117
93
|
className: "buttons-logs",
|
118
94
|
titleAttr: "View Log",
|
@@ -141,7 +117,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
141
117
|
.rows({ selected: true })
|
142
118
|
.data()
|
143
119
|
.toArray()
|
144
|
-
.map((d) => d.
|
120
|
+
.map((d) => d.id);
|
145
121
|
await deleteDevices(selectedDevices);
|
146
122
|
},
|
147
123
|
className: "buttons-delete",
|
@@ -154,7 +130,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
154
130
|
.rows({ selected: true })
|
155
131
|
.data()
|
156
132
|
.toArray()
|
157
|
-
.map((d) => d.
|
133
|
+
.map((d) => d.id);
|
158
134
|
await forceUpdateDevices(selectedDevices);
|
159
135
|
},
|
160
136
|
className: "buttons-force-update",
|
@@ -167,7 +143,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
167
143
|
.rows({ selected: true })
|
168
144
|
.data()
|
169
145
|
.toArray()
|
170
|
-
.map((d) => d.
|
146
|
+
.map((d) => d.id);
|
171
147
|
await pinDevices(selectedDevices);
|
172
148
|
},
|
173
149
|
className: "buttons-pin",
|
@@ -187,7 +163,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
187
163
|
});
|
188
164
|
|
189
165
|
setInterval(() => {
|
190
|
-
|
166
|
+
updateDeviceList();
|
191
167
|
}, TABLE_UPDATE_TIME);
|
192
168
|
|
193
169
|
await updateSoftwareSelection();
|
@@ -307,7 +283,7 @@ async function updateDeviceName() {
|
|
307
283
|
.rows({ selected: true })
|
308
284
|
.data()
|
309
285
|
.toArray()
|
310
|
-
.map((d) => d.
|
286
|
+
.map((d) => d.id);
|
311
287
|
const name = document.getElementById("device-name").value;
|
312
288
|
|
313
289
|
try {
|
@@ -324,7 +300,7 @@ async function updateDeviceRollout() {
|
|
324
300
|
.rows({ selected: true })
|
325
301
|
.data()
|
326
302
|
.toArray()
|
327
|
-
.map((d) => d.
|
303
|
+
.map((d) => d.id);
|
328
304
|
const feed = document.getElementById("device-selected-feed").value;
|
329
305
|
const software = "rollout";
|
330
306
|
|
@@ -342,7 +318,7 @@ async function updateDeviceManualSoftware() {
|
|
342
318
|
.rows({ selected: true })
|
343
319
|
.data()
|
344
320
|
.toArray()
|
345
|
-
.map((d) => d.
|
321
|
+
.map((d) => d.id);
|
346
322
|
const feed = null;
|
347
323
|
const software = document.getElementById("selected-sw").value;
|
348
324
|
|
@@ -360,7 +336,7 @@ async function updateDeviceLatest() {
|
|
360
336
|
.rows({ selected: true })
|
361
337
|
.data()
|
362
338
|
.toArray()
|
363
|
-
.map((d) => d.
|
339
|
+
.map((d) => d.id);
|
364
340
|
const feed = null;
|
365
341
|
const software = "latest";
|
366
342
|
|
@@ -404,5 +380,21 @@ async function pinDevices(devices) {
|
|
404
380
|
}
|
405
381
|
|
406
382
|
function updateDeviceList() {
|
407
|
-
|
383
|
+
const scrollPosition = $("#device-table").parent().scrollTop(); // Get current scroll position
|
384
|
+
|
385
|
+
const selectedRows = dataTable
|
386
|
+
.rows({ selected: true })
|
387
|
+
.data()
|
388
|
+
.toArray()
|
389
|
+
.map((d) => d.id);
|
390
|
+
|
391
|
+
dataTable.ajax.reload(() => {
|
392
|
+
dataTable.rows().every(function () {
|
393
|
+
const rowData = this.data();
|
394
|
+
if (selectedRows.includes(rowData.id)) {
|
395
|
+
this.select();
|
396
|
+
}
|
397
|
+
});
|
398
|
+
$("#device-table").parent().scrollTop(scrollPosition); // Restore scroll position after reload
|
399
|
+
}, false);
|
408
400
|
}
|
goosebit/ui/static/js/login.js
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
loginForm = document.getElementById("login_form");
|
1
|
+
const loginForm = document.getElementById("login_form");
|
2
|
+
const errorContainer = document.getElementById("login_error");
|
2
3
|
|
3
4
|
async function login() {
|
4
5
|
const formData = new FormData(loginForm);
|
@@ -8,16 +9,31 @@ async function login() {
|
|
8
9
|
method: "POST",
|
9
10
|
body: formData,
|
10
11
|
});
|
11
|
-
|
12
|
+
|
13
|
+
if (response.status === 401) {
|
14
|
+
const result = await response.json();
|
15
|
+
errorContainer.textContent = result.detail || "Failed to login.";
|
16
|
+
errorContainer.classList.remove("d-none");
|
17
|
+
return;
|
18
|
+
}
|
19
|
+
|
20
|
+
if (!response.ok) {
|
21
|
+
errorContainer.textContent = "Something went wrong. Please try again.";
|
22
|
+
errorContainer.classList.remove("d-none");
|
23
|
+
return;
|
24
|
+
}
|
25
|
+
|
26
|
+
const tokenData = await response.json();
|
12
27
|
document.cookie = `session_id=${tokenData.access_token}; path=/`;
|
13
28
|
location.reload();
|
14
|
-
} catch (
|
15
|
-
|
16
|
-
|
29
|
+
} catch (error) {
|
30
|
+
errorContainer.textContent = "Network error. Please try again.";
|
31
|
+
errorContainer.classList.remove("d-none");
|
17
32
|
}
|
18
33
|
}
|
19
34
|
|
20
35
|
loginForm.addEventListener("submit", (event) => {
|
21
36
|
event.preventDefault();
|
37
|
+
errorContainer.classList.add("d-none"); // Hide error before new attempt
|
22
38
|
login();
|
23
39
|
});
|
goosebit/ui/static/js/logs.js
CHANGED
@@ -1,25 +1,10 @@
|
|
1
|
-
document.addEventListener("DOMContentLoaded", () => {
|
2
|
-
const
|
1
|
+
document.addEventListener("DOMContentLoaded", async () => {
|
2
|
+
const res = await get_request(`/ui/bff/devices/${device}/log`);
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
const logElem = document.getElementById("device-log");
|
5
|
+
logElem.textContent = res.log;
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
}
|
11
|
-
logElem.textContent += res.log;
|
12
|
-
|
13
|
-
const progressElem = document.getElementById("install-progress");
|
14
|
-
progressElem.style.width = `${res.progress}%`;
|
15
|
-
progressElem.innerHTML = `${res.progress}%`;
|
16
|
-
});
|
7
|
+
const progressElem = document.getElementById("install-progress");
|
8
|
+
progressElem.style.width = `${res.progress}%`;
|
9
|
+
progressElem.innerHTML = `${res.progress}%`;
|
17
10
|
});
|
18
|
-
|
19
|
-
function create_ws(s) {
|
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);
|
25
|
-
}
|
@@ -1,29 +1,51 @@
|
|
1
1
|
let dataTable;
|
2
2
|
|
3
|
+
const renderFunctions = {
|
4
|
+
paused: (data, type) => {
|
5
|
+
if (type === "display") {
|
6
|
+
const color = data ? "danger" : "muted";
|
7
|
+
return `
|
8
|
+
<div class="text-${color}">
|
9
|
+
●
|
10
|
+
</div>
|
11
|
+
`;
|
12
|
+
}
|
13
|
+
return data;
|
14
|
+
},
|
15
|
+
created_at: (data, type) => new Date(data).toLocaleString(),
|
16
|
+
};
|
17
|
+
|
3
18
|
document.addEventListener("DOMContentLoaded", async () => {
|
19
|
+
const columnConfig = await get_request("/ui/bff/rollouts/columns");
|
20
|
+
for (const col in columnConfig.columns) {
|
21
|
+
const colDesc = columnConfig.columns[col];
|
22
|
+
const colName = colDesc.data;
|
23
|
+
if (renderFunctions[colName]) {
|
24
|
+
columnConfig.columns[col].render = renderFunctions[colName];
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
4
28
|
dataTable = new DataTable("#rollout-table", {
|
5
29
|
responsive: true,
|
6
30
|
paging: true,
|
7
31
|
processing: true,
|
8
32
|
serverSide: true,
|
9
|
-
order:
|
33
|
+
order: {
|
34
|
+
name: "created_at",
|
35
|
+
dir: "desc",
|
36
|
+
},
|
10
37
|
scrollCollapse: true,
|
11
38
|
scroller: true,
|
12
39
|
scrollY: "65vh",
|
13
40
|
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
41
|
select: true,
|
24
42
|
rowId: "id",
|
25
43
|
ajax: {
|
26
44
|
url: "/ui/bff/rollouts",
|
45
|
+
data: (data) => {
|
46
|
+
// biome-ignore lint/performance/noDelete: really has to be deleted
|
47
|
+
delete data.columns;
|
48
|
+
},
|
27
49
|
contentType: "application/json",
|
28
50
|
},
|
29
51
|
initComplete: () => {
|
@@ -34,32 +56,10 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
34
56
|
targets: "_all",
|
35
57
|
searchable: false,
|
36
58
|
orderable: false,
|
59
|
+
render: (data) => data || "-",
|
37
60
|
},
|
38
61
|
],
|
39
|
-
columns:
|
40
|
-
{ data: "id", visible: false },
|
41
|
-
{ data: "created_at", orderable: true, render: (data) => new Date(data).toLocaleString() },
|
42
|
-
{ data: "name", searchable: true, orderable: true },
|
43
|
-
{ data: "feed", searchable: true, orderable: true },
|
44
|
-
{ data: "sw_file" },
|
45
|
-
{ data: "sw_version" },
|
46
|
-
{
|
47
|
-
data: "paused",
|
48
|
-
render: (data, type) => {
|
49
|
-
if (type === "display" || type === "filter") {
|
50
|
-
const color = data ? "danger" : "muted";
|
51
|
-
return `
|
52
|
-
<div class="text-${color}">
|
53
|
-
●
|
54
|
-
</div>
|
55
|
-
`;
|
56
|
-
}
|
57
|
-
return data;
|
58
|
-
},
|
59
|
-
},
|
60
|
-
{ data: "success_count" },
|
61
|
-
{ data: "failure_count" },
|
62
|
-
],
|
62
|
+
columns: columnConfig.columns,
|
63
63
|
layout: {
|
64
64
|
top1Start: {
|
65
65
|
buttons: [],
|
@@ -191,7 +191,11 @@ async function createRollout() {
|
|
191
191
|
}
|
192
192
|
|
193
193
|
function updateRolloutList() {
|
194
|
-
|
194
|
+
const scrollPosition = $("#rollout-table").parent().scrollTop(); // Get current scroll position
|
195
|
+
|
196
|
+
dataTable.ajax.reload(() => {
|
197
|
+
$("#rollout-table").parent().scrollTop(scrollPosition); // Restore scroll position after reload
|
198
|
+
}, false);
|
195
199
|
}
|
196
200
|
|
197
201
|
async function deleteRollouts(ids) {
|