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.
- goosebit/__init__.py +8 -5
- goosebit/api/__init__.py +1 -1
- goosebit/api/devices.py +60 -36
- goosebit/api/download.py +28 -14
- goosebit/api/firmware.py +37 -44
- goosebit/api/helper.py +30 -0
- goosebit/api/rollouts.py +87 -0
- goosebit/api/routes.py +15 -7
- goosebit/auth/__init__.py +37 -21
- goosebit/db.py +5 -0
- goosebit/models.py +125 -6
- goosebit/permissions.py +33 -13
- goosebit/realtime/__init__.py +1 -1
- goosebit/realtime/logs.py +4 -6
- goosebit/settings.py +38 -29
- goosebit/telemetry/__init__.py +28 -0
- goosebit/telemetry/prometheus.py +10 -0
- goosebit/ui/__init__.py +1 -1
- goosebit/ui/routes.py +36 -39
- goosebit/ui/static/js/devices.js +191 -239
- goosebit/ui/static/js/firmware.js +234 -88
- goosebit/ui/static/js/index.js +83 -84
- goosebit/ui/static/js/logs.js +17 -10
- goosebit/ui/static/js/rollouts.js +198 -0
- goosebit/ui/static/js/util.js +66 -0
- goosebit/ui/templates/devices.html +75 -42
- goosebit/ui/templates/firmware.html +150 -34
- goosebit/ui/templates/index.html +9 -23
- goosebit/ui/templates/login.html +58 -27
- goosebit/ui/templates/logs.html +18 -3
- goosebit/ui/templates/nav.html +78 -25
- goosebit/ui/templates/rollouts.html +76 -0
- 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 +112 -24
- goosebit/updater/manager.py +237 -94
- goosebit/updater/routes.py +7 -8
- goosebit/updates/__init__.py +70 -0
- goosebit/updates/swdesc.py +83 -0
- goosebit-0.1.2.dist-info/METADATA +123 -0
- 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 -26
- goosebit/updater/misc.py +0 -69
- goosebit/updater/updates.py +0 -93
- goosebit-0.1.0.dist-info/METADATA +0 -37
- goosebit-0.1.0.dist-info/RECORD +0 -48
- {goosebit-0.1.0.dist-info → goosebit-0.1.2.dist-info}/LICENSE +0 -0
- {goosebit-0.1.0.dist-info → goosebit-0.1.2.dist-info}/WHEEL +0 -0
@@ -0,0 +1,198 @@
|
|
1
|
+
let dataTable;
|
2
|
+
|
3
|
+
document.addEventListener("DOMContentLoaded", async () => {
|
4
|
+
dataTable = new DataTable("#rollout-table", {
|
5
|
+
responsive: true,
|
6
|
+
paging: true,
|
7
|
+
processing: true,
|
8
|
+
serverSide: true,
|
9
|
+
order: [1, "desc"],
|
10
|
+
scrollCollapse: true,
|
11
|
+
scroller: true,
|
12
|
+
scrollY: "65vh",
|
13
|
+
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
|
+
select: true,
|
24
|
+
rowId: "id",
|
25
|
+
ajax: {
|
26
|
+
url: "/api/rollouts/all",
|
27
|
+
contentType: "application/json",
|
28
|
+
},
|
29
|
+
initComplete: () => {
|
30
|
+
updateBtnState();
|
31
|
+
},
|
32
|
+
columnDefs: [
|
33
|
+
{
|
34
|
+
targets: [1, 2, 3, 4],
|
35
|
+
searchable: true,
|
36
|
+
orderable: true,
|
37
|
+
},
|
38
|
+
{
|
39
|
+
targets: "_all",
|
40
|
+
searchable: false,
|
41
|
+
orderable: false,
|
42
|
+
},
|
43
|
+
],
|
44
|
+
columns: [
|
45
|
+
{ data: "id", visible: false },
|
46
|
+
{ data: "created_at", render: (data) => new Date(data).toLocaleString() },
|
47
|
+
{ data: "name" },
|
48
|
+
{ data: "feed" },
|
49
|
+
{ data: "flavor" },
|
50
|
+
{ data: "fw_file" },
|
51
|
+
{ data: "fw_version" },
|
52
|
+
{
|
53
|
+
data: "paused",
|
54
|
+
render: (data, type) => {
|
55
|
+
if (type === "display" || type === "filter") {
|
56
|
+
const color = data ? "danger" : "muted";
|
57
|
+
return `
|
58
|
+
<div class="text-${color}">
|
59
|
+
●
|
60
|
+
</div>
|
61
|
+
`;
|
62
|
+
}
|
63
|
+
return data;
|
64
|
+
},
|
65
|
+
},
|
66
|
+
{ data: "success_count" },
|
67
|
+
{ data: "failure_count" },
|
68
|
+
],
|
69
|
+
layout: {
|
70
|
+
top1Start: {
|
71
|
+
buttons: [],
|
72
|
+
},
|
73
|
+
bottom1Start: {
|
74
|
+
buttons: [
|
75
|
+
{
|
76
|
+
text: '<i class="bi bi-plus" ></i>',
|
77
|
+
action: () => {
|
78
|
+
new bootstrap.Modal("#rollout-create-modal").show();
|
79
|
+
},
|
80
|
+
className: "buttons-create",
|
81
|
+
titleAttr: "Create Rollout",
|
82
|
+
},
|
83
|
+
{
|
84
|
+
text: '<i class="bi bi-play-fill" ></i>',
|
85
|
+
action: (e, dt) => {
|
86
|
+
const selectedRollouts = dt
|
87
|
+
.rows({ selected: true })
|
88
|
+
.data()
|
89
|
+
.toArray()
|
90
|
+
.map((d) => d.id);
|
91
|
+
pauseRollouts(selectedRollouts, false);
|
92
|
+
},
|
93
|
+
className: "buttons-resume",
|
94
|
+
titleAttr: "Resume Rollouts",
|
95
|
+
},
|
96
|
+
{
|
97
|
+
text: '<i class="bi bi-pause-fill" ></i>',
|
98
|
+
action: (e, dt) => {
|
99
|
+
const selectedRollouts = dt
|
100
|
+
.rows({ selected: true })
|
101
|
+
.data()
|
102
|
+
.toArray()
|
103
|
+
.map((d) => d.id);
|
104
|
+
pauseRollouts(selectedRollouts, true);
|
105
|
+
},
|
106
|
+
className: "buttons-pause",
|
107
|
+
titleAttr: "Pause Rollouts",
|
108
|
+
},
|
109
|
+
{
|
110
|
+
text: '<i class="bi bi-trash" ></i>',
|
111
|
+
action: (e, dt) => {
|
112
|
+
const selectedRollouts = dt
|
113
|
+
.rows({ selected: true })
|
114
|
+
.data()
|
115
|
+
.toArray()
|
116
|
+
.map((d) => d.id);
|
117
|
+
deleteRollouts(selectedRollouts);
|
118
|
+
},
|
119
|
+
className: "buttons-delete",
|
120
|
+
titleAttr: "Delete Rollouts",
|
121
|
+
},
|
122
|
+
],
|
123
|
+
},
|
124
|
+
},
|
125
|
+
});
|
126
|
+
|
127
|
+
dataTable
|
128
|
+
.on("select", () => {
|
129
|
+
updateBtnState();
|
130
|
+
})
|
131
|
+
.on("deselect", () => {
|
132
|
+
updateBtnState();
|
133
|
+
});
|
134
|
+
|
135
|
+
updateRolloutList();
|
136
|
+
|
137
|
+
await updateFirmwareSelection();
|
138
|
+
});
|
139
|
+
|
140
|
+
function updateBtnState() {
|
141
|
+
if (dataTable.rows({ selected: true }).any()) {
|
142
|
+
document.querySelector("button.buttons-delete").classList.remove("disabled");
|
143
|
+
} else {
|
144
|
+
document.querySelector("button.buttons-delete").classList.add("disabled");
|
145
|
+
}
|
146
|
+
|
147
|
+
if (dataTable.rows((_, data) => data.paused, { selected: true }).any()) {
|
148
|
+
document.querySelector("button.buttons-resume").classList.remove("disabled");
|
149
|
+
} else {
|
150
|
+
document.querySelector("button.buttons-resume").classList.add("disabled");
|
151
|
+
}
|
152
|
+
|
153
|
+
if (dataTable.rows((_, data) => !data.paused, { selected: true }).any()) {
|
154
|
+
document.querySelector("button.buttons-pause").classList.remove("disabled");
|
155
|
+
} else {
|
156
|
+
document.querySelector("button.buttons-pause").classList.add("disabled");
|
157
|
+
}
|
158
|
+
}
|
159
|
+
|
160
|
+
async function createRollout() {
|
161
|
+
const name = document.getElementById("rollout-selected-name").value;
|
162
|
+
const feed = document.getElementById("rollout-selected-feed").value;
|
163
|
+
const flavor = document.getElementById("rollout-selected-flavor").value;
|
164
|
+
const firmware_id = document.getElementById("selected-fw").value;
|
165
|
+
|
166
|
+
try {
|
167
|
+
await post("/api/rollouts", { name, feed, flavor, firmware_id });
|
168
|
+
} catch (error) {
|
169
|
+
console.error("Rollout creation failed:", error);
|
170
|
+
}
|
171
|
+
|
172
|
+
setTimeout(updateRolloutList, 50);
|
173
|
+
}
|
174
|
+
|
175
|
+
function updateRolloutList() {
|
176
|
+
dataTable.ajax.reload();
|
177
|
+
}
|
178
|
+
|
179
|
+
async function deleteRollouts(ids) {
|
180
|
+
try {
|
181
|
+
await post("/api/rollouts/delete", { ids });
|
182
|
+
} catch (error) {
|
183
|
+
console.error("Rollouts deletion failed:", error);
|
184
|
+
}
|
185
|
+
|
186
|
+
updateBtnState();
|
187
|
+
setTimeout(updateRolloutList, 50);
|
188
|
+
}
|
189
|
+
|
190
|
+
async function pauseRollouts(ids, paused) {
|
191
|
+
try {
|
192
|
+
await post("/api/rollouts/update", { ids, paused });
|
193
|
+
} catch (error) {
|
194
|
+
console.error(`Rollouts ${paused ? "pausing" : "unpausing"} failed:`, error);
|
195
|
+
}
|
196
|
+
|
197
|
+
setTimeout(updateRolloutList, 50);
|
198
|
+
}
|
@@ -0,0 +1,66 @@
|
|
1
|
+
function secondsToRecentDate(t) {
|
2
|
+
if (t == null) {
|
3
|
+
return null;
|
4
|
+
}
|
5
|
+
const time = Number(t);
|
6
|
+
const d = Math.floor(time / 86400);
|
7
|
+
const h = Math.floor((time % 86400) / 3600);
|
8
|
+
const m = Math.floor(((time % 86400) % 3600) / 60);
|
9
|
+
const s = Math.floor(((time % 86400) % 3600) % 60);
|
10
|
+
|
11
|
+
if (d > 0) {
|
12
|
+
return d + (d === 1 ? " day" : " days");
|
13
|
+
}
|
14
|
+
if (h > 0) {
|
15
|
+
return h + (h === 1 ? " hour" : " hours");
|
16
|
+
}
|
17
|
+
if (m > 0) {
|
18
|
+
return m + (m === 1 ? " minute" : " minutes");
|
19
|
+
}
|
20
|
+
return s + (s === 1 ? " second" : " seconds");
|
21
|
+
}
|
22
|
+
|
23
|
+
async function updateFirmwareSelection(addSpecialMode = false) {
|
24
|
+
try {
|
25
|
+
const response = await fetch("/api/firmware/all");
|
26
|
+
if (!response.ok) {
|
27
|
+
console.error("Retrieving firmwares failed.");
|
28
|
+
return;
|
29
|
+
}
|
30
|
+
const data = (await response.json()).data;
|
31
|
+
const selectElem = document.getElementById("selected-fw");
|
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
|
+
}
|
44
|
+
|
45
|
+
for (const item of data) {
|
46
|
+
const optionElem = document.createElement("option");
|
47
|
+
optionElem.value = item.id;
|
48
|
+
optionElem.textContent = item.name;
|
49
|
+
selectElem.appendChild(optionElem);
|
50
|
+
}
|
51
|
+
} catch (error) {
|
52
|
+
console.error("Failed to fetch device data:", error);
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
async function post(url, object) {
|
57
|
+
const response = await fetch(url, {
|
58
|
+
method: "POST",
|
59
|
+
headers: { "Content-Type": "application/json" },
|
60
|
+
body: JSON.stringify(object),
|
61
|
+
});
|
62
|
+
|
63
|
+
if (!response.ok) {
|
64
|
+
throw new Error(`POST ${url} failed for ${JSON.stringify(object)}`);
|
65
|
+
}
|
66
|
+
}
|
@@ -1,43 +1,28 @@
|
|
1
|
-
{% extends "nav.html" %}
|
2
|
-
{% block content %}
|
1
|
+
{% extends "nav.html" %} {% block content %}
|
3
2
|
<div class="container-fluid">
|
4
3
|
<div class="row p-2 d-flex justify-content-center">
|
5
4
|
<div class="col">
|
6
5
|
<table id="device-table" class="table table-hover">
|
7
6
|
<thead>
|
8
7
|
<tr>
|
9
|
-
<th>
|
10
|
-
|
11
|
-
</th>
|
12
|
-
<th>
|
13
|
-
|
14
|
-
</th>
|
15
|
-
<th>
|
16
|
-
|
17
|
-
</th>
|
18
|
-
<th>
|
19
|
-
|
20
|
-
</th>
|
21
|
-
<th>
|
22
|
-
|
23
|
-
</th>
|
24
|
-
<th>
|
25
|
-
Update File
|
26
|
-
</th>
|
27
|
-
<th>
|
28
|
-
Last IP
|
29
|
-
</th>
|
30
|
-
<th>
|
31
|
-
Last Seen
|
32
|
-
</th>
|
33
|
-
<th>
|
34
|
-
State
|
35
|
-
</th>
|
8
|
+
<th>Up</th>
|
9
|
+
<th>UUID</th>
|
10
|
+
<th>Name</th>
|
11
|
+
<th>Model</th>
|
12
|
+
<th>Revision</th>
|
13
|
+
<th>Feed</th>
|
14
|
+
<th>Flavor</th>
|
15
|
+
<th>Installed Firmware</th>
|
16
|
+
<th>Target Firmware</th>
|
17
|
+
<th>Update Mode</th>
|
18
|
+
<th>State</th>
|
19
|
+
<th>Force Update</th>
|
20
|
+
<th>Progress</th>
|
21
|
+
<th>Last IP</th>
|
22
|
+
<th>Last Seen</th>
|
36
23
|
</tr>
|
37
24
|
</thead>
|
38
|
-
<tbody id="devices-list">
|
39
|
-
|
40
|
-
</tbody>
|
25
|
+
<tbody id="devices-list"></tbody>
|
41
26
|
</table>
|
42
27
|
</div>
|
43
28
|
</div>
|
@@ -47,16 +32,42 @@
|
|
47
32
|
<div class="modal-content">
|
48
33
|
<div class="modal-header">
|
49
34
|
<h5 class="modal-title">Configure Devices</h5>
|
50
|
-
|
35
|
+
<button
|
36
|
+
type="button"
|
37
|
+
class="btn-close"
|
38
|
+
data-bs-dismiss="modal"
|
39
|
+
aria-label="Close"
|
40
|
+
></button>
|
51
41
|
</div>
|
52
42
|
<div class="modal-body">
|
53
|
-
<
|
54
|
-
|
55
|
-
|
43
|
+
<input
|
44
|
+
id="rollout-selected-feed"
|
45
|
+
class="form-control mb-3"
|
46
|
+
placeholder="Feed"
|
47
|
+
/>
|
48
|
+
<input
|
49
|
+
id="rollout-selected-flavor"
|
50
|
+
class="form-control mb-3"
|
51
|
+
placeholder="Flavor"
|
52
|
+
/>
|
53
|
+
<select class="form-select" id="selected-fw"></select>
|
56
54
|
</div>
|
57
55
|
<div class="modal-footer">
|
58
|
-
<button
|
59
|
-
|
56
|
+
<button
|
57
|
+
type="button"
|
58
|
+
class="btn btn-secondary"
|
59
|
+
data-bs-dismiss="modal"
|
60
|
+
>
|
61
|
+
Close
|
62
|
+
</button>
|
63
|
+
<button
|
64
|
+
type="button"
|
65
|
+
class="btn btn-outline-light"
|
66
|
+
data-bs-dismiss="modal"
|
67
|
+
onclick="updateDeviceConfig()"
|
68
|
+
>
|
69
|
+
Save changes
|
70
|
+
</button>
|
60
71
|
</div>
|
61
72
|
</div>
|
62
73
|
</div>
|
@@ -66,14 +77,36 @@
|
|
66
77
|
<div class="modal-content">
|
67
78
|
<div class="modal-header">
|
68
79
|
<h5 class="modal-title">Rename Device</h5>
|
69
|
-
|
80
|
+
<button
|
81
|
+
type="button"
|
82
|
+
class="btn-close"
|
83
|
+
data-bs-dismiss="modal"
|
84
|
+
aria-label="Close"
|
85
|
+
></button>
|
70
86
|
</div>
|
71
87
|
<div class="modal-body">
|
72
|
-
<input
|
88
|
+
<input
|
89
|
+
id="device-selected-name"
|
90
|
+
class="form-control"
|
91
|
+
placeholder="Name"
|
92
|
+
/>
|
73
93
|
</div>
|
74
94
|
<div class="modal-footer">
|
75
|
-
<button
|
76
|
-
|
95
|
+
<button
|
96
|
+
type="button"
|
97
|
+
class="btn btn-secondary"
|
98
|
+
data-bs-dismiss="modal"
|
99
|
+
>
|
100
|
+
Close
|
101
|
+
</button>
|
102
|
+
<button
|
103
|
+
type="button"
|
104
|
+
class="btn btn-outline-light"
|
105
|
+
data-bs-dismiss="modal"
|
106
|
+
onclick="updateDeviceName()"
|
107
|
+
>
|
108
|
+
Save changes
|
109
|
+
</button>
|
77
110
|
</div>
|
78
111
|
</div>
|
79
112
|
</div>
|
@@ -1,47 +1,163 @@
|
|
1
|
-
{% extends "nav.html" %}
|
2
|
-
{% block content %}
|
1
|
+
{% extends "nav.html" %} {% block content %}
|
3
2
|
<div class="container-fluid">
|
4
3
|
<div class="row p-2 pt-4 g-4 d-flex justify-content-center">
|
5
4
|
{% if "firmware.write" in request.user.permissions %}
|
6
|
-
<div class="col col-12
|
7
|
-
<
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
<input class="form-control" type="file" accept=".swu" id="file-upload" name="file" />
|
16
|
-
</div>
|
17
|
-
<div class="col">
|
18
|
-
<input class="btn btn-primary w-100" id="file-upload-submit" type="submit" value="Upload" />
|
19
|
-
</div>
|
20
|
-
<h3>Upload Progress</h3>
|
21
|
-
<div class="col">
|
22
|
-
<div class="progress w-100" style=" height: 40px;" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
|
23
|
-
<div class="progress-bar progress-bar-striped progress-bar-animated" id="upload-progress" style="width: 0%;">0%</div>
|
24
|
-
</div>
|
25
|
-
</div>
|
26
|
-
</div>
|
27
|
-
</form>
|
28
|
-
</div>
|
29
|
-
</div>
|
5
|
+
<div class="col col-12">
|
6
|
+
<button
|
7
|
+
type="button"
|
8
|
+
class="btn btn-lg btn-outline-light w-100"
|
9
|
+
data-bs-toggle="modal"
|
10
|
+
data-bs-target="#upload-modal"
|
11
|
+
>
|
12
|
+
Upload
|
13
|
+
</button>
|
30
14
|
</div>
|
31
15
|
{% endif %}
|
32
|
-
<div class="col
|
33
|
-
<
|
34
|
-
<
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
16
|
+
<div class="col">
|
17
|
+
<table id="firmware-table" class="table table-hover">
|
18
|
+
<thead>
|
19
|
+
<tr>
|
20
|
+
<th>ID</th>
|
21
|
+
<th>Name</th>
|
22
|
+
<th>Version</th>
|
23
|
+
<th>Size</th>
|
24
|
+
</tr>
|
25
|
+
</thead>
|
26
|
+
<tbody id="firmware-list"></tbody>
|
27
|
+
</table>
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
</div>
|
39
31
|
|
40
|
-
|
32
|
+
{% if "firmware.write" in request.user.permissions %}
|
33
|
+
<div class="modal modal-lg fade" id="upload-modal">
|
34
|
+
<div class="modal-dialog modal-dialog-centered modal-xl">
|
35
|
+
<div class="modal-content">
|
36
|
+
<div class="modal-header">
|
37
|
+
<ul
|
38
|
+
class="nav nav-underline nav-justified w-100"
|
39
|
+
role="tablist"
|
40
|
+
>
|
41
|
+
<li class="nav-item">
|
42
|
+
<button
|
43
|
+
class="nav-link active"
|
44
|
+
aria-current="page"
|
45
|
+
id="upload-tab"
|
46
|
+
data-bs-toggle="tab"
|
47
|
+
data-bs-target="#upload-tab-content"
|
48
|
+
type="button"
|
49
|
+
role="tab"
|
50
|
+
>
|
51
|
+
Upload File
|
52
|
+
</button>
|
53
|
+
</li>
|
54
|
+
<li class="nav-item">
|
55
|
+
<button
|
56
|
+
class="nav-link"
|
57
|
+
id="url-tab"
|
58
|
+
data-bs-toggle="tab"
|
59
|
+
data-bs-target="#url-tab-content"
|
60
|
+
type="button"
|
61
|
+
role="tab"
|
62
|
+
>
|
63
|
+
Remote URL
|
64
|
+
</button>
|
65
|
+
</li>
|
66
|
+
</ul>
|
67
|
+
</div>
|
68
|
+
<div class="tab-content">
|
69
|
+
<div class="tab-pane active" id="upload-tab-content">
|
70
|
+
<div class="modal-body">
|
71
|
+
<form id="upload-form">
|
72
|
+
<div class="row g-3 row-cols-1">
|
73
|
+
<div id="upload-alerts"></div>
|
74
|
+
<div class="col">
|
75
|
+
<input
|
76
|
+
class="form-control"
|
77
|
+
type="file"
|
78
|
+
accept=".swu"
|
79
|
+
id="file-upload"
|
80
|
+
name="file"
|
81
|
+
/>
|
82
|
+
</div>
|
83
|
+
<div class="col">
|
84
|
+
<input
|
85
|
+
class="btn btn-outline-light w-100"
|
86
|
+
id="file-upload-submit"
|
87
|
+
type="submit"
|
88
|
+
value="Upload"
|
89
|
+
/>
|
90
|
+
<div
|
91
|
+
class="progress border border-light bg-dark d-none"
|
92
|
+
style="height: 36px"
|
93
|
+
role="progressbar"
|
94
|
+
aria-valuenow="0"
|
95
|
+
aria-valuemin="0"
|
96
|
+
aria-valuemax="100"
|
97
|
+
>
|
98
|
+
<div
|
99
|
+
class="progress-bar progress-bar-striped bg-success progress-bar-animated"
|
100
|
+
id="upload-progress"
|
101
|
+
style="width: 0%"
|
102
|
+
>
|
103
|
+
0%
|
104
|
+
</div>
|
105
|
+
</div>
|
106
|
+
</div>
|
107
|
+
</div>
|
108
|
+
</form>
|
109
|
+
</div>
|
110
|
+
</div>
|
111
|
+
<div class="tab-pane" id="url-tab-content">
|
112
|
+
<div class="modal-body">
|
113
|
+
<form id="url-form">
|
114
|
+
<div class="row g-3 row-cols-1">
|
115
|
+
<div id="url-alerts"></div>
|
116
|
+
<div class="col">
|
117
|
+
<div class="input-group">
|
118
|
+
<span class="input-group-text"
|
119
|
+
>File URL</span
|
120
|
+
>
|
121
|
+
<input
|
122
|
+
class="form-control"
|
123
|
+
type="url"
|
124
|
+
id="file-url"
|
125
|
+
/>
|
126
|
+
</div>
|
127
|
+
</div>
|
128
|
+
<div class="col">
|
129
|
+
<input
|
130
|
+
class="btn btn-outline-light w-100"
|
131
|
+
id="url-submit"
|
132
|
+
type="submit"
|
133
|
+
value="Upload"
|
134
|
+
/>
|
135
|
+
<div
|
136
|
+
class="progress border border-light bg-dark d-none"
|
137
|
+
style="height: 36px"
|
138
|
+
role="progressbar"
|
139
|
+
aria-valuenow="0"
|
140
|
+
aria-valuemin="0"
|
141
|
+
aria-valuemax="100"
|
142
|
+
>
|
143
|
+
<div
|
144
|
+
class="progress-bar progress-bar-striped bg-success progress-bar-animated"
|
145
|
+
id="url-progress"
|
146
|
+
style="width: 0%"
|
147
|
+
>
|
148
|
+
0%
|
149
|
+
</div>
|
150
|
+
</div>
|
151
|
+
</div>
|
152
|
+
</div>
|
153
|
+
</form>
|
154
|
+
</div>
|
41
155
|
</div>
|
42
156
|
</div>
|
43
157
|
</div>
|
44
158
|
</div>
|
45
159
|
</div>
|
160
|
+
{% endif %}
|
161
|
+
<script src="{{ url_for('static', path='js/util.js') }}"></script>
|
46
162
|
<script src="{{ url_for('static', path='js/firmware.js') }}"></script>
|
47
163
|
{% endblock content %}
|
goosebit/ui/templates/index.html
CHANGED
@@ -1,34 +1,20 @@
|
|
1
|
-
{% extends "nav.html" %}
|
2
|
-
{% block content %}
|
1
|
+
{% extends "nav.html" %} {% block content %}
|
3
2
|
<div class="container-fluid">
|
4
3
|
<div class="row p-2 d-flex justify-content-center">
|
5
4
|
<div class="col">
|
6
5
|
<table id="device-table" class="table table-hover">
|
7
6
|
<thead>
|
8
7
|
<tr>
|
9
|
-
<th>
|
10
|
-
|
11
|
-
</th>
|
12
|
-
<th>
|
13
|
-
|
14
|
-
</th>
|
15
|
-
<th>
|
16
|
-
UUID
|
17
|
-
</th>
|
18
|
-
<th>
|
19
|
-
Firmware
|
20
|
-
</th>
|
21
|
-
<th>
|
22
|
-
Last IP
|
23
|
-
</th>
|
24
|
-
<th>
|
25
|
-
Last Seen
|
26
|
-
</th>
|
8
|
+
<th>Name</th>
|
9
|
+
<th>Up</th>
|
10
|
+
<th>UUID</th>
|
11
|
+
<th>Firmware</th>
|
12
|
+
<th>Progress</th>
|
13
|
+
<th>Last IP</th>
|
14
|
+
<th>Last Seen</th>
|
27
15
|
</tr>
|
28
16
|
</thead>
|
29
|
-
<tbody id="devices-list">
|
30
|
-
|
31
|
-
</tbody>
|
17
|
+
<tbody id="devices-list"></tbody>
|
32
18
|
</table>
|
33
19
|
</div>
|
34
20
|
</div>
|