├── .dependabot └── config.yml ├── .github ├── CODEOWNERS └── pull_request_template.md ├── .gitignore ├── .gitmodules ├── .tekton ├── frontend-starter-app-pull-request.yaml └── frontend-starter-app-push.yaml ├── ARCHITECTURE.md ├── LICENSE ├── README.md ├── build_deploy.sh ├── config ├── empty.js └── jest.setup.ts ├── deploy └── frontend.yaml ├── docs ├── frontend-operator │ ├── basic-configuration.md │ ├── bundle-segment-position.svg │ ├── feo-sections.svg │ ├── index.md │ ├── local-development.md │ ├── modules.md │ ├── navigation.md │ ├── pre-requisites.md │ ├── search.md │ └── services.md └── index.md ├── eslint.config.js ├── fec.config.js ├── jest.config.js ├── mkdocs.yaml ├── package-lock.json ├── package.json ├── pr_check.sh ├── src ├── App.scss ├── App.tsx ├── AppEntry.tsx ├── Components │ ├── AppLink │ │ └── index.tsx │ └── SampleComponent │ │ ├── sample-component.scss │ │ ├── sample-component.test.tsx │ │ └── sample-component.tsx ├── Routes │ ├── NoPermissionsPage │ │ └── NoPermissionsPage.tsx │ ├── OopsPage │ │ └── OopsPage.tsx │ └── SamplePage │ │ ├── SamplePage.tsx │ │ └── sample-page.scss ├── Routing.tsx ├── entry.ts ├── index.html └── utils │ └── utils.ts └── tsconfig.json /.dependabot/config.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | update_configs: 3 | - package_manager: "javascript" 4 | directory: "/" 5 | update_schedule: "live" 6 | default_reviewers: 7 | - "karelhala" 8 | allowed_updates: 9 | - match: 10 | dependency_name: "@redhat-cloud-services/frontend*" 11 | - match: 12 | dependency_name: "@patternfly/*" 13 | dependency_type: "direct" 14 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This is a comment. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in 5 | # the repo. Unless a later match takes precedence. 6 | * @RedHatInsights/experience-ui-committers 7 | * @RedHatInsights/experience-services-committers 8 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | 4 | 5 | 6 | description text... 7 | 8 | [RHCLOUDXXXX](https://issues.redhat.com/browse/RHCLOUDXXXX) 9 | 10 | --- 11 | 12 | ### Screenshots 13 | 14 | 15 | 16 | #### Before: 17 | 18 | 19 | #### After: 20 | 21 | 22 | --- 23 | 24 | ### Checklist ☑️ 25 | - [ ] PR only fixes one issue or story 26 | - [ ] Change reviewed for extraneous code 27 | - [ ] UI best practices adhered to 28 | - [ ] Commits squashed and meaningfully named 29 | - [ ] All PR checks pass locally (build, lint, test, E2E) 30 | 31 | ## 32 | - [ ] _(Optional) QE: Needs QE attention (OUIA changed, perceived impact to tests, no test coverage)_ 33 | - [ ] _(Optional) QE: Has been mentioned_ 34 | - [ ] _(Optional) UX: Needs UX attention (end user UX modified, missing designs)_ 35 | - [ ] _(Optional) UX: Has been mentioned_ 36 | ## 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # vscode config 4 | .vscode/* 5 | 6 | 7 | # ide config 8 | .idea/ 9 | 10 | # dependencies 11 | node_modules 12 | 13 | # production 14 | dist 15 | 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | 20 | .DS_Store 21 | coverage 22 | 23 | # cache folder 24 | .cache 25 | .swc 26 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "build-tools"] 2 | path = build-tools 3 | url = https://github.com/RedHatInsights/insights-frontend-builder-common.git 4 | -------------------------------------------------------------------------------- /.tekton/frontend-starter-app-pull-request.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tekton.dev/v1 2 | kind: PipelineRun 3 | metadata: 4 | annotations: 5 | build.appstudio.openshift.io/repo: https://github.com/RedHatInsights/frontend-starter-app?rev={{revision}} 6 | build.appstudio.redhat.com/commit_sha: '{{revision}}' 7 | build.appstudio.redhat.com/pull_request_number: '{{pull_request_number}}' 8 | build.appstudio.redhat.com/target_branch: '{{target_branch}}' 9 | pipelinesascode.tekton.dev/max-keep-runs: "3" 10 | pipelinesascode.tekton.dev/on-cel-expression: event == "pull_request" && target_branch 11 | == "master" 12 | pipelinesascode.tekton.dev/pipeline: https://github.com/RedHatInsights/konflux-pipelines/raw/main/pipelines/platform-ui/docker-build-run-unit-tests.yaml 13 | creationTimestamp: null 14 | labels: 15 | appstudio.openshift.io/application: frontend-starter-app 16 | appstudio.openshift.io/component: frontend-starter-app 17 | pipelines.appstudio.openshift.io/type: build 18 | name: frontend-starter-app-on-pull-request 19 | namespace: rh-platform-experien-tenant 20 | spec: 21 | params: 22 | - name: git-url 23 | value: '{{source_url}}' 24 | - name: revision 25 | value: '{{revision}}' 26 | - name: output-image 27 | value: quay.io/redhat-user-workloads/rh-platform-experien-tenant/frontend-starter-app/frontend-starter-app:on-pr-{{revision}} 28 | - name: image-expires-after 29 | value: 5d 30 | - name: dockerfile 31 | value: build-tools/Dockerfile 32 | - name: path-context 33 | value: . 34 | - name: unit-tests-script 35 | value: | 36 | #!/bin/bash 37 | set -ex 38 | 39 | npm install 40 | npm run lint 41 | npm test -- --runInBand --no-cache 42 | pipelineRef: 43 | name: docker-build 44 | taskRunTemplate: 45 | serviceAccountName: build-pipeline-frontend-starter-app 46 | workspaces: 47 | - name: workspace 48 | volumeClaimTemplate: 49 | metadata: 50 | creationTimestamp: null 51 | spec: 52 | accessModes: 53 | - ReadWriteOnce 54 | resources: 55 | requests: 56 | storage: 1Gi 57 | status: {} 58 | - name: git-auth 59 | secret: 60 | secretName: '{{ git_auth_secret }}' 61 | status: {} 62 | -------------------------------------------------------------------------------- /.tekton/frontend-starter-app-push.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tekton.dev/v1 2 | kind: PipelineRun 3 | metadata: 4 | annotations: 5 | build.appstudio.openshift.io/repo: https://github.com/RedHatInsights/frontend-starter-app?rev={{revision}} 6 | build.appstudio.redhat.com/commit_sha: '{{revision}}' 7 | build.appstudio.redhat.com/target_branch: '{{target_branch}}' 8 | pipelinesascode.tekton.dev/max-keep-runs: "3" 9 | pipelinesascode.tekton.dev/on-cel-expression: event == "push" && target_branch 10 | == "master" 11 | pipelinesascode.tekton.dev/pipeline: https://github.com/RedHatInsights/konflux-pipelines/raw/main/pipelines/platform-ui/docker-build-run-unit-tests.yaml 12 | creationTimestamp: null 13 | labels: 14 | appstudio.openshift.io/application: frontend-starter-app 15 | appstudio.openshift.io/component: frontend-starter-app 16 | pipelines.appstudio.openshift.io/type: build 17 | name: frontend-starter-app-on-push 18 | namespace: rh-platform-experien-tenant 19 | spec: 20 | params: 21 | - name: git-url 22 | value: '{{source_url}}' 23 | - name: revision 24 | value: '{{revision}}' 25 | - name: output-image 26 | value: quay.io/redhat-user-workloads/rh-platform-experien-tenant/frontend-starter-app/frontend-starter-app:{{revision}} 27 | - name: dockerfile 28 | value: build-tools/Dockerfile 29 | - name: path-context 30 | value: . 31 | - name: unit-tests-script 32 | value: | 33 | #!/bin/bash 34 | set -ex 35 | 36 | npm install 37 | npm run lint 38 | npm test -- --runInBand --no-cache 39 | pipelineRef: 40 | name: docker-build 41 | taskRunTemplate: 42 | serviceAccountName: build-pipeline-frontend-starter-app 43 | workspaces: 44 | - name: workspace 45 | volumeClaimTemplate: 46 | metadata: 47 | creationTimestamp: null 48 | spec: 49 | accessModes: 50 | - ReadWriteOnce 51 | resources: 52 | requests: 53 | storage: 1Gi 54 | status: {} 55 | - name: git-auth 56 | secret: 57 | secretName: '{{ git_auth_secret }}' 58 | status: {} 59 | -------------------------------------------------------------------------------- /ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # Architecture 2 | 3 | This repo's source code builds a static React app to be served on https://cloud.redhat.com. 4 | 5 | The React app bundled using [Webpack](https://webpack.js.org) includes: 6 | - [@patternfly/react-core](https://github.com/patternfly/patternfly-react) as the component library 7 | - A [react-router-dom BrowserRouter](https://reacttraining.com/react-router/web/api/BrowserRouter) for routing pages 8 | - Uses the HTML5 history API (pushState, replaceState and the popstate event) to keep UI in sync with the URL 9 | - @redhat-cloud-services/frontend-components-notifications/redux is provided for chromed notifications 10 | - [React.lazy and React.Suspense](https://reactjs.org/docs/code-splitting.html#reactlazy) for asynchronously loading components 11 | 12 | These assets are loaded via [Insights chrome](https://github.com/RedHatInsights/insights-chrome) which provides user auth, top and side nav (aka chroming), and a `
` to inject into. 13 | 14 | ## Webpack 15 | 16 | This repo uses a [shared common config](https://www.npmjs.com/package/@redhat-cloud-services/frontend-components-config) with sensible defaults to build and run your application. 17 | 18 | This repo uses [federated modules](https://webpack.js.org/concepts/module-federation/) to seamlessly load multiple applications at runtime. 19 | 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # frontend-starter-app 2 | 3 | React.js starter app for Red Hat Hybrid cloud console UI modules that includes Patternfly and shared Red Hat cloud service frontend components. 4 | 5 | ## Initial etc/hosts setup 6 | 7 | In order to access the https://[env].foo.redhat.com in your browser, you have to add entries to your `/etc/hosts` file. This is a **one-time** setup that has to be done only once (unless you modify hosts) on each devel machine. 8 | 9 | Best way is to edit manually `/etc/hosts` on your localhost line: 10 | 11 | ``` 12 | 127.0.0.1 localhost prod.foo.redhat.com stage.foo.redhat.com 13 | ``` 14 | 15 | Alternatively you can do this by running following command: 16 | ```bash 17 | npm run patch:hosts 18 | ``` 19 | 20 | If this command throws an error run it as a `sudo`: 21 | ```bash 22 | sudo npm run patch:hosts 23 | ``` 24 | 25 | ## Getting started 26 | 27 | 1. ```npm install``` 28 | 29 | 2. ```npm run start``` 30 | 1. If you are running the [chrome-service-backend](https://github.com/RedHatInsights/chrome-service-backend) locally, set the environment variable `CHROME_SERVICE` to the port that it is listening on (by default `8000`). For example, `CHROME_SERVICE=8000 npm run start`. 31 | 32 | 3. Open browser in URL listed in the terminal output 33 | 34 | Update `appUrl` string inside `fec.config.js` according to your application URL. [Read more](http://front-end-docs-insights.apps.ocp4.prod.psi.redhat.com/ui-onboarding/fec-binary#TODO:documentalloptions). 35 | 36 | ### Frontend operator 37 | 38 | HCC uses OpenShift frontend operator to collect metadata about individual UI modules and creates environment based UI configuration that is used by the Chrome UI shell application to construct the frontend. 39 | 40 | To learn about the operator and its configuration follow [this link](./docs/frontend-operator/index.md) 41 | 42 | ### Testing 43 | 44 | `npm run verify` will run `npm run lint` (eslint) and `npm test` (Jest) 45 | 46 | -------------------------------------------------------------------------------- /build_deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # -------------------------------------------- 4 | # Export vars for helper scripts to use 5 | # -------------------------------------------- 6 | # name of app-sre "application" folder this component lives in; needs to match for quay 7 | export COMPONENT="frontend-starter-app" 8 | # Needs to match the quay repo name set by app.yaml in app-interface 9 | export IMAGE="quay.io/cloudservices/frontend-starter-app" 10 | export WORKSPACE=${WORKSPACE:-$APP_ROOT} # if running in jenkins, use the build's workspace 11 | export APP_ROOT=$(pwd) 12 | COMMON_BUILDER=https://raw.githubusercontent.com/RedHatInsights/insights-frontend-builder-common/master 13 | 14 | set -exv 15 | # source is preferred to | bash -s in this case to avoid a subshell 16 | source <(curl -sSL $COMMON_BUILDER/src/frontend-build.sh) 17 | BUILD_RESULTS=$? 18 | 19 | # Stubbed out for now 20 | mkdir -p $WORKSPACE/artifacts 21 | cat << EOF > $WORKSPACE/artifacts/junit-dummy.xml 22 | 23 | 24 | 25 | EOF 26 | 27 | # teardown_docker 28 | exit $BUILD_RESULTS 29 | -------------------------------------------------------------------------------- /config/empty.js: -------------------------------------------------------------------------------- 1 | // Used as an empty module to save bundle size 2 | module.exports = {}; 3 | -------------------------------------------------------------------------------- /config/jest.setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/jest-globals'; 2 | -------------------------------------------------------------------------------- /deploy/frontend.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/RedHatInsights/frontend-components/refs/heads/master/packages/config-utils/src/feo/spec/frontend-crd.schema.json 2 | --- 3 | apiVersion: v1 4 | kind: Template 5 | metadata: 6 | name: frontend-starter-app 7 | objects: 8 | - apiVersion: cloud.redhat.com/v1alpha1 9 | kind: Frontend 10 | metadata: 11 | name: frontend-starter-app 12 | spec: 13 | feoConfigEnabled: true 14 | serviceTiles: 15 | - id: starter-link 16 | section: automation 17 | group: ansible 18 | title: Starter app FEO integration testing tile 19 | href: /staging/starter 20 | description: This tile will not show in production and exists only for integration testing purposes 21 | icon: AnsibleIcon 22 | searchEntries: 23 | - id: "starter" 24 | title: "Starter app FEO testing link" 25 | href: /staging/foo/starter 26 | description: "An experimental link to test FEO features integration" 27 | alt_title: 28 | - Starter 29 | - Starter app 30 | bundleSegments: 31 | - segmentId: starter-app-two 32 | bundleId: staging 33 | position: 200 34 | navItems: 35 | - id: frontend-starter-app-two 36 | title: Starter app link two 37 | href: /staging/foo/starter 38 | - segmentId: starter-app 39 | bundleId: staging 40 | position: 100 41 | navItems: 42 | - id: frontend-starter-app 43 | title: Starter app link 44 | href: /staging/starter 45 | API: 46 | versions: 47 | - v1 48 | envName: ${ENV_NAME} 49 | title: Starter App 50 | deploymentRepo: https://github.com/RedHatInsights/frontend-starter-app 51 | frontend: 52 | paths: 53 | - /apps/frontend-starter-app 54 | image: ${IMAGE}:${IMAGE_TAG} 55 | 56 | module: 57 | manifestLocation: '/apps/frontend-starter-app/fed-mods.json' 58 | modules: 59 | - id: 'overview' 60 | module: './RootApp' 61 | routes: 62 | - pathname: /staging/starter 63 | - id: 'feo-hidden-route' 64 | module: './RootApp' 65 | routes: 66 | - pathname: /staging/foo/starter 67 | 68 | parameters: 69 | - name: ENV_NAME 70 | required: true 71 | - name: IMAGE_TAG 72 | required: true 73 | - name: IMAGE 74 | value: quay.io/cloudservices/frontend-starter-app 75 | -------------------------------------------------------------------------------- /docs/frontend-operator/basic-configuration.md: -------------------------------------------------------------------------------- 1 | # Basic configuration 2 | 3 | This configuration ensures the frontend module is properly deployed to the cluster. Only attributes that can be changed from the starter app template are described. 4 | 5 | ## Basic API 6 | 7 | The following section describes the individual attributes available for customization. Attributes that have a set value are not described. 8 | 9 | ### **`metadata.name`** 10 | *string* 11 | 12 | A unique name for your frontend template. It has to be changed after creating a new module from the template. It is recommended not to change it during the project lifecycle even if the project is renamed. 13 | 14 | ### **`objects`** 15 | *array* 16 | 17 | Frontend resource specification. Even though defined as an array, only one item is allowed. 18 | 19 | ### **`objects[0].metadata.name`** 20 | *string* 21 | 22 | A unique name for your frontend resource. It can be the same as the `metadata.name` value. 23 | 24 | ### **`objects[0].spec.frontend.paths`** 25 | *array* 26 | 27 | Define a network pathname for the static assets. Multiple pathnames can be defined. In HCC, the pathname is always prefixed with `/app`. 28 | 29 | ```yaml 30 | objects: 31 | - spec: 32 | frontend: 33 | paths: 34 | - /apps/ui-module 35 | ``` 36 | 37 | ### **`objects[0].spec.feoConfigEnabled`** 38 | *bool* 39 | *(optional)* 40 | 41 | Enables Chrome UI configuration generation for the Frontend resource. 42 | 43 | #### **`akamaiCacheBustPaths`** 44 | *array* 45 | *(optional)* 46 | 47 | Additional configuration for CDN cache refresh. By default only the manifest files are refresh after deployment. 48 | 49 | ```yaml 50 | objects: 51 | - spec: 52 | akamaiCacheBustPaths: 53 | - '/apps/foo/bar' 54 | - '/apps/foo/baz.css' 55 | ``` 56 | -------------------------------------------------------------------------------- /docs/frontend-operator/bundle-segment-position.svg: -------------------------------------------------------------------------------- 1 | Bundle navigationFrontend A CRDFrontend B CRDSegment B-1Position 200Segment A-1Position 100Segment A-2Position 300 -------------------------------------------------------------------------------- /docs/frontend-operator/index.md: -------------------------------------------------------------------------------- 1 | # Frontend operator 2 | 3 | This documentation does not include technical details about the operator itself. If you are interested in the operator, rather than the integration of HCC UI modules with it, please follow [this link](https://github.com/RedHatInsights/frontend-operator). 4 | 5 | ## Pre requisites 6 | 7 | Please check the [pre-requisites section](./pre-requisites.md) first. 8 | 9 | ## Migration guide 10 | If your UI modules do not leverage the FEO integration features, please follow the [migration guide](https://github.com/RedHatInsights/chrome-service-backend/blob/main/docs/feo-migration-guide.md). 11 | 12 | ## Why? 13 | 14 | Having the Chroming UI configuration defined in a centralized project is no longer viable solution. Though working and effective, centralized configuration is not efficient. In order to further scale the number of UI modules, the configuration of UI features has to be moved directly to the UI modules repositories. 15 | 16 | Moving the UI module configuration directly to the source repositories, will enable individual teams to control and maintain relevant configuration, enabling self servicing the changes. 17 | 18 | It will also offload significant work from the platform team and thus decreasing the deployment time of Chrome UI configuration changes. 19 | 20 | The frontend operator provides the exact tooling necessary to decentralized the configuration and generate necessary data for the Chrome UI to operate in normal manner. 21 | 22 | ## Frontend resource 23 | 24 | Each frontend module must have a k8s template that defines the Frontend resource. The Frontend resource is used by the operator to not only create deployments but also collect important data that are used to construct the HCC frontend and enhance the user experience. 25 | 26 | The Frontend resource is by default located in the [`deploy/frontend.yaml` file](../../deploy/frontend.yaml). 27 | 28 | ## Resource scheme 29 | 30 | If your IDE supports validating yaml files from a json schema, you can reference it on this link: 31 | 32 | ```bash 33 | https://raw.githubusercontent.com/RedHatInsights/frontend-components/refs/heads/master/packages/config-utils/src/feo/spec/frontend-crd.schema.json 34 | ``` 35 | For VSCode, you can add a following line to the top of the yaml 36 | 37 | ```yaml 38 | # yaml-language-server: $schema=https://raw.githubusercontent.com/RedHatInsights/frontend-components/refs/heads/master/packages/config-utils/src/feo/spec/frontend-crd.schema.json 39 | ``` 40 | 41 | ## Documentation topics 42 | - [Pre requisites](./pre-requisites.md) 43 | - [Deployment configuration](./basic-configuration.md) 44 | - [UI module routing configuration](./modules.md) 45 | - [Main navigation](./navigation.md) 46 | - [Search](./search.md) 47 | - [Services tiles](./services.md) 48 | - [Local development](./local-development.md) 49 | -------------------------------------------------------------------------------- /docs/frontend-operator/local-development.md: -------------------------------------------------------------------------------- 1 | # Local development 2 | 3 | When using the FEC binary or the webpack development proxy directly (see specified versions in [Pre requisites](./pre-requisites.md)), the configuration that appears in [`deploy/frontend.yaml` file](../../deploy/frontend.yaml) will automatically be intercepted and substituted when running the development server locally. This automatic interception includes modules, search results, service tiles, and navigation. 4 | 5 | `[fec] Info: Watching frontend CRC file for changes` when running the FEC utility indicates the file is being watched and the necessary version of FEC is in use. 6 | 7 | `[fec] Info: Frontend CRD has changed, reloading the file` when changes are made and saved to the [`deploy/frontend.yaml` file](../../deploy/frontend.yaml) indicates the new values will be used (page refresh may be required). 8 | -------------------------------------------------------------------------------- /docs/frontend-operator/modules.md: -------------------------------------------------------------------------------- 1 | # UI module configuration 2 | 3 | This configuration is used to initialize the Chrome UI remote module registry and routing structure. If a UI module does not have this configuration, it will never be rendered via Chrome UI as it will not know of its existence. 4 | 5 | The configuration is located under the **`objects[0].spec.module`** property in the `frontend.yaml` file. 6 | 7 | ## **`objects[0].spec.module`** 8 | 9 | Configures the module registry and HCC browser routing. 10 | 11 | ### **`manifest-location`** 12 | 13 | Location of the main module federation metadata file. This is used to initialize the Scalprum module registry. 14 | 15 | ### **`modules`** 16 | *array* 17 | *(optional)* 18 | 19 | Module routing configuration. Each Frontend can expose multiple modules on multiple routes. Frontends don't request to have exposed routes. Their modules can be used directly in other Frontends. 20 | 21 | ### **`modules[].id`** 22 | *string* 23 | 24 | Unique identifier of the exposed UI module. 25 | 26 | ### **`modules[].module`** 27 | *string* 28 | 29 | The name of the federated module to be used on routes. By default, the name is `./RootApp`. Any exposed module defined in `fec.config.js` can be used as a route. 30 | 31 | ### **`modules[].routes`** 32 | *array* 33 | 34 | An array of routes to which the federated modules should be injected. A route has multiple attributes to help configure each route. 35 | 36 | ### **`modules[].routes[].pathname`** 37 | *string* 38 | 39 | The frontend pathname on which the defined module should be rendered. 40 | 41 | ### **`modules[].routes[].exact`** 42 | *bool* 43 | *(optional)* 44 | 45 | Mark the pathname as exact. The Chrome router will not match any nested routes of this module. 46 | 47 | ```yaml 48 | objects: 49 | - spec: 50 | module: 51 | modules: 52 | routes: 53 | - pathname: /foo/bar 54 | exact: true 55 | 56 | # Chrome UI will only match /foo/bar pathname 57 | # Chrome UI will not match /foo/bar/* -> /foo/bar/baz 58 | ``` 59 | 60 | ### **`modules[].routes[].props`** 61 | *object* 62 | *(optional)* 63 | 64 | Props to be injected to the React component. Useful when single module is re-used in multiple routes but requires additional context to properly initialize the route. 65 | 66 | ### **`modules[].routes[].supportCaseData`** 67 | *object* 68 | *(optional)* 69 | 70 | Adds additional context to support case payload based on active route, when user creates a support case. 71 | 72 | This is a local route configuration for support case context. If a UI module has global support context defined, it will be ignored, and local context is used. 73 | 74 | ### **`modules[].routes[].supportCaseData.version`** 75 | *string* 76 | 77 | Current version of the module. 78 | 79 | ### **`modules[].routes[].supportCaseData.product`** 80 | *string* 81 | 82 | A human readable product label. 83 | 84 | ### **`modules[].routes[].permissions`** 85 | *array* 86 | *(optional)* 87 | 88 | A list of permission checks. If any check fails, the route will not render. The permissions function are listed in [Chrome UI docs](https://github.com/RedHatInsights/insights-chrome/blob/master/docs/navigation.md#permissions) 89 | 90 | Each permission needs a permission method identifier and arguments if there are any. 91 | 92 | ```yaml 93 | objects: 94 | - spec: 95 | module: 96 | modules: 97 | routes: 98 | - pathname: /foo/bar 99 | permissions: 100 | - method: hasPermissions 101 | args: [["sources:foo:bar"]] 102 | ``` 103 | 104 | ### **`config`** 105 | *object* 106 | *deprecated* 107 | *(optional)* 108 | 109 | Use the [`moduleConfig instead`](#moduleconfig) 110 | 111 | ### **`moduleConfig`** 112 | *object* 113 | *(optional)* 114 | 115 | Additional global configuration for the entire UI module 116 | 117 | ### **`moduleConfig.supportCaseData`** 118 | *object* 119 | *(optional)* 120 | 121 | See [`modules[].routes[].supportCaseData`](#modulesroutessupportcasedata) 122 | 123 | This is a global configuration for support case context. If a route has support defined, it will override the global support config. 124 | 125 | **`moduleConfig.ssoScopes`** 126 | *array* 127 | *(optional)* 128 | 129 | Define additional KC scopes required for the UI module. Chrome will re-authenticate current user, if defined scopes were not yet used during the user session. 130 | 131 | ```yaml 132 | objects: 133 | - spec: 134 | module: 135 | moduleConfig: 136 | ssoScopes: 137 | - scopeA 138 | - scopeB 139 | ``` 140 | 141 | **`defaultDocumentTitle`** 142 | *string* 143 | *(optional)* 144 | 145 | A default browser title used when module is loaded. Can be further changed via browser API. 146 | 147 | **`analytics`** 148 | *object* 149 | *(optional)* 150 | 151 | COnfiguration to provide additional context to the Chrome UI analytics tooling 152 | 153 | **`analytics.APIKey`** 154 | 155 | *string* 156 | 157 | APIKey used by segment.io to forward user metrics to one or multiple targets. If none, is provided, default settings are used. 158 | 159 | 160 | -------------------------------------------------------------------------------- /docs/frontend-operator/navigation.md: -------------------------------------------------------------------------------- 1 | # Navigation 2 | 3 | The main navigation in HCC UI is configured via the frontend operator. The operator has two navigation definition options: 4 | - Bundle segments 5 | - Navigation segments 6 | 7 | ## Bundle segments 8 | 9 | A bundle segment is a navigation configuration segment that is directly added into a bundle navigation. A bundle navigation is a global navigation for a particular bundle. A bundle is a section of HCC, divided by a top-level pathname. 10 | 11 | For example, the Insights bundle includes all applications under the `/insights` pathname segment. The Settings bundle apps are under the `/settings` pathname segment, and so on. 12 | 13 | A bundle segment can be injected into a single bundle navigation. 14 | 15 | ## Bundle segment spec 16 | 17 | An example of a bundle segment: 18 | 19 | ```yaml 20 | objects: 21 | - spec: 22 | bundleSegments: 23 | - segmentId: bundle-segment-id 24 | bundleId: target-bundle-id 25 | position: 400 26 | navItems: [] 27 | 28 | ``` 29 | 30 | ### **`segmentId`** 31 | *string* 32 | 33 | Unique identifier of bundle segment 34 | 35 | ### **`bundleId`** 36 | *string* 37 | 38 | An ID if the targeted bundle. The list available bundles is defined by the environment in which HCC is running. For example, the stage environment might have different list of bundles than production. 39 | 40 | ### **`position`** 41 | *number* 42 | 43 | A priority of the segment, defining the position of the segment navigation items. It is recommended to leave enough "space" between individual segments in a bundle. Steps of 100 are recommended. This will ensure future segments can be comfortably injected into the bundle if needed. 44 | 45 | If navigation items from different UI modules are supposed to be in between navigation items within a segment **the segment should be split in two**. 46 | 47 | ![Bungle segments positioning](./bundle-segment-position.svg) 48 | 49 | ### **`navItems`** 50 | *array* 51 | 52 | Actual nav item definitions of the segment. There are two types of valid nav items: 53 | - [Direct navigation item](#direct-navigation-item) 54 | - [Navigation segment](#navigationsegment) 55 | 56 | Follow the links to learn more about them and their purpose. 57 | 58 | ## Direct navigation item 59 | 60 | Direct navigation items directly define navigation items and their attributes. These will be used in majority of the cases. 61 | 62 | Direct navigation items can be used both in [Bundle segments](#bundle-segments) and [Navigation segments](#navigation-segment). 63 | 64 | ### Simple navigation item attributes 65 | 66 | A simple link. Can be internal or external. 67 | 68 | ```ts 69 | type DirectNavItem = { 70 | title: string; 71 | href: string; 72 | id: string; 73 | isHidden?: boolean; 74 | isExternal?: boolean; 75 | product?: string; 76 | notifier?: string; 77 | isBeta?: boolean; 78 | permissions?: Permission[] 79 | } 80 | ``` 81 | 82 | #### **`title`** 83 | *string* 84 | 85 | Human readable title. 86 | 87 | #### **`href`** 88 | *string* 89 | 90 | Equivalent to the `href` attribute for the `a` HTML element. 91 | 92 | #### **`id`** 93 | *string* 94 | 95 | Unique identifier of the nav item. 96 | 97 | #### **`isHidden`** 98 | *bool* 99 | *(optional)* 100 | 101 | Adds the HTML element to the DOM, but does not display it. 102 | 103 | > If you are looking to conditionally show/hide navigation items use the [permissions attribute](#permissions) instead. 104 | 105 | #### **`isExternal`** 106 | *bool* 107 | *(optional)* 108 | 109 | The link leads to a different domain. Clicking the link will open a new browser tab. 110 | 111 | #### **`product`** 112 | *string* 113 | *(optional)* 114 | 115 | A human readable description of the product to which the link leads. Can be used to provide additional context to the Chrome UI. 116 | 117 | #### **`notifier`** 118 | *string* 119 | *(optional)* 120 | 121 | Display a notification icon if a specific event was registered in Chrome UI. 122 | 123 | #### **`isBeta`** 124 | *bool* 125 | *(optional)* 126 | 127 | Link leads to a feature that is in preview mode. User will be switched to preview version of the HCC UI if in stable mode. 128 | 129 | #### **`permissions`** 130 | *array* 131 | *(optional)* 132 | 133 | Rules to conditionally show/hide the navigation item. The permissions function are listed in [Chrome UI docs](https://github.com/RedHatInsights/insights-chrome/blob/master/docs/navigation.md#permissions) 134 | 135 | ### Expandable navigation item 136 | 137 | A navigation item that can be expanded and show more navigation items as its children. Clicking the expandable navigation item does not redirect user to any URL. 138 | 139 | > It is not recommended to have a deeply nested navigation structure. Multiple levels lead to degraded user experience and visual experience. 140 | 141 | ```ts 142 | type ExpandableNavigationItem = { 143 | expandable: bool; 144 | title: string; 145 | id: string; 146 | routes: (DirectNavItem | ExpandableNavigationItem | NavigationSegment)[] 147 | permissions?: Permission[] 148 | } 149 | ``` 150 | #### **`expandable`** 151 | *bool* 152 | 153 | A flag to mark navigation item as expandable. 154 | 155 | #### **`title`** 156 | *string* 157 | 158 | Human readable title. 159 | 160 | #### **`id`** 161 | *string* 162 | 163 | Unique identifier of the nav item. 164 | 165 | #### **`routes`** 166 | *array* 167 | 168 | Child navigation items of the expandable navigation item. 169 | 170 | #### **`permissions`** 171 | *array* 172 | *(optional)* 173 | 174 | Rules to conditionally show/hide the navigation item. The permissions function are listed in [Chrome UI docs](https://github.com/RedHatInsights/insights-chrome/blob/master/docs/navigation.md#permissions) 175 | 176 | ### Navigation group 177 | 178 | Groups navigation items into a section. The section has a label above all the links and can have an icon. **Child items are always visible, unlike for expandable items**. 179 | 180 | ```ts 181 | type NavigationItemGroup = { 182 | groupId: string; 183 | title: string; 184 | navItems: (DirectNavItem | ExpandableNavigationItem | NavigationSegment)[] 185 | icon?: string; 186 | permissions?: Permission[] 187 | } 188 | ``` 189 | #### **`groupId`** 190 | *string* 191 | 192 | A unique identifier of the group 193 | 194 | #### **`title`** 195 | *string* 196 | 197 | Human readable group label. 198 | 199 | #### **`navItems`** 200 | *array* 201 | 202 | Child navigation items of the navigation group. 203 | 204 | 205 | #### **`icon`** 206 | *string* 207 | *(optional)* 208 | 209 | A PF icon to display along the group label. 210 | 211 | #### **`permissions`** 212 | *array* 213 | *(optional)* 214 | 215 | Rules to conditionally show/hide the navigation item. The permissions function are listed in [Chrome UI docs](https://github.com/RedHatInsights/insights-chrome/blob/master/docs/navigation.md#permissions) 216 | 217 | ## Navigation segment 218 | 219 | Navigation segment is a group of navigation items, that has to be explicitly placed into a [Bundle segment](#bundle-segments) or as a child of [Expandable item](#expandable-navigation-item) or [Navigation group](#navigation-group). Segments should be used only if: 220 | - An exact copy of navigation items in segment should be used. 221 | - A navigation from foreign UI module needs to be injected into UI module bundle segment in a nested structure (expandable or group). 222 | 223 | An example of navigation segment: 224 | 225 | ```yaml 226 | objects: 227 | - metadata: 228 | name: ui-module-A 229 | spec: 230 | navigationSegments: 231 | - segmentId: ui-module-A-segment-one 232 | navItems: 233 | - title: Foreign link A 234 | id: foreign-link-a 235 | href: /foreign-link-a 236 | - title: Foreign link B 237 | id: foreign-link-b 238 | href: /foreign-link-b 239 | 240 | ``` 241 | 242 | An example of placing a navigation segment into bundle segment: 243 | 244 | ```yaml 245 | objects: 246 | - metadata: 247 | name: ui-module-B 248 | spec: 249 | bundleSegments: 250 | - segmentId: module-B-segment 251 | bundleId: foo 252 | position: 444 253 | navItems: 254 | - segmentRef: 255 | segmentId: ui-module-A-segment-one 256 | frontendName: ui-module-A 257 | - title: Local link 258 | id: local-link 259 | href: /foo/bar 260 | 261 | ``` 262 | To prevent duplicate IDs and any ambiguity when referencing navigation segments, not only correct segment ID has to be used, but also the Frontend origin of the segment. 263 | 264 | 265 | The FEO will join the two resources. The final structure of the `module-B-segment` navigation segment will look like this after the reconciliation: 266 | 267 | ```yaml 268 | objects: 269 | - metadata: 270 | name: ui-module-B 271 | spec: 272 | bundleSegments: 273 | - segmentId: module-B-segment 274 | bundleId: foo 275 | position: 444 276 | navItems: 277 | - title: Foreign link A 278 | id: foreign-link-a 279 | href: /foreign-link-a 280 | - title: Foreign link B 281 | id: foreign-link-b 282 | href: /foreign-link-b 283 | - title: Local link 284 | id: local-link 285 | href: /foo/bar 286 | ``` 287 | 288 | Any type of [Direct navigation item](#direct-navigation-item) can be used while defining navigation segment items. 289 | 290 | ### **`segmentId`** 291 | *string* 292 | 293 | A unique ID of a segment withing a single Frontend resources. 294 | 295 | ### **`navItems`** 296 | *array* 297 | 298 | > It is recommended to use direct navigation items if possible. 299 | 300 | Actual nav item definitions of the segment. There are two types of valid nav items: 301 | - [Direct navigation item](#direct-navigation-item) 302 | - [Navigation segment](#navigationsegment) 303 | -------------------------------------------------------------------------------- /docs/frontend-operator/pre-requisites.md: -------------------------------------------------------------------------------- 1 | # Pre-requisites 2 | 3 | In order to use all frontend operator features, your projects must: 4 | - Use containerized builds and deployments for all environments. 5 | - Have the up-to-date version of the build and development configurations: 6 | - `@redhat-cloud-services/frontend-components-config@6.5.4` if you are using the FEC binary for your build and development, or if you are creating your webpack config using the presets from the package. 7 | - `@redhat-cloud-services/frontend-components-config-utilities@4.3.1` if you are using the webpack development proxy directly and not via the config package. 8 | - To ensure ephemeral deployments are ready foe the new features, please upgrade your bonfire version to `6.0.3`. 9 | -------------------------------------------------------------------------------- /docs/frontend-operator/search.md: -------------------------------------------------------------------------------- 1 | # Search 2 | 3 | The search configuration allows you to define search entries that will be indexed and displayed in the HCC UI search. 4 | 5 | ## Search entry spec 6 | 7 | An example of a search entry: 8 | 9 | ```yaml 10 | objects: 11 | - spec: 12 | searchEntries: 13 | - id: "search-entry-id" 14 | title: "Search Entry Title" 15 | description: "Detailed description of the search entry" 16 | href: "/path/to/resource" 17 | alt_title: ["Synonym1", "Synonym2"] 18 | isExternal: false 19 | permissions: [] 20 | ``` 21 | 22 | ### **`id`** 23 | *string* 24 | 25 | Unique ID of the search entry within the Frontend resource. 26 | 27 | 28 | ### **`title`** 29 | *string* 30 | 31 | A title of the search entry 32 | 33 | 34 | ### **`description`** 35 | *string* 36 | 37 | Detailed description of the search result. Should describe the location towards the search entry leads. 38 | 39 | 40 | ### **`href`** 41 | *string* 42 | 43 | Equivalent to the `href` attribute for the `a` HTML element. 44 | 45 | 46 | ### **`alt_title`** 47 | *array\* 48 | *(optional)* 49 | 50 | A list of synonyms that could describe the features. For example, an entry with title of `Red Hat Enterprise Linux` can have `RHEL` as one of the alt_title items. 51 | 52 | 53 | #### **`isExternal`** 54 | *bool* 55 | *(optional)* 56 | 57 | The link leads to a different domain. Clicking the link will open a new browser tab. 58 | 59 | #### **`permissions`** 60 | *array* 61 | *(optional)* 62 | 63 | Rules to conditionally show/hide the search entry. The permissions function are listed in [Chrome UI docs](https://github.com/RedHatInsights/insights-chrome/blob/master/docs/navigation.md#permissions) 64 | -------------------------------------------------------------------------------- /docs/frontend-operator/services.md: -------------------------------------------------------------------------------- 1 | # All services dropdown items 2 | 3 | To add a tile to the all services dropdown, a service tile must be defined. 4 | 5 | Service tiles can be injected into a section and within a section into a group. 6 | 7 | Sections, groups, and individual tiles are always sorted alphabetically in the all services dropdown. 8 | 9 | ## Service tiles spec 10 | 11 | The service tiles are defined in the following place: 12 | 13 | ```yaml 14 | objects: 15 | - spec: 16 | serviceTiles: 17 | - id: "landing-tile" 18 | section: automation 19 | group: ansible 20 | title: "Landing tile" 21 | description: "Landing page description" 22 | href: / 23 | icon: SomeIcon 24 | ``` 25 | 26 | ### **`id`** 27 | *string* 28 | 29 | A unique ID of service tile within group 30 | 31 | 32 | ### **`section`** 33 | *string* 34 | 35 | ID of a services dropdown section. The list available sections is defined by the environment in which HCC is running. For example, the stage environment might have different list of sections than production. 36 | 37 | ### **`group`** 38 | *string* 39 | 40 | ID of a services dropdown section group. The list available section groups is defined by the environment in which HCC is running. For example, the stage environment might have different list of section groups than production. 41 | 42 | 43 | ### **`title`** 44 | *string* 45 | 46 | A title of the service tile entry 47 | 48 | 49 | ### **`description`** 50 | *string* 51 | 52 | Detailed description of the service tile. Should describe the location towards the service tile leads. 53 | 54 | 55 | ### **`href`** 56 | *string* 57 | 58 | Equivalent to the `href` attribute for the `a` HTML element. 59 | 60 | #### **`icon`** 61 | *string* 62 | *(optional)* 63 | 64 | Icon of the service tile 65 | 66 | #### **`isExternal`** 67 | *bool* 68 | *(optional)* 69 | 70 | The link leads to a different domain. Clicking the link will open a new browser tab. 71 | 72 | #### **`permissions`** 73 | *array* 74 | *(optional)* 75 | 76 | Rules to conditionally show/hide the service tile item. The permissions function are listed in [Chrome UI docs](https://github.com/RedHatInsights/insights-chrome/blob/master/docs/navigation.md#permissions) 77 | 78 | 79 | ## Example distribution 80 | 81 | ![Example of service tiles distribution](./feo-sections.svg) 82 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Frontend starter app docs 2 | 3 | - [Frontend Operator](./frontend-operator/index.md) 4 | - [Basic configuration](./frontend-operator/basic-configuration.md) 5 | - [UI module configuration](./frontend-operator/modules.md) 6 | - [Navigation](./frontend-operator//navigation.md) 7 | - [Services dropdown](./frontend-operator/services.md) 8 | - [Search](./frontend-operator//search.md) -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-require-imports */ 2 | const { defineConfig } = require('eslint/config'); 3 | const fecPlugin = require('@redhat-cloud-services/eslint-config-redhat-cloud-services'); 4 | const tsParser = require('@typescript-eslint/parser'); 5 | const tseslint = require('typescript-eslint'); 6 | 7 | module.exports = defineConfig( 8 | fecPlugin, 9 | { 10 | languageOptions: { 11 | globals: { 12 | insights: 'readonly', 13 | }, 14 | }, 15 | ignores: ['node_modules/*', 'dist/*'], 16 | rules: { 17 | requireConfigFile: 'off', 18 | 'sort-imports': [ 19 | 'error', 20 | { 21 | ignoreDeclarationSort: true, 22 | }, 23 | ], 24 | }, 25 | }, 26 | tseslint.configs.recommended, 27 | { 28 | files: ['src/**/*.ts', 'src/**/*.tsx'], 29 | languageOptions: { 30 | parser: tsParser, 31 | }, 32 | rules: { 33 | 'react/prop-types': 'off', 34 | '@typescript-eslint/no-unused-vars': 'error', 35 | }, 36 | }, 37 | ); 38 | -------------------------------------------------------------------------------- /fec.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | appUrl: '/staging/starter', 3 | debug: true, 4 | useProxy: true, 5 | proxyVerbose: true, 6 | /** 7 | * Change accordingly to your appname in package.json. 8 | * The `sassPrefix` attribute is only required if your `appname` includes the dash `-` characters. 9 | * If the dash character is present, you will have add a camelCase version of it to the sassPrefix. 10 | * If it does not contain the dash character, remove this configuration. 11 | */ 12 | sassPrefix: '.frontend-starter-app, .frontendStarterApp', 13 | /** 14 | * Change to false after your app is registered in configuration files 15 | */ 16 | interceptChromeConfig: false, 17 | /** 18 | * Add additional webpack plugins 19 | */ 20 | plugins: [], 21 | _unstableHotReload: process.env.HOT === 'true', 22 | moduleFederation: { 23 | exclude: ['react-router-dom'], 24 | shared: [ 25 | { 26 | 'react-router-dom': { 27 | singleton: true, 28 | import: false, 29 | version: '^6.3.0', 30 | }, 31 | }, 32 | ], 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const transformIgnorePatterns = ['node_modules/(?!(uuid)/)']; 2 | 3 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 4 | module.exports = { 5 | preset: 'ts-jest/presets/js-with-babel-esm', 6 | testEnvironment: 'jsdom', 7 | coverageDirectory: './coverage/', 8 | collectCoverage: true, 9 | collectCoverageFrom: ['src/**/*.js', '!src/**/stories/*'], 10 | roots: ['/src/'], 11 | moduleNameMapper: { 12 | '\\.(css|scss)$': 'identity-obj-proxy', 13 | }, 14 | transformIgnorePatterns, 15 | setupFilesAfterEnv: ['/config/jest.setup.ts'], 16 | }; 17 | -------------------------------------------------------------------------------- /mkdocs.yaml: -------------------------------------------------------------------------------- 1 | docs_dir: ./docs 2 | site_name: Frontend starter app 3 | 4 | nav: 5 | - Home: index.md 6 | - 'Frontend Operator': 7 | - Overview: /frontend-operator/index.md 8 | - 'Basic configuration': ./frontend-operator/basic-configuration.md 9 | - 'UI module configuration': ./frontend-operator/modules.md 10 | - Navigation: ./frontend-operator//navigation.md 11 | - 'Services dropdown': ./frontend-operator/services.md 12 | - Search: ./frontend-operator//search.md 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend-starter-app", 3 | "version": "1.2.0", 4 | "private": false, 5 | "engines": { 6 | "node": ">=18.0.0", 7 | "npm": ">=8.0.0" 8 | }, 9 | "scripts": { 10 | "build": "fec build", 11 | "deploy": "npm-run-all build lint test", 12 | "lint": "npm-run-all lint:*", 13 | "lint:js": "eslint src", 14 | "lint:js:fix": "eslint src --fix", 15 | "patch:hosts": "fec patch-etc-hosts", 16 | "start": "HOT=true fec dev", 17 | "test": "jest", 18 | "postinstall": "ts-patch install && rimraf .cache", 19 | "verify": "npm-run-all build lint test" 20 | }, 21 | "dependencies": { 22 | "@patternfly/react-core": "^6.2.2", 23 | "@patternfly/react-table": "^6.2.2", 24 | "@redhat-cloud-services/frontend-components": "^6.0.7", 25 | "@redhat-cloud-services/frontend-components-notifications": "^6.0.0", 26 | "@redhat-cloud-services/frontend-components-utilities": "^6.0.5", 27 | "react": "^18.3.1", 28 | "react-dom": "^18.3.1", 29 | "react-router-dom": "^6.26.2" 30 | }, 31 | "devDependencies": { 32 | "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^3.0.0", 33 | "@redhat-cloud-services/frontend-components-config": "^6.6.1", 34 | "@redhat-cloud-services/tsc-transform-imports": "^1.0.16", 35 | "@testing-library/jest-dom": "^6.5.0", 36 | "@testing-library/react": "^14.3.1", 37 | "@types/jest": "^29.5.13", 38 | "@types/react": "^18.3.11", 39 | "@types/react-dom": "^18.3.0", 40 | "@types/react-router-dom": "^5.3.3", 41 | "@typescript-eslint/parser": "^8.31.0", 42 | "identity-obj-proxy": "^3.0.0", 43 | "jest-environment-jsdom": "^29.7.0", 44 | "npm-run-all": "^4.1.5", 45 | "rimraf": "^5.0.10", 46 | "ts-jest": "^29.2.5", 47 | "ts-patch": "^3.2.1", 48 | "typescript": "^5.6.2", 49 | "typescript-eslint": "^8.31.0", 50 | "webpack-bundle-analyzer": "4.10.2" 51 | }, 52 | "insights": { 53 | "appname": "frontend-starter-app" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /pr_check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # -------------------------------------------- 4 | # Export vars for helper scripts to use 5 | # -------------------------------------------- 6 | # name of app-sre "application" folder this component lives in; needs to match for quay 7 | export COMPONENT="frontend-starter-app" 8 | # Needs to match the quay repo name set by app.yaml in app-interface 9 | export IMAGE="quay.io/cloudservices/$COMPONENT" 10 | export WORKSPACE=${WORKSPACE:-$APP_ROOT} # if running in jenkins, use the build's workspace 11 | export APP_ROOT=$(pwd) 12 | COMMON_BUILDER=https://raw.githubusercontent.com/RedHatInsights/insights-frontend-builder-common/master 13 | 14 | 15 | set -exv 16 | # source is preferred to | bash -s in this case to avoid a subshell 17 | source <(curl -sSL $COMMON_BUILDER/src/frontend-build.sh) 18 | BUILD_RESULTS=$? 19 | 20 | # Stubbed out for now 21 | mkdir -p $WORKSPACE/artifacts 22 | cat << EOF > $WORKSPACE/artifacts/junit-dummy.xml 23 | 24 | 25 | 26 | EOF 27 | 28 | # teardown_docker 29 | exit $BUILD_RESULTS 30 | -------------------------------------------------------------------------------- /src/App.scss: -------------------------------------------------------------------------------- 1 | // Importing Global Variables 2 | @import "~@redhat-cloud-services/frontend-components-utilities/styles/_all"; 3 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useEffect } from 'react'; 2 | import NotificationsProvider from '@redhat-cloud-services/frontend-components-notifications/NotificationsProvider'; 3 | import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome'; 4 | 5 | import Routing from './Routing'; 6 | import './App.scss'; 7 | 8 | const App = () => { 9 | const { updateDocumentTitle } = useChrome(); 10 | 11 | useEffect(() => { 12 | // You can use directly the name of your app 13 | updateDocumentTitle('Starter app'); 14 | }, []); 15 | 16 | return ( 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /src/AppEntry.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import App from './App'; 3 | 4 | const AppEntry = () => ; 5 | 6 | export default AppEntry; 7 | -------------------------------------------------------------------------------- /src/Components/AppLink/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link, LinkProps } from 'react-router-dom'; 3 | import { linkBasename, mergeToBasename } from '../../utils/utils'; 4 | 5 | const AppLink = (props: LinkProps) => ( 6 | 7 | ); 8 | 9 | export default AppLink; 10 | -------------------------------------------------------------------------------- /src/Components/SampleComponent/sample-component.scss: -------------------------------------------------------------------------------- 1 | @import "~@redhat-cloud-services/frontend-components-utilities/styles/variables"; 2 | 3 | .sample-component { 4 | font-size: $ins-fontSize; 5 | margin: $ins-margin; 6 | display: block; 7 | } 8 | -------------------------------------------------------------------------------- /src/Components/SampleComponent/sample-component.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import SampleComponent from './sample-component'; 4 | import '@testing-library/jest-dom'; 5 | 6 | test('expect sample-component to render children', () => { 7 | const children =

Hello

; 8 | 9 | render({children}); 10 | expect(screen.getByRole('heading')).toHaveTextContent('Hello'); 11 | }); 12 | -------------------------------------------------------------------------------- /src/Components/SampleComponent/sample-component.tsx: -------------------------------------------------------------------------------- 1 | import './sample-component.scss'; 2 | import React from 'react'; 3 | 4 | const SampleComponent: React.FC = (props) => { 5 | return {props.children} ; 6 | }; 7 | 8 | export default SampleComponent; 9 | -------------------------------------------------------------------------------- /src/Routes/NoPermissionsPage/NoPermissionsPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | 3 | import { Main } from '@redhat-cloud-services/frontend-components/Main'; 4 | import { NotAuthorized } from '@redhat-cloud-services/frontend-components/NotAuthorized'; 5 | import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome'; 6 | 7 | const NoPermissionsPage = () => { 8 | const { appAction } = useChrome(); 9 | 10 | useEffect(() => { 11 | appAction('no-permissions'); 12 | }, []); 13 | 14 | return ( 15 |
16 | 17 |
18 | ); 19 | }; 20 | 21 | export default NoPermissionsPage; 22 | -------------------------------------------------------------------------------- /src/Routes/OopsPage/OopsPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { Main } from '@redhat-cloud-services/frontend-components/Main'; 3 | import { Unavailable } from '@redhat-cloud-services/frontend-components/Unavailable'; 4 | import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome'; 5 | 6 | const OopsPage = () => { 7 | const { appAction } = useChrome(); 8 | 9 | useEffect(() => { 10 | appAction('oops-page'); 11 | }, []); 12 | 13 | return ( 14 |
15 | 16 |
17 | ); 18 | }; 19 | 20 | export default OopsPage; 21 | -------------------------------------------------------------------------------- /src/Routes/SamplePage/SamplePage.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense, lazy, useEffect } from 'react'; 2 | 3 | import { 4 | Button, 5 | Spinner, 6 | Stack, 7 | StackItem, 8 | Title, 9 | } from '@patternfly/react-core'; 10 | import { Main } from '@redhat-cloud-services/frontend-components/Main'; 11 | import { 12 | PageHeader, 13 | PageHeaderTitle, 14 | } from '@redhat-cloud-services/frontend-components/PageHeader'; 15 | import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome'; 16 | 17 | const SampleComponent = lazy( 18 | () => import('../../Components/SampleComponent/sample-component'), 19 | ); 20 | 21 | import './sample-page.scss'; 22 | import AppLink from '../../Components/AppLink'; 23 | import { useAddNotification } from '@redhat-cloud-services/frontend-components-notifications/hooks'; 24 | 25 | const SamplePage = () => { 26 | const { appAction } = useChrome(); 27 | const addNotification = useAddNotification(); 28 | 29 | useEffect(() => { 30 | appAction('sample-page'); 31 | }, []); 32 | 33 | const handleAlert = () => { 34 | addNotification({ 35 | variant: 'success', 36 | title: 'Notification title', 37 | description: 'notification description', 38 | }); 39 | }; 40 | 41 | return ( 42 | 43 | 44 | 45 |

This is page header text

46 |
47 |
48 | 49 | 50 | 51 | {' '} 52 | Alerts{' '} 53 | 54 | 58 | 59 | 60 | }> 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | {' '} 69 | Links{' '} 70 | 71 | 72 | 73 | How to handle 500s in app 74 | 75 | 76 | 77 | How to handle 403s in app 78 | 79 | 80 | 81 | 82 | 83 |
84 |
85 | ); 86 | }; 87 | 88 | export default SamplePage; 89 | -------------------------------------------------------------------------------- /src/Routes/SamplePage/sample-page.scss: -------------------------------------------------------------------------------- 1 | // js generates class '.page__{rootClass}' that wraps the content in your page added to the root tag 2 | // Found in Routes.js as the rootClass 3 | -------------------------------------------------------------------------------- /src/Routing.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense, lazy, useMemo } from 'react'; 2 | import { Route as RouterRoute, Routes as RouterRoutes } from 'react-router-dom'; 3 | import { InvalidObject } from '@redhat-cloud-services/frontend-components/InvalidObject'; 4 | import { Bullseye, Spinner } from '@patternfly/react-core'; 5 | 6 | const SamplePage = lazy( 7 | () => 8 | import( 9 | /* webpackChunkName: "SamplePage" */ './Routes/SamplePage/SamplePage' 10 | ), 11 | ); 12 | const OopsPage = lazy( 13 | () => import(/* webpackChunkName: "OopsPage" */ './Routes/OopsPage/OopsPage'), 14 | ); 15 | const NoPermissionsPage = lazy( 16 | () => 17 | import( 18 | /* webpackChunkName: "NoPermissionsPage" */ './Routes/NoPermissionsPage/NoPermissionsPage' 19 | ), 20 | ); 21 | 22 | const routes = [ 23 | { 24 | path: 'no-permissions', 25 | element: NoPermissionsPage, 26 | }, 27 | { 28 | path: 'oops', 29 | element: OopsPage, 30 | }, 31 | { 32 | path: '/', 33 | element: SamplePage, 34 | }, 35 | /* Catch all unmatched routes */ 36 | { 37 | route: { 38 | path: '*', 39 | }, 40 | element: InvalidObject, 41 | }, 42 | ]; 43 | 44 | interface RouteType { 45 | path?: string; 46 | element: React.ComponentType; 47 | childRoutes?: RouteType[]; 48 | elementProps?: Record; 49 | } 50 | 51 | const renderRoutes = (routes: RouteType[] = []) => 52 | routes.map(({ path, element: Element, childRoutes, elementProps }) => ( 53 | }> 54 | {renderRoutes(childRoutes)} 55 | 56 | )); 57 | 58 | const Routing = () => { 59 | const renderedRoutes = useMemo(() => renderRoutes(routes), [routes]); 60 | return ( 61 | 64 | 65 | 66 | } 67 | > 68 | {renderedRoutes} 69 | 70 | ); 71 | }; 72 | 73 | export default Routing; 74 | -------------------------------------------------------------------------------- /src/entry.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHatInsights/frontend-starter-app/23df4b0a57c2cae849e041d0015156c28a15be0a/src/entry.ts -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Starter App 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import { To } from 'react-router-dom'; 2 | 3 | export const linkBasename = '/staging/starter'; 4 | export const mergeToBasename = (to: To, basename: string): To => { 5 | if (typeof to === 'string') { 6 | // replace possible "//" after basename 7 | return `${basename}/${to}`.replace( 8 | new RegExp(`^${basename}//`), 9 | `${basename}/`, 10 | ); 11 | } 12 | 13 | return { 14 | ...to, 15 | pathname: `${basename}/${to.pathname}`.replace( 16 | new RegExp(`^${basename}//`), 17 | `${basename}/`, 18 | ), 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "sourceMap": true, 5 | "noImplicitAny": true, 6 | "module": "esnext", 7 | "target": "esnext", 8 | "jsx": "react", 9 | "allowJs": true, 10 | "moduleResolution": "node", 11 | "removeComments": false, 12 | "strict": true, 13 | "skipLibCheck": true, 14 | "allowSyntheticDefaultImports": true, 15 | "esModuleInterop": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "resolveJsonModule": true, 18 | "types": ["node", "jest", "@testing-library/jest-dom"], 19 | }, 20 | "include": [ 21 | "src/**/*.ts", 22 | "src/**/*.tsx", 23 | "src/**/*.js", 24 | "src/**/*.jsx", 25 | "config/**/*.ts", 26 | ] 27 | } 28 | --------------------------------------------------------------------------------