From ae8211c0b415ef4ac4f4968d5867e6034a8c94cb Mon Sep 17 00:00:00 2001 From: Anso Date: Mon, 27 Apr 2026 15:47:51 -0400 Subject: [PATCH] fix(fleet): forward main node tier to remote config fetch and hide local-only fields (#811) The /fleet/configuration endpoint fetched remote node config via a direct backend-to-backend fetch that omitted the distributed license headers (x-sencho-tier, x-sencho-variant). Remote nodes evaluated their own Community tier and returned locked: true for Webhooks, Scanning, and Backup even when the main node held a Skipper/Admiral license. Forward the same tier/variant headers that remoteNodeProxy already injects so tier gates on remote nodes honour the main node's license. MFA and Backup are also hidden for remote node cards in the Status tab: - MFA is a user session feature managed on the main node; remote nodes are accessed via node_proxy Bearer tokens with no userId, so the value was always "Not set" and provided no useful information. - Backup (Sencho Cloud Backup) runs fleet-wide from the main node and captures all nodes' compose files; remote nodes never configure it independently, so showing it there was misleading. Removing both fields from remote cards keeps the grid at 6 items (3 even pairs) and eliminates stale or irrelevant data. --- backend/src/routes/fleet.ts | 16 +++++++--- .../components/fleet/FleetConfiguration.tsx | 29 +++++++++++-------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/backend/src/routes/fleet.ts b/backend/src/routes/fleet.ts index ed0a2d40..ddc85d03 100644 --- a/backend/src/routes/fleet.ts +++ b/backend/src/routes/fleet.ts @@ -26,7 +26,7 @@ import { sanitizeForLog } from '../utils/safeLog'; import { CloudBackupService } from '../services/CloudBackupService'; import { NotificationService } from '../services/NotificationService'; import { buildLocalConfigurationStatus, type ConfigurationStatus } from './dashboard'; -import { LicenseService } from '../services/LicenseService'; +import { LicenseService, PROXY_TIER_HEADER, PROXY_VARIANT_HEADER } from '../services/LicenseService'; const updateTracker = FleetUpdateTrackerService.getInstance(); const UPDATE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes @@ -330,8 +330,9 @@ fleetRouter.get('/configuration', authMiddleware, async (req: Request, res: Resp const db = DatabaseService.getInstance(); const nodes = db.getNodes(); const userId = req.user?.userId ?? 0; - const localTier = LicenseService.getInstance().getTier(); - const localVariant = LicenseService.getInstance().getVariant(); + const ls = LicenseService.getInstance(); + const localTier = ls.getTier(); + const localVariant = ls.getVariant(); const results = await Promise.allSettled( nodes.map(async (node: Node): Promise => { @@ -352,7 +353,14 @@ fleetRouter.get('/configuration', authMiddleware, async (req: Request, res: Resp try { const resp = await fetch( `${node.api_url.replace(/\/$/, '')}/api/dashboard/configuration`, - { headers: { Authorization: `Bearer ${node.api_token}` }, signal: AbortSignal.timeout(10000) }, + { + headers: { + Authorization: `Bearer ${node.api_token}`, + [PROXY_TIER_HEADER]: localTier, + [PROXY_VARIANT_HEADER]: localVariant ?? '', + }, + signal: AbortSignal.timeout(10000), + }, ); const configuration = resp.ok ? (await resp.json() as ConfigurationStatus) : null; return { diff --git a/frontend/src/components/fleet/FleetConfiguration.tsx b/frontend/src/components/fleet/FleetConfiguration.tsx index d0b52bd7..48c0c82d 100644 --- a/frontend/src/components/fleet/FleetConfiguration.tsx +++ b/frontend/src/components/fleet/FleetConfiguration.tsx @@ -44,6 +44,7 @@ function SummaryRow({ icon: Icon, label, value, locked, requiredTier }: { } function NodeCard({ node }: { node: FleetNodeConfiguration }) { + const isRemote = node.type === 'remote'; if (!node.configuration) { return ( @@ -51,7 +52,7 @@ function NodeCard({ node }: { node: FleetNodeConfiguration }) {
{node.name} - {node.type === 'remote' ? 'Remote' : 'Local'} + {isRemote ? 'Remote' : 'Local'} Offline @@ -76,7 +77,7 @@ function NodeCard({ node }: { node: FleetNodeConfiguration }) {
{node.name} - {node.type === 'remote' ? 'Remote' : 'Local'} + {isRemote ? 'Remote' : 'Local'}
@@ -101,8 +102,10 @@ function NodeCard({ node }: { node: FleetNodeConfiguration }) { } locked={automation.webhooks.locked} requiredTier={automation.webhooks.locked ? automation.webhooks.requiredTier : undefined} /> - + {!isRemote && ( + + )} - + {!isRemote && ( + + )}