fix: Finder 404 shows helpful message + fuzzy switch name matching
- api() helper now parses JSON body on non-2xx responses so error.suggestion is available in catch blocks - runFinder() catch shows 'Switch not found' + suggestion instead of 'Error: HTTP 404' - finder.ts: normalized search (removes hyphens/spaces) + token-based fallback so 'sg350-28' → 'SG350-28', 'N9K-C93180' → Nexus 93180, etc.
This commit is contained in:
parent
3a00224ef0
commit
99778d8639
@ -23,7 +23,11 @@ finderRouter.get("/", async (req, res) => {
|
|||||||
return res.status(400).json({ error: "Parameter 'switch' is required" });
|
return res.status(400).json({ error: "Parameter 'switch' is required" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 1: Find the switch
|
// Step 1: Find the switch — try multiple search strategies
|
||||||
|
const q = String(switchQuery);
|
||||||
|
// Normalized form: remove hyphens/spaces for fuzzy match (sg350-28 → sg35028)
|
||||||
|
const qNorm = q.replace(/[\s\-_]/g, "");
|
||||||
|
|
||||||
const switchResult = await pool.query(
|
const switchResult = await pool.query(
|
||||||
`SELECT sw.id, sw.model, sw.series, sw.ports_config, sw.max_speed_gbps,
|
`SELECT sw.id, sw.model, sw.series, sw.ports_config, sw.max_speed_gbps,
|
||||||
v.name AS vendor_name, sw.image_url, sw.datasheet_r2_key
|
v.name AS vendor_name, sw.image_url, sw.datasheet_r2_key
|
||||||
@ -31,21 +35,39 @@ finderRouter.get("/", async (req, res) => {
|
|||||||
JOIN vendors v ON sw.vendor_id = v.id
|
JOIN vendors v ON sw.vendor_id = v.id
|
||||||
WHERE sw.model ILIKE $1
|
WHERE sw.model ILIKE $1
|
||||||
OR sw.model ILIKE '%' || $1 || '%'
|
OR sw.model ILIKE '%' || $1 || '%'
|
||||||
OR sw.search_vector @@ plainto_tsquery('english', $1)
|
OR REPLACE(REPLACE(sw.model, '-', ''), ' ', '') ILIKE '%' || $2 || '%'
|
||||||
|
OR sw.search_vector @@ plainto_tsquery('english', $3)
|
||||||
ORDER BY
|
ORDER BY
|
||||||
CASE WHEN sw.model ILIKE $1 THEN 0
|
CASE WHEN sw.model ILIKE $1 THEN 0
|
||||||
WHEN sw.model ILIKE $1 || '%' THEN 1
|
WHEN sw.model ILIKE $1 || '%' THEN 1
|
||||||
ELSE 2 END
|
WHEN REPLACE(REPLACE(sw.model, '-', ''), ' ', '') ILIKE $2 || '%' THEN 2
|
||||||
|
ELSE 3 END
|
||||||
LIMIT 5`,
|
LIMIT 5`,
|
||||||
[switchQuery]
|
[q, qNorm, q.replace(/[^\w\s]/g, " ")]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (switchResult.rows.length === 0) {
|
if (switchResult.rows.length === 0) {
|
||||||
|
// Try one more time with individual tokens
|
||||||
|
const tokens = q.split(/[\s\-_]+/).filter((t) => t.length >= 3);
|
||||||
|
let fallbackResult = { rows: [] as any[] };
|
||||||
|
if (tokens.length > 0) {
|
||||||
|
fallbackResult = await pool.query(
|
||||||
|
`SELECT sw.id, sw.model, sw.series, sw.ports_config, sw.max_speed_gbps,
|
||||||
|
v.name AS vendor_name, sw.image_url, sw.datasheet_r2_key
|
||||||
|
FROM switches sw JOIN vendors v ON sw.vendor_id = v.id
|
||||||
|
WHERE ${tokens.map((_, i) => `sw.model ILIKE '%' || $${i + 1} || '%'`).join(" AND ")}
|
||||||
|
LIMIT 5`,
|
||||||
|
tokens
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (fallbackResult.rows.length === 0) {
|
||||||
return res.status(404).json({
|
return res.status(404).json({
|
||||||
error: "Switch not found",
|
error: `Switch "${q}" not found in the database`,
|
||||||
suggestion: "Try a partial model name like 'N9K-C93180' or 'QFX5120'"
|
suggestion: `Try a partial model name, e.g. "${tokens[0] || q.substring(0, 6)}" — or check spelling. Available: Cisco Nexus, Arista, Juniper QFX, Edgecore, Mellanox.`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
switchResult.rows.push(...fallbackResult.rows);
|
||||||
|
}
|
||||||
|
|
||||||
const sw = switchResult.rows[0];
|
const sw = switchResult.rows[0];
|
||||||
|
|
||||||
|
|||||||
@ -1083,9 +1083,19 @@ function el(id) { return document.getElementById(id); }
|
|||||||
function api(path) {
|
function api(path) {
|
||||||
return fetch(API + path).then(function(r) {
|
return fetch(API + path).then(function(r) {
|
||||||
var ct = r.headers.get('content-type') || '';
|
var ct = r.headers.get('content-type') || '';
|
||||||
|
if (ct.indexOf('application/json') === -1) {
|
||||||
if (!r.ok) throw new Error('HTTP ' + r.status);
|
if (!r.ok) throw new Error('HTTP ' + r.status);
|
||||||
if (ct.indexOf('application/json') === -1) throw new Error('Server returned non-JSON response');
|
throw new Error('Server returned non-JSON response');
|
||||||
return r.json();
|
}
|
||||||
|
// Parse JSON even on error so callers can read error.message / error.suggestion
|
||||||
|
return r.json().then(function(body) {
|
||||||
|
if (!r.ok) {
|
||||||
|
var err = new Error(body.error || ('HTTP ' + r.status));
|
||||||
|
err.body = body;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
return body;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2836,7 +2846,14 @@ async function runFinder() {
|
|||||||
tcvrHtml;
|
tcvrHtml;
|
||||||
|
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
results.innerHTML = '<div class="card" style="border-left:3px solid #c1121f">Error: ' + e.message + '</div>';
|
var body = e.body || {};
|
||||||
|
var msg = body.error || e.message || 'Unknown error';
|
||||||
|
var suggestion = body.suggestion || '';
|
||||||
|
results.innerHTML = '<div class="card" style="border-left:3px solid #c1121f;padding:1rem">'
|
||||||
|
+ '<div style="font-weight:700;margin-bottom:0.3rem">Switch not found</div>'
|
||||||
|
+ '<div style="color:var(--text-dim);font-size:0.85rem">' + esc(msg) + '</div>'
|
||||||
|
+ (suggestion ? '<div style="color:var(--text-dim);font-size:0.8rem;margin-top:0.4rem">💡 ' + esc(suggestion) + '</div>' : '')
|
||||||
|
+ '</div>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user