-
-
Notifications
You must be signed in to change notification settings - Fork 260
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Resolves #1814. Stacked on #1828. This PR adds a new “Status” dialog to the “Network” submenu, which displays basic network connectivity information in (almost) realtime. The screen recording below shows two browser windows side by side, and how the Network Status dialog updates itself automatically when there are changes. https://github.com/user-attachments/assets/b11081cc-364a-4816-a8c2-474750d8def4 Notes: - I’ve set the update frequency to `2,5s`, which seemed like a reasonable compromise between “realtime enough” and not overloading the backend on lower-latency connections. - For the case when the IP address or MAC address are absent, I’ve debated whether to show `n/a` or whether to hide the row altogether. I’m not strongly attached to the `n/a` placeholder, but to me it felt better to show the row consistently, instead of having the UI jump around as fields are inserted and removed. <a data-ca-tag href="https://codeapprove.com/pr/tiny-pilot/tinypilot/1829"><img src="https://codeapprove.com/external/github-tag-allbg.png" alt="Review on CodeApprove" /></a> --------- Co-authored-by: Jan Heuermann <[email protected]>
- Loading branch information
1 parent
3e4df95
commit 3d33c4d
Showing
5 changed files
with
286 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
143 changes: 143 additions & 0 deletions
143
app/templates/custom-elements/network-status-dialog.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
<template id="network-status-dialog-template"> | ||
<style> | ||
@import "css/style.css"; | ||
@import "css/button.css"; | ||
|
||
#initializing, | ||
#display { | ||
display: none; | ||
} | ||
|
||
:host([state="initializing"]) #initializing, | ||
:host([state="display"]) #display { | ||
display: block; | ||
} | ||
|
||
.info-container { | ||
margin-bottom: 1em; | ||
} | ||
</style> | ||
|
||
<div id="initializing"> | ||
<h3>Determining Network Status</h3> | ||
<div> | ||
<progress-spinner></progress-spinner> | ||
</div> | ||
</div> | ||
|
||
<div id="display"> | ||
<h3>Network Status</h3> | ||
<div class="info-container"> | ||
<network-status-interface id="status-ethernet"></network-status-interface> | ||
<network-status-interface id="status-wifi"></network-status-interface> | ||
</div> | ||
<div class="button-container"> | ||
<button id="close-button" type="button">Close</button> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<script type="module"> | ||
import { | ||
DialogClosedEvent, | ||
DialogCloseStateChangedEvent, | ||
DialogFailedEvent, | ||
} from "/js/events.js"; | ||
import { getNetworkStatus } from "/js/controllers.js"; | ||
|
||
(function () { | ||
const template = document.querySelector("#network-status-dialog-template"); | ||
|
||
customElements.define( | ||
"network-status-dialog", | ||
class extends HTMLElement { | ||
_states = { | ||
INITIALIZING: "initializing", | ||
DISPLAY: "display", | ||
}; | ||
_statesWithoutDialogClose = new Set([this._states.INITIALIZING]); | ||
|
||
connectedCallback() { | ||
this.attachShadow({ mode: "open" }).appendChild( | ||
template.content.cloneNode(true) | ||
); | ||
this._elements = { | ||
statusEthernet: this.shadowRoot.querySelector("#status-ethernet"), | ||
statusWifi: this.shadowRoot.querySelector("#status-wifi"), | ||
}; | ||
this._shouldAutoUpdate = false; | ||
this._updateTicker = null; | ||
this.shadowRoot | ||
.querySelector("#close-button") | ||
.addEventListener("click", () => { | ||
this.dispatchEvent(new DialogClosedEvent()); | ||
}); | ||
|
||
// For all events that terminate the dialog, make sure to stop the | ||
// update ticker, otherwise the status requests would continue to be | ||
// fired even when the dialog is not visible anymore. | ||
["dialog-closed", "dialog-failed"].forEach((evtName) => { | ||
this.addEventListener(evtName, () => { | ||
this._shouldAutoUpdate = false; | ||
clearTimeout(this._updateTicker); | ||
}); | ||
}); | ||
} | ||
|
||
get _state() { | ||
return this.getAttribute("state"); | ||
} | ||
|
||
set _state(newValue) { | ||
this.setAttribute("state", newValue); | ||
this.dispatchEvent( | ||
new DialogCloseStateChangedEvent( | ||
!this._statesWithoutDialogClose.has(newValue) | ||
) | ||
); | ||
} | ||
|
||
async initialize() { | ||
this._state = this._states.INITIALIZING; | ||
await this._update(); | ||
this._state = this._states.DISPLAY; | ||
this._shouldAutoUpdate = true; | ||
this._startUpdateLoop(); | ||
} | ||
|
||
_startUpdateLoop() { | ||
// The update loop is based on `setTimeout`, not `setInterval`, | ||
// because the latter would continue to trigger even if the | ||
// update function lags and happens to be slower than the interval. | ||
// That would result in a lot of parallel, pending requests. | ||
this._updateTicker = setTimeout(async () => { | ||
await this._update(); | ||
if (this._shouldAutoUpdate) { | ||
this._startUpdateLoop(); | ||
} | ||
}, 2500); | ||
} | ||
|
||
async _update() { | ||
let networkStatus; | ||
try { | ||
networkStatus = await getNetworkStatus(); | ||
} catch (error) { | ||
this.dispatchEvent( | ||
new DialogFailedEvent({ | ||
title: "Failed to Determine Network Status", | ||
details: error, | ||
}) | ||
); | ||
return; | ||
} | ||
this._elements.statusEthernet.update( | ||
"Ethernet", | ||
networkStatus.ethernet | ||
); | ||
this._elements.statusWifi.update("Wi-Fi", networkStatus.wifi); | ||
} | ||
} | ||
); | ||
})(); | ||
</script> |
124 changes: 124 additions & 0 deletions
124
app/templates/custom-elements/network-status-interface.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
<template id="network-status-interface-template"> | ||
<style> | ||
@import "css/style.css"; | ||
|
||
:host { | ||
display: flex; | ||
margin-bottom: 0.75em; | ||
} | ||
|
||
#name { | ||
width: 39%; | ||
text-align: right; | ||
font-weight: bold; | ||
} | ||
|
||
#data { | ||
display: flex; | ||
flex-direction: column; | ||
flex: 1; | ||
align-items: start; | ||
margin-left: 1em; | ||
} | ||
|
||
#data div { | ||
margin-bottom: 0.3em; | ||
} | ||
|
||
.connection-indicator { | ||
display: flex; | ||
align-items: center; | ||
} | ||
|
||
.status-dot { | ||
vertical-align: middle; | ||
height: 0.9em; | ||
width: 0.9em; | ||
margin-right: 0.5rem; | ||
border-radius: 50%; | ||
display: inline-block; | ||
} | ||
|
||
:host([is-connected=""]) .status-dot { | ||
background-color: var(--brand-green-bright); | ||
} | ||
|
||
:host(:not([is-connected])) .status-dot { | ||
background-color: var(--brand-red-bright); | ||
} | ||
|
||
.label-connected, | ||
.label-disconnected { | ||
display: none; | ||
} | ||
|
||
:host([is-connected=""]) .label-connected, | ||
:host(:not([is-connected])) .label-disconnected { | ||
display: inline; | ||
} | ||
|
||
#ip-address, | ||
#mac-address { | ||
margin-left: 0.3em; | ||
} | ||
</style> | ||
|
||
<div id="name"><!-- Filled programmatically --></div> | ||
<div id="data"> | ||
<div class="connection-indicator"> | ||
<span class="status-dot status-dot-connected"></span> | ||
<span class="label-connected">Connected</span> | ||
<span class="label-disconnected">Disconnected</span> | ||
</div> | ||
<div> | ||
IP Address: | ||
<span id="ip-address" class="monospace" | ||
><!-- Filled programmatically --></span | ||
> | ||
</div> | ||
<div> | ||
MAC Address: | ||
<span id="mac-address" class="monospace" | ||
><!-- Filled programmatically --></span | ||
> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<script type="module"> | ||
(function () { | ||
const template = document.querySelector( | ||
"#network-status-interface-template" | ||
); | ||
|
||
customElements.define( | ||
"network-status-interface", | ||
class extends HTMLElement { | ||
connectedCallback() { | ||
this.attachShadow({ mode: "open" }).appendChild( | ||
template.content.cloneNode(true) | ||
); | ||
this._elements = { | ||
name: this.shadowRoot.querySelector("#name"), | ||
ipAddress: this.shadowRoot.querySelector("#ip-address"), | ||
macAddress: this.shadowRoot.querySelector("#mac-address"), | ||
}; | ||
} | ||
|
||
/** | ||
* @param {string} name - The display name of the interface | ||
* @param {Object} status | ||
* @param {boolean} status.isConnected | ||
* @param {string} [status.ipAddress] | ||
* @param {string} [status.macAddress] | ||
*/ | ||
update(name, status) { | ||
this._elements.name.innerText = name; | ||
this.toggleAttribute("is-connected", status.isConnected); | ||
this._elements.ipAddress.innerText = status.ipAddress || "n/a"; | ||
this._elements.macAddress.innerText = status.macAddress || "n/a"; | ||
} | ||
} | ||
); | ||
})(); | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters