Skip to content

Commit 455cda2

Browse files
Janni2006Jan-Matthis
andauthored
Minor Bug fixes and multiple feature updates (#29)
* The time remaining until the next recording is parsed accordingly and displayed at the bottom. It also works if no recording is scheduled in the next 24 hours. Not tested for the 'capturing' case. Need to parse the yml file to get the agent id and the username/password * The calendar is updated every 60 seconds, the view is updated every second * Created an interface for the planned events * index.js now uses the calendar interface created by the main.go * displayed diff does not get negative * removed some unused lines * Removed a bug for an empty calendar * Removed some comments; Remaining time shows just minutes and seconds if less than one hour is remaining * Set the additional information field to the top of the screen * removed an old comment * Fixed the bug mentioned by Lars and followed up on most of the other comments * changed the endpoint to lowercase * Update Go module and add Prometheus metrics endpoint * Add metrics configuration to configuration file and add stateCollector * Added error handling for JSON decoding and improved logging; updated configuration file for Prometheus metrics * fix timestamp reporting 123 * Update golang.yml * Only use newest * Possibly disabled touch action * No longer use regular expression to parse JSON * Update Readme * Do not trust any proxies * Add network status endpoint and configurable timeout * Show no network connection * Add network status popup and update network information display * Add hostname display to network status popup and update styles * Remove duplicated Prometheus metrics setup --------- Co-authored-by: Jan-Matthis <[email protected]>
1 parent e11e520 commit 455cda2

File tree

6 files changed

+529
-54
lines changed

6 files changed

+529
-54
lines changed

README.md

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ Software backend for displays showing the current state of Opencast capture agen
1111
- Touchscreen: https://www.waveshare.com/7inch-hdmi-lcd-c.htm
1212
- IO Board: https://www.waveshare.com/compute-module-4-poe-board-b.htm
1313
- Miscellaneuos:
14-
- HDMI Cable: https://www.amazon.de/dp/B07R6CWPH1?th=1
15-
- USB Cable: https://amazon.de/dp/B095LS6S2Y
16-
- Ethernet Cable: https://www.amazon.de/ACT-Netzwerkkabel-gewinkelt-Flexibles-Datenzentren-Schwarz/dp/B0CYHG3HV2?th=1 (Alternatively, any other 90° right angled cable can be used)
14+
- HDMI Cable: https://www.amazon.de/dp/B07R6CWPH1?th=1
15+
- USB Cable: https://amazon.de/dp/B095LS6S2Y
16+
- Ethernet Cable: https://www.amazon.de/ACT-Netzwerkkabel-gewinkelt-Flexibles-Datenzentren-Schwarz/dp/B0CYHG3HV2?th=1 (Alternatively, any other 90° right angled cable can be used)
1717

1818
### Instuctions
1919

@@ -23,14 +23,14 @@ Software backend for displays showing the current state of Opencast capture agen
2323
4. [Run `rpiboot`](https://github.com/raspberrypi/usbboot) to mount the CM4 file system
2424
5. Start [Raspberry Pi Imager](https://www.raspberrypi.com/software/):
2525
![Raspberry Pi Imager OS](https://github.com/user-attachments/assets/d4ad6f07-90db-46b6-97eb-44303d7e6500)
26-
- Device: Raspberry Pi 4
27-
- OS: Raspberry Pi OS Lite (64 bit)
28-
- Target: Select the CM4 Module Filesystem
29-
- Additional settings:
30-
![Raspberry Pi settings](https://github.com/user-attachments/assets/f8f4c1f4-eea0-480f-ad4e-1bc2baea252d)
31-
- Enable SSH public key
32-
- Set default SSH key
33-
- Disable telemetry
26+
- Device: Raspberry Pi 4
27+
- OS: Raspberry Pi OS Lite (64 bit)
28+
- Target: Select the CM4 Module Filesystem
29+
- Additional settings:
30+
![Raspberry Pi settings](https://github.com/user-attachments/assets/f8f4c1f4-eea0-480f-ad4e-1bc2baea252d)
31+
- Enable SSH public key
32+
- Set default SSH key
33+
- Disable telemetry
3434
6. Write image to CM
3535
7. Set `boot` to `off`
3636
8. Boot Raspberry Pi
@@ -70,6 +70,14 @@ https://github.com/virtUOS/opencast-ca-display/assets/1008395/ead22cd2-9d7a-4d26
7070
- The laptop is running an Opencast capture agent
7171
- When the laptop starts capturing video, the display shows an active recording
7272

73+
## Ports
74+
75+
By default this application listens on two Ports.
76+
| Port | Usage |
77+
| --- | --- |
78+
|:8080 | Hosting of the website shown by the display|
79+
|:9100 | Metrics endpoint |
80+
7381
## Opencast User
7482

7583
To improve security, you can limit the access rights for the Opencast user by
@@ -86,7 +94,7 @@ To do this, first create a new security rule in your Opencast's
8694
<sec:intercept-url pattern="/capture-admin/**" access="ROLE_ADMIN, ROLE_CAPTURE_AGENT" />
8795
```
8896

89-
Next, go to the Opencast REST Docs → `/user-utils` and fill out the form for
97+
Next, go to the Opencast REST Docs → `/user-utils` and fill out the form for
9098
`POST /` with data like this:
9199

92100
- username: `ca-display`

assets/index.html

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,30 @@
11
<!DOCTYPE html>
22
<html lang=en>
33
<head>
4-
<link rel=stylesheet type=text/css href=assets/style.css />
5-
<script src=assets/index.js></script>
4+
<link href=assets/style.css rel=stylesheet type=text/css />
5+
<script src=assets/index.js></script>
66
</head>
77
<body>
88

9-
<img id=logo />
10-
<div id=text></div>
9+
<img id=logo />
10+
<div id=text></div>
11+
<div id=info></div>
1112

13+
<div class="network-button" id="network-button"></div>
14+
15+
<div class="network-popup" id="network-popup">
16+
<div class="network-popup-content">
17+
<div>Netzwerk-Informationen</div>
18+
<div id="network-status">
19+
<div>Verbindungsstatus: <span id="connection-status">Wird geladen...</span></div>
20+
<div class="hostname-container">Hostname: <span id="hostname">-</span></div>
21+
</div>
22+
<div id="network-interfaces">
23+
<h4>Netzwerkschnittstellen</h4>
24+
<div id="interfaces-list">Wird geladen...</div>
25+
</div>
26+
<button id="close-popup">Schließen</button>
27+
</div>
28+
</div>
1229
</body>
13-
</html>
30+
</html>

assets/index.js

Lines changed: 204 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,223 @@
11
var config = null;
2+
var is_active = null;
3+
var calendar = [];
4+
var network_status = {
5+
interfaces: [],
6+
connected: false,
7+
hostename: ""
8+
};
29

310
/**
411
* Load configuration and initialize timer
512
*/
6-
fetch('/config')
7-
.then(response => response.json())
8-
.then(cfg => {
9-
config = cfg;
10-
console.info('config', config)
11-
setInterval(updateTimer, 2000);
12-
})
13+
fetch("/config")
14+
.then((response) => response.json())
15+
.then((cfg) => {
16+
config = cfg;
17+
console.info("config", config);
18+
updateNetworkStatus();
19+
setInterval(updateTimer, 1000);
20+
setInterval(updateNetworkStatus, 1000);
21+
updateCalendar();
22+
setInterval(updateCalendar, 60000);
23+
});
1324

1425
/**
1526
* Update the view
1627
*/
1728
function updateView(active) {
18-
// Update text
19-
document.getElementById('text').innerText = active.text;
29+
// Update text
30+
document.getElementById("text").innerText = active.text;
2031

21-
// Update colors
22-
const body = document.getElementsByTagName('body')[0];
23-
body.style.backgroundColor = active.background;
24-
body.style.color = active.color;
32+
// Update colors
33+
const body = document.getElementsByTagName("body")[0];
34+
body.style.backgroundColor = active.background;
35+
body.style.color = active.color;
2536

26-
// Update logo
27-
document.getElementById('logo').src = active.image.replace(/\s/g, '');
37+
// Update logo
38+
document.getElementById("logo").src = active.image.replace(/\s/g, "");
39+
if (network_status.connected) {
40+
document.getElementById("info").innerText = parseCalendar(active);
41+
}
2842
}
2943

3044
/**
3145
* Check for capture agent status
3246
*/
3347
function updateTimer() {
34-
fetch('/status')
35-
.then(response => {
36-
if (!response.ok) {
37-
const active = config.unknown;
38-
active.text = response.statusText;
39-
updateView(active);
40-
throw Error(response.statusText);
41-
}
42-
return response.json()
43-
}).then(capturing => {
44-
console.debug('capturing', capturing)
45-
updateView(capturing ? config.capturing : config.idle);
46-
})
48+
if (network_status.connected == true) {
49+
fetch("/status")
50+
.then((response) => {
51+
if (!response.ok) {
52+
const active = config.unknown;
53+
active.text = response.statusText;
54+
updateView(active);
55+
throw Error(response.statusText);
56+
}
57+
return response.json();
58+
})
59+
.then((capturing) => {
60+
console.debug("capturing", capturing);
61+
is_active = capturing;
62+
updateView(capturing ? config.capturing : config.idle);
63+
});
64+
} else {
65+
console.log("Network status is not connected");
66+
const active = config.unknown;
67+
active.text = "No network connection";
68+
updateView(active);
69+
}
4770
}
71+
72+
function parseCalendar(active) {
73+
console.debug("Is Active? ", is_active);
74+
if (calendar.length == 0) {
75+
console.debug("Calendar is empty");
76+
if (!is_active) {
77+
return active.none;
78+
}
79+
}
80+
let time_remaining = 0;
81+
82+
const event_time = is_active ? calendar[0].end : calendar[0].start;
83+
84+
now = Date.now();
85+
// TODO Maybe switch 'is_active' to 'capturing' here?
86+
//t = is_active ? calendar[0].End : calendar[0].Start;
87+
time_remaining = event_time - now;
88+
console.debug("Time Remaining: ", time_remaining, event_time, now);
89+
90+
hours =
91+
time_remaining > 0 ? Math.floor(time_remaining / (1000 * 60 * 60)) : 0;
92+
minutes =
93+
time_remaining > 0 ? Math.floor((time_remaining / (1000 * 60)) % 60) : 0;
94+
seconds = time_remaining > 0 ? Math.floor((time_remaining / 1000) % 60) : 0;
95+
96+
hours = hours < 10 ? "0" + hours : hours;
97+
minutes = minutes < 10 ? "0" + minutes : minutes;
98+
seconds = seconds < 10 ? "0" + seconds : seconds;
99+
100+
time_remaining = `${hours}:${minutes}:${seconds}`;
101+
console.debug("Compare ", is_active, now, event_time);
102+
return is_active && now < calendar[0].Start
103+
? ""
104+
: active.info + " " + time_remaining;
105+
}
106+
107+
function updateCalendar() {
108+
if (network_status.connected) {
109+
fetch("/calendar")
110+
.then((response) => {
111+
console.debug("Calendar fetched; Status ", response.status);
112+
return response.json();
113+
})
114+
.then((json) => {
115+
console.log("Calendar ", json);
116+
calendar = json;
117+
});
118+
}
119+
}
120+
121+
function updateNetworkStatus() {
122+
fetch("/network_info")
123+
.then((response) => {
124+
return response.json();
125+
})
126+
.then((json) => {
127+
network_status = json;
128+
if (!network_status.connected) {
129+
console.log("No network");
130+
const active = config.unknown;
131+
active.text = "No network connection";
132+
updateView(active);
133+
}
134+
});
135+
}
136+
137+
document.addEventListener('DOMContentLoaded', function () {
138+
// Netzwerk-Button und Popup initialisieren
139+
const networkButton = document.getElementById('network-button');
140+
const networkPopup = document.getElementById('network-popup');
141+
const closePopup = document.getElementById('close-popup');
142+
143+
// Event-Listener für den versteckten Button
144+
networkButton.addEventListener('click', function () {
145+
fetchNetworkInfo();
146+
networkPopup.style.display = 'block';
147+
});
148+
149+
// Event-Listener für den Schließen-Button
150+
closePopup.addEventListener('click', function () {
151+
networkPopup.style.display = 'none';
152+
});
153+
154+
// Popup schließen, wenn außerhalb geklickt wird
155+
window.addEventListener('click', function (event) {
156+
if (event.target === networkPopup) {
157+
networkPopup.style.display = 'none';
158+
}
159+
});
160+
161+
// Netzwerkinformationen abrufen
162+
function fetchNetworkInfo() {
163+
fetch('/network_info')
164+
.then(response => response.json())
165+
.then(data => {
166+
updateNetworkInfo(data);
167+
})
168+
.catch(error => {
169+
console.error('Fehler beim Abrufen der Netzwerkinformationen:', error);
170+
document.getElementById('interfaces-list').innerHTML = 'Fehler beim Laden der Netzwerkinformationen.';
171+
});
172+
}
173+
174+
// Netzwerkinformationen aktualisieren
175+
function updateNetworkInfo(data) {
176+
// Verbindungsstatus anzeigen
177+
const connectionStatus = document.getElementById('connection-status');
178+
connectionStatus.textContent = data.connected ? 'Verbunden' : 'Nicht verbunden';
179+
connectionStatus.className = data.connected ? 'connected' : 'disconnected';
180+
181+
// Hostname anzeigen
182+
const hostnameElement = document.getElementById('hostname');
183+
if (data.hostname) {
184+
hostnameElement.textContent = data.hostname;
185+
hostnameElement.parentElement.style.display = 'block';
186+
} else {
187+
hostnameElement.parentElement.style.display = 'none';
188+
}
189+
190+
// Netzwerkschnittstellen anzeigen
191+
const interfacesList = document.getElementById('interfaces-list');
192+
interfacesList.innerHTML = '';
193+
194+
if (data.interfaces && data.interfaces.length > 0) {
195+
data.interfaces.forEach(net_interface => {
196+
const interfaceDiv = document.createElement('div');
197+
console.log(net_interface);
198+
interfaceDiv.className = 'interface-item';
199+
200+
let interfaceHtml = `<div class="interface-name">${net_interface.name}</div>`;
201+
202+
if (net_interface.mac && net_interface.mac !== "") {
203+
interfaceHtml += `<div>MAC: ${net_interface.mac}</div>`;
204+
}
205+
206+
if (net_interface.addr && net_interface.addr.length > 0) {
207+
console.log(net_interface.addr);
208+
console.log(net_interface.addr.join(', '));
209+
interfaceHtml += `<div>IP-Adressen: ${net_interface.addr.join(', ')}</div>`;
210+
}
211+
212+
if (net_interface.flags) {
213+
interfaceHtml += `<div>Flags: ${net_interface.flags}</div>`;
214+
}
215+
216+
interfaceDiv.innerHTML = interfaceHtml;
217+
interfacesList.appendChild(interfaceDiv);
218+
});
219+
} else {
220+
interfacesList.innerHTML = 'Keine Netzwerkschnittstellen gefunden.';
221+
}
222+
}
223+
});

0 commit comments

Comments
 (0)