Skip to content
Draft
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
4 changes: 2 additions & 2 deletions .mk/standalone.mk
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ start-frontend-standalone: install-frontend ## Run frontend as standalone
start-standalone: YQ build-backend install-frontend ## Run backend and frontend as standalone
$(YQ) '.server.port |= 9002 | .server.metricsPort |= 9003 | .consoleMode |= "Standalone"' ./config/sample-config.yaml > ./config/config.yaml
@echo "### Starting backend on http://localhost:9002"
bash -c "trap 'fuser -k 9002/tcp' EXIT; \
bash -c "trap 'lsof -ti tcp:9002 | xargs kill -9 2>/dev/null || true' EXIT; \
./plugin-backend $(CMDLINE_ARGS) & cd web && FLAVOR=${FLAVOR} npm run start:standalone"

.PHONY: start-standalone-mock
start-standalone-mock: YQ build-backend install-frontend ## Run backend using mocks and frontend as standalone
$(YQ) '.server.port |= 9002 | .server.metricsPort |= 9003 | .consoleMode |= "Mock"' ./config/sample-config.yaml > ./config/config.yaml
@echo "### Starting backend on http://localhost:9002 using mock"
bash -c "trap 'fuser -k 9002/tcp' EXIT; \
bash -c "trap 'lsof -ti tcp:9002 | xargs kill -9 2>/dev/null || true' EXIT; \
./plugin-backend $(CMDLINE_ARGS) & cd web && FLAVOR=${FLAVOR} npm run start:standalone"

.PHONY: just-build-frontend
Expand Down
87 changes: 68 additions & 19 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ Best practices for AI coding agents on NetObserv Web Console.

## Project Context

**NetObserv Web Console** - OpenShift Console dynamic plugin for network observability visualization (also deployable as standalone app).
**NetObserv Web Console** - OpenShift Console dynamic plugin for network
observability visualization (also deployable as standalone app).

**Stack:**
- **Frontend**: TypeScript, React 18, PatternFly 6, React Router 7, i18next 25
- **Plugin SDK**: `@openshift-console/dynamic-plugin-sdk` 4.22+ (plugin mode only)
- **Backend**: Go HTTP server for Loki queries, Kubernetes resources, Prometheus metrics
- **Plugin SDK**: `@openshift-console/dynamic-plugin-sdk` 4.22+ (plugin mode
only)
- **Backend**: Go HTTP server for Loki queries, Kubernetes resources, Prometheus
metrics

**Deployment Modes:**
- **Plugin**: Integrated into OpenShift Console (OCP 4.22+)
Expand All @@ -25,7 +28,9 @@ Best practices for AI coding agents on NetObserv Web Console.
| 4.15-4.18 | `main-pf5` | PF5 |
| ≤ 4.14 | `main-pf4` | PF4 |

See [OpenShift Console PatternFly documentation](https://github.com/openshift/console/tree/main/frontend/packages/console-dynamic-plugin-sdk#patternfly) for plugin compatibility details.
See
[OpenShift Console PatternFly documentation](https://github.com/openshift/console/tree/main/frontend/packages/console-dynamic-plugin-sdk#patternfly)
for plugin compatibility details.

**Key Directories:**
- `web/src/components/`: React components (forms, tables, topology, etc.)
Expand All @@ -44,7 +49,8 @@ See [OpenShift Console PatternFly documentation](https://github.com/openshift/co

### 🚨 OpenShift Console Plugin SDK
- Plugin mode must use `@openshift-console/dynamic-plugin-sdk` APIs
- Plugin mode must follow OpenShift Console conventions for navigation, extensions, theming
- Plugin mode must follow OpenShift Console conventions for navigation,
extensions, theming
- Standalone mode uses the same codebase but without Console integration
- Test both plugin and standalone modes

Expand All @@ -69,7 +75,9 @@ See [OpenShift Console PatternFly documentation](https://github.com/openshift/co

Be specific about file paths, existing patterns, and testing requirements.

**Good**: "Add dnslatency to ColumnsId enum in web/src/utils/columns.ts. Define in config/sample-config.yaml. Update Loki query in pkg/loki/flow_query.go. Test both modes."
**Good**: "Add dnslatency to ColumnsId enum in web/src/utils/columns.ts. Define
in config/sample-config.yaml. Update Loki query in pkg/loki/flow_query.go. Test
both modes."

**Bad**: "Add DNS latency column"

Expand All @@ -78,7 +86,8 @@ Be specific about file paths, existing patterns, and testing requirements.
2. Reference existing patterns (columns, filters, Loki queries)
3. i18n for UI strings, dual-mode testing
4. Check package.json before adding dependencies
5. Column workflow: columns.ts enum → sample-config.yaml → optional RecordField rendering
5. Column workflow: columns.ts enum → sample-config.yaml → optional RecordField
rendering

## Common Task Templates

Expand Down Expand Up @@ -111,13 +120,18 @@ FlowCollector CRD field changed in operator:
## Repository-Specific Context

### Frontend Architecture
- **Custom Hooks**: Logic extracted into focused hooks in `web/src/utils/*-hook.ts` (capabilities, URL sync, fetching, theme, storage, etc.)
- **Context**: `NetflowContext` in `web/src/model/netflow-context.ts` shares config/capabilities across components
- **Custom Hooks**: Logic extracted into focused hooks in
`web/src/utils/*-hook.ts` (capabilities, URL sync, fetching, theme, storage,
etc.)
- **Context**: `NetflowContext` in `web/src/model/netflow-context.ts` shares
config/capabilities across components
- **React Router**: v7, centralized in `web/src/utils/url.ts`

### Plugin vs Standalone Modes
- **Plugin mode**: Console integration (localhost:9001), requires Console clone for dev
- **Standalone mode**: Independent app (`make start-standalone` or `make start-standalone-mock`), build with `STANDALONE=true make images`
- **Plugin mode**: Console integration (localhost:9001), requires Console clone
for dev
- **Standalone mode**: Independent app (`make start-standalone` or
`make start-standalone-mock`), build with `STANDALONE=true make images`
- FLAVOR=`enduser` limits production standalone to Network Traffic/Health tabs

### Loki Query Optimization
Expand All @@ -126,9 +140,13 @@ FlowCollector CRD field changed in operator:
- Mock mode: `make start-standalone-mock` or `make serve-mock`

### Frontend Configuration
- **Operator-Generated (Production)**: ConfigMap from FlowCollector CR, fetched via `/api/frontend-config`
- Source: [static-frontend-config.yaml](https://github.com/netobserv/netobserv-operator/blob/main/internal/controller/consoleplugin/config/static-frontend-config.yaml) (operator repo) + FlowCollector spec
- **Critical**: Changes to `config/sample-config.yaml` frontend section MUST be synced to operator's `static-frontend-config.yaml`
- **Operator-Generated (Production)**: ConfigMap from FlowCollector CR, fetched
via `/api/frontend-config`
- Source:
[static-frontend-config.yaml](https://github.com/netobserv/netobserv-operator/blob/main/internal/controller/consoleplugin/config/static-frontend-config.yaml)
(operator repo) + FlowCollector spec
- **Critical**: Changes to `config/sample-config.yaml` frontend section MUST
be synced to operator's `static-frontend-config.yaml`
- **Development**: `config/sample-config.yaml` for local testing only

### PatternFly Components
Expand Down Expand Up @@ -158,11 +176,36 @@ Review for:

## Testing

- **Unit**: Jest 30 + React Testing Library 16 (`web/src/**/__tests__/`), Go tests (`pkg/*_test.go`)
- **Unit**: Jest 30 + React Testing Library 16 (`web/src/**/__tests__/`), Go
tests (`pkg/*_test.go`)
- **E2E**: `web/cypress/e2e/` - runs on mock data
- **Integration**: `web/cypress/integration-tests/` - requires OpenShift cluster (main branch: OCP 4.19+)
- **Integration**: `web/cypress/integration-tests/` - requires OpenShift cluster
(main branch: OCP 4.19+)
- **Run Cypress**: `make cypress` or `cd web && npm run cypress:open`

## Views Feature

Pre-configured view presets that auto-select relevant panels, columns, and
topology metrics for a specific feature in one click.

**Adding a new view preset:**
1. Add new `ViewPresetId` to the union type in `views.ts`
2. Add `ViewPreset` entry to `viewPresets[]` array with `requiredFeature`,
`panels`, `columns`, `topologyMetricType`
3. Add i18n extraction hint comment in `view-selector.tsx`
4. Run `make i18n`
5. Add unit tests in `web/src/model/__tests__/views.spec.ts`

**Column IDs in presets:**
- Use `ColumnsId` enum values for standard columns
- Use raw string IDs (e.g. `'XlatSrcAddr'`) for columns not in the enum (dynamic
config-only columns like Xlat* for packetTranslation)
- `ViewPreset.columns` is typed as `string[]` for this reason

**Local dev with features enabled:**
- Enable features in `config/sample-config.yaml` under `frontend.features`
- Restart `make start-standalone-mock` to regenerate `config/config.yaml`

## Quick Reference

**Essential Commands:**
Expand All @@ -180,12 +223,17 @@ make image-build image-push # Build and push image
**Key Files:**
- Frontend config: [web/src/model/config.ts](web/src/model/config.ts)
- API routes: [web/src/api/routes.ts](web/src/api/routes.ts)
- Loki queries: [pkg/loki/flow_query.go](pkg/loki/flow_query.go), [pkg/loki/topology_query.go](pkg/loki/topology_query.go)
- Loki queries: [pkg/loki/flow_query.go](pkg/loki/flow_query.go),
[pkg/loki/topology_query.go](pkg/loki/topology_query.go)
- Backend routes: [pkg/server/routes.go](pkg/server/routes.go)
- Backend handlers: [pkg/handler/handlers.go](pkg/handler/handlers.go)
- Table columns: [web/src/utils/columns.ts](web/src/utils/columns.ts)
- UI schema: [web/src/components/forms/config/uiSchema.ts](web/src/components/forms/config/uiSchema.ts)
- UI schema:
[web/src/components/forms/config/uiSchema.ts](web/src/components/forms/config/uiSchema.ts)
- Sample config: [config/sample-config.yaml](config/sample-config.yaml)
- Views presets: [web/src/model/views.ts](web/src/model/views.ts)
- Views selector:
[web/src/components/dropdowns/view-selector.tsx](web/src/components/dropdowns/view-selector.tsx)

## AI Workflow Example

Expand Down Expand Up @@ -215,5 +263,6 @@ Before commit:
- [README.md](README.md) - Setup, build, test, deploy, run locally
- [CONTRIBUTING.md](CONTRIBUTING.md) - Contribution guidelines

**Remember**: AI agents need clear context. Always review generated code, test thoroughly in both plugin and standalone modes, and follow project conventions.
**Remember**: AI agents need clear context. Always review generated code, test
thoroughly in both plugin and standalone modes, and follow project conventions.

4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,13 @@ endif
start: YQ build-backend install-frontend ## Run backend and frontend
$(YQ) '.server.port |= 9002 | .server.metricsPort |= 9003 | .consoleMode |= "Standalone"' ./config/sample-config.yaml > ./config/config.yaml
@echo "### Starting backend on http://localhost:9002"
bash -c "trap 'fuser -k 9002/tcp' EXIT; \
bash -c "trap 'lsof -ti tcp:9002 | xargs kill -9 2>/dev/null || true' EXIT; \
./plugin-backend $(CMDLINE_ARGS) & cd web && npm run start"

.PHONY: start-backend
start-backend: YQ build-backend
$(YQ) '.server.port |= 9002 | .server.metricsPort |= 9003 | .consoleMode |= "Standalone"' ./config/sample-config.yaml > ./config/config.yaml
bash -c "trap 'fuser -k 9002/tcp' EXIT; \
bash -c "trap 'lsof -ti tcp:9002 | xargs kill -9 2>/dev/null || true' EXIT; \
./plugin-backend $(CMDLINE_ARGS)"

.PHONY: bridge
Expand Down
39 changes: 19 additions & 20 deletions config/sample-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,14 @@ frontend:
# - heartbeat
# - endConnection
features:
# eBPF agent features
# - pktDrop
# - dnsTracking
# - flowRTT
# - multiNetworks
# - packetTranslation
# processor features
# - multiCluster
# - zones
- pktDrop
- dnsTracking
- flowRTT
- packetTranslation
- networkEvents
- tlsTracking
- udnMapping
# processor features: multiCluster, zones
portNaming:
enable: true
portNames:
Expand Down Expand Up @@ -425,7 +424,7 @@ frontend:
tooltip: Network name, such as Secondary network or UDN.
field: SrcK8S_NetworkName
filter: src_network
default: true
default: false
width: 15
feature: multiNetworks
- id: DstK8S_Name
Expand Down Expand Up @@ -566,7 +565,7 @@ frontend:
tooltip: Network name, such as Secondary network or UDN.
field: DstK8S_NetworkName
filter: dst_network
default: true
default: false
width: 15
feature: multiNetworks
- id: K8S_Name
Expand Down Expand Up @@ -687,7 +686,7 @@ frontend:
tooltip: TLS version found in handshake headers
field: TLSVersion
filter: tls_version
default: true
default: false
width: 10
feature: tlsTracking
- id: TLSCipherSuite
Expand Down Expand Up @@ -861,7 +860,7 @@ frontend:
tooltip: Time elapsed between DNS request and response.
field: DnsLatencyMs
filter: dns_latency
default: true
default: false
width: 5
feature: dnsTracking
- id: DNSResponseCode
Expand All @@ -870,7 +869,7 @@ frontend:
tooltip: DNS RCODE name from response header.
field: DnsFlagsResponseCode
filter: dns_flag_response_code
default: true
default: false
width: 5
feature: dnsTracking
- id: DNSErrNo
Expand All @@ -887,23 +886,23 @@ frontend:
tooltip: TCP Smoothed Round Trip Time (SRTT)
field: TimeFlowRttNs
filter: time_flow_rtt
default: true
default: false
width: 5
feature: flowRTT
- id: NetworkEvents
name: Network Events
tooltip: Network events flow monitor
field: NetworkEvents
filter: network_events
default: true
default: false
width: 15
feature: networkEvents
- id: XlatZoneId
group: Xlat
name: Xlat zone id
field: ZoneId
filter: xlat_zone_id
default: true
default: false
width: 5
feature: packetTranslation
- id: XlatSrcAddr
Expand All @@ -928,7 +927,7 @@ frontend:
group: Xlat
name: Xlat Src Kubernetes Object
calculated: kubeObject(XlatSrcK8S_Type,XlatSrcK8S_Namespace,XlatSrcK8S_Name,1) or concat(XlatSrcAddr,':',XlatSrcPort)
default: true
default: false
width: 15
feature: packetTranslation
- id: XlatDstAddr
Expand All @@ -953,7 +952,7 @@ frontend:
group: Xlat
name: Xlat Dst Kubernetes Object
calculated: kubeObject(XlatDstK8S_Type,XlatDstK8S_Namespace,XlatDstK8S_Name,1) or concat(XlatDstAddr,':',XlatDstPort)
default: true
default: false
width: 15
feature: packetTranslation
- id: XlatK8S_Object
Expand All @@ -968,7 +967,7 @@ frontend:
tooltip: Status of the IPsec encryption (on egress, provided by the kernel function xfrm_output) or decryption (on ingress, via xfrm_input).
field: IPSecStatus
filter: ipsec_status
default: true
default: false
width: 10
feature: ipsec
filters:
Expand Down
10 changes: 9 additions & 1 deletion web/locales/en/plugin__netobserv-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,15 @@
"S": "S",
"XS": "XS",
"None": "None",
"All Traffic": "All Traffic",
"Packet Drops": "Packet Drops",
"DNS Latency": "DNS Latency",
"Flow RTT": "Flow RTT",
"TLS Tracking": "TLS Tracking",
"UDN Mapping": "UDN Mapping",
"Network Events": "Network Events",
"Packet Translation": "Packet Translation",
"View": "View",
"Cluster metrics": "Cluster metrics",
"Bandwidth": "Bandwidth",
"Nodes": "Nodes",
Expand Down Expand Up @@ -515,7 +524,6 @@
"{{n}} Port(s)": "{{n}} Port(s)",
"{{n}} Protocol(s)": "{{n}} Protocol(s)",
"DNS latency": "DNS latency",
"Flow RTT": "Flow RTT",
"Duration": "Duration",
"Collection latency": "Collection latency",
"Flow per request limit reached, following metrics can be inaccurate. Narrow down your search or increase limit.": "Flow per request limit reached, following metrics can be inaccurate. Narrow down your search or increase limit.",
Expand Down
10 changes: 8 additions & 2 deletions web/src/components/__tests-data__/panels.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import * as _ from 'lodash';
import { getDefaultOverviewPanels, OverviewPanel } from '../../utils/overview-panels';

export const CustomPanelsSample = ['Flows', 'DnsFlows'];
export const SamplePanel = { id: 'top_avg_byte_rates', isSelected: true } as OverviewPanel;
export const DefaultPanels = getDefaultOverviewPanels().filter(p => p.isSelected);
export const ShuffledDefaultPanels: OverviewPanel[] = _.shuffle(DefaultPanels);
// Use a fixed set for modal tests to avoid shuffle-order flakiness
export const ShuffledDefaultPanels: OverviewPanel[] = [
{ id: 'top_avg_byte_rates', isSelected: true },
{ id: 'byte_rates', isSelected: true },
{ id: 'top_sankey', isSelected: true },
{ id: 'overview', isSelected: true },
{ id: 'inbound_region', isSelected: true }
];
Loading
Loading