Skip to content

Adding attached httproutes to gateway view #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
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
20 changes: 9 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@

OpenShift plugin for managing Kubernetes Gateway API resources



## Screenshots

![Overview](docs/images/overview.gif)

## Running

- Target a running OCP with `oc login`
Expand Down Expand Up @@ -110,13 +109,13 @@ with this namespace as follows:

```tsx
conster Header: React.FC = () => {
const { t } = useTranslation('plugin__kuadrant-console-plugin');
const { t } = useTranslation('plugin__gateway-api-console-plugin');
return <h1>{t('Hello, World!')}</h1>;
};
```

For labels in `console-extensions.json`, you can use the format
`%plugin__kuadrant-console-plugin~My Label%`. Console will replace the value with
`%plugin__gateway-api-console-plugin~My Label%`. Console will replace the value with
the message for the current language from the `plugin__kuadrant-console`
namespace. For example:

Expand All @@ -126,7 +125,7 @@ namespace. For example:
"properties": {
"id": "admin-demo-section",
"perspective": "admin",
"name": "%plugin__kuadrant-console-plugin~Plugin Template%"
"name": "%plugin__gateway-api-console-plugin~Plugin Template%"
}
}
```
Expand Down Expand Up @@ -165,18 +164,17 @@ Update `settings.json` (File > Preferences > Settings):
```json
"editor.formatOnSave": true
```

## Version matrix

| kuadrant-console-plugin version | PatternFly version | Openshift console version | Dynamic Plugin SDK |
|---------------------------------|--------------------|---------------------------|--------------------|
| v0.0.3 - v0.0.18 | 5 | v4.17.x | v1.6.0 |
| TBD | 5 | v4.18.x | v1.8.0 |
| TBD | 6 | v4.19.x | TBD |
| ------------------------------- | ------------------ | ------------------------- | ------------------ |
| v0.0.3 - v0.0.18 | 5 | v4.17.x | v1.6.0 |
| TBD | 5 | v4.18.x | v1.8.0 |
| TBD | 6 | v4.19.x | TBD |

Openshift console is configured to share modules with its dynamic plugins (console plugins). For more information on versions and changes to the shared modules, please see the shared modules [documentation](https://www.npmjs.com/package/@openshift-console/dynamic-plugin-sdk?activeTab=readme)



## References

- [Console Plugin SDK README](https://github.com/openshift/console/tree/master/frontend/packages/console-dynamic-plugin-sdk)
Expand Down
18 changes: 17 additions & 1 deletion console-extensions.json
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
[]
[
{
"type": "console.tab/horizontalNav",
"properties": {
"model": {
"group": "gateway.networking.k8s.io",
"version": "v1",
"kind": "Gateway"
},
"page": {
"name": "Attached",
"href": "attached"
},
"component": { "$codeRef": "GatewaySingleOverview" }
}
}
]
17 changes: 6 additions & 11 deletions downstream.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,11 @@ const replacements = {
mappings: {
// Direct string replacements for links.ts
// Order matters: specific URLs first to prevent partial matches
'https://docs.kuadrant.io/latest/kuadrant-operator/doc/user-guides/secure-protect-connect-single-multi-cluster/':
`https://docs.redhat.com/en/documentation/red_hat_connectivity_link/${version}/html-single/configuring_and_deploying_gateway_policies_with_connectivity_link/index`,
'https://docs.kuadrant.io/latest/kuadrant-operator/doc/observability/examples/':
`https://docs.redhat.com/en/documentation/red_hat_connectivity_link/${version}/html-single/connectivity_link_observability_guide/index`,
'https://docs.kuadrant.io':
`https://docs.redhat.com/en/documentation/red_hat_connectivity_link/${version}/`,
'https://github.com/Kuadrant/kuadrant-operator/releases':
`https://docs.redhat.com/en/documentation/red_hat_connectivity_link/${version}/html-single/release_notes_for_connectivity_link_${version}/index`,
'Kuadrant': 'Connectivity Link',
'https://docs.kuadrant.io/latest/kuadrant-operator/doc/user-guides/secure-protect-connect-single-multi-cluster/': `https://docs.redhat.com/en/documentation/red_hat_connectivity_link/${version}/html-single/configuring_and_deploying_gateway_policies_with_connectivity_link/index`,
'https://docs.kuadrant.io/latest/kuadrant-operator/doc/observability/examples/': `https://docs.redhat.com/en/documentation/red_hat_connectivity_link/${version}/html-single/connectivity_link_observability_guide/index`,
'https://docs.kuadrant.io': `https://docs.redhat.com/en/documentation/red_hat_connectivity_link/${version}/`,
'https://github.com/Kuadrant/kuadrant-operator/releases': `https://docs.redhat.com/en/documentation/red_hat_connectivity_link/${version}/html-single/release_notes_for_connectivity_link_${version}/index`,
Kuadrant: 'Connectivity Link',
},
},

Expand All @@ -37,7 +33,7 @@ const replacements = {
type: 'regex',
patterns: [
{
search: /%plugin__kuadrant-console-plugin~Kuadrant%/g,
search: /%plugin__gateway-api-console-plugin~Kuadrant%/g,
replace: 'Connectivity Link',
},
],
Expand All @@ -50,7 +46,6 @@ const replacements = {
},
};


function replaceSimpleStrings(filePath, mappings) {
try {
let content = fs.readFileSync(filePath, 'utf-8');
Expand Down
2 changes: 1 addition & 1 deletion i18n-scripts/i18next-parser.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module.exports = {
locales: ['en'],
namespaceSeparator: '~',
reactNamespace: false,
defaultNamespace: 'plugin__kuadrant-console-plugin',
defaultNamespace: 'plugin__gateway-api-console-plugin',
useKeysAsDefaultValue: true,

// see below for more details
Expand Down
2 changes: 1 addition & 1 deletion i18next-parser.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module.exports = {
locales: ['en'],
namespaceSeparator: '~',
reactNamespace: false,
defaultNamespace: 'plugin__kuadrant-console-plugin',
defaultNamespace: 'plugin__gateway-api-console-plugin',
useKeysAsDefaultValue: true,

// see below for more details
Expand Down
1 change: 0 additions & 1 deletion locales/en/gateway-api-console-plugin.json

This file was deleted.

19 changes: 19 additions & 0 deletions locales/en/plugin__gateway-api-console-plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"Attached Resources": "Attached Resources",
"Gateway Console Plugin": "Gateway Console Plugin",
"Name": "Name",
"Namespace": "Namespace",
"No attached routes found": "No attached routes found",
"No route resources matched": "No route resources matched",
"Some references for the HTTPRoute could not be resolved.": "Some references for the HTTPRoute could not be resolved.",
"Status": "Status",
"The Gateway configuration is accepted but not yet programmed.": "The Gateway configuration is accepted but not yet programmed.",
"The Gateway has issues and is not ready to serve traffic.": "The Gateway has issues and is not ready to serve traffic.",
"The Gateway is accepted and programmed in the data plane.": "The Gateway is accepted and programmed in the data plane.",
"The Gateway is accepted, programmed, and ready to serve traffic.": "The Gateway is accepted, programmed, and ready to serve traffic.",
"The HTTPRoute is accepted by at least one parent gateway.": "The HTTPRoute is accepted by at least one parent gateway.",
"The HTTPRoute is not accepted by any parent gateways.": "The HTTPRoute is not accepted by any parent gateways.",
"The resource is being processed.": "The resource is being processed.",
"The status of the resource is unknown.": "The status of the resource is unknown.",
"Type": "Type"
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"loadType": "Preload"
},
"exposedModules": {
"GatewaySingleOverview": "./components/GatewaySingleOverview"
},
"dependencies": {
"@console/pluginAPI": "*"
Expand Down
228 changes: 228 additions & 0 deletions src/components/AttachedResources.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { Alert, AlertGroup, EmptyState, EmptyStateBody, Title } from '@patternfly/react-core';
import {
K8sResourceKind,
ResourceLink,
useK8sWatchResources,
VirtualizedTable,
TableData,
RowProps,
TableColumn,
WatchK8sResource,
} from '@openshift-console/dynamic-plugin-sdk';
import { SearchIcon } from '@patternfly/react-icons';
import { getStatusLabel } from '../utils/statusLabel';

type AttachedResourcesProps = {
resource: K8sResourceKind;
};

const AttachedResources: React.FC<AttachedResourcesProps> = ({ resource }) => {
const { t } = useTranslation('plugin__gateway-api-console-plugin');

const associatedResources: { [key: string]: WatchK8sResource } = {
HTTPRoute: {
groupVersionKind: { group: 'gateway.networking.k8s.io', version: 'v1', kind: 'HTTPRoute' },
isList: true,
// Search cluster-wide for HTTPRoutes that might reference this gateway
},
};

const watchedResources = useK8sWatchResources<{ [key: string]: K8sResourceKind[] }>(
associatedResources,
);

const resourceGroup = resource.apiVersion.includes('/') ? resource.apiVersion.split('/')[0] : '';

const attachedRoutes = React.useMemo(() => {
let routesArray: K8sResourceKind[] = [];

// Process HTTPRoutes
const httpRoutes = watchedResources.HTTPRoute;
console.log('HTTPROUTES', httpRoutes);

if (httpRoutes?.loaded && !httpRoutes.loadError && httpRoutes.data) {
console.log('ALL HTTP ROUTES:', httpRoutes.data);
const matchingRoutes = (httpRoutes.data as K8sResourceKind[]).filter((route) => {
console.log('CHECKING ROUTE:', route.metadata.name);
console.log('ROUTE STATUS:', route.status);

const statusParents = route.status?.parents ?? [];
console.log('STATUS PARENTS FOR ROUTE', route.metadata.name, ':', statusParents);

// Also check spec.parentRefs as fallback for debugging
const specParents = route.spec?.parentRefs ?? [];
console.log('SPEC PARENTS FOR ROUTE', route.metadata.name, ':', specParents);

console.log('LOOKING FOR GATEWAY:', {
name: resource.metadata.name,
namespace: resource.metadata.namespace,
kind: resource.kind,
group: resourceGroup,
});

// Check both status.parents and spec.parentRefs for matches
const checkParentRef = (parentRef: any) => {

Check warning on line 66 in src/components/AttachedResources.tsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
if (!parentRef) return false;

const refNamespace = parentRef.namespace ?? resource.metadata.namespace;
const refGroup = parentRef.group ?? 'gateway.networking.k8s.io';
const refKind = parentRef.kind ?? 'Gateway';

const matches =
parentRef.name === resource.metadata.name &&
refNamespace === resource.metadata.namespace &&
refGroup === resourceGroup &&
refKind === resource.kind;

console.log('MATCH CHECK:', {
parentRef,
expected: {
name: resource.metadata.name,
namespace: resource.metadata.namespace,
group: resourceGroup,
kind: resource.kind,
},
actual: {
name: parentRef.name,
namespace: refNamespace,
group: refGroup,
kind: refKind,
},
matches,
});

return matches;
};

// First check status.parents
const statusMatch = statusParents.some((parent: any) => {

Check warning on line 100 in src/components/AttachedResources.tsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
console.log('CHECKING STATUS PARENT REF:', parent.parentRef);
return checkParentRef(parent.parentRef);
});

// Also check spec.parentRefs as fallback
const specMatch = specParents.some((parentRef: any) => {

Check warning on line 106 in src/components/AttachedResources.tsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
console.log('CHECKING SPEC PARENT REF:', parentRef);
return checkParentRef(parentRef);
});

return statusMatch || specMatch;
});
console.log('MATCHING ROUTES:', matchingRoutes);

routesArray = routesArray.concat(matchingRoutes);
}

return routesArray;
}, [watchedResources, resource, resourceGroup]);

const columns: TableColumn<K8sResourceKind>[] = [
{
title: t('plugin__gateway-api-console-plugin~Name'),
id: 'name',
sort: 'metadata.name',
},
{
title: t('plugin__gateway-api-console-plugin~Type'),
id: 'type',
sort: 'kind',
},
{
title: t('plugin__gateway-api-console-plugin~Namespace'),
id: 'namespace',
sort: 'metadata.namespace',
},
{
title: t('plugin__gateway-api-console-plugin~Status'),
id: 'status',
},
];

const AttachedResourceRow: React.FC<RowProps<K8sResourceKind>> = ({ obj, activeColumnIDs }) => {
const [group, version] = obj.apiVersion.includes('/')
? obj.apiVersion.split('/')
: ['', obj.apiVersion];
return (
<>
{columns.map((column) => {
switch (column.id) {
case 'name':
return (
<TableData key={column.id} id={column.id} activeColumnIDs={activeColumnIDs}>
<ResourceLink
groupVersionKind={{ group, version, kind: obj.kind }}
name={obj.metadata.name}
namespace={obj.metadata.namespace}
/>
</TableData>
);
case 'type':
return (
<TableData key={column.id} id={column.id} activeColumnIDs={activeColumnIDs}>
{obj.kind}
</TableData>
);
case 'namespace':
return (
<TableData key={column.id} id={column.id} activeColumnIDs={activeColumnIDs}>
{obj.metadata.namespace || '-'}
</TableData>
);
case 'status':
return (
<TableData key={column.id} id={column.id} activeColumnIDs={activeColumnIDs}>
{getStatusLabel(obj)}
</TableData>
);
default:
return null;
}
})}
</>
);
};

const allLoaded = Object.values(watchedResources).every((res) => res.loaded);
const loadErrors = Object.values(watchedResources)
.filter((res) => res.loadError)
.map((res) => res.loadError);
const combinedLoadError =
loadErrors.length > 0 ? new Error(loadErrors.map((err) => err.message).join('; ')) : null;

return (
<div>
{combinedLoadError && (
<AlertGroup>
<Alert title="Error loading attached resources" variant="danger" isInline>
{combinedLoadError.message}
</Alert>
</AlertGroup>
)}
{attachedRoutes.length === 0 && allLoaded ? (
<EmptyState
titleText={
<Title headingLevel="h4" size="lg">
{t('No attached routes found')}
</Title>
}
icon={SearchIcon}
>
<EmptyStateBody>{t('No route resources matched')}</EmptyStateBody>
</EmptyState>
) : (
<VirtualizedTable<K8sResourceKind>
data={attachedRoutes}
unfilteredData={attachedRoutes}
loaded={allLoaded}
loadError={combinedLoadError}
columns={columns}
Row={AttachedResourceRow}
/>
)}
</div>
);
};

export default AttachedResources;
Loading
Loading