Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 122 additions & 42 deletions web/src/components/forms/resource-status.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,29 @@ import { useTranslation } from 'react-i18next';
import { useNavigate } from '../../utils/url';
import { ComponentStatus, ExporterStatus } from './pipeline';

/** `FlowCollector.status` fields used by this form (mirrors operator CRD shape). */
export type FlowCollectorStatus = {
conditions?: K8sResourceCondition[];
components?: {
agent?: ComponentStatus;
processor?: ComponentStatus;
plugin?: ComponentStatus;
};
integrations?: {
loki?: ComponentStatus;
monitoring?: ComponentStatus;
exporters?: ExporterStatus[];
};
};

function flowCollectorStatus(existing: K8sResourceKind | null): FlowCollectorStatus | undefined {
const raw = existing?.status;
if (raw == null || typeof raw !== 'object') {
return undefined;
}
return raw as FlowCollectorStatus;
}

export type ResourceStatusProps = {
group: string;
version: string;
Expand Down Expand Up @@ -46,17 +69,17 @@ const stateColor = (state: string | undefined): LabelColor => {
const StateIcon: FC<{ state: string | undefined }> = ({ state }) => {
switch (state) {
case 'Ready':
return <CheckCircleIcon color="var(--pf-v5-global--success-color--100)" />;
return <CheckCircleIcon color="var(--pf-t--global--icon--color--status--success--default)" />;
case 'Degraded':
return <ExclamationTriangleIcon color="var(--pf-v5-global--warning-color--100)" />;
return <ExclamationTriangleIcon color="var(--pf-t--global--icon--color--status--warning--default)" />;
case 'Failure':
return <ExclamationCircleIcon color="var(--pf-v5-global--danger-color--100)" />;
return <ExclamationCircleIcon color="var(--pf-t--global--icon--color--status--danger--default)" />;
case 'InProgress':
return <HourglassHalfIcon color="var(--pf-v5-global--info-color--100)" />;
return <HourglassHalfIcon color="var(--pf-t--global--icon--color--status--info--default)" />;
case 'Unused':
return <BanIcon color="var(--pf-v5-global--disabled-color--100)" />;
return <BanIcon color="var(--pf-t--global--icon--color--disabled)" />;
default:
return <UnknownIcon color="var(--pf-v5-global--disabled-color--100)" />;
return <UnknownIcon color="var(--pf-t--global--icon--color--disabled)" />;
}
};

Expand Down Expand Up @@ -135,7 +158,7 @@ const ComponentStatusTable: FC<{
))}
{unusedComponents.length > 0 && (
<Tr>
<Td colSpan={4} style={{ color: 'var(--pf-v5-global--disabled-color--100)', fontStyle: 'italic' }}>
<Td colSpan={4} style={{ color: 'var(--pf-t--global--text--color--disabled)', fontStyle: 'italic' }}>
{t('Unused: {{list}}', { list: unusedComponents.map(c => c.name).join(', ') })}
</Td>
</Tr>
Expand All @@ -146,6 +169,68 @@ const ComponentStatusTable: FC<{
);
};

/**
* `Waiting*` FlowCollector conditions use inverted polarity (operator `statuses.go`): `True` means not ready.
* Component state on the same status object is the source of truth for Failure / Degraded / InProgress.
*/
const WAITING_NO_STATUS_FIELD = new Set(['FlowCollectorController', 'StaticController', 'NetworkPolicy']);

type ConditionTone = 'error' | 'warning' | 'progress' | 'success' | 'unused' | 'unknown';

function waitingComponentState(st: FlowCollectorStatus | undefined, suffix: string): string | undefined {
if (!st) return undefined;
const { components, integrations } = st;
switch (suffix) {
case 'EBPFAgents':
return components?.agent?.state;
case 'WebConsole':
return components?.plugin?.state;
case 'FLPMonolith':
case 'FLPParent':
case 'FLPTransformer':
return components?.processor?.state;
case 'Monitoring':
return integrations?.monitoring?.state;
case 'LokiStack':
case 'DemoLoki':
return integrations?.loki?.state;
default:
return undefined;
}
}

/** One tone per row: drives icon and message color. */
function conditionTone(c: K8sResourceCondition, fcStatus: FlowCollectorStatus | undefined): ConditionTone {
const { type, status, reason } = c;

if (type === 'ConfigurationIssue') {
if (status === 'True' && reason === 'Error') return 'error';
if (status === 'True' && reason === 'Warnings') return 'warning';
return 'unknown';
}
Comment on lines +206 to +210
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Render ConfigurationIssue=False as healthy.

ConfigurationIssue is a negative condition from the operator: True means an issue exists. With the current fallback, the normal False state renders as unknown instead of success.

Proposed change
   if (type === 'ConfigurationIssue') {
     if (status === 'True' && reason === 'Error') return 'error';
     if (status === 'True' && reason === 'Warnings') return 'warning';
+    if (status === 'False') return 'success';
     return 'unknown';
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/forms/resource-status.tsx` around lines 206 - 210, The
block handling type === 'ConfigurationIssue' treats non-True states as 'unknown'
causing a normal False condition to render unhealthy; update the conditional to
explicitly return 'success' (or the component's healthy token) when status ===
'False' so that ConfigurationIssue=False is rendered healthy—modify the type ===
'ConfigurationIssue' branch (using the existing status and reason variables and
replacing the fallback return 'unknown') to return 'success' for status ===
'False', keep the existing checks for status === 'True' && reason === 'Error' ->
'error' and status === 'True' && reason === 'Warnings' -> 'warning'.


if (type.startsWith('Waiting')) {
const suffix = type.slice('Waiting'.length);
if (status === 'False' && reason === 'Ready') return 'success';
// Operator `setUnused` sets reason `ComponentUnused`; `toCondition` default is `Unused`.
if (status === 'Unknown' && (reason === 'Unused' || reason === 'ComponentUnused')) return 'unused';
if (status !== 'True') return 'unknown';

const st = waitingComponentState(fcStatus, suffix);
if (st === 'Failure') return 'error';
if (st === 'Degraded') return 'warning';
if (st === 'InProgress') return 'progress';
if (st === 'Ready') return 'success';
return WAITING_NO_STATUS_FIELD.has(suffix) ? 'error' : 'progress';
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

if (type === 'Ready' && status === 'True' && reason === 'Ready,Degraded') return 'warning';
if (status === 'True') return 'success';
if (status === 'False' && reason === 'Pending') return 'progress';
if (status === 'False' && reason !== 'Valid') return 'error';
return 'unknown';
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

export const ResourceStatus: FC<ResourceStatusProps> = ({
group,
version,
Expand All @@ -172,30 +257,32 @@ export const ResourceStatus: FC<ResourceStatusProps> = ({
);
}

const fcStatus = flowCollectorStatus(existing);

const components: ComponentRowData[] = [];
if (existing.status?.components?.agent) {
components.push({ id: 'agent', name: t('eBPF Agent'), status: existing.status.components.agent });
if (fcStatus?.components?.agent) {
components.push({ id: 'agent', name: t('eBPF Agent'), status: fcStatus.components.agent });
}
if (existing.status?.components?.processor) {
components.push({ id: 'processor', name: t('Flowlogs Pipeline'), status: existing.status.components.processor });
if (fcStatus?.components?.processor) {
components.push({ id: 'processor', name: t('Flowlogs Pipeline'), status: fcStatus.components.processor });
}
if (existing.status?.components?.plugin) {
components.push({ id: 'plugin', name: t('Console Plugin'), status: existing.status.components.plugin });
if (fcStatus?.components?.plugin) {
components.push({ id: 'plugin', name: t('Console Plugin'), status: fcStatus.components.plugin });
}
if (existing.status?.integrations?.loki) {
components.push({ id: 'loki', name: 'Loki', status: existing.status.integrations.loki });
if (fcStatus?.integrations?.loki) {
components.push({ id: 'loki', name: 'Loki', status: fcStatus.integrations.loki });
}
if (existing.status?.integrations?.monitoring) {
components.push({ id: 'monitoring', name: t('Monitoring'), status: existing.status.integrations.monitoring });
if (fcStatus?.integrations?.monitoring) {
components.push({ id: 'monitoring', name: t('Monitoring'), status: fcStatus.integrations.monitoring });
}
const exporters: ExporterStatus[] = existing.status?.integrations?.exporters || [];
const exporters: ExporterStatus[] = fcStatus?.integrations?.exporters || [];

const sortConditions = [
(c: K8sResourceCondition) => c.type === 'Ready',
(c: K8sResourceCondition) => c.type === 'ConfigurationIssue',
(c: K8sResourceCondition) => c.type === 'KafkaReady'
];
const conditions = ((existing?.status?.conditions || []) as K8sResourceCondition[]).sort((a, b) => {
const conditions = (fcStatus?.conditions || []).sort((a, b) => {
for (const pred of sortConditions) {
if (pred(a) && pred(b)) {
return 0;
Expand Down Expand Up @@ -232,16 +319,7 @@ export const ResourceStatus: FC<ResourceStatusProps> = ({
</Thead>
<Tbody>
{conditions.map((condition, i) => {
const isWarning =
condition.type === 'ConfigurationIssue' && condition.status === 'True' && condition.reason === 'Warnings';
const isError =
(condition.type === 'ConfigurationIssue' &&
condition.status === 'True' &&
condition.reason === 'Error') ||
(condition.type !== 'ConfigurationIssue' &&
condition.status === 'False' &&
condition.reason !== 'Pending' &&
condition.reason !== 'Valid');
const tone = conditionTone(condition, fcStatus);
return (
<Tr
id={`${condition.type}-row`}
Expand All @@ -250,27 +328,29 @@ export const ResourceStatus: FC<ResourceStatusProps> = ({
key={i}
>
<Td>
{isError ? (
<ExclamationCircleIcon color="var(--pf-v5-global--danger-color--100)" />
) : isWarning ? (
<ExclamationTriangleIcon color="var(--pf-v5-global--warning-color--100)" />
) : condition.status === 'True' && condition.type !== 'ConfigurationIssue' ? (
<CheckCircleIcon color="var(--pf-v5-global--success-color--100)" />
) : condition.status === 'False' && condition.reason === 'Pending' ? (
<HourglassHalfIcon color="var(--pf-v5-global--info-color--100)" />
{tone === 'error' ? (
<ExclamationCircleIcon color="var(--pf-t--global--icon--color--status--danger--default)" />
) : tone === 'warning' ? (
<ExclamationTriangleIcon color="var(--pf-t--global--icon--color--status--warning--default)" />
) : tone === 'progress' ? (
<HourglassHalfIcon color="var(--pf-t--global--icon--color--status--info--default)" />
) : tone === 'unused' ? (
<BanIcon color="var(--pf-t--global--icon--color--disabled)" />
) : tone === 'success' ? (
<CheckCircleIcon color="var(--pf-t--global--icon--color--status--success--default)" />
) : (
<UnknownIcon color="var(--pf-v5-global--disabled-color--100)" />
<UnknownIcon color="var(--pf-t--global--icon--color--disabled)" />
)}{' '}
{condition.type}
</Td>
<Td>{condition.status}</Td>
<Td>{condition.reason}</Td>
<Td
style={
isWarning
? { color: 'var(--pf-v5-global--warning-color--200)' }
: isError
? { color: 'var(--pf-v5-global--danger-color--100)' }
tone === 'warning'
? { color: 'var(--pf-t--global--text--color--status--warning--default)' }
: tone === 'error'
? { color: 'var(--pf-t--global--text--color--status--danger--default)' }
: undefined
}
>
Expand Down
12 changes: 6 additions & 6 deletions web/src/components/status/flowcollector-status-icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,22 @@ export const FlowCollectorStatusIcon: React.FC<FlowCollectorStatusIconProps> = (
const icon = React.useMemo(() => {
switch (status) {
case 'ready':
return <ConnectedIcon color="var(--pf-v5-global--success-color--100)" />;
return <ConnectedIcon color="var(--pf-t--global--icon--color--status--success--default)" />;
case 'degraded':
return <ExclamationTriangleIcon color="var(--pf-v5-global--warning-color--100)" />;
return <ExclamationTriangleIcon color="var(--pf-t--global--icon--color--status--warning--default)" />;
case 'pending':
return <ExclamationTriangleIcon color="var(--pf-v5-global--warning-color--100)" />;
return <ExclamationTriangleIcon color="var(--pf-t--global--icon--color--status--warning--default)" />;
case 'error':
return <ExclamationCircleIcon color="var(--pf-v5-global--danger-color--100)" />;
return <ExclamationCircleIcon color="var(--pf-t--global--icon--color--status--danger--default)" />;
case 'onHold':
return <PauseCircleIcon color="var(--pf-v5-global--info-color--100)" />;
return <PauseCircleIcon color="var(--pf-t--global--icon--color--status--info--default)" />;
case 'loading':
return <Spinner size="md" />;
}
}, [status]);

return (
<Tooltip content={tooltipContent} position="bottom">
<Tooltip id="flowcollector-status-tooltip" content={tooltipContent} position="bottom">
<span style={{ display: 'inline-flex', verticalAlign: 'middle' }}>{icon}</span>
</Tooltip>
);
Expand Down
Loading