mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-07 09:24:09 +00:00
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.
This commit is contained in:
@@ -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<FleetNodeConfiguration> => {
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 (
|
||||
<Card className="bg-card shadow-card-bevel">
|
||||
@@ -51,7 +52,7 @@ function NodeCard({ node }: { node: FleetNodeConfiguration }) {
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span className="font-medium text-sm text-stat-value">{node.name}</span>
|
||||
<Badge variant="outline" className="text-[10px] font-normal py-0 px-1.5 text-stat-subtitle">
|
||||
{node.type === 'remote' ? 'Remote' : 'Local'}
|
||||
{isRemote ? 'Remote' : 'Local'}
|
||||
</Badge>
|
||||
<WifiOff className="h-3.5 w-3.5 text-stat-subtitle ml-auto" strokeWidth={1.5} />
|
||||
<span className="text-xs text-stat-subtitle">Offline</span>
|
||||
@@ -76,7 +77,7 @@ function NodeCard({ node }: { node: FleetNodeConfiguration }) {
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span className="font-medium text-sm text-stat-value">{node.name}</span>
|
||||
<Badge variant="outline" className="text-[10px] font-normal py-0 px-1.5 text-stat-subtitle">
|
||||
{node.type === 'remote' ? 'Remote' : 'Local'}
|
||||
{isRemote ? 'Remote' : 'Local'}
|
||||
</Badge>
|
||||
<div className="ml-auto flex items-center gap-1">
|
||||
<CheckCircle2 className="h-3.5 w-3.5 text-success" strokeWidth={1.5} />
|
||||
@@ -101,8 +102,10 @@ function NodeCard({ node }: { node: FleetNodeConfiguration }) {
|
||||
}
|
||||
locked={automation.webhooks.locked}
|
||||
requiredTier={automation.webhooks.locked ? automation.webhooks.requiredTier : undefined} />
|
||||
<SummaryRow icon={Shield} label="MFA"
|
||||
value={security.mfaEnabled === null ? 'Not set' : security.mfaEnabled ? 'On' : 'Off'} />
|
||||
{!isRemote && (
|
||||
<SummaryRow icon={Shield} label="MFA"
|
||||
value={security.mfaEnabled === null ? 'Not set' : security.mfaEnabled ? 'On' : 'Off'} />
|
||||
)}
|
||||
<SummaryRow icon={Shield} label="Scanning"
|
||||
value={
|
||||
security.scanPolicies.locked
|
||||
@@ -111,14 +114,16 @@ function NodeCard({ node }: { node: FleetNodeConfiguration }) {
|
||||
}
|
||||
locked={security.scanPolicies.locked}
|
||||
requiredTier={security.scanPolicies.locked ? security.scanPolicies.requiredTier : undefined} />
|
||||
<SummaryRow icon={HardDrive} label="Backup"
|
||||
value={
|
||||
backup.locked
|
||||
? ''
|
||||
: backup.provider === 'disabled' ? 'Disabled' : 'Enabled'
|
||||
}
|
||||
locked={backup.locked}
|
||||
requiredTier={backup.locked ? backup.requiredTier : undefined} />
|
||||
{!isRemote && (
|
||||
<SummaryRow icon={HardDrive} label="Backup"
|
||||
value={
|
||||
backup.locked
|
||||
? ''
|
||||
: backup.provider === 'disabled' ? 'Disabled' : 'Enabled'
|
||||
}
|
||||
locked={backup.locked}
|
||||
requiredTier={backup.locked ? backup.requiredTier : undefined} />
|
||||
)}
|
||||
<SummaryRow icon={HardDrive} label="Crash detect"
|
||||
value={thresholds.globalCrash ? 'On' : 'Off'} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user