Skip to content
Open
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: 3 additions & 1 deletion .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ parser: '@typescript-eslint/parser'
parserOptions:
ecmaFeatures:
jsx: true
ecmaVersion: 2016
ecmaVersion: 2021
sourceType: module
plugins:
- prettier
Expand All @@ -19,6 +19,8 @@ plugins:
rules:
prettier/prettier:
- error
react/react-in-jsx-scope: # Not required with React 17+
- off
settings:
react:
version: detect
22 changes: 15 additions & 7 deletions .stylelintrc.yaml
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
extends:
- stylelint-config-standard
rules:
# Disallow color names and hex colors as these don't work well with dark mode.
# Use PF global variables instead:
# https://patternfly-react-main.surge.sh/developer-resources/global-css-variables#global-css-variables
# Disallow custom colors as these don't work well with dark mode.
# Use PF semantic tokens instead:
# https://www.patternfly.org/tokens/all-patternfly-tokens
color-named: never
color-no-hex: true
# PatternFly CSS vars don't conform to stylelint's regex. Disable this rule.
custom-property-pattern: null
function-disallowed-list:
- rgb
# Disable the standard rule to allow BEM-style classnames with underscores.
- rgba
- hsl
- hsla
- oklch
- oklch
- hwb
- lab
- lch
# PatternFly CSS vars don't conform to stylelint's regex. Disable this rule.
custom-property-pattern: null
# Disable the standard rule to allow BEM-style class names with underscores.
selector-class-pattern: null
# Disallow CSS classnames prefixed with .pf- or .co- as these prefixes are
# Disallow CSS class names prefixed with .pf- or .co- as these prefixes are
# reserved by PatternFly and OpenShift console.
selector-disallowed-list:
- "*"
Expand Down
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{
"typescript.tsdk": "./node_modules/typescript/lib",
"editor.formatOnSave": false,
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need this to be an explicit option?

Does setting editor.codeActionsOnSave cause editor.formatOnSave to be enabled by default?

Copy link
Member Author

@logonoff logonoff Nov 21, 2025

Choose a reason for hiding this comment

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

We should explicitly disable formatOnSave (even overriding user workspace preferences) because it has problems with our eslint config. codeActionsOnSave is run before formatOnSave, but the formatOnSave formatting is incorrect according to the linter. This is very annoying.

"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.ts": "explicit"
},
"search.exclude": {
"**/node_modules": true
},
Expand Down
7 changes: 4 additions & 3 deletions OWNERS
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
approvers:
- christianvogt
- florkbr
- spadgett
- vojtechszocs
- jhadvig
- TheRealJon
- logonoff
- rhamilto
- spadgett
- TheRealJon
- vojtechszocs
component: Management Console
36 changes: 16 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
# OpenShift Console Plugin Template
# OpenShift console plugin template

This project is a minimal template for writing a new OpenShift Console dynamic
plugin.

[Dynamic plugins](https://github.com/openshift/console/tree/master/frontend/packages/console-dynamic-plugin-sdk)
allow you to extend the
[OpenShift UI](https://github.com/openshift/console)
[Openshift console plugins](https://github.com/openshift/console/tree/main/frontend/packages/console-dynamic-plugin-sdk)
allow you to extend the [OpenShift web console](https://github.com/openshift/console)
at runtime, adding custom pages and other extensions. They are based on
[webpack module federation](https://webpack.js.org/concepts/module-federation/).
Plugins are registered with console using the `ConsolePlugin` custom resource
and enabled in the console operator config by a cluster administrator.

Using the latest `v1` API version of `ConsolePlugin` CRD, requires OpenShift 4.12
and higher. For using old `v1alpha1` API version us OpenShift version 4.10 or 4.11.

For an example of a plugin that works with OpenShift 4.11, see the `release-4.11` branch.
For a plugin that works with OpenShift 4.10, see the `release-4.10` branch.
The `main` branch of this repository contains an example plugin which works
with the latest version. To see an example of a plugin which works with an older
version, visit the appropriate `release-4.x` branch.

[Node.js](https://nodejs.org/en/) and [yarn](https://yarnpkg.com) are required
to build and run the example. To run OpenShift console in a container, either
Expand All @@ -24,17 +21,17 @@ to build and run the example. To run OpenShift console in a container, either

## Getting started

> [!IMPORTANT]
> [!IMPORTANT]
> To use this template, **DO NOT FORK THIS REPOSITORY**! Click **Use this template**, then select
> [**Create a new repository**](https://github.com/new?template_name=networking-console-plugin&template_owner=openshift)
> [**Create a new repository**](https://github.com/new?template_name=console-plugin-template&template_owner=openshift)
> to create a new repository.
>
> ![A screenshot showing where the "Use this template" button is located](https://i.imgur.com/AhaySbU.png)
>
> **Forking this repository** for purposes outside of contributing to this repository
> **will cause issues**, as users cannot have more than one fork of a template repository
> at a time. This could prevent future users from forking and contributing to your plugin.
>
>
> Your fork would also behave like a template repository, which might be confusing for
> contributiors, because it is not possible for repositories generated from a template
> repository to contribute back to the template.
Expand Down Expand Up @@ -177,7 +174,7 @@ naming conflicts. For example, the plugin template uses the
with this namespace as follows:

```tsx
conster Header: React.FC = () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

😆 conster sounds like a const but stronger

const Header: React.FC = () => {
const { t } = useTranslation('plugin__console-plugin-template');
return <h1>{t('Hello, World!')}</h1>;
};
Expand Down Expand Up @@ -207,15 +204,14 @@ plugin template when adding or changing messages.
This project adds prettier, eslint, and stylelint. Linting can be run with
`yarn run lint`.

The stylelint config disallows hex colors since these cause problems with dark
mode (starting in OpenShift console 4.11). You should use the
[PatternFly global CSS variables](https://patternfly-react-main.surge.sh/developer-resources/global-css-variables#global-css-variables)
The stylelint config disallows defining colors since these cause problems with dark
mode. Use [PatternFly semantic tokens](https://www.patternfly.org/tokens/all-patternfly-tokens)
for colors instead.

The stylelint config also disallows naked element selectors like `table` and
`.pf-` or `.co-` prefixed classes. This prevents plugins from accidentally
overwriting default console styles, breaking the layout of existing pages. The
best practice is to prefix your CSS classnames with your plugin name to avoid
best practice is to prefix your CSS class names with your plugin name to avoid
conflicts. Please don't disable these rules without understanding how they can
break console styles!

Expand All @@ -225,10 +221,10 @@ Steps to generate reports

1. In command prompt, navigate to root folder and execute the command `yarn run cypress-merge`
2. Then execute command `yarn run cypress-generate`
The cypress-report.html file is generated and should be in (/integration-tests/screenshots) directory
The cypress-report.html file is generated and should be in (/integration-tests/screenshots) directory.

## References

- [Console Plugin SDK README](https://github.com/openshift/console/tree/master/frontend/packages/console-dynamic-plugin-sdk)
- [Console Plugin SDK README](https://github.com/openshift/console/tree/main/frontend/packages/console-dynamic-plugin-sdk)
- [Customization Plugin Example](https://github.com/spadgett/console-customization-plugin)
- [Dynamic Plugin Enhancement Proposal](https://github.com/openshift/enhancements/blob/master/enhancements/console/dynamic-plugins.md)
- [Dynamic Plugin Enhancement Proposal](https://github.com/openshift/enhancements/blob/main/enhancements/console/dynamic-plugins.md)
2 changes: 1 addition & 1 deletion console-extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"type": "console.navigation/href",
"properties": {
"id": "example",
"name": "%plugin__console-plugin-template~Plugin Example%",
"name": "%plugin__console-plugin-template~Plugin example%",
"href": "/example",
"perspective": "admin",
"section": "home"
Expand Down
5 changes: 4 additions & 1 deletion integration-tests/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import * as wp from '@cypress/webpack-preprocessor';

module.exports = (on, config) => {
module.exports = (
on: Cypress.PluginEvents,
config: Cypress.PluginConfigOptions,
): Cypress.PluginConfigOptions => {
const options = {
webpackOptions: {
resolve: {
Expand Down
8 changes: 5 additions & 3 deletions integration-tests/tests/example-page.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { checkErrors } from '../support';

const PLUGIN_TEMPLATE_NAME = 'console-plugin-template';
const PLUGIN_TEMPLATE_PULL_SPEC = Cypress.env('PLUGIN_TEMPLATE_PULL_SPEC');
export const isLocalDevEnvironment = Cypress.config('baseUrl').includes('localhost');
// We know that the baseUrl is always set because it's in the cypress config
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
export const isLocalDevEnvironment = Cypress.config('baseUrl')!.includes('localhost');

export const guidedTour = {
close: () => {
Expand Down Expand Up @@ -87,8 +89,8 @@ describe('Console plugin template test', () => {

it('Verify the example page title', () => {
cy.get('[data-quickstart-id="qs-nav-home"]').click();
cy.get('[data-test="nav"]').contains('Plugin Example').click();
cy.get('[data-test="nav"]').contains('Plugin example').click();
cy.url().should('include', '/example');
cy.get('[data-test="example-page-title"]').should('contain', 'Hello, Plugin!');
cy.get('title').should('contain', 'Hello, plugin!');
});
});
14 changes: 5 additions & 9 deletions locales/en/plugin__console-plugin-template.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
{
"After cloning this project, replace references to": "After cloning this project, replace references to",
"and other plugin metadata in package.json with values for your plugin.": "and other plugin metadata in package.json with values for your plugin.",
"console-template-plugin": "console-template-plugin",
"exposedModules": "exposedModules",
"Hello, Plugin!": "Hello, Plugin!",
"in package.json mapping the reference to the module.": "in package.json mapping the reference to the module.",
"Plugin Example": "Plugin Example",
"After cloning this project, replace references to <1>console-template-plugin</1> and other plugin metadata in package.json with values for your plugin.": "After cloning this project, replace references to <1>console-template-plugin</1> and other plugin metadata in package.json with values for your plugin.",
"Hello, plugin!": "Hello, plugin!",
"Plugin example": "Plugin example",
"Success!": "Success!",
"This is a custom page contributed by the console plugin template. The extension that adds the page is declared in console-extensions.json in the project root along with the corresponding nav item. Update console-extensions.json to change or add extensions. Code references in console-extensions.json must have a corresponding property": "This is a custom page contributed by the console plugin template. The extension that adds the page is declared in console-extensions.json in the project root along with the corresponding nav item. Update console-extensions.json to change or add extensions. Code references in console-extensions.json must have a corresponding property",
"This is a custom page contributed by the console plugin template. The extension that adds the page is declared in console-extensions.json in the project root along with the corresponding nav item. Update console-extensions.json to change or add extensions. Code references in console-extensions.json must have a corresponding property <2>exposedModules</2> in package.json mapping the reference to the module.": "This is a custom page contributed by the console plugin template. The extension that adds the page is declared in console-extensions.json in the project root along with the corresponding nav item. Update console-extensions.json to change or add extensions. Code references in console-extensions.json must have a corresponding property <2>exposedModules</2> in package.json mapping the reference to the module.",
"Your plugin is working.": "Your plugin is working."
}
}
61 changes: 29 additions & 32 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,49 +24,53 @@
"webpack": "node -r ts-node/register ./node_modules/.bin/webpack"
},
"devDependencies": {
"@cypress/webpack-preprocessor": "^5.15.5",
"@openshift-console/dynamic-plugin-sdk": "1.4.0",
"@openshift-console/dynamic-plugin-sdk-webpack": "1.1.1",
"@babel/core": "^7.28.5",
"@babel/preset-env": "^7.28.5",
"@cypress/webpack-preprocessor": "^7.0.2",
"@openshift-console/dynamic-plugin-sdk": "4.20.0",
"@openshift-console/dynamic-plugin-sdk-webpack": "4.20.0",
"@patternfly/react-core": "^6.2.2",
"@patternfly/react-icons": "^6.2.2",
"@patternfly/react-table": "^6.2.2",
"@types/node": "^18.0.0",
"@types/node": "^22.0.0",
"@types/react": "^17.0.37",
"@types/react-helmet": "^6.1.4",
"@types/react-router-dom": "^5.3.2",
"@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/parser": "^5.14.0",
"copy-webpack-plugin": "^6.4.1",
"css-loader": "^6.7.1",
"cypress": "^12.17.4",
"@types/react-router-dom": "^5.3.3",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"babel-loader": "^10.0.0",
"copy-webpack-plugin": "^13.0.1",
"css-loader": "^7.1.2",
"cypress": "^14.2.1",
"cypress-multi-reporters": "^1.6.2",
"eslint": "^8.10.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.1",
"i18next-parser": "^3.11.0",
"mocha-junit-reporter": "^2.2.0",
"mochawesome": "^7.1.3",
"i18next": "^23.11.5",
"i18next-parser": "^9.3.0",
"mocha": "^10.2.0",
"mocha-junit-reporter": "^2.2.1",
"mochawesome": "^7.1.4",
"mochawesome-merge": "^4.3.0",
"pluralize": "^8.0.0",
"prettier": "^2.7.1",
"prettier-stylelint": "^0.4.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-helmet": "^6.1.0",
"react-i18next": "^11.7.3",
"react-router": "5.3.x",
"react-router-dom": "5.3.x",
"style-loader": "^3.3.1",
"stylelint": "^15.3.0",
"stylelint-config-standard": "^31.0.0",
"ts-loader": "^9.3.1",
"ts-node": "^10.8.1",
"typescript": "^4.7.4",
"webpack": "5.75.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.7.4"
"react-router-dom-v5-compat": "^6.11.2",
"style-loader": "^4.0.0",
"stylelint": "^16.25.0",
"stylelint-config-standard": "^39.0.1",
"ts-loader": "^9.5.4",
"ts-node": "^10.9.2",
"typescript": "^5.9.3",
"webpack": "^5.75.0",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.2"
},
"consolePlugin": {
"name": "console-plugin-template",
Expand All @@ -77,14 +81,7 @@
"ExamplePage": "./components/ExamplePage"
},
"dependencies": {
"@console/pluginAPI": "*"
"@console/pluginAPI": "^4.19.0"
Copy link
Contributor

Choose a reason for hiding this comment

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

This should encourage real world plugins to utilize the @console/pluginAPI dependency 👍

}
},
"peerDependencies": {
"@babel/core": "^7.0.1",
"@babel/preset-env": "^7.0.0",
"babel-loader": "^8.0.2",
"i18next": "^23.11.5",
"mocha": "^10.5.1"
}
}
34 changes: 16 additions & 18 deletions src/components/ExamplePage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as React from 'react';
import Helmet from 'react-helmet';
import { useTranslation } from 'react-i18next';
import { Content, PageSection, Title } from '@patternfly/react-core';
import { DocumentTitle, ListPageHeader } from '@openshift-console/dynamic-plugin-sdk';
import { Trans, useTranslation } from 'react-i18next';
import { Content, PageSection } from '@patternfly/react-core';
import { CheckCircleIcon } from '@patternfly/react-icons';
import './example.css';

Expand All @@ -10,12 +9,8 @@ export default function ExamplePage() {

return (
<>
<Helmet>
<title data-test="example-page-title">{t('Hello, Plugin!')}</title>
</Helmet>
<PageSection>
<Title headingLevel="h1">{t('Hello, Plugin!')}</Title>
</PageSection>
<DocumentTitle>{t('Hello, plugin!')}</DocumentTitle>
<ListPageHeader title={t('Hello, plugin!')} />
<PageSection>
<Content component="p">
<span className="console-plugin-template__nice">
Expand All @@ -24,16 +19,19 @@ export default function ExamplePage() {
{t('Your plugin is working.')}
</Content>
<Content component="p">
{t(
'This is a custom page contributed by the console plugin template. The extension that adds the page is declared in console-extensions.json in the project root along with the corresponding nav item. Update console-extensions.json to change or add extensions. Code references in console-extensions.json must have a corresponding property',
)}
<code>{t('exposedModules')}</code>{' '}
{t('in package.json mapping the reference to the module.')}
<Trans t={t}>
This is a custom page contributed by the console plugin template. The extension that
adds the page is declared in console-extensions.json in the project root along with the
corresponding nav item. Update console-extensions.json to change or add extensions. Code
references in console-extensions.json must have a corresponding property{' '}
<code>exposedModules</code> in package.json mapping the reference to the module.
</Trans>
</Content>
<Content component="p">
{t('After cloning this project, replace references to')}{' '}
<code>{t('console-template-plugin')}</code>{' '}
{t('and other plugin metadata in package.json with values for your plugin.')}
<Trans t={t}>
After cloning this project, replace references to <code>console-template-plugin</code>{' '}
and other plugin metadata in package.json with values for your plugin.
</Trans>
</Content>
</PageSection>
</>
Expand Down
6 changes: 3 additions & 3 deletions src/components/example.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* Prefixing your CSS classes with your plugin name is a best practice to avoid
* collisions with other plugin styles. */
.console-plugin-template__nice {
/* Use PF global vars for colors to support dark mode in OpenShift 4.11.
* https://patternfly-react-main.surge.sh/developer-resources/global-css-variables */
color: var(--pf-global--palette--blue-400);
/* Use PatternFly semantic tokens for colors to support theming.
* https://www.patternfly.org/tokens/all-patternfly-tokens */
color: var(--pf-t--global--color--brand--default);
}
Loading