Fix host agent type badge flapping in UI
Add transient empty payload protection for hosts data, matching the existing protection for dockerHosts. When a websocket message contains an empty hosts array (transient state), the UI now waits for 3 consecutive empty payloads before applying the change. This prevents the "Host" type badge from disappearing intermittently in Settings → Agents → Managed Agents when the unified agent is reporting both host and docker data. Related to #773
This commit is contained in:
parent
a7b1cf2ae9
commit
6c1a0815ea
1 changed files with 49 additions and 6 deletions
|
|
@ -84,6 +84,12 @@ export function createWebSocketStore(url: string) {
|
|||
let consecutiveEmptyDockerUpdates = 0;
|
||||
let hasReceivedNonEmptyDockerHosts = false;
|
||||
|
||||
// Track consecutive empty hosts payloads (same protection as dockerHosts)
|
||||
// This prevents "Host" type badge from disappearing when transient empty
|
||||
// hosts arrays are received. See #773.
|
||||
let consecutiveEmptyHostUpdates = 0;
|
||||
let hasReceivedNonEmptyHosts = false;
|
||||
|
||||
const mergeDockerHostRevocations = (incomingHosts: DockerHost[]) => {
|
||||
if (!Array.isArray(incomingHosts) || incomingHosts.length === 0) {
|
||||
return incomingHosts;
|
||||
|
|
@ -294,6 +300,8 @@ export function createWebSocketStore(url: string) {
|
|||
isReconnecting = false;
|
||||
consecutiveEmptyDockerUpdates = 0;
|
||||
hasReceivedNonEmptyDockerHosts = false;
|
||||
consecutiveEmptyHostUpdates = 0;
|
||||
hasReceivedNonEmptyHosts = false;
|
||||
|
||||
// Start heartbeat to keep connection alive
|
||||
if (heartbeatInterval) {
|
||||
|
|
@ -461,16 +469,51 @@ export function createWebSocketStore(url: string) {
|
|||
logger.debug('[WebSocket] Received null dockerHosts payload');
|
||||
}
|
||||
|
||||
// Prepare hosts data if present
|
||||
// Prepare hosts data if present (with same transient empty protection as dockerHosts)
|
||||
let processedHosts: Host[] | null = null;
|
||||
let shouldApplyHosts = false;
|
||||
|
||||
if (hasHostsUpdate) {
|
||||
processedHosts = Array.isArray(message.data.hosts)
|
||||
? mergeHostRevocations(message.data.hosts)
|
||||
: message.data.hosts;
|
||||
if (Array.isArray(message.data.hosts)) {
|
||||
const incomingHosts = message.data.hosts;
|
||||
if (incomingHosts.length === 0) {
|
||||
consecutiveEmptyHostUpdates += 1;
|
||||
|
||||
shouldApplyHosts =
|
||||
!hasReceivedNonEmptyHosts ||
|
||||
consecutiveEmptyHostUpdates >= 3 ||
|
||||
message.type === WEBSOCKET.MESSAGE_TYPES.INITIAL_STATE;
|
||||
|
||||
if (shouldApplyHosts) {
|
||||
logger.debug('[WebSocket] Updating hosts', {
|
||||
count: incomingHosts.length,
|
||||
});
|
||||
processedHosts = mergeHostRevocations(incomingHosts);
|
||||
} else {
|
||||
logger.debug('[WebSocket] Skipping transient empty hosts payload', {
|
||||
streak: consecutiveEmptyHostUpdates,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
consecutiveEmptyHostUpdates = 0;
|
||||
hasReceivedNonEmptyHosts = true;
|
||||
shouldApplyHosts = true;
|
||||
logger.debug('[WebSocket] Updating hosts', {
|
||||
count: incomingHosts.length,
|
||||
});
|
||||
processedHosts = mergeHostRevocations(incomingHosts);
|
||||
}
|
||||
} else {
|
||||
logger.warn('[WebSocket] Received non-array hosts payload', {
|
||||
type: typeof message.data.hosts,
|
||||
});
|
||||
}
|
||||
} else if (message.data.hosts === null) {
|
||||
logger.debug('[WebSocket] Received null hosts payload');
|
||||
}
|
||||
|
||||
// Apply updates - batch together if both are present to prevent flapping
|
||||
if (shouldApplyDockerHosts && processedHosts !== null) {
|
||||
if (shouldApplyDockerHosts && shouldApplyHosts) {
|
||||
// Both dockerHosts and hosts in this message - batch them atomically
|
||||
batch(() => {
|
||||
setState('dockerHosts', reconcile(processedDockerHosts!, { key: 'id' }));
|
||||
|
|
@ -503,7 +546,7 @@ export function createWebSocketStore(url: string) {
|
|||
pruneMetricsByPrefix(getMetricKeyPrefix('dockerHost'), hostIds);
|
||||
pruneMetricsByPrefix(getMetricKeyPrefix('dockerContainer'), dockerContainerIds);
|
||||
}
|
||||
if (processedHosts !== null) {
|
||||
if (shouldApplyHosts && processedHosts !== null) {
|
||||
setState('hosts', reconcile(processedHosts, { key: 'id' }));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue