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
25 changes: 24 additions & 1 deletion web/locales/en/plugin__netobserv-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"n/a": "n/a",
"View alert details": "View alert details",
"View health dashboard": "View health dashboard",
"View FlowCollector status": "View FlowCollector status",
"Name": "Name",
"Subnet label": "Subnet label",
"IP": "IP",
Expand Down Expand Up @@ -202,9 +203,13 @@
"Cancel": "Cancel",
"Delete": "Delete",
"Network Observability FlowCollector status": "Network Observability FlowCollector status",
"FlowCollector status": "FlowCollector status",
"Configuration error": "Configuration error",
"Configuration warnings": "Configuration warnings",
"Network Observability is on hold": "Network Observability is on hold",
"Execution mode is set to OnHold. All operator-managed workloads have been deleted, while preserving other resources. To change execution mode, update or remove \"spec.execution.mode\" in the FlowCollector resource.": "Execution mode is set to OnHold. All operator-managed workloads have been deleted, while preserving other resources. To change execution mode, update or remove \"spec.execution.mode\" in the FlowCollector resource.",
"Edit FlowCollector": "Edit FlowCollector",
"FlowCollector must be ready to open Network Traffic": "FlowCollector must be ready to open Network Traffic",
"Open Network Traffic page": "Open Network Traffic page",
"An error occured while retreiving FlowCollector: {{error}}": "An error occured while retreiving FlowCollector: {{error}}",
"Create FlowCollector": "Create FlowCollector",
Expand All @@ -226,6 +231,10 @@
"Metric": "Metric",
"Data": "Data",
"Review": "Review",
"eBPF agents": "eBPF agents",
"Flowlogs pipeline": "Flowlogs pipeline",
"Monitoring": "Monitoring",
"Console plugin": "Console plugin",
"It will disable flow collection globally. All resources managed by the FlowCollector will be deleted, such as pods and services.": "It will disable flow collection globally. All resources managed by the FlowCollector will be deleted, such as pods and services.",
"The following metric will stop being collected: ": "The following metric will stop being collected: ",
"Delete {{kind}} {{name}}?": "Delete {{kind}} {{name}}?",
Expand All @@ -234,7 +243,16 @@
"Create {{kind}}": "Create {{kind}}",
"Update by completing the form. Current values are from the existing resource.": "Update by completing the form. Current values are from the existing resource.",
"Create by completing the form. Default values are provided as example.": "Create by completing the form. Default values are provided as example.",
"Component statuses": "Component statuses",
"Component": "Component",
"State": "State",
"Replicas": "Replicas",
"Unused: {{list}}": "Unused: {{list}}",
"{{kind}} resource doesn't exists yet.": "{{kind}} resource doesn't exists yet.",
"eBPF Agent": "eBPF Agent",
"Flowlogs Pipeline": "Flowlogs Pipeline",
"Console Plugin": "Console Plugin",
"Conditions": "Conditions",
"Type": "Type",
"Status": "Status",
"Reason": "Reason",
Expand Down Expand Up @@ -343,7 +361,6 @@
"Mode": "Mode",
"alert": "alert",
"recording": "recording",
"State": "State",
"Severity": "Severity",
"Active since": "Active since",
"Labels": "Labels",
Expand Down Expand Up @@ -500,6 +517,12 @@
"Results": "Results",
"Query summary": "Query summary",
"Find in view": "Find in view",
"FlowCollector is ready": "FlowCollector is ready",
"FlowCollector is degraded": "FlowCollector is degraded",
"FlowCollector is pending": "FlowCollector is pending",
"FlowCollector has errors": "FlowCollector has errors",
"FlowCollector is on hold": "FlowCollector is on hold",
"Loading FlowCollector status...": "Loading FlowCollector status...",
"Show all graphs": "Show all graphs",
"Focus on this graph": "Focus on this graph",
"Show total": "Show total",
Expand Down
129 changes: 77 additions & 52 deletions web/moduleMapper/dummy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,76 +196,97 @@ export function useK8sWatchResource(req: any) {
{
"lastTransitionTime": "2025-04-08T09:01:44Z",
"message": "4 ready components, 0 with failure, 1 pending",
"reason": "Pending",
"status": "False",
"reason": "Ready,Degraded",
"status": "True",
"type": "Ready"
},
{
"lastTransitionTime": "2025-04-08T09:01:44Z",
"message": "Deployment netobserv-plugin not ready: 1/1 (Deployment does not have minimum availability.)",
"reason": "DeploymentNotReady",
"status": "True",
"type": "WaitingFlowCollectorLegacy"
"lastTransitionTime": "2025-04-08T09:01:43Z",
"message": "",
"reason": "Valid",
"status": "False",
"type": "ConfigurationIssue"
},
{
"lastTransitionTime": "2025-04-08T09:01:44Z",
"message": "",
"reason": "Ready",
"status": "False",
"type": "WaitingMonitoring"
"status": "True",
"type": "AgentReady"
},
{
"lastTransitionTime": "2025-04-08T09:01:43Z",
"lastTransitionTime": "2025-04-08T09:01:45Z",
"message": "",
"reason": "Ready",
"status": "False",
"type": "WaitingNetworkPolicy"
"status": "True",
"type": "ProcessorReady"
},
{
"lastTransitionTime": "2025-04-08T09:01:43Z",
"message": "",
"reason": "Valid",
"lastTransitionTime": "2025-04-08T09:01:44Z",
// eslint-disable-next-line max-len
"message": "Deployment netobserv-plugin not ready: 1/1 (Deployment does not have minimum availability.)",
"reason": "DeploymentNotReady",
"status": "False",
"type": "ConfigurationIssue"
"type": "PluginReady"
},
{
"lastTransitionTime": "2026-01-15T15:44:51Z",
// eslint-disable-next-line max-len
"message": "LokiStack has issues [name: loki, namespace: netobserv]: Warning: The schema configuration does not contain the most recent schema version and needs an update; Degraded: Missing object storage secret",
"reason": "LokiStackIssues",
"status": "False",
"type": "LokiIssue"
"lastTransitionTime": "2025-04-08T09:01:44Z",
"message": "",
"reason": "Ready",
"status": "True",
"type": "MonitoringReady"
},
{
"lastTransitionTime": "2026-01-15T16:05:26Z",
// eslint-disable-next-line max-len
"message": "LokiStack has warnings [name: loki, namespace: netobserv]: Warning: The schema configuration does not contain the most recent schema version and needs an update",
"message": "LokiStack has warnings [name: loki, namespace: netobserv]: Warning: schema needs update",
"reason": "LokiStackWarnings",
"status": "True",
"type": "LokiWarning"
}
],
"components": {
"agent": {
"state": "Ready",
"desiredReplicas": 3,
"readyReplicas": 3
},
{
"lastTransitionTime": "2025-04-08T09:01:45Z",
"message": "",
"reason": "Ready",
"status": "False",
"type": "WaitingFLPParent"
"processor": {
"state": "Ready",
"desiredReplicas": 1,
"readyReplicas": 1
},
{
"lastTransitionTime": "2025-04-08T09:01:45Z",
"message": "",
"reason": "Ready",
"status": "False",
"type": "WaitingFLPMonolith"
},
{
"lastTransitionTime": "2025-04-08T09:01:44Z",
"message": "Transformer only used with Kafka",
"reason": "ComponentUnused",
"status": "Unknown",
"type": "WaitingFLPTransformer"
"plugin": {
"state": "InProgress",
"reason": "DeploymentNotReady",
// eslint-disable-next-line max-len
"message": "Deployment netobserv-plugin not ready: 1/1",
"desiredReplicas": 1,
"readyReplicas": 0
}
]
},
"integrations": {
"monitoring": {
"state": "Ready"
},
"loki": {
"state": "Unused"
},
"exporters": [
{
"name": "kafka-exporter",
"type": "Kafka",
"state": "Ready",
"reason": "Configured"
},
{
"name": "otel-exporter",
"type": "OpenTelemetry",
"state": "Ready",
"reason": "Configured"
}
]
}
}
setResource(fc);
break;
Expand All @@ -289,15 +310,19 @@ export function useK8sWatchResource(req: any) {
// simulate an update
setTimeout(() => {
const fc = _.cloneDeep(resource);
// 70% chance to update, 30% chance to skip
if (Math.random() < 0.7 && fc.status && fc.status.conditions) {
const randomIndex = Math.floor(Math.random() * fc.status.conditions.length);
const condition = fc.status.conditions[randomIndex];
condition.status = condition.status === 'True' ? 'False' : 'True';

// Enable/disable Loki based on LokiIssue status
if (condition.type === 'LokiIssue' || condition.type === 'LokiWarning') {
fc.spec!.loki.enable = condition.status === 'True';
if (Math.random() < 0.7 && fc.status) {
const states = ['Ready', 'InProgress', 'Degraded', 'Failure'];
const r = Math.random();
if (r < 0.5 && fc.status.components) {
const comps = ['agent', 'processor', 'plugin'];
const comp = comps[Math.floor(Math.random() * comps.length)];
if (fc.status.components[comp]) {
fc.status.components[comp].state = states[Math.floor(Math.random() * states.length)];
}
} else if (fc.status.integrations) {
if (fc.status.integrations.monitoring) {
fc.status.integrations.monitoring.state = states[Math.floor(Math.random() * states.length)];
}
}
}
setResource(fc);
Expand Down
1 change: 1 addition & 0 deletions web/setup-tests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jest.mock('@openshift-console/dynamic-plugin-sdk', () => {
return null;
},
useResolvedExtensions: jest.fn(),
useK8sWatchResource: jest.fn(() => [null, false, null]),
useK8sModels: () => {
return [{}, false];
},
Expand Down
5 changes: 4 additions & 1 deletion web/src/components/alerts/banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from '@patternfly/react-core';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from '../../utils/url';
import { flowCollectorStatusPath, useNavigate } from '../../utils/url';
import './banner.css';

export interface AlertBannerProps {
Expand Down Expand Up @@ -41,6 +41,9 @@ export const AlertBanner: React.FC<AlertBannerProps> = ({ rule, onDelete }) => {
<React.Fragment>
<AlertActionLink onClick={routeAlert}>{t('View alert details')}</AlertActionLink>
<AlertActionLink onClick={routeDashboard}>{t('View health dashboard')}</AlertActionLink>
<AlertActionLink onClick={() => navigate(flowCollectorStatusPath)}>
{t('View FlowCollector status')}
</AlertActionLink>
</React.Fragment>
}
>
Expand Down
72 changes: 58 additions & 14 deletions web/src/components/forms/flowCollector-status.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
import React, { FC } from 'react';

import { Alert, AlertVariant, Button, Flex, FlexItem, PageSection, Text, Title } from '@patternfly/react-core';
import {
Alert,
AlertVariant,
Button,
Flex,
FlexItem,
PageSection,
TextContent,
Title,
Tooltip
} from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
import { flowCollectorEditPath, flowCollectorNewPath, netflowTrafficPath, useNavigate } from '../../utils/url';
import { FlowCollectorStatusIcon } from '../status/flowcollector-status-icon';
import './forms.css';
import { Pipeline } from './pipeline';
import { ResourceStatus } from './resource-status';
import { Consumer, ResourceWatcher } from './resource-watcher';
import { getFlowCollectorOverallStatus } from './utils';

export type FlowCollectorStatusProps = {};

Expand All @@ -26,20 +38,48 @@ export const FlowCollectorStatus: FC<FlowCollectorStatusProps> = () => {
>
<Consumer>
{ctx => {
// Check if operator is in hold mode
const isOnHold = ctx.data?.spec?.execution?.mode === 'OnHold';
const status = getFlowCollectorOverallStatus(ctx.data, ctx.loadError);
const showTrafficButton = status === 'ready' || status === 'degraded';
const configIssue = (
(ctx.data?.status?.conditions as Array<{
type: string;
status: string;
reason?: string;
message?: string;
}>) || []
).find(c => c.type === 'ConfigurationIssue' && c.status === 'True');

return (
<PageSection id="pageSection">
<div id="pageHeader">
<Title headingLevel="h1" size="2xl">
{t('Network Observability FlowCollector status')}
</Title>
<Flex alignItems={{ default: 'alignItemsCenter' }} spaceItems={{ default: 'spaceItemsSm' }}>
<FlexItem>
<Title headingLevel="h1" size="2xl">
{t('Network Observability FlowCollector status')}
</Title>
</FlexItem>
<FlexItem>
<Button variant="plain" aria-label={t('FlowCollector status')} style={{ cursor: 'default' }}>
<FlowCollectorStatusIcon status={status} />
</Button>
</FlexItem>
</Flex>
</div>
{ctx.data && (
<Flex className="status-container" direction={{ default: 'column' }}>
{configIssue && (
<FlexItem>
<Alert
variant={configIssue.reason === 'Error' ? AlertVariant.danger : AlertVariant.warning}
isInline
title={configIssue.reason === 'Error' ? t('Configuration error') : t('Configuration warnings')}
>
{configIssue.message}
</Alert>
</FlexItem>
)}
<FlexItem flex={{ default: 'flex_1' }}>
{isOnHold ? (
{status === 'onHold' ? (
<Alert variant={AlertVariant.info} isInline title={t('Network Observability is on hold')}>
{t(
// eslint-disable-next-line max-len
Expand Down Expand Up @@ -72,28 +112,32 @@ export const FlowCollectorStatus: FC<FlowCollectorStatusProps> = () => {
{t('Edit FlowCollector')}
</Button>
</FlexItem>
{!isOnHold && (
<FlexItem>
<FlexItem>
<Tooltip
content={t('FlowCollector must be ready to open Network Traffic')}
trigger={showTrafficButton ? 'manual' : 'mouseenter focus'}
>
<Button
id="open-network-traffic"
data-test-id="open-network-traffic"
variant="link"
onClick={() => navigate(netflowTrafficPath)}
isAriaDisabled={!showTrafficButton}
onClick={() => showTrafficButton && navigate(netflowTrafficPath)}
>
{t('Open Network Traffic page')}
</Button>
</FlexItem>
)}
</Tooltip>
</FlexItem>
</Flex>
</FlexItem>
</Flex>
)}
{ctx.loadError && (
<Flex direction={{ default: 'column' }}>
<FlexItem>
<Text>
<TextContent>
{t('An error occured while retreiving FlowCollector: {{error}}', { error: ctx.loadError })}
</Text>
</TextContent>
</FlexItem>
<FlexItem alignSelf={{ default: 'alignSelfCenter' }}>
<Button id="create-flow-collector" onClick={() => navigate(flowCollectorNewPath)}>
Expand Down
Loading
Loading