This is an OpenShift Console dynamic plugin for managing Kuadrant resources. It's built on top of the console-plugin-template but significantly extended with domain-specific functionality for gateway and policy management.
kuadrant-console-plugin/
├── src/
│ ├── components/ # React components
│ │ ├── apikey/ # API Key request and approval components
│ │ ├── apiproduct/ # API Product catalog components
│ │ ├── dnspolicy/ # DNSPolicy components and types
│ │ ├── gateway/ # Gateway components
│ │ ├── httproute/ # HTTPRoute components
│ │ ├── issuer/ # Certificate issuer components
│ │ ├── ratelimitpolicy/ # RateLimitPolicy components
│ │ ├── tlspolicy/ # TLSPolicy components
│ │ └── topology/ # Topology visualization components
│ ├── hooks/ # Custom React hooks
│ │ └── topology/ # Topology-specific hooks
│ ├── utils/ # Utility functions
│ │ └── topology/ # Topology utilities and parsers
│ └── constants/ # Configuration constants
├── charts/ # Helm chart for deployment
├── locales/ # i18n translation files
├── config/
│ └── rbac/ # RBAC role definitions
├── e2e/ # End-to-end Playwright tests
├── docs/ # Documentation and images
└── scripts/ # Build and deployment scripts
- React + TypeScript: Core UI framework
- PatternFly 6: Red Hat's design system
- OpenShift Console SDK: Dynamic plugin integration
- Kubernetes Client: For resource management
- webpack: Build tooling
- i18next: Internationalisation
The plugin manages these Custom Resource Definitions (CRDs):
- AuthPolicy (
kuadrant.io/v1) - Authentication and authorisation policies - RateLimitPolicy (
kuadrant.io/v1) - Rate limiting configurations - DNSPolicy (
kuadrant.io/v1) - DNS management for gateways - TLSPolicy (
kuadrant.io/v1) - TLS certificate configurations - Gateway (
gateway.networking.k8s.io/v1) - Kubernetes Gateway API - HTTPRoute (
gateway.networking.k8s.io/v1) - HTTP routing rules
- PlanPolicy (
extensions.kuadrant.io/v1alpha1) - Rate limiting plans with tiers - APIProduct (
devportal.kuadrant.io/v1alpha1) - Published API catalog entries - APIKeyRequest (
devportal.kuadrant.io/v1alpha1) - Consumer requests for API access - APIKey (
devportal.kuadrant.io/v1alpha1) - API key credentials for consumers - APIKeyApproval (
devportal.kuadrant.io/v1alpha1) - Approval records for API key requests
// use this pattern for real-time updates
const resource = {
groupVersionKind: { group: 'kuadrant.io', version: 'v1', kind: 'AuthPolicy' },
isList: true,
namespace: activeNamespace
};
const [data, loaded, error] = useK8sWatchResource(resource);All policy creation forms follow a similar structure:
- Toggle between Form and YAML views
- Form validation before submission
- Use
KuadrantCreateUpdatecomponent for save operations - Redirect to list view after success
const [errorAlertMsg, setErrorAlertMsg] = React.useState('');
try {
await k8sCreate({ model, data: resource });
history.push(redirectUrl);
} catch (error) {
setErrorAlertMsg(error.message);
}const accessReviews = useAccessReviews(resourceAttributes);
const canRead = accessReviews[0];The plugin supports configurable Topology and Prometheus metrics for gateway traffic monitoring. This allows the console to work with different Gateway API implementations (OpenShift 4.19+, OSSM, etc.).
Configuration is managed through:
src/utils/configLoader.ts- Configuration schema and defaultssrc/utils/metricsQueries.ts- Query utilities- Environment variables in deployment manifests
Example ENV Configuration:
TOPOLOGY_CONFIGMAP_NAME: "topology"
TOPOLOGY_CONFIGMAP_NAMESPACE: "kuadrant-system"
METRICS_WORKLOAD_SUFFIX: "-openshift-default"- KuadrantOverviewPage: Main dashboard with gateway health status
- PolicyTopologyPage: Visual representation of gateway/route/policy relationships (refactored into modular components)
CustomNode: Renders topology nodes with icons and context menusResourceFilterToolbar: Manages resource type filteringTopologyControls: Zoom/pan control buttonsuseVisualizationController: Hook managing visualisation lifecycleuseTopologyData: Hook processing ConfigMap data into nodes/edgesgraphParser: DOT string parsing and transitive edge preservation
- ResourceList: Generic component for displaying K8s resources
- KuadrantCreateUpdate: Handles create/update operations for all policy types
- APIProductsListPage: API product catalog browsing and management
- APIProductCreatePage: Form for creating/editing API products with metadata
- APIProductOverviewTab: Overview tab for API product details page
- APIProductDefinitionTab: Definition/OpenAPI spec tab for API product details
- APIProductPoliciesTab: Policies tab showing associated rate limit policies
- APIProductAPIKeysTab: API keys tab showing approved keys for the product
- APIKeyApprovalPage: Admin interface for reviewing and approving API key requests
- MyAPIKeysPage: User interface for requesting and managing API keys
The Policy Topology view is built on PatternFly React Topology and visualises relationships between Gateways, HTTPRoutes, and Kuadrant Policies.
1. View State Preservation
useTopologyDatadistinguishes between three scenarios:- Initial load: Fit to screen
- Filter changes (user interaction): Refit to screen to show newly visible nodes
- Data updates (ConfigMap changes): Preserve zoom/pan to avoid jarring view resets
- This prevents the "odd shifting" behaviour where the user's focus would unexpectedly move during topology updates
2. Resource Metadata Management
- All resource metadata (GVK, plural names,
showInTopologyByDefault) comes from the centralizedsrc/utils/resources.tsregistry - Uses static registry instead of dynamic API discovery for instant controller creation
- Single source of truth prevents inconsistencies and reduces complexity
3. Controller Lifecycle
useVisualizationControllercreates the controller once on mount usinguseRefwith initialisation flag- Controller is never recreated during the component lifecycle, preventing view resets
- Component factory and layout factory are registered once at creation time
4. State Management
- GVK mapping and selected resource types use React state (
useState), not module-level variables - Module-level object mutations don't trigger React re-renders, causing initialisation failures
- Always use state for values that affect rendering or hook dependencies
# start development server
yarn start
# build for production
yarn build
# run linting
yarn lint
# build i18n files
yarn i18n
# start local OpenShift console
yarn start-consoleThe project uses multiple testing approaches:
- TypeScript: Type safety and compile-time checks
- ESLint & Stylelint: Code quality and style enforcement
- i18n validation: Translation file consistency check via
test-frontend.sh - Playwright E2E tests: End-to-end browser testing for critical user flows
End-to-end tests are located in the e2e/ directory and use Playwright:
# Run all e2e tests
yarn test:e2e
# Run a single test file
npx playwright test --config=e2e/playwright.config.ts e2e/tests/apikey-approvals.spec.ts
# Run a specific test by name (using grep filter)
npx playwright test --config=e2e/playwright.config.ts e2e/tests/apikey-approvals.spec.ts -g "reject request with reason shows success toast"
# Setup test environment
yarn test:e2e:setup
# Teardown test environment
yarn test:e2e:teardownE2E tests cover key workflows including:
- API Product creation and management
- API Key request and approval flows
- Gateway and policy management
- Topology visualization
Every test must be tagged with exactly one of:
@smoke— fast, critical path tests; run on every PR (~11 min total). Tag tests that cover the most important user flows and are reliable.@nightly— slower or edge-case tests; run on a nightly schedule. Tag validation edge cases, duplicate coverage, and slower flows.
// smoke — runs on every PR
test('approve request', { tag: '@smoke' }, async ({ page }) => { ... })
// nightly — runs on schedule only
test('validate empty title shows error', { tag: '@nightly' }, async ({ page }) => { ... })Rules:
- Every test must have exactly one tag (
@smokeor@nightly) — untagged tests are skipped in smoke runs but do run in nightly (full suite has no--grepfilter) - When adding a new test, default to
@nightly; only use@smokefor critical, reliable flows - When adding a new spec file, add it to the mapping in
build/suite-router.sh(see below)
The suite router maps changed source files to relevant e2e spec files so PRs only run tests for the components they touch.
How it works:
- Runs
git diff origin/main...HEADto get the list of changed files - If any shared file changed (
src/utils/,src/hooks/,src/constants/,e2e/tests/helpers.ts, workflow files, etc.) → runs all@smoketests (safe fallback) - Otherwise, matches changed paths against
COMPONENT_MAP→ runs only the relevant spec files with--grep @smoke - If no mapping matches → runs all
@smoketests (safe fallback)
Fallback behaviour:
| Changed files | Tests run |
|---|---|
src/components/apikey/ |
3 apikey spec files × @smoke |
src/utils/ (shared) |
all @smoke |
| Unrecognised path | all @smoke |
When adding a new spec file, add an if block in build/suite-router.sh:
if echo "$CHANGED" | grep -qE "^src/components/myfeature/"; then
SPECS="$SPECS myfeature.spec.ts"
fiIf you forget, the suite router falls back to running all smoke tests — no tests will be skipped incorrectly, but the optimisation won't apply.
-
Real-time Updates: Always use
useK8sWatchResourceinstead ofk8sListfor resources that need to update automatically -
Gateway Health Logic: A gateway is considered healthy when it has both:
Acceptedcondition with statusTrueProgrammedcondition with statusTrue
-
Form Validation: Each policy type has specific validation rules in its form component
-
Namespace Handling: The plugin supports both single namespace and all-namespaces mode (#ALL_NS#)
-
Downstream Builds: Special tooling exists for Red Hat downstream builds (RHCL - Red Hat Connectivity Link)
- Use PatternFly CSS variables (e.g.,
var(--pf-v6-global--primary-color--100)) - Prefix custom CSS classes with plugin name to avoid conflicts
- Follow existing patterns for consistency
- Use the
useTranslationhook for all user-facing strings - Keep components focused and create sub-components when needed
IMPORTANT: Always scope CSS rules with kuadrant-specific prefixes to prevent bleeding into other parts of the console. Global selectors like .ReactVirtualized__Table__rowColumn or .pf-* classes should always be prefixed with a kuadrant container class (e.g., .kuadrant-overview-page .ReactVirtualized__Table__rowColumn). This is critical for plugin isolation and preventing unintended side effects on the host console or other plugins.
- Gateway counts not updating: Ensure using
useK8sWatchResourceinstead of one-time fetches - Form validation failing: Check that all required fields are properly validated
- RBAC errors: Verify the user has appropriate permissions for the resource type
- Build errors: Run
yarn cleanbefore building - Page layout issues in console: Don't wrap content in
Pagecomponent - OpenShift console provides the page structure - Topology view resetting on updates: Preserve zoom/pan state by checking
isInitialLoad - Dark theme text visibility: Use theme-aware CSS selectors (
.pf-theme-dark,.pf-v6-theme-dark) - Race conditions in React hooks: Use
useRefwith initialisation flags to prevent re-creation of expensive objects - Dynamic values in factory functions: Pass getter functions instead of values to ensure current state is accessed
- Topology fit-to-screen not working: Check for controller recreation due to changing dependencies
- Topology not rendering after refactor: CRITICAL - GVK mapping must be stored in React state (
useState), not module-level variables. Module-level object mutations don't trigger React re-renders, causing the controller to never initialize when the mapping is populated asynchronously
The plugin was upgraded from PatternFly 5 to PatternFly 6. Key changes include:
Text/TextContentreplaced withContentPageSectionvariant only accepts "default" or "secondary" (not "light")EmptyStatestructure simplified (EmptyStateIcon removed, header moved to props)- Modal imports moved from
@patternfly/react-core/deprecatedto@patternfly/react-core
--pf-global--→--pf-v6-global----pf-v5-→--pf-v6-.pf-v5-c-→.pf-v6-c-
- Don't use
Pagewrapper in plugin components - OpenShift console provides the page structure - Use
PageSectiondirectly as the top-level component - Removed unnecessary
Flex/FlexItemwrappers around single items
To ensure text visibility in both light and dark themes:
- Use theme-specific CSS selectors (
.pf-theme-dark,.pf-v6-theme-dark) - Apply contrasting colours: white text (#ffffff) for dark theme, dark text (#151515) for light theme
- Policy nodes use light blue backgrounds with theme-appropriate opacity
- Always test components in both light and dark themes
When planning a new feature, enhancement, or significant change:
- Interview relentlessly: Ask questions about every aspect of the plan until reaching a shared understanding with the user
- Walk the design tree: Go down each branch of the design tree, resolving dependencies between decisions one-by-one
- Ask questions one at a time: Don't overwhelm with multiple questions at once
- Provide recommendations: For each question, provide a recommended answer based on codebase patterns
- Explore first: If a question can be answered by exploring the codebase, do that instead of asking
This ensures alignment before implementation and prevents wasted effort on incorrect assumptions.
When adding new features:
- Follow existing component patterns
- Add types for new resources
- Update
console-extensions.jsonfor new routes - Add i18n keys for new strings
- Test with a local OpenShift console instance