From 8bdbc30376cc7b077431da78be61e1fd3e7d1770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zachary=20Gu=C3=A9not?= Date: Sun, 13 Sep 2026 04:29:45 +0200 Subject: [PATCH] fix(integrations): label sites with the console name Site Manager shows Co-Authored-By: Claude Opus 5 --- .../test/unifi-site-manager-types.spec.ts | 43 ++++++++++++++++- .../unifi-site-manager-types.ts | 47 +++++++++++++++++-- 2 files changed, 85 insertions(+), 5 deletions(-) diff --git a/packages/integrations/src/unifi-site-manager/test/unifi-site-manager-types.spec.ts b/packages/integrations/src/unifi-site-manager/test/unifi-site-manager-types.spec.ts index c213cc115..e2b345034 100644 --- a/packages/integrations/src/unifi-site-manager/test/unifi-site-manager-types.spec.ts +++ b/packages/integrations/src/unifi-site-manager/test/unifi-site-manager-types.spec.ts @@ -162,14 +162,14 @@ describe("mapSites", () => { }, }; - const host = { id: "host-1", reportedState: { deviceState: "updateAvailable" } }; + const host = { id: "host-1", reportedState: { name: "Console A", deviceState: "updateAvailable" } }; test("maps a complete site", () => { const [mapped] = mapSites([site], [host], [], new Map(), NOW); expect(mapped).toMatchObject({ siteId: "site-1", - name: "Example site", + name: "Console A", wanUptimePct: 99.5, ispName: "Example ISP", externalIps: ["203.0.113.10", "203.0.113.11"], @@ -180,12 +180,51 @@ describe("mapSites", () => { }); }); + /** + * Le défaut relevé sur le compte réel : une console gardait la description de + * site par défaut alors que Site Manager l'affiche sous son propre nom. C'est + * le nom de console que le propriétaire reconnaît. + */ + test("labels a site with its console name rather than a differing site description", () => { + const [mapped] = mapSites([{ ...site, meta: { desc: "Default", name: "default" } }], [host], [], new Map(), NOW); + + expect(mapped!.name).toBe("Console A"); + }); + + /** Le nom de console seul ne distinguerait plus les deux lignes. */ + test("composes the console name with the site description when one console carries several sites", () => { + const mapped = mapSites( + [ + { ...site, siteId: "site-1", meta: { desc: "Warehouse", name: "default" } }, + { ...site, siteId: "site-2", meta: { desc: "Loading dock", name: "dock" } }, + ], + [host], + [], + new Map(), + NOW, + ); + + expect(mapped.map((entry) => entry.name)).toEqual(["Console A · Warehouse", "Console A · Loading dock"]); + }); + + test("falls back to the site description when no host names the console", () => { + const [mapped] = mapSites([site], [], [], new Map(), NOW); + + expect(mapped!.name).toBe("Example site"); + }); + test("falls back to meta.name when there is no description", () => { const [mapped] = mapSites([{ ...site, meta: { name: "default" } }], [], [], new Map(), NOW); expect(mapped!.name).toBe("default"); }); + test("falls back to the site identifier when nothing is named", () => { + const [mapped] = mapSites([{ siteId: "site-1", meta: {} }], [], [], new Map(), NOW); + + expect(mapped!.name).toBe("site-1"); + }); + /** * `null` veut dire « aucun host ne correspond », ce qui n'est pas « la console * est à jour ». Le widget doit pouvoir les distinguer. diff --git a/packages/integrations/src/unifi-site-manager/unifi-site-manager-types.ts b/packages/integrations/src/unifi-site-manager/unifi-site-manager-types.ts index e4728ed1f..bb88d2afe 100644 --- a/packages/integrations/src/unifi-site-manager/unifi-site-manager-types.ts +++ b/packages/integrations/src/unifi-site-manager/unifi-site-manager-types.ts @@ -52,6 +52,8 @@ export const siteManagerHostSchema = z.looseObject({ id: z.string(), reportedState: z .looseObject({ + /** Le nom de la console, celui que Site Manager affiche. Voir `resolveSiteName`. */ + name: z.string().optional(), deviceState: z.string().optional(), /** * `.nullable().optional()` et non `.nullish()` : « présent et explicitement @@ -292,14 +294,52 @@ const resolveConsoleUpdate = (host: SiteManagerHost | undefined): boolean | null return null; }; +/** + * Le libellé d'une ligne est le nom de la CONSOLE, pas la description du site. + * + * Site Manager nomme ses lignes d'après `host.reportedState.name` : c'est ce que + * le propriétaire du compte lit chez Ubiquiti, donc ce qu'il reconnaît dans le + * widget. `meta.desc` est une autre chose — la description du site *à + * l'intérieur* de la console — qui ne coïncide que lorsqu'on a nommé les deux + * pareil. Sur le compte réel, une console garde le `meta.desc` par défaut + * « Default » alors que Site Manager l'affiche sous son propre nom : le widget + * étiquetait donc « Default » un site que son propriétaire identifie autrement. + * `meta.name` est pire encore — le slug interne, « default » sur les deux + * consoles. + * + * Une console peut porter plusieurs sites. Son nom ne distingue alors plus les + * lignes entre elles, et la description les départage. + */ +const resolveSiteName = (site: SiteManagerSite, host: SiteManagerHost | undefined, sitesOnConsole: number): string => { + const consoleName = host?.reportedState?.name?.trim(); + if (consoleName === undefined || consoleName === "") return site.meta.desc ?? site.meta.name ?? site.siteId; + + const description = site.meta.desc?.trim(); + if (sitesOnConsole > 1 && description !== undefined && description !== "") return `${consoleName} · ${description}`; + + return consoleName; +}; + +/** Compté une fois sur tout le compte, pas par ligne. */ +const countSitesPerHost = (sites: SiteManagerSite[]): Map => { + const counts = new Map(); + for (const site of sites) { + if (site.hostId === undefined) continue; + counts.set(site.hostId, (counts.get(site.hostId) ?? 0) + 1); + } + return counts; +}; + export const mapSites = ( sites: SiteManagerSite[], hosts: SiteManagerHost[], metrics: IspMetricsEntry[], sdWanBySite: Map, now: Date, -): UnifiSiteManagerSite[] => - sites.map((site) => { +): UnifiSiteManagerSite[] => { + const sitesPerHost = countSitesPerHost(sites); + + return sites.map((site) => { const statistics = site.statistics; const counts = statistics?.counts; const host = site.hostId === undefined ? undefined : hosts.find((candidate) => candidate.id === site.hostId); @@ -310,7 +350,7 @@ export const mapSites = ( return { siteId: site.siteId, - name: site.meta.desc ?? site.meta.name ?? site.siteId, + name: resolveSiteName(site, host, site.hostId === undefined ? 1 : (sitesPerHost.get(site.hostId) ?? 1)), wanUptimePct: statistics?.percentages?.wanUptime ?? null, ispName: statistics?.ispInfo?.name ?? null, externalIps: Object.values(statistics?.wans ?? {}) @@ -327,3 +367,4 @@ export const mapSites = ( sdWan: sdWanBySite.get(site.siteId) ?? null, }; }); +};