Files
OTGateway/src_data/pages/network.html
T
Elvin Luff f9a814ca11 feat: Implement W5500 SPI ethernet support (#286)
* feat: implement feature gate & w5500 SPI ethernet

* feat: eth conn restart handling, wifi-ap connection type, BLE sensor
gate

* fix: bring eth down when disabled

* fix: disable WiFi scan when ethernet is active

* feat: refactor uplink types into separate classes

* fix: compare enum, not string

* chore: remove dead code

* fix: remove out of scope persistNow(), move w5500 to networksettings,
add base for future ethernet implementations

* fix: keep wifi armed while reconnecting, reset IP when moving from
static to DHCP, refactor wifi loop to use return

* fix: clear out connectionType string for enum
2026-11-11 08:31:36 +03:00

337 lines
13 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title data-i18n>network.title</title>
<link rel="stylesheet" href="/static/app.css?{BUILD_TIME}" />
</head>
<body>
<header class="container">
<nav>
<ul>
<li>
<a href="/">
<div class="logo" data-i18n>logo</div>
</a>
</li>
</ul>
<ul>
<li>
<select id="lang" aria-label="Lang">
<option value="en" selected>EN</option>
<option value="cn">CN</option>
<option value="it">IT</option>
<option value="nl">NL</option>
<option value="ru">RU</option>
</select>
</li>
</ul>
</nav>
</header>
<main class="container">
<article>
<div>
<hgroup>
<h2 data-i18n>network.name</h2>
<p></p>
</hgroup>
<div id="network-settings-busy" aria-busy="true"></div>
<form action="/api/network/settings" id="network-settings" class="hidden">
<label>
<span data-i18n>network.params.hostname</span>
<input type="text" name="hostname" maxlength="24" pattern="[A-Za-z0-9]+[A-Za-z0-9\-]+[A-Za-z0-9]+" required>
</label>
<label>
<input type="checkbox" name="useDhcp" value="true">
<span data-i18n>network.params.dhcp</span>
</label>
<br />
<hr />
<h4 data-i18n>network.section.static</h4>
<label>
<span data-i18n>network.params.ip</span>
<input type="text" name="staticConfig[ip]" value="true" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
</label>
<label>
<span data-i18n>network.params.gateway</span>
<input type="text" name="staticConfig[gateway]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
</label>
<label>
<span data-i18n>network.params.subnet</span>
<input type="text" name="staticConfig[subnet]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
</label>
<label>
<span data-i18n>network.params.dns</span>
<input type="text" name="staticConfig[dns]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
</label>
<button type="submit" data-i18n>button.save</button>
</form>
</div>
</article>
<article>
<div>
<hgroup>
<h3 data-i18n>network.section.availableNetworks</h3>
<p></p>
</hgroup>
<form action="/api/network/scan" id="network-scan">
<div style="max-height: 25rem;" class="overflow-auto">
<table id="networks" role="grid">
<thead>
<tr>
<th scope="col" data-i18n>network.scan.pos</th>
<th scope="col" data-i18n>network.wifi.ssid</th>
<th scope="col" data-i18n>network.scan.info</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<p id="scan-unavailable" class="hidden" data-i18n>network.scan.unavailable</p>
<button type="submit" data-i18n>button.refresh</button>
</form>
<hr />
<div>
<hgroup>
<h2 data-i18n>network.section.staSettings</h2>
<p></p>
</hgroup>
<div id="sta-settings-busy" aria-busy="true"></div>
<form action="/api/network/settings" id="sta-settings" class="hidden">
<label>
<span data-i18n>network.wifi.ssid</span>
<input type="text" name="sta[ssid]" maxlength="32" required>
</label>
<label>
<span data-i18n>network.wifi.password</span>
<input type="password" name="sta[password]" maxlength="64" required>
</label>
<label>
<span data-i18n>network.wifi.channel</span>
<input type="number" inputmode="numeric" name="sta[channel]" min="0" max="12" step="1" required>
<small data-i18n>network.sta.channel.note</small>
</label>
<button type="submit" data-i18n>button.save</button>
</form>
</div>
</div>
</article>
<article>
<div>
<hgroup>
<h2 data-i18n>network.section.apSettings</h2>
<p></p>
</hgroup>
<div id="ap-settings-busy" aria-busy="true"></div>
<form action="/api/network/settings" id="ap-settings" class="hidden">
<label>
<span data-i18n>network.wifi.ssid</span>
<input type="text" name="ap[ssid]" maxlength="32" required>
</label>
<label>
<span data-i18n>network.wifi.password</span>
<input type="text" name="ap[password]" maxlength="64" required>
</label>
<label>
<span data-i18n>network.wifi.channel</span>
<input type="number" inputmode="numeric" name="ap[channel]" min="1" max="12" step="1" required>
</label>
<button type="submit" data-i18n>button.save</button>
</form>
</div>
</article>
<article id="ethernet-section" class="hidden">
<div>
<hgroup>
<h3 data-i18n>network.section.ethernet</h3>
<p></p>
</hgroup>
<div id="ethernet-settings-busy" aria-busy="true"></div>
<form action="/api/network/settings" id="ethernet-settings" class="hidden">
<label>
<input type="checkbox" name="ethernet[enabled]" value="true">
<span data-i18n>network.ethernet.enable</span>
</label>
<small id="ethernet-not-configured" class="hidden" data-i18n>network.ethernet.notConfigured</small>
<div class="grid">
<label>
<span data-i18n>network.ethernet.clk</span>
<input type="number" inputmode="numeric" name="ethernet[pins][clk]" min="0" max="254" step="1">
</label>
<label>
<span data-i18n>network.ethernet.mosi</span>
<input type="number" inputmode="numeric" name="ethernet[pins][mosi]" min="0" max="254" step="1">
</label>
<label>
<span data-i18n>network.ethernet.miso</span>
<input type="number" inputmode="numeric" name="ethernet[pins][miso]" min="0" max="254" step="1">
</label>
</div>
<div class="grid">
<label>
<span data-i18n>network.ethernet.cs</span>
<input type="number" inputmode="numeric" name="ethernet[pins][cs]" min="0" max="254" step="1">
</label>
<label>
<span data-i18n>network.ethernet.irq</span>
<input type="number" inputmode="numeric" name="ethernet[pins][irq]" min="0" max="254" step="1">
</label>
<label>
<span data-i18n>network.ethernet.rst</span>
<input type="number" inputmode="numeric" name="ethernet[pins][rst]" min="0" max="254" step="1">
</label>
</div>
<small data-i18n>network.ethernet.note</small>
<button type="submit" data-i18n>button.save</button>
</form>
</div>
</article>
</main>
<footer class="container">
<small>
<b>Made by Laxilef</b>
• <a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary" data-i18n>nav.license</a>
• <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary" data-i18n>nav.source</a>
• <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary" data-i18n>nav.help</a>
• <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary" data-i18n>nav.issues</a>
• <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary" data-i18n>nav.releases</a>
</small>
</footer>
<script src="/static/app.js?{BUILD_TIME}"></script>
<script>
document.addEventListener('DOMContentLoaded', async () => {
const lang = new Lang(document.getElementById('lang'));
await lang.build();
const fillData = (data) => {
setInputValue("[name='hostname']", data.hostname);
setCheckboxValue("[name='useDhcp']", data.useDhcp);
setInputValue("[name='staticConfig[ip]']", data.staticConfig.ip);
setInputValue("[name='staticConfig[gateway]']", data.staticConfig.gateway);
setInputValue("[name='staticConfig[subnet]']", data.staticConfig.subnet);
setInputValue("[name='staticConfig[dns]']", data.staticConfig.dns);
setBusy('#network-settings-busy', '#network-settings', false);
const ethernet = data.ethernet || {};
const ethernetConfigured = !!ethernet.configured;
const ethernetPins = ethernet.pins || {};
const pinValue = (value) => (value ?? 255) < 255 ? value : '';
setInputValue("[name='ethernet[pins][clk]']", pinValue(ethernetPins.clk));
setInputValue("[name='ethernet[pins][mosi]']", pinValue(ethernetPins.mosi));
setInputValue("[name='ethernet[pins][miso]']", pinValue(ethernetPins.miso));
setInputValue("[name='ethernet[pins][cs]']", pinValue(ethernetPins.cs));
setInputValue("[name='ethernet[pins][irq]']", pinValue(ethernetPins.irq));
setInputValue("[name='ethernet[pins][rst]']", pinValue(ethernetPins.rst));
setCheckboxValue("[name='ethernet[enabled]']", ethernetConfigured ? !!ethernet.enabled : false);
const ethernetCheckbox = document.querySelector("[name='ethernet[enabled]']");
if (ethernetCheckbox) {
ethernetCheckbox.disabled = !ethernetConfigured;
}
document.getElementById('ethernet-not-configured')?.classList.toggle('hidden', ethernetConfigured);
setBusy('#ethernet-settings-busy', '#ethernet-settings', false);
setInputValue("[name='sta[ssid]']", data.sta.ssid);
setInputValue("[name='sta[password]']", data.sta.password);
setInputValue("[name='sta[channel]']", data.sta.channel);
setBusy('#sta-settings-busy', '#sta-settings', false);
setInputValue("[name='ap[ssid]']", data.ap.ssid);
setInputValue("[name='ap[password]']", data.ap.password);
setInputValue("[name='ap[channel]']", data.ap.channel);
setBusy('#ap-settings-busy', '#ap-settings', false);
};
const applyCapabilities = (info) => {
// pages and firmware ship together: the section stays hidden unless the build says it can do ethernet
const supported = info?.features?.ethernet === true;
document.getElementById('ethernet-section')?.classList.toggle('hidden', !supported);
return supported;
};
try {
const [response, infoResponse] = await Promise.all([
fetch("/api/network/settings", {
cache: "no-cache",
credentials: "include"
}),
fetch("/api/info", {
cache: "no-cache",
credentials: "include"
})
]);
if (!response.ok) {
throw new Error('Response not valid');
}
const info = infoResponse.ok ? await infoResponse.json() : null;
const ethernetSupported = applyCapabilities(info);
const result = await response.json();
fillData(result);
setupForm('#network-settings', fillData, ['hostname']);
// scanning needs the radio, which stays parked while ethernet carries the uplink
const radioParked = info?.network?.connectionType === 'ethernet';
setupNetworkScanForm('#network-scan', '#networks', !radioParked);
document.querySelector('#network-scan button[type="submit"]').disabled = radioParked;
document.querySelector('#scan-unavailable').classList.toggle('hidden', !radioParked);
if (ethernetSupported) {
setupForm('#ethernet-settings', fillData);
}
setupForm('#sta-settings', fillData, ['sta.ssid', 'sta.password']);
setupForm('#ap-settings', fillData, ['ap.ssid', 'ap.password']);
} catch (error) {
console.log(error);
}
});
</script>
</body>
</html>