Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/757-setup-driver-not-listed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ftw": patch
---

The setup wizard's driver picker no longer dead-ends when a device has no catalog entry. A "My device is not listed…" option explains that more drivers can be installed from the signed repository after setup (Settings → Devices), links to requesting a driver that doesn't exist yet, and lets onboarding continue without the device.
12 changes: 12 additions & 0 deletions web/setup.html
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,18 @@ <h2>Pick driver</h2>

<div id="driver-description" class="driver-desc" style="display:none"></div>

<div id="driver-not-listed" class="driver-desc" style="display:none">
<p>This list is what's installed on this gateway. More drivers can be
installed from the signed driver repository after setup, in
<strong>Settings &rarr; Devices</strong>.</p>
<p style="margin-top:8px">No driver for your device anywhere? Ask for one in the
<a href="https://github.com/srcfl/device-drivers/issues" target="_blank" rel="noopener">driver
repository's issue tracker</a> &mdash; include the device make, model
and how it talks (Modbus, MQTT, HTTP&hellip;).</p>
<button class="btn-primary" style="margin-top:12px" onclick="skipUnlistedDevice()">Continue
without this device</button>
</div>

<div class="wizard-actions">
<button class="btn-secondary" onclick="goStep(3)">Back</button>
<button class="btn-primary" id="driver-next-btn" onclick="goStep(5)" disabled>Continue</button>
Expand Down
32 changes: 32 additions & 0 deletions web/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
var selectedCatalog = null; // CatalogEntry from /api/drivers/catalog
var driverCatalog = []; // full catalog cache

// Picker option for a device with no catalog entry. Deliberately not a
// number so it can never be mistaken for a catalog index.
var NOT_LISTED = '__not_listed__';

// --- Step navigation ---

function renderDots() {
Expand Down Expand Up @@ -51,6 +55,13 @@
goStep(configuredDrivers.length > 0 ? 6 : 2);
};

// Forward path for a device the catalog cannot serve: the devices
// summary when something is already configured, otherwise straight on
// to the integrations.
window.skipUnlistedDevice = function () {
goStep(configuredDrivers.length > 0 ? 6 : 7);
};

// --- Step 3: Scan ---

window.startScan = function () {
Expand Down Expand Up @@ -202,6 +213,14 @@
sel.appendChild(opt);
});

// The escape hatch. Without it, a device with no catalog entry
// dead-ends this step: Continue stays disabled and the only
// affordance left is Back.
var notListed = document.createElement('option');
notListed.value = NOT_LISTED;
notListed.textContent = 'My device is not listed…';
sel.appendChild(notListed);

// A positive fingerprint preselects the matching catalog driver while
// still sending the operator through the normal configuration form.
if (selectedDevice && selectedDevice.matchedFilename) {
Expand All @@ -222,13 +241,26 @@
var sel = document.getElementById('driver-select');
var btn = document.getElementById('driver-next-btn');
var descEl = document.getElementById('driver-description');
var notListedEl = document.getElementById('driver-not-listed');

if (!sel.value) {
selectedCatalog = null;
btn.disabled = true;
descEl.style.display = 'none';
notListedEl.style.display = 'none';
return;
}

if (sel.value === NOT_LISTED) {
// Nothing to configure, so Continue stays held; the panel offers
// its own forward path instead.
selectedCatalog = null;
btn.disabled = true;
descEl.style.display = 'none';
notListedEl.style.display = 'block';
return;
}
notListedEl.style.display = 'none';

selectedCatalog = driverCatalog[parseInt(sel.value, 10)];
btn.disabled = false;
Expand Down
30 changes: 30 additions & 0 deletions web/setup.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,33 @@ describe("price provider defaults", () => {
assert.match(PRICE_JS, /\["sourceful", "elprisetjustnu", "entsoe", "none"\], "sourceful"/);
});
});

describe("setup wizard driver picker — not-listed escape hatch (#757)", () => {
it("appends the not-listed option after the catalog entries", () => {
assert.match(JS, /NOT_LISTED\s*=\s*['"]__not_listed__['"]/,
"the sentinel must be a value that can never parse as a catalog index");
assert.match(JS, /My device is not listed/,
"the picker must offer the escape hatch in its own words");
});

it("handles the sentinel before parsing a catalog index", () => {
assert.match(JS, /sel\.value\s*===\s*NOT_LISTED[\s\S]*?parseInt\(sel\.value,\s*10\)/,
"onDriverSelected must branch on the sentinel before parseInt runs on it");
});

it("ships the guidance panel with both forward paths", () => {
assert.match(HTML, /id=["']driver-not-listed["']/,
"the panel the sentinel reveals must exist in the markup");
assert.match(HTML, /Settings\s*&rarr;\s*Devices/,
"it must say where repository drivers install after setup");
assert.match(HTML, /device-drivers\/issues/,
"it must link to requesting a driver that does not exist yet");
assert.match(HTML, /skipUnlistedDevice\(\)/,
"it must let onboarding continue without the device");
});

it("skips to the devices summary only when a device already exists", () => {
assert.match(JS, /skipUnlistedDevice[\s\S]*?configuredDrivers\.length > 0 \? 6 : 7/,
"an empty summary step is a second dead-end; go straight to integrations");
});
});