From 658c19e45797f0515c61f0ae7cc70cb198b30c44 Mon Sep 17 00:00:00 2001 From: hansjone Date: Thu, 30 Jul 2026 02:34:25 +0000 Subject: [PATCH] fix(auth): attach bearer token to managed-NE import and template download These raw fetch/navigation paths skipped Authorization after login was required, same class of bug as topology discover stream. Co-authored-by: Cursor --- web/src/pages/NePage.tsx | 6 ++++-- web/src/services/api.ts | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/web/src/pages/NePage.tsx b/web/src/pages/NePage.tsx index 088f9e7..a39a6a2 100644 --- a/web/src/pages/NePage.tsx +++ b/web/src/pages/NePage.tsx @@ -13,7 +13,7 @@ import { fetchManagedNeMeta, fetchManagedNeStats, importManagedNe, - managedNeImportTemplateUrl, + downloadManagedNeImportTemplate, syncUmeManagedNe, updateManagedNe, type ManagedNeStats, @@ -589,7 +589,9 @@ export function NePage() { diff --git a/web/src/services/api.ts b/web/src/services/api.ts index 701ad64..973761b 100644 --- a/web/src/services/api.ts +++ b/web/src/services/api.ts @@ -330,10 +330,41 @@ export const deleteUmeManagedNe = () => export const managedNeImportTemplateUrl = (format: "xlsx" | "csv" = "xlsx") => `/v1/managed-ne/import/template?format=${format}`; +export const downloadManagedNeImportTemplate = async (format: "xlsx" | "csv" = "xlsx"): Promise => { + const path = managedNeImportTemplateUrl(format); + const res = await fetch(path, { headers: authHeaders() }); + if (res.status === 401) { + handleUnauthorized(path); + throw new Error("unauthorized"); + } + if (!res.ok) throw new Error(`${res.status} template`); + const blob = await res.blob(); + const cd = res.headers.get("content-disposition") || ""; + const matched = /filename\*?=(?:UTF-8''|")?([^\";]+)/i.exec(cd); + const filename = matched + ? decodeURIComponent(matched[1].replace(/"/g, "")) + : `managed_ne_import_template.${format}`; + const url = URL.createObjectURL(blob); + try { + const a = document.createElement("a"); + a.href = url; + a.download = filename; + a.click(); + } finally { + URL.revokeObjectURL(url); + } +}; + export const importManagedNe = async (file: File): Promise => { const form = new FormData(); form.append("file", file); - const res = await fetch("/v1/managed-ne/import", { method: "POST", body: form }); + const path = "/v1/managed-ne/import"; + // Do not set content-type — browser must add multipart boundary. + const res = await fetch(path, { method: "POST", headers: authHeaders(), body: form }); + if (res.status === 401) { + handleUnauthorized(path); + throw new Error("unauthorized"); + } const text = await res.text(); const data = text ? JSON.parse(text) : {}; if (!res.ok) throw new Error(String((data as { detail?: string }).detail || `${res.status} import`));