Merge remote-tracking branch 'origin/merge-master-to-epinio-dev' into epinio-dev

This commit is contained in:
Richard Cox 2022-10-17 15:13:52 +01:00
commit 20a3eb3f25
622 changed files with 31428 additions and 13423 deletions

View File

@ -227,6 +227,8 @@ steps:
- name: build
pull: default
image: node:14
environment:
EMBED_PKG: https://releases.rancher.com/harvester-ui/plugin/harvester-1.0.3.tar.gz
commands:
- ./scripts/build-embedded
@ -270,6 +272,8 @@ steps:
- name: build
pull: default
image: node:14
environment:
EMBED_PKG: https://releases.rancher.com/harvester-ui/plugin/harvester-1.0.3.tar.gz
commands:
- ./scripts/build-embedded
@ -304,6 +308,8 @@ steps:
- name: build
pull: default
image: node:14
environment:
EMBED_PKG: https://releases.rancher.com/harvester-ui/plugin/harvester-1.0.3.tar.gz
commands:
- ./scripts/build-embedded
@ -327,10 +333,10 @@ steps:
# steps:
# - name: fossa
# image: rancher/drone-fossa:latest
# failure: ignore
# settings:
# api_key:
# from_secret: FOSSA_API_KEY
# when:
# instance:
# - drone-publish.rancher.io
# - drone-publish.rancher.io

View File

@ -1,5 +1,5 @@
module.exports = {
root: true,
// root: true,
env: {
browser: true,
node: true
@ -181,5 +181,9 @@ module.exports = {
rules: { 'jest/prefer-expect-assertions': 'off' },
extends: ['plugin:jest/all']
},
{
files: ['docusaurus/**/*.{js,ts}'],
rules: { 'no-use-before-define': 'off' },
},
]
};

View File

@ -1,3 +1,11 @@
---
name: Bug report
about: Report a bug with Rancher Dashboard
title: "<bug title: summarize bug in one line>"
labels: kind/bug
assignees: ''
---
<!--------- For bugs and general issues --------->
**Setup**
- Rancher version:
@ -19,11 +27,3 @@
**Additional context**
<!--Add any other context about the problem here. -->
<!--------- For feature requests --------->
**Detailed Description**
<!--- Provide a detailed description of the change or addition you are proposing -->
**Context**
<!--- Why is this change important to you? How would you use it? -->
<!--- How can it benefit other users? -->

View File

@ -0,0 +1,19 @@
---
name: Feature request
about: Suggest a new feature or enhancement for Rancher Dashboard
title: "<feature title: summarize feature in one line>"
labels: 'kind/enhancement'
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
**Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. -->
**Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->

9
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@ -0,0 +1,9 @@
---
name: Question
about: Question on Rancher Dashboard UI
title: "<question title: summarize question in one line>"
labels: kind/question
assignees: ''
---

View File

@ -11,6 +11,7 @@ Fixes #
### Areas or cases that should be tested
<!-- Areas that should be tested can include Airgap checks, Rancher upgrades, K8s upgrade, etc. -->
<!-- Which browser did you use for local testing? The reviewer should test with a different browser. -->
<!-- Add missing steps or rewrite them if have been missed or to complement existing information. This should define a clear way to reproduce it and not an approximation. -->
### Areas which could experience regressions

36
.github/workflows/docusaurus.yaml vendored Normal file
View File

@ -0,0 +1,36 @@
name: Publish Docusaurus
on:
push:
branches:
- master
jobs:
deploy:
name: Publish Docusaurus
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 16.x
cache: yarn
- name: Install dependencies
run: cd docusaurus/ && yarn install --frozen-lockfile
- name: Build website
run: cd docusaurus/ && yarn build
# Popular action to deploy to GitHub Pages:
# Docs: https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-docusaurus
- name: Publish Docusaurus
uses: peaceiris/actions-gh-pages@v3
with:
deploy_key: ${{ secrets.GH_PAGES_DEPLOY_KEY }}
# Build output to publish to the `gh-pages` branch:
publish_dir: ./docusaurus/build
# The following lines assign commit authorship to the official
# GH-Actions bot for deploys to `gh-pages` branch:
# https://github.com/actions/checkout/issues/13#issuecomment-724415212
user_name: github-actions[bot]
user_email: 41898282+github-actions[bot]@users.noreply.github.com

View File

@ -0,0 +1,41 @@
name: Build and Publish Rancher Components
on:
push:
tags:
- 'components-v*'
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-node@v3
with:
node-version: '16.x'
registry-url: 'https://registry.npmjs.org'
scope: '@rancher'
- name: Install
run: yarn install --frozen-lockfile
- name: Lint
run: yarn lint:lib
- name: Build
run: yarn build:lib
- name: Unit Test
run: yarn test:ci ./pkg/rancher-components
- name: Publish to npm
run: yarn publish:lib
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@ -25,7 +25,7 @@ jobs:
- name: Install & Build
run:
RANCHER_ENV=epinio EXCLUDES_PKG=rancher-components ./.github/workflows/scripts/build-dashboard.sh
RANCHER_ENV=epinio EXCLUDES_PKG=rancher-components,harvester EXCLUDES_NUXT_PLUGINS=plugins/plugin,plugins/version ./.github/workflows/scripts/build-dashboard.sh
- name: Upload Build
uses: actions/upload-artifact@v2

View File

@ -7,6 +7,7 @@ echo "ROUTER_BASE: $ROUTER_BASE"
echo
echo "RANCHER_ENV: $RANCHER_ENV"
echo "EXCLUDES_PKG: $EXCLUDES_PKG"
echo "EXCLUDES_NUXT_PLUGINS: $EXCLUDES_NUXT_PLUGINS"
echo
echo "RELEASE_DIR: $RELEASE_DIR"
RELEASE_LOCATION="$RELEASE_DIR/$ARTIFACT_NAME"
@ -28,7 +29,7 @@ echo Installing dependencies
yarn install --frozen-lockfile
echo Building
NUXT_ENV_commit=$GITHUB_SHA NUXT_ENV_version=$GITHUB_REF_NAME OUTPUT_DIR="$ARTIFACT_LOCATION" ROUTER_BASE="$ROUTER_BASE" RANCHER_ENV=$RANCHER_ENV API=$API RESOURCE_BASE=$RESOURCE_BASE EXCLUDES_PKG=$EXCLUDES_PKG yarn run build --spa
NUXT_ENV_commit=$GITHUB_SHA NUXT_ENV_version=$GITHUB_REF_NAME OUTPUT_DIR="$ARTIFACT_LOCATION" ROUTER_BASE="$ROUTER_BASE" RANCHER_ENV=$RANCHER_ENV API=$API RESOURCE_BASE=$RESOURCE_BASE EXCLUDES_PKG=$EXCLUDES_PKG EXCLUDES_NUXT_PLUGINS=$EXCLUDES_NUXT_PLUGINS yarn run build --spa
echo Creating tar
tar -czf $RELEASE_LOCATION.tar.gz -C $ARTIFACT_LOCATION .

View File

@ -1,5 +1,9 @@
name: Tests
on:
push:
branches:
- master
- 'release-*'
pull_request:
branches:
- master
@ -10,6 +14,7 @@ on:
description: 'Environment to run tests against'
type: environment
required: true
env:
TEST_USERNAME: admin
TEST_PASSWORD: password
@ -18,17 +23,18 @@ env:
API: https://127.0.0.1
TEST_PROJECT_ID: rancher-dashboard
CYPRESS_API_URL: http://139.59.134.103:1234/
TEST_RUN_ID: ${{github.event.pull_request.title}}_${{github.run_number}}_id_${{github.run_id}}
TEST_RUN_ID: ${{github.run_number}}-${{github.run_attempt}}-${{github.event.pull_request.title || github.event.head_commit.message}}
CYPRESS_coverage: true
jobs:
e2e-test:
if: "!contains( github.event.pull_request.labels.*.name, 'ci/skip-e2e')"
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
- uses: actions/checkout@v3
with:
fetch-depth: 1
- uses: actions/setup-node@v3
with:
node-version: '14.x'
@ -39,31 +45,31 @@ jobs:
run: yarn e2e:pre-prod
- name: Run tests
run: yarn e2e:prod
run: |
yarn e2e:prod
mkdir -p coverage-artifacts/coverage
cp coverage/e2e/coverage-final.json coverage-artifacts/coverage/coverage-e2e.json
- name: Upload coverage
uses: actions/upload-artifact@v3
with:
name: ${{github.run_number}}-${{github.run_attempt}}-coverage
path: coverage-artifacts/**/*
- name: Upload screenshots
uses: actions/upload-artifact@v2
if: ${{ failure() }}
with:
name: run_${{github.run_number}}_id_${{github.run_id}}_screenshots
name: ${{github.run_number}}-${{github.run_attempt}}-screenshots
path: cypress/screenshots
# Disabled due freezing issues related to the CI machine resources
# - name: Upload videos
# uses: actions/upload-artifact@v2
# if: ${{ failure() }}
# with:
# name: run_${{github.run_number}}_id_${{github.run_id}}_videos
# path: cypress/videos
unit-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
- uses: actions/checkout@v3
with:
fetch-depth: 1
- uses: actions/setup-node@v3
with:
node-version: '14.x'
@ -71,22 +77,24 @@ jobs:
run: yarn install
- name: Run tests
run: yarn test:ci
run: |
yarn test:ci
mkdir -p coverage-artifacts/coverage
cp coverage/unit/coverage-final.json coverage-artifacts/coverage/coverage-unit.json
- name: Upload coverage
uses: actions/upload-artifact@v3
with:
name: run_${{github.run_number}}_id_${{github.run_id}}_coverage
path: coverage/**/*
name: ${{github.run_number}}-${{github.run_attempt}}-coverage
path: coverage-artifacts/**/*
i18n:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
- uses: actions/checkout@v3
with:
fetch-depth: 1
- uses: actions/setup-node@v3
with:
node-version: '14.x'
@ -99,11 +107,10 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
- uses: actions/checkout@v3
with:
fetch-depth: 1
- uses: actions/setup-node@v3
with:
node-version: '14.x'
@ -112,3 +119,53 @@ jobs:
- name: Run linters
run: yarn lint
coverage:
if: "!contains( github.event.pull_request.labels.*.name, 'ci/skip-e2e')"
runs-on: ubuntu-latest
needs:
- unit-test
- e2e-test
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 1
- uses: actions/setup-node@v3
with:
node-version: '14.x'
- name: Download Coverage Artifacts
uses: actions/download-artifact@v2
with:
name: ${{github.run_number}}-${{github.run_attempt}}-coverage
- name: Merge coverage files
run: |
ls
yarn coverage
ls coverage
# Job is flaky now and then, but we do not want to interrupt the development flow
- name: Upload unit test coverage to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: unit
files: ./coverage/coverage-unit.json
fail_ci_if_error: false
- name: Upload e2e test coverage to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: e2e
files: ./coverage/coverage-e2e.json
fail_ci_if_error: false
- name: Upload merged coverage to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: merged
files: ./coverage/coverage.json
fail_ci_if_error: false

3
.gitignore vendored
View File

@ -109,4 +109,5 @@ shell/rancher-components
# standalone script
scripts/standalone/ui
scripts/standalone/cert
scripts/standalone/cert
scripts/standalone/node

4
.nycrc Normal file
View File

@ -0,0 +1,4 @@
{
"reporter": [ "json", "text-summary"],
"report-dir": "coverage/e2e"
}

View File

@ -43,6 +43,9 @@ const storePlugin = {
Vue.use(storePlugin);
export const parameters = {
previewTabs: {
canvas: { hidden: false },
},
actions: { argTypesRegex: "^on[A-Z].*" },
layout: 'centered',
// viewMode: 'docs',
@ -57,12 +60,12 @@ export const parameters = {
dark: {
...themes.dark,
brandTitle: 'Rancher Storybook',
brandImage: 'https://raw.githubusercontent.com/rancher/dashboard/master/assets/images/pl/dark/rancher-logo.svg'
brandImage: '/dark/rancher-logo.svg'
},
light: {
...themes.normal,
brandTitle: 'Rancher Storybook',
brandImage: 'https://raw.githubusercontent.com/rancher/dashboard/master/assets/images/pl/rancher-logo.svg'
brandImage: '/rancher-logo.svg'
},
darkClass: 'theme-dark',
lightClass: 'theme-light',

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 637.2 93.8" style="enable-background:new 0 0 637.2 93.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#0075A8;}
.st1{fill:#f8f8f8;}
.st2{fill:#CCCCCC;}
.st3{fill:#B3B3B3;}
.st4{fill:#FFFFFF;}
</style>
<g>
<g>
<path class="st0" d="M200.7,20.6l-2.2-13c-0.7-4.2-2.3-7.6-3.6-7.6c-1.3,0-2.4,3.5-2.4,7.7v3.4c0,4.2-3.5,7.7-7.7,7.7h-3.4
c-0.2,0-0.5,0-0.7,0v9.4c0.2,0,0.5,0,0.7,0h12.8C198.5,28.2,201.4,24.8,200.7,20.6"/>
<path class="st0" d="M170,9.6h-20.8c-0.2,0-0.3,0-0.5,0h-21.3c-0.3,0-0.5,0-0.7,0.1v-2c0-4.2-1.1-7.7-2.4-7.7
c-1.3,0-2.9,3.4-3.6,7.6l-2.2,13c-0.7,4.2,2.2,7.6,6.4,7.6h12.8c1.3,0,2.6-0.2,3.6-0.6c-0.4,2.2-2.3,3.8-4.6,3.8h-18
c-2.9,0-5.1-2.6-4.6-5.5l1.8-10.9c0.5-2.9-1.7-5.5-4.6-5.5H22.1c-1.9,0-3.5,1.1-4.3,2.8L1,38c-0.3,0.4-0.3,1,0.1,1.4l3.3,3.9
c0.4,0.5,1.1,0.6,1.6,0.2l11.4-9v54.8c0,2.6,2.1,4.7,4.7,4.7h25.4c2.6,0,4.7-2.1,4.7-4.7v-19c0-2.6,2.1-4.7,4.7-4.7h63.3
c2.6,0,4.7,2.1,4.7,4.7v19c0,2.6,2.1,4.7,4.7,4.7h25.4c2.6,0,4.7-2.1,4.7-4.7V68.6h-13.5c-4.2,0-7.7-3.5-7.7-7.7V47.8
c0-2.5,1.2-4.7,3.1-6.1v15.7c0,4.2,3.5,7.7,7.7,7.7H170c4.2,0,7.7-3.5,7.7-7.7v-40C177.7,13.1,174.2,9.6,170,9.6"/>
</g>
<g>
<g>
<path class="st1" d="M217.7,12.2h28.6c13.6,0,22.7,6.6,22.7,19.3c0,10.4-7.1,16.6-14,18.8c2.1,1.7,3.6,4.1,4.8,6.6
c2.9,5.9,4.8,12.3,10.9,12.3c1.5,0,2.8-0.5,2.8-0.5L272.1,81c0,0-3.7,0.9-6.9,0.9c-8.2,0-12.9-3.2-17.8-14.2
c-2.1-4.9-4.9-13.6-8.7-13.6h-3.9v27.4h-17.2V12.2z M234.9,24.7v17h6.2c4.9,0,10.7-1.5,10.7-8.9c0-6.1-3.9-8.1-8.6-8.1H234.9z"/>
<path class="st1" d="M292.2,12.2h17.4l23.4,69.3h-17.4l-4.3-13.1h-23.2l-4.2,13.1h-15L292.2,12.2z M292.2,55.7h14.9l-4.7-14.6
c-1.6-4.9-2.4-11.9-2.4-11.9h-0.4c0,0-1,7.1-2.6,11.8L292.2,55.7z"/>
<path class="st1" d="M333.6,12.2H352l18.2,34.4c1.5,3,3.6,8.2,5.2,12.3h0.4c-0.2-3.9-0.7-9.4-0.7-13.5V12.2h14.1v69.3h-18
l-18.9-34.3c-1.7-3.1-3.6-7.6-5-11.3h-0.4c0.3,3.9,0.8,8.5,0.8,12.3v33.3h-14.1V12.2z"/>
<path class="st1" d="M392.9,46.5c0-25.1,12.4-35.4,31.6-35.4c20.2,0,28.3,11.1,26.1,26.1l-16,1.4c1.7-11-2.6-15.4-10.4-15.4
c-7.5,0-13.6,5.5-13.6,23.4c0,18.9,6.6,23.7,14.1,23.7c7.1,0,12.8-4.4,11.6-13.5l15,1.5c1.1,14.1-9,24.4-27.5,24.4
C405,82.8,392.9,71.7,392.9,46.5"/>
<polygon class="st1" points="454.8,12.2 472,12.2 472,38.4 493,38.4 493,12.2 510.2,12.2 510.2,81.6 493,81.6 493,52.2 472,52.2
472,81.6 454.8,81.6 "/>
<polygon class="st1" points="517.1,12.2 563,12.2 563,25 534.2,25 534.2,39.9 558.3,39.9 558.3,52.6 534.2,52.6 534.2,68.8
564,68.8 564,81.6 517.1,81.6 "/>
<path class="st1" d="M567.3,12.2h28.6c13.6,0,22.7,6.6,22.7,19.3c0,10.4-7.1,16.6-14,18.8c2.1,1.7,3.6,4.1,4.8,6.6
c2.9,5.9,4.8,12.3,10.9,12.3c1.5,0,2.8-0.5,2.8-0.5L621.7,81c0,0-3.7,0.9-6.9,0.9c-8.2,0-12.9-3.2-17.8-14.2
c-2.1-4.9-4.9-13.6-8.7-13.6h-3.9v27.4h-17.2V12.2z M584.4,24.7v17h6.2c4.9,0,10.7-1.5,10.7-8.9c0-6.1-3.9-8.1-8.6-8.1H584.4z"/>
</g>
<g>
<path class="st1" d="M625.2,16.6c0-3.9,2.9-5.6,5.6-5.6c2.7,0,5.6,1.7,5.6,5.6c0,3.8-2.9,5.5-5.6,5.5
C628.2,22.2,625.2,20.5,625.2,16.6z M635.1,16.6c0-3.1-2-4.4-4.2-4.4c-2.2,0-4.3,1.3-4.3,4.4c0,3,2.1,4.4,4.3,4.4
C633.1,21,635.1,19.7,635.1,16.6z M629,13.8h2c1,0,2,0.3,2,1.7c0,0.8-0.6,1.3-1.3,1.5l1.3,2.3h-1.2l-1.2-2.2h-0.5v2.2H629V13.8z
M631.1,16.3c0.5,0,0.9-0.3,0.9-0.8c0-0.6-0.5-0.7-0.9-0.7h-1v1.5H631.1z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 637.2 93.8" style="enable-background:new 0 0 637.2 93.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#0075A8;}
.st1{fill:#59595B;}
.st2{fill:#CCCCCC;}
.st3{fill:#B3B3B3;}
.st4{fill:#FFFFFF;}
</style>
<g>
<g>
<path class="st0" d="M200.7,20.6l-2.2-13c-0.7-4.2-2.3-7.6-3.6-7.6c-1.3,0-2.4,3.5-2.4,7.7v3.4c0,4.2-3.5,7.7-7.7,7.7h-3.4
c-0.2,0-0.5,0-0.7,0v9.4c0.2,0,0.5,0,0.7,0h12.8C198.5,28.2,201.4,24.8,200.7,20.6"/>
<path class="st0" d="M170,9.6h-20.8c-0.2,0-0.3,0-0.5,0h-21.3c-0.3,0-0.5,0-0.7,0.1v-2c0-4.2-1.1-7.7-2.4-7.7
c-1.3,0-2.9,3.4-3.6,7.6l-2.2,13c-0.7,4.2,2.2,7.6,6.4,7.6h12.8c1.3,0,2.6-0.2,3.6-0.6c-0.4,2.2-2.3,3.8-4.6,3.8h-18
c-2.9,0-5.1-2.6-4.6-5.5l1.8-10.9c0.5-2.9-1.7-5.5-4.6-5.5H22.1c-1.9,0-3.5,1.1-4.3,2.8L1,38c-0.3,0.4-0.3,1,0.1,1.4l3.3,3.9
c0.4,0.5,1.1,0.6,1.6,0.2l11.4-9v54.8c0,2.6,2.1,4.7,4.7,4.7h25.4c2.6,0,4.7-2.1,4.7-4.7v-19c0-2.6,2.1-4.7,4.7-4.7h63.3
c2.6,0,4.7,2.1,4.7,4.7v19c0,2.6,2.1,4.7,4.7,4.7h25.4c2.6,0,4.7-2.1,4.7-4.7V68.6h-13.5c-4.2,0-7.7-3.5-7.7-7.7V47.8
c0-2.5,1.2-4.7,3.1-6.1v15.7c0,4.2,3.5,7.7,7.7,7.7H170c4.2,0,7.7-3.5,7.7-7.7v-40C177.7,13.1,174.2,9.6,170,9.6"/>
</g>
<g>
<g>
<path class="st1" d="M217.7,12.2h28.6c13.6,0,22.7,6.6,22.7,19.3c0,10.4-7.1,16.6-14,18.8c2.1,1.7,3.6,4.1,4.8,6.6
c2.9,5.9,4.8,12.3,10.9,12.3c1.5,0,2.8-0.5,2.8-0.5L272.1,81c0,0-3.7,0.9-6.9,0.9c-8.2,0-12.9-3.2-17.8-14.2
c-2.1-4.9-4.9-13.6-8.7-13.6h-3.9v27.4h-17.2V12.2z M234.9,24.7v17h6.2c4.9,0,10.7-1.5,10.7-8.9c0-6.1-3.9-8.1-8.6-8.1H234.9z"/>
<path class="st1" d="M292.2,12.2h17.4l23.4,69.3h-17.4l-4.3-13.1h-23.2l-4.2,13.1h-15L292.2,12.2z M292.2,55.7h14.9l-4.7-14.6
c-1.6-4.9-2.4-11.9-2.4-11.9h-0.4c0,0-1,7.1-2.6,11.8L292.2,55.7z"/>
<path class="st1" d="M333.6,12.2H352l18.2,34.4c1.5,3,3.6,8.2,5.2,12.3h0.4c-0.2-3.9-0.7-9.4-0.7-13.5V12.2h14.1v69.3h-18
l-18.9-34.3c-1.7-3.1-3.6-7.6-5-11.3h-0.4c0.3,3.9,0.8,8.5,0.8,12.3v33.3h-14.1V12.2z"/>
<path class="st1" d="M392.9,46.5c0-25.1,12.4-35.4,31.6-35.4c20.2,0,28.3,11.1,26.1,26.1l-16,1.4c1.7-11-2.6-15.4-10.4-15.4
c-7.5,0-13.6,5.5-13.6,23.4c0,18.9,6.6,23.7,14.1,23.7c7.1,0,12.8-4.4,11.6-13.5l15,1.5c1.1,14.1-9,24.4-27.5,24.4
C405,82.8,392.9,71.7,392.9,46.5"/>
<polygon class="st1" points="454.8,12.2 472,12.2 472,38.4 493,38.4 493,12.2 510.2,12.2 510.2,81.6 493,81.6 493,52.2 472,52.2
472,81.6 454.8,81.6 "/>
<polygon class="st1" points="517.1,12.2 563,12.2 563,25 534.2,25 534.2,39.9 558.3,39.9 558.3,52.6 534.2,52.6 534.2,68.8
564,68.8 564,81.6 517.1,81.6 "/>
<path class="st1" d="M567.3,12.2h28.6c13.6,0,22.7,6.6,22.7,19.3c0,10.4-7.1,16.6-14,18.8c2.1,1.7,3.6,4.1,4.8,6.6
c2.9,5.9,4.8,12.3,10.9,12.3c1.5,0,2.8-0.5,2.8-0.5L621.7,81c0,0-3.7,0.9-6.9,0.9c-8.2,0-12.9-3.2-17.8-14.2
c-2.1-4.9-4.9-13.6-8.7-13.6h-3.9v27.4h-17.2V12.2z M584.4,24.7v17h6.2c4.9,0,10.7-1.5,10.7-8.9c0-6.1-3.9-8.1-8.6-8.1H584.4z"/>
</g>
<g>
<path class="st1" d="M625.2,16.6c0-3.9,2.9-5.6,5.6-5.6c2.7,0,5.6,1.7,5.6,5.6c0,3.8-2.9,5.5-5.6,5.5
C628.2,22.2,625.2,20.5,625.2,16.6z M635.1,16.6c0-3.1-2-4.4-4.2-4.4c-2.2,0-4.3,1.3-4.3,4.4c0,3,2.1,4.4,4.3,4.4
C633.1,21,635.1,19.7,635.1,16.6z M629,13.8h2c1,0,2,0.3,2,1.7c0,0.8-0.6,1.3-1.3,1.5l1.3,2.3h-1.2l-1.2-2.2h-0.5v2.2H629V13.8z
M631.1,16.3c0.5,0,0.9-0.3,0.9-0.8c0-0.6-0.5-0.7-0.9-0.7h-1v1.5H631.1z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -17,20 +17,26 @@
"typescript.preferences.importModuleSpecifier": "non-relative",
"cSpell.words": [
"autoscroll",
"cacerts",
"chainable",
"Codecov",
"epinio",
"hevi",
"kube",
"kubeconfig",
"kubectl",
"Kubernetes",
"kubevirt",
"nuxt",
"overcommit",
"prepending",
"protip",
"pvcs",
"testid",
"tolerations",
"userpreferences",
"virtualmachine",
"vuex"
"vuex",
"whatsnew"
],
}

View File

@ -11,7 +11,7 @@ During the transition to the new folder structured in 2.6.5 required by the plug
- Run the script `./scripts/rejig -d` to move folders to their old location and update imports again
Use this to convert newer branches to the old format (possibly useful for branches)
For more information on plugins see [Plugins](./docs/developer/PLUGINS.md).
For more information on plugins see [Plugins](./docusaurus/docs/guide/plugins.md).
## Running for Development
This is what you probably want to get started.

View File

@ -1,4 +1,5 @@
import { defineConfig } from 'cypress';
// Required for env vars to be available in cypress
require('dotenv').config();
/**
@ -23,20 +24,33 @@ const getSpecPattern = (): string[] => {
const baseUrl = (process.env.TEST_BASE_URL || 'https://localhost:8005').replace(/\/$/, '');
export default defineConfig({
projectId: process.env.TEST_PROJECT_ID,
defaultCommandTimeout: 60000,
trashAssetsBeforeRuns: true,
env: {
projectId: process.env.TEST_PROJECT_ID,
defaultCommandTimeout: 60000,
trashAssetsBeforeRuns: true,
env: {
baseUrl,
username: process.env.TEST_USERNAME,
password: process.env.TEST_PASSWORD,
bootstrapPassword: process.env.CATTLE_BOOTSTRAP_PASSWORD,
coverage: false,
codeCoverage: {
exclude: [
'cypress/**/*.*',
'**/__tests__/**/*.*',
'**/shell/scripts/**/*.*',
],
include: [
'shell/**/*.{vue,ts,js}',
'pkg/rancher-components/src/components/**/*.{vue,ts,js}',
]
},
username: process.env.TEST_USERNAME,
password: process.env.TEST_PASSWORD,
bootstrapPassword: process.env.CATTLE_BOOTSTRAP_PASSWORD,
},
e2e: {
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
return require('./cypress/plugins/index.ts')(on, config);
// For more info: https://docs.cypress.io/guides/tooling/code-coverage
require('@cypress/code-coverage/task')(on, config);
return config;
},
experimentalSessionAndOrigin: true,
specPattern: getSpecPattern(),

View File

@ -58,12 +58,4 @@ export default class BurgerMenuPo extends ComponentPo {
clusters(): Cypress.Chainable {
return this.self().find('.body .clusters .cluster.selector.option');
}
/**
* Get i18n menu
* @returns {Cypress.Chainable}
*/
localization(): Cypress.Chainable {
return this.self().getId('locale-selector');
}
}

View File

@ -21,12 +21,6 @@ describe('Side Menu: main', () => {
burgerMenuPo.clusters().should('exist');
});
it('Can display the localization menu', () => {
const burgerMenuPo = new BurgerMenuPo();
burgerMenuPo.localization().should('exist');
});
it('Can display at least one menu category label', () => {
const burgerMenuPo = new BurgerMenuPo();

View File

@ -0,0 +1,125 @@
const { baseUrl } = Cypress.config();
const clusterManagerPath = `${ baseUrl }/c/local/manager/provisioning.cattle.io.cluster`;
const clusterRequestBase = `${ baseUrl }/v1/provisioning.cattle.io.clusters/fleet-default`;
const timestamp = +new Date();
const clusterNamePartial = `e2e-test-create`;
const clusterName = `${ clusterNamePartial }-${ timestamp }`;
const clusterNameImport = `${ clusterNamePartial }-${ timestamp }-import`;
describe('Cluster Manager', () => {
beforeEach(() => {
cy.login();
});
describe('using RKE2', () => {
describe('given custom selection', () => {
it('can create new cluster', () => {
cy.userPreferences();
cy.visit(clusterManagerPath);
cy.getId('cluster-manager-list-create').click();
cy.getId('cluster-manager-create-rke-switch').click();
cy.getId('cluster-manager-create-grid-2-0').click();
cy.getId('name-ns-description-name').type(clusterName);
cy.getId('rke2-custom-create-save').click();
cy.url().should('include', `${ clusterManagerPath }/fleet-default/${ clusterName }#registration`);
});
it('can create new imported generic cluster', () => {
cy.visit(clusterManagerPath);
cy.getId('cluster-manager-list-import').click();
cy.getId('cluster-manager-create-grid-1-0').click();
cy.getId('name-ns-description-name').type(clusterNameImport);
cy.getId('cluster-manager-import-save').click();
cy.url().should('include', `${ clusterManagerPath }/fleet-default/${ clusterNameImport }#registration`);
});
it('can navigate to local cluster explore product', () => {
const clusterName = 'local';
cy.visit(clusterManagerPath);
// Click explore button for the cluster row within the table matching given name
cy.contains(clusterName).parent().parent().parent()
.within(() => cy.getId('cluster-manager-list-explore-management').click());
cy.url().should('include', `/c/${ clusterName }/explorer`);
});
it(`can see cluster's details`, () => {
cy.visit(clusterManagerPath);
// Click action menu button for the cluster row within the table matching given name
cy.contains(clusterName).parent().parent().parent()
.within(() => cy.getId('-action-button', '$').click());
cy.getId('action-menu-0-item').click();
cy.contains(`Custom - ${ clusterName }`).should('exist');
});
it('can view cluster YAML editor', () => {
cy.visit(clusterManagerPath);
// Click action menu button for the cluster row within the table matching given name
cy.contains(clusterName).parent().parent().parent()
.within(() => cy.getId('-action-button', '$').click());
cy.getId('action-menu-1-item').click();
cy.getId('yaml-editor-code-mirror').contains(clusterName);
});
it('can edit cluster and see changes afterwards', () => {
cy.intercept('PUT', `${ clusterRequestBase }/${ clusterName }`).as('saveRequest');
cy.visit(clusterManagerPath);
// Click action menu button for the cluster row within the table matching given name
cy.contains(clusterName).parent().parent().parent()
.within(() => cy.getId('-action-button', '$').click());
cy.getId('action-menu-0-item').click();
cy.getId('name-ns-description-description').type(clusterName);
cy.getId('rke2-custom-create-save').click();
cy.wait('@saveRequest').then(() => {
cy.visit(`${ clusterManagerPath }/fleet-default/${ clusterName }?mode=edit#basic`);
cy.getId('name-ns-description-description').find('input').should('have.value', clusterName);
});
});
it('can delete cluster', () => {
cy.intercept('DELETE', `${ clusterRequestBase }/${ clusterName }`).as('deleteRequest');
cy.visit(clusterManagerPath);
// Click action menu button for the cluster row within the table matching given name
cy.contains(clusterName).as('rowCell').parent().parent()
.parent()
.within(() => cy.getId('-action-button', '$').click());
cy.getId('action-menu-4-item').click();
cy.getId('prompt-remove-input').type(clusterName);
cy.getId('prompt-remove-confirm-button').click();
cy.wait('@deleteRequest').then(() => {
cy.get('@rowCell').should('not.exist');
});
});
it('can delete multiple clusters', () => {
cy.intercept('DELETE', `${ clusterRequestBase }/${ clusterNameImport }`).as('deleteRequest');
cy.visit(clusterManagerPath);
// Get row from a given name
cy.contains(clusterNameImport).as('rowCell')
// Click checkbox for the cluster row within the table matching given name
.parent().parent()
.parent()
.within(() => cy.getId('-checkbox', '$').click({ multiple: true }));
// Single buttons are replaced with action menu on mobile
cy.getId('sortable-table-promptRemove').click({ force: true });
cy.get('@rowCell').then((row) => {
// In the markdown we have ALWAYS whitespace
cy.getId('prompt-remove-input').type(row.text().trim());
});
cy.getId('prompt-remove-confirm-button').click();
cy.wait('@deleteRequest').then(() => {
cy.get('@rowCell').should('not.exist');
});
});
});
});
});

View File

@ -37,6 +37,8 @@ describe('Rancher setup', () => {
.should('eq', true);
rancherSetupAuthVerify.submit();
cy.location('pathname', { timeout: 15000 }).should('include', '/home');
// TODO: This assertion is commented as it started to fail after rebasing and cannot be corrected as it's not possible to run Rancher locally
// cy.wait('@firstLoginReq').then((login) => {
// expect(login.response?.statusCode).to.equal(200);

38
cypress/globals.d.ts vendored Normal file
View File

@ -0,0 +1,38 @@
type Matcher = '$' | '^' | '~' | '*' | '';
// eslint-disable-next-line no-unused-vars
declare namespace Cypress {
interface Chainable {
login(username?: string, password?: string, cacheSession?: boolean): Chainable<Element>;
byLabel(label: string,): Chainable<Element>;
/**
* Wrapper for cy.get() to simply define the data-testid value that allows you to pass a matcher to find the element.
* @param id Value used for the data-testid attribute of the element.
* @param matcher Matching character used for attribute value:
* - `$`: Suffixed with this value
* - `^`: Prefixed with this value
* - `~`: Contains this value as whitespace separated words
* - `*`: Contains this value
*/
getId(id: string, matcher?: Matcher): Chainable<Element>;
/**
* Wrapper for cy.find() to simply define the data-testid value that allows you to pass a matcher to find the element.
* @param id Value used for the data-testid attribute of the element.
* @param matcher Matching character used for attribute value:
* - `$`: Suffixed with this value
* - `^`: Prefixed with this value
* - `~`: Contains this value as whitespace separated words
* - `*`: Contains this value
*/
findId(id: string, matcher?: Matcher): Chainable<Element>;
/**
* Override user preferences to default values, allowing to pass custom preferences for a deterministic scenario
* Leave empty for reset to default values
*/
// eslint-disable-next-line no-undef
userPreferences(preferences?: Partial<UserPreferences>): Chainable<null>;
}
}

View File

@ -1,53 +0,0 @@
/* eslint-disable no-console */
/// <reference types="cypress" />
require('dotenv').config();
const { rmdir } = require('fs');
/**
* @type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
module.exports = (
on: Cypress.PluginEvents,
config: Cypress.PluginConfigOptions
) => {
return config;
};
/**
* Only upload videos for specs with failing
* Run this function after every spec to delete passed tests video
* https://docs.cypress.io/guides/guides/screenshots-and-videos#Only-upload-videos-for-specs-with-failing-or-retried-tests
* TODO: Integrate this function when enabling videos once again with #6048
*
* @param on
*/
// eslint-disable-next-line no-unused-vars
const deletePassedVideos = (on: Cypress.PluginEvents) => {
on('after:spec', (_, results) => {
// console.log(results);
if (results && results.video) {
const failures = results.tests.filter(({ state }) => state === 'failed');
if (!failures.length) {
console.log('Deleting video for passed tests');
return new Promise((resolve, reject) => {
rmdir(
results.video,
{ maxRetries: 10, recursive: true },
(err: Error) => {
if (err) {
console.error(err);
return reject(err);
}
return resolve();
}
);
});
}
}
});
};

View File

@ -1,4 +1,5 @@
import { LoginPagePo } from '@/cypress/e2e/po/pages/login-page.po';
import { Matcher } from '~/cypress/support/types';
/**
* Login local authentication, including first login and bootstrap if not cached
@ -49,11 +50,18 @@ Cypress.Commands.add('byLabel', (label) => {
return cy.get('.labeled-input').contains(label).siblings('input');
});
/**
* Wrap the cy.find() command to simplify the selector declaration of the data-testid
*/
Cypress.Commands.add('findId', (id: string, matcher?: Matcher = '') => {
return cy.find(`[data-testid${ matcher }="${ id }"]`);
});
/**
* Wrap the cy.get() command to simplify the selector declaration of the data-testid
*/
Cypress.Commands.add('getId', (id: string) => {
return cy.get(`[data-testid="${ id }"]`);
Cypress.Commands.add('getId', (id: string, matcher?: Matcher = '') => {
return cy.get(`[data-testid${ matcher }="${ id }"]`);
});
/**
@ -66,7 +74,8 @@ Cypress.Commands.add('userPreferences', (preferences: Partial<UserPreferences> =
statusCode: 201,
body: {
data: [{
data: {
type: 'userpreference',
data: {
'after-login-route': '\"home\"',
cluster: 'local',
'group-by': 'none',

View File

@ -1,19 +1,6 @@
import '@cypress/code-coverage/support';
import './commands';
declare global {
// eslint-disable-next-line no-unused-vars
namespace Cypress {
interface Chainable {
login(username?: string, password?: string, cacheSession?: boolean): Chainable<Element>;
byLabel(label: string,): Chainable<Element>;
getId(id: string,): Chainable<Element>;
// eslint-disable-next-line no-undef
userPreferences(preferences?: Partial<UserPreferences>): Chainable<null>;
}
}
}
// TODO handle redirection errors better?
// we see a lot of 'error navigation cancelled' uncaught exceptions that don't actually break anything; ignore them here
Cypress.on('uncaught:exception', (err, runnable) => {

View File

@ -1,61 +0,0 @@
# Auth Providers
## Github
### Developer Set up
Follow the in-dashboard instructions when configuring a Github auth provider.
### Multiple GitHub auth configs
The auth system supports multiple GitHub auth URLs and using the appropriate one based on the Host header that a request comes in on. Configuring this is not exposed in the regular UI, but is particularly useful for development against a server that already has GitHub setup.
In `management.cattle.io.authconfig`, edit the `github` entry. Add a `hostnameToClientId` map of Host header value -> GitHub client ID:
```yaml
hostnameToClientId:
"localhost:8005": <your GitHub Client ID for localhost:8005>
```
In the `secret`, namespace `cattle-global-data`, edit `githubconfig-clientsecret`. Add GitHub client ID -> base64-encoded client secret to the `data` section:
```yaml
data:
clientsecret: <the normal client secret already configured>
<your client id>: <your base64-encoded client secret for localhost:8005>
```
## Keycloak
### Developer Set Up (SAML)
Use the steps below to set up a Keycloak instance for dev environments and configure an Auth Provider for it.
1. Bring up a local Keycloak instance in docker using the instructions at [here](https://www.keycloak.org/getting-started/getting-started-docker).
> Ensure that the admin user has a first name, last name and email. These fields are referenced in the Keycloak client's mappers which are then referenced in the Rancher's auth provider config.
> Double check the client has the correct checkboxes set, specifically the Mappers `group` entry.
1. Using either the Ember or Vue UI set up the Keycloak auth provider by follow the instructions at [here](https://rancher.com/docs/rancher/v2.6/en/admin-settings/authentication/keycloak-saml/)
| Field | Value |
|-------|-------|
| Display Name Field | givenName |
| User Name Field | email |
| UID Field | email |
| Groups Field| member |
| Entity ID Field| Depending on Rancher API Url. For instance when running Dashboard locally `https://192.168.86.26:8005/v1-saml/keycloak/saml/metadata` |
| Rancher API Host | Depending on Rancher API Url. For instance when running Dashboard locally `https://192.168.86.26:8005/`|
| Private Key | For key and cert files, export the Client in the Keycloak UI via the `Clients` list page and extract & wrap the `saml.signing.certificate` and `saml.signing.private.key` as cert files (see [step 5](https://gist.github.com/PhilipSchmid/506b33cd74ddef4064d30fba50635c5b) for more info). |
| Certificate | See Private Key section above|
| Metadata | For the SAML Metadata, download as per Rancher docs. Be sure to follow the `NOTE` instructions regarding `EntitiesDescriptor` and `EntityDescriptor`. For a better set of instructions see [step 6](https://gist.github.com/PhilipSchmid/506b33cd74ddef4064d30fba50635c5b)|
### Developer Set Up (OIDC)
1. In Vue UI set up the Keycloak OIDC provider with the following values
| Field | Value |
|-------|-------|
| Client ID | Find via the keycloak console |
| Client Secret | Find via the keycloak console (client's credentials tab) |
| Private Key (optional) | |
| Certificate (optional) | |
| Keycloak URL | URL of keycloak instance (no path) |
| Keycloak Realm | Find via the keycloak console (above menu on left or in path after /realms/) |
> The user used when enabling the provider must be an Admin or in a group

View File

@ -1,28 +0,0 @@
# Directory Structure
The directory structure is mostly flat, with each top level dir being for a different important thing (or just required by Nuxt to be there).
## Other commonly changed stuff
Path | Used for
-----|---------
assets | CSS, fonts, images, translations, etc resources which get processed during build
components | All general components which don't have a separate special directory elsewhere
layouts | The outermost components for rendering different kinds of pages (Nuxt)
store | [Vuex](https://vuex.vuejs.org/) stores which maintain all the state for the life of a page load
utils | Misc parsers, utilities, and other (usually) standalone code that doesn't fit anywhere else
## The rest
These are mostly standard Nuxt dirs that you won't need to go into very often.
Path | Used for
-----|---------
static | Static files which get directly copied into the build with no processing
middleware | Hooks called on every page load
mixins | Code that is defined once and then applied to several different other components
pages | The structure in here defines the routes that are available, and what gets rendered when one is hit
plugins | Add-ons to modify vue/nuxt or load additional 3rd-party code. The "steve" API client also notably lives here.
scripts | Shell scripts for building and related tasks, used by CI and `npm run ...` commands
server | Server-side middleware and dev SSL cert
test | Unit tests (or lack thereof)

View File

@ -1,4 +0,0 @@
# FOSSA
FOSSA is part of our efforts to ensure license compliance and scan for CVEs in dependencies. The results are published to a Fossa dashboard, you can request access to the Fossa dashboard via SUSE IT SD.

View File

@ -1,19 +0,0 @@
# Terminology
This is part of the developer [getting started guide](../../README.md).
The official Kubernetes [glossary](https://kubernetes.io/docs/reference/glossary/?fundamental=true) also explains Kubernetes-specific terminology.
| Term | Description |
|-------|--------------|
| Dashboard / Cluster Explorer / Vue UI | The application in this repository. It will slowly replace the older Ember UI |
| Manager / Cluster Manager / Ember UI | The old [Ember based UI](https://github.com/rancher/ui) |
| Norman | Old Rancher API which has mostly been superseded by Steve |
| Steve | New Rancher API |
| Rancher (Product) | A [Kubernetes Management Platform](https://rancher.com/products/rancher/). This product includes the Rancher API and UIs |
| RKE | [Rancher Kubernetes Engine](https://rancher.com/products/rke/) - A certified Kubernetes distribution |
| SSR | Server Side Rendering. Disabled by default when developing the Dashboard (enabled pre 2.6.6) |
| SPA | Single Page Application. Enabled by default in production |
| Vue | [A frontend client framework used by the Dashboard](https://vuejs.org/) |
| Vuex | [Frontend state management](https://vuex.vuejs.org/) |
| Nuxt | [Vue framework helper](https://nuxtjs.org/)|

View File

@ -1,309 +0,0 @@
# Testing
## E2E Tests
This repo is configured for end-to-end testing with [Cypress](https://docs.cypress.io/api/table-of-contents) and the CI will run using a blank state of Rancher executed locally. The aim is however to enable also tests using remote instances of Ranchers.
Because of this, we extend the [Cypress best practices](https://docs.cypress.io/guides/references/best-practices#How-It-Works), so be sure to read them before write any test.
### Initial Setup
For the cypress test runner to consume the UI, you should specify the environment variables:
- Local authentication credentials
- `TEST_USERNAME`, default `admin`
- `TEST_PASSWORD`, user password or custom during first Rancher run
- `CATTLE_BOOTSTRAP_PASSWORD`, initialization password which will also be used as `admin` password (do not pick `admin`)
- `TEST_BASE_URL` // URL used by Cypress to run the tests, default `https://localhost:8005`
- `TEST_SKIP_SETUP` // Avoid to execute bootstrap setup tests for already initialized Rancher instances
- Dashboard
- `TEST_PROJECT_ID` // Project ID used by Cypress/Sorry cypress to run the tests
- `TEST_RUN_ID` (optional) // Identifier for your dashboard run, default value is timestamp
### Development with watch/dev
While writing the tests, you can simply run Rancher dashboard and then open the Cypress dashboard with the commands
- `yarn dev`
- `yarn cy:open`
The Cypress dashboard will contain the options and the list of test suites. These will automatically re-run if they are altered (hot reloading).
For further information, consult [official documentation](https://docs.cypress.io/guides/guides/command-line#cypress-open).
### E2E Dashboard
#### Self-hosted: Sorry Cypress
Link to the dashboard: http://139.59.134.103:8080/
E2E tests can be added and displayed in a dashboard by defining the project ID with the env var `TEST_PROJECT_ID`, then run the script:
```bash
yarn cy:run:sorry
```
#### Cypress Dashboard
E2E tests can be displayed in Cypress dashboard by defining the project ID with the env var `TEST_PROJECT_ID`, then run the script by passing the parameters
```bash
yarn cy:run --record --key YOUR_RECORD_KEY_HERE
```
These values are provided when you create a new project within Cypress dashboard or within `Project settings`.
It's also possible to run a workflow in GitHub Actions E2E test using these values to record on personal dashboards.
### Local and CI/prod run
It is possible to start the project and run all the tests at once with a single command. There's however a difference between `dev` and `production` run. The first will not require an official certificate and will build the project in `.nuxt`, while the production will enable all the SSL configurations to run encrypted.
- `yarn e2e:pre-dev`, to optionally initialize Docker and build the project, if not already done
- `yarn e2e:dev`, single run local development
- `yarn e2e:pre-prod`, to optionally initialize Docker and build the project, required for GitHub Actions
- `yarn e2e:dev`, for production use case and CI, which will also restart Docker and build the project
### Custom Commands
As Cypress common practice, some custom commands have been created within `command.ts` file to simplify the development process. Please consult Cypress documentation for more details about when and how to use them.
Worth mentioning the `cy.getId()` command, as it is mainly used to select elements. This would require to add `data-testid` to your element inside the markup.
### Writing tests
Test specs should be grouped logically, normally by page or area of the Dashboard but also by a specific feature or component.
Tests should make use of common Page Object (PO) components. These can be pages or individual components which expose a useful set of tools, but most importantly contain the selectors for the DOM elements that need to be used. These will ensure changes to the underlying components don't require a rewrite of many many tests. They also allow parent components to easily search for children (for example easily finding all anchors in a section instead of the whole page). Given that tests are typescript it should be easy to explore the functionality.
Some examples of PO functionality
```ts
HomePage.gotTo()
new HomePagePo().checkIsCurrentPage()
new BurgerMenuPo().clusters()
new AsyncButtonPO('[data-testid="my-button"]').isDisabled()
new LoginPagePo().username().set('admin')
```
POs all inherit a root `component.po`. Common component functionality can be added there. They also expose their core cypress (chainable) element.
There are a large number of pages and components in the Dashboard and only a small set of POs. These will be expanded as the tests grow.
Note: When selecting an element be sure to use the attribute `data-testid`, even in case of lists where elements are distinguished by an index suffix.
### Tips
The Cypress UI is very much your friend. There you can click pick tests to run, easily visually track the progress of the test, see the before/after state of each cypress command (specifically good for debugging failed steps), see https requests, etc.
Tests can also be restricted before cypress runs, or at run time, by prepending `.only` to the run.
```ts
describe.only('Burger Side Nav Menu', () => {
beforeEach
```
```ts
it.only('Opens and closes on menu icon click', () => {
```
## Unit tests
The dashboard is configured to run unit tests with Jest in combination of vue-test-utils, for Vue scoped cases.
Requirements to accept tests:
- JS and TS formats
- Suffix with `.test` or `.spec`
- Contained in any directory `__tests__`
Adopted commands:
- `yarn test`, run and watch every test
- `yarn test:ci`, script used for CI, which outputs a coverage report to `/coverage` folder
Example tests can be found in `/components/__tests__`. For more information about testing vue components, see the [vue test utils](https://vue-test-utils.vuejs.org/) and [jest](https://jestjs.io/docs/getting-started) docs.
### VSCode debugging tools
It is possible to use debugging tools within Jest via VSCode. To do so, open the debugger panel (Ctrl/Cmd+Shift+D) and select the `Debug Jest Tests` option from the dropdown. This will start a debug session with the Jest tests, allowing you to set breakpoint, inspect code and visualize variables on the panel itself. As usual it's possible to execute the tests by `F5` after selecting the right option.
### Style guide
On top of the recommendation provided by the [Vue documentation](https://vuejs.org/guide/scaling-up/testing.html), it is also encouraged to follow these patterns to create readable and aimed tests.
#### Describe and test/it statement
To clearly state the scope of the test, it's convenient to define in the first `describe` always define with a noun the name of the function, method, or component being tested. Multiple assertions may be grouped together under a common statement in `describe` block, as it helps to avoid repetition and ensure a set of tests to be included. Each `test`/`it` block should then start with a verb related to what is the expectation.
```ts
describe('myfunction', () => {
describe('given the same parameter', () => {
it('should return the same result', () => {
// Test code
});
it('should return something else for a second parameter', () => {
// Test code
});
});
});
```
For further information, consult the [Jest API](https://jestjs.io/docs/api#describename-fn) documentation.
#### Simple tests
Test with the highest readability and reliability should avoid logic, as this will increase line of code and have to be tested as well. Static data is then preferred over computation and should be declared always within the describe or test or as close as possible.
Don't:
```ts
test('define if is required to use this in our component from the response', () => {
const myData = externalFunction(externalData);
for(data in myData) {
data.key = 'something else'
}
expect(isRequired(myData)).toBe(true);
});
```
Do:
```ts
describe('FX: isRequired', () => {
test('should return true', () => {
const myData = { key: 'required case' };
const result = isRequired(myData);
expect(result).toBe(true);
});
});
```
#### AAA pattern
Adoption of AAA format (arrange, act, assert) for tests.
- Arrange is where you prepare test, e.g. set properties to a component or declare variables
- Act is when an event or function is triggered
- Assertion correspond to the expectation of the test
Don't:
```ts
describe('FX: isRequired', () => {
test('should return true', () => {
let myData = { key: 'required case' };
expect(myData).toBeTruthy();
myData.key = 'something else';
const result = isRequired(myData);
expect(result).toBe(true);
myData['key2'] = 'another key/value';
expect(result).toBe(false);
});
});
```
Do:
```ts
describe('FX: isRequired', () => {
test('should return true', () => {
const myData = { key: 'required case' };
const result = isRequired(myData);
expect(result).toBe(true);
});
test('should return false if malformed data', () => {
const myData = {
key: 'required case',
key2: 'another key/value'
};
const result = isRequired(myData);
expect(result).toBe(false);
});
});
```
#### Behaviors over implementations
As also defined in the Vue documentation for [component](https://vuejs.org/guide/scaling-up/testing.html#component-testing) and [composable testing](https://vuejs.org/guide/scaling-up/testing.html#testing-composables), it is recommended to test rendered elements over internal API of the component.
Following an input example as in the [documentation](https://v2.vuejs.org/v2/cookbook/unit-testing-vue-components.html?redirect=true#Base-Example).
Don't:
```ts
const wrapper = mount(YourComponent);
const inputWrapper = wrapper.find(`[data-testid=your-component]`;
inputWrapper.setValue(1);
expect(wrapper.emitted('input')[0][0]).toBe(1);
```
Do:
```ts
const wrapper = mount(YourComponent);
const inputWrapper = wrapper.find(`[data-testid=your-component]`;
inputWrapper.setValue(1);
expect(wrapper.text()).toContain('1')
```
#### Parameterization
When multiple cases are required to be tested for the same component, it is recommended to avoid multiple actions and assertions or even worse logic, but rather rely on Jest functions to parametrize the test.
Don't:
```ts
describe('FX: isRequired', () => {
test('should return true', () => {
let myData = { key: 'required case' };
expect(myData).toBeTruthy();
myData.key = 'something else';
expect(result).toBe(true);
myData.key = 'another value';
expect(result).toBe(true);
});
});
```
Do:
```ts
describe('FX: isRequired', () => {
test.each([
'required case',
'something else',
'another value',
])('should return true', (key) => {
const myData = { key };
const result = isRequired(myData);
expect(result).toBe(true);
});
});
```

20
docusaurus/.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

40
docusaurus/README.md Normal file
View File

@ -0,0 +1,40 @@
# Website
This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
> Note: Docusaurus 2 expected node version `>=16.14`.
### Installation
```
$ yarn docs:install
```
### Local Development
> Note this command will open a web browser on the locally served site (http://localhost:3000)
```
$ yarn docs:start
```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
> Note this command will open a web browser on the locally served site (http://localhost:3000)
### Build
```
$ yarn docs:build
```
### Adding new documents
Guide for [sidebars.js](https://docusaurus.io/docs/sidebar).
> Note: We are using `sidebars.js` file to generated custome sidebar.
1. Create a Markdown file, greeting.md, and place it under the docs directory.
2. Add file name in `sidebars.js`.

View File

@ -0,0 +1 @@
module.exports = { presets: [require.resolve('@docusaurus/core/lib/babel/preset')] };

View File

@ -0,0 +1,64 @@
# Auth Providers
## Github
### Developer Set up
Follow the in-dashboard instructions when configuring a Github auth provider.
### Multiple GitHub auth configs
The auth system supports multiple GitHub auth URLs and using the appropriate one based on the Host header that a request comes in on. Configuring this is not exposed in the regular UI, but is particularly useful for development against a server that already has GitHub setup.
In `management.cattle.io.authconfig`, edit the `github` entry. Add a `hostnameToClientId` map of Host header value -> GitHub client ID:
```yaml
hostnameToClientId:
"localhost:8005": <your GitHub Client ID for localhost:8005>
```
In the `secret`, namespace `cattle-global-data`, edit `githubconfig-clientsecret`. Add GitHub client ID -> base64-encoded client secret to the `data` section:
```yaml
data:
clientsecret: <the normal client secret already configured>
<your client id>: <your base64-encoded client secret for localhost:8005>
```
## Keycloak
### Developer Set Up (SAML)
Use the steps below to set up a Keycloak instance for dev environments and configure an Auth Provider for it.
1. Bring up a local Keycloak instance in docker using the instructions at [here](https://www.keycloak.org/getting-started/getting-started-docker).
> Ensure that the admin user has a first name, last name and email. These fields are referenced in the Keycloak client's mappers which are then referenced in the Rancher's auth provider config.
> Double check the client has the correct checkboxes set, specifically the Mappers `group` entry.
1. Using either the Ember or Vue UI set up the Keycloak auth provider by follow the instructions at [here](https://rancher.com/docs/rancher/v2.6/en/admin-settings/authentication/keycloak-saml/)
| Field | Value |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Display Name Field | givenName |
| User Name Field | email |
| UID Field | email |
| Groups Field | member |
| Entity ID Field | Depending on Rancher API Url. For instance when running Dashboard locally `https://192.168.86.26:8005/v1-saml/keycloak/saml/metadata` |
| Rancher API Host | Depending on Rancher API Url. For instance when running Dashboard locally `https://192.168.86.26:8005/` |
| Private Key | For key and cert files, export the Client in the Keycloak UI via the `Clients` list page and extract & wrap the `saml.signing.certificate` and `saml.signing.private.key` as cert files (see [step 5](https://gist.github.com/PhilipSchmid/506b33cd74ddef4064d30fba50635c5b) for more info). |
| Certificate | See Private Key section above |
| Metadata | For the SAML Metadata, download as per Rancher docs. Be sure to follow the `NOTE` instructions regarding `EntitiesDescriptor` and `EntityDescriptor`. For a better set of instructions see [step 6](https://gist.github.com/PhilipSchmid/506b33cd74ddef4064d30fba50635c5b) |
### Developer Set Up (OIDC)
1. In Vue UI set up the Keycloak OIDC provider with the following values
| Field | Value |
| ---------------------- | ---------------------------------------------------------------------------- |
| Client ID | Find via the keycloak console |
| Client Secret | Find via the keycloak console (client's credentials tab) |
| Private Key (optional) | |
| Certificate (optional) | |
| Keycloak URL | URL of keycloak instance (no path) |
| Keycloak Realm | Find via the keycloak console (above menu on left or in path after /realms/) |
> The user used when enabling the provider must be an Admin or in a group

View File

@ -25,13 +25,13 @@ This is in contrast to the Norman API, which had a concept of 'actions' - impera
In a production setup these are all handled natively by Rancher. For development of dashboard, they are proxied to the Rancher install that the `API` environment variable points at.
Endpoint | Notes
-------------------------|-------
`/v3` | Norman API
`/v3-public` | Norman unauthenticated API (mostly for info required to login)
`/v1` | Steve API for the management ("local") cluster
`/k8s/clusters/<id>` | Proxy straight to the native k8s API for the given downstream cluster
`/k8s/clusters/<id>/v1` | Steve API for given downstream cluster via the server proxy
| Endpoint | Notes |
| ----------------------- | --------------------------------------------------------------------- |
| `/v3` | Norman API |
| `/v3-public` | Norman unauthenticated API (mostly for info required to login) |
| `/v1` | Steve API for the management ("local") cluster |
| `/k8s/clusters/<id>` | Proxy straight to the native k8s API for the given downstream cluster |
| `/k8s/clusters/<id>/v1` | Steve API for given downstream cluster via the server proxy |
The older Norman API is served on `/v3`. The newer Steve API (see [here](https://github.com/rancher/api-spec/blob/master/specification.md) for spec) is served on `/v1` .
@ -104,29 +104,29 @@ There are 3 main stores for communicating with different parts of the Rancher AP
And then a bunch of others:
Name | For
---------------------|-------
action-menu | Maintains the current selection for tables and handling bulk operations on them
auth | Authentication, logging in and out, etc
catalog | Stores the index data for Helm catalogs and methods to find charts, determine if upgrades are available, etc
github | Part of authentication, communicating with the GitHub API
growl | Global "growl" notifications in the corner of the screen
i18n | Internationalization
index | The root store, manages things like which cluster you're connected to and what namespaces should be shown
prefs | User preferences
type-map | Meta-information about all the k8s types that are available to the current user and how they should be displayed
wm | "Window manager" at the bottom of the screen for things like container shells and logs.
| Name | For |
| ----------- | ---------------------------------------------------------------------------------------------------------------- |
| action-menu | Maintains the current selection for tables and handling bulk operations on them |
| auth | Authentication, logging in and out, etc |
| catalog | Stores the index data for Helm catalogs and methods to find charts, determine if upgrades are available, etc |
| github | Part of authentication, communicating with the GitHub API |
| growl | Global "growl" notifications in the corner of the screen |
| i18n | Internationalization |
| index | The root store, manages things like which cluster you're connected to and what namespaces should be shown |
| prefs | User preferences |
| type-map | Meta-information about all the k8s types that are available to the current user and how they should be displayed |
| wm | "Window manager" at the bottom of the screen for things like container shells and logs. |
Store objects are accessed in different ways, below are common ways they are referenced by models and components
|Location|type|object|example|
|----|----|----|----|
| `/model/<resource type>` | Dispatching Actions | `this.$dispatch` | `this.$dispatch('cluster/find', { type: WORKLOAD_TYPES.JOB, id: relationship.toId }, { root: true })`
| `/model/<resource type>` | Access getters (store type) | `this.$getters` | `this.$getters['schemaFor'](this.type)`
| `/model/<resource type>` | Access getters (all) | `this.$rootGetters` | `this.$rootGetters['productId']`
| component | Dispatching Actions | `this.$store.dispatch` | ``this.$store.dispatch(`${ inStore }/find`, { type: row.type, id: row.id })``
| component | Access getters | `this.$store.getters` | `this.$store.getters['rancher/byId'](NORMAN.PRINCIPAL, this.value)`
| Location | type | object | example |
| ------------------------ | --------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------- |
| `/model/<resource type>` | Dispatching Actions | `this.$dispatch` | `this.$dispatch('cluster/find', { type: WORKLOAD_TYPES.JOB, id: relationship.toId }, { root: true })` |
| `/model/<resource type>` | Access getters (store type) | `this.$getters` | `this.$getters['schemaFor'](this.type)` |
| `/model/<resource type>` | Access getters (all) | `this.$rootGetters` | `this.$rootGetters['productId']` |
| component | Dispatching Actions | `this.$store.dispatch` | ``this.$store.dispatch(`${ inStore }/find`, { type: row.type, id: row.id })`` |
| component | Access getters | `this.$store.getters` | `this.$store.getters['rancher/byId'](NORMAN.PRINCIPAL, this.value)` |
> Prefixing a property in a model with `$`, as per `model` rows above, results in calling properties on the store object directly.
@ -183,13 +183,13 @@ We also 'dehydrate' resources by stripping out properties with double underscore
Most of the options to create and fetch resources can be achieved via dispatching actions defined in `/plugins/dashboard-store/actions.js`
| Function | Action | Example Command | Description |
|-------------|--------|-----------------|-----|
| `create` | Create | `$store.$dispatch('<store type>/create', <new object>)`| Creates a new Proxy object of the required type (`type` property must be included in the new object) |
| `clone` | Clone | `$store.$dispatch('<store type>/clone', { resource: <existing object> })` | Performs a deep clone and creates a proxy from it |
| `findAll` | Fetch all of a resource type and watch for changes to the returned resources so that the list updates as it changes. | `$store.dispatch('<store type>/findAll', { type: <resource type> })` | Fetches all resources of the given type. Also, when applicable, will register the type for automatic updates. If the type has already been fetched return the local cached list instead |
| `find` | Fetch a resource by ID and watch for changes to that individual resource. | `$store.dispatch('<store type>/find', { type: <resource type>, id: <resource id> })` | Finds the resource matching the ID. If the type has already been fetched return the local cached instance. |
| `findMatching` | Fetch resources by label and watch for changes to the returned resources, a live array that updates as it changes. | `$store.dispatch('<store type>/findMatching', { type: <resource type>, selector: <label name:value map> })` | Fetches resources that have `metadata.labels` matching that of the name-value properties in the selector. Does not support match expressions. |
| Function | Action | Example Command | Description |
| -------------- | -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `create` | Create | `$store.$dispatch('<store type>/create', <new object>)` | Creates a new Proxy object of the required type (`type` property must be included in the new object) |
| `clone` | Clone | `$store.$dispatch('<store type>/clone', { resource: <existing object> })` | Performs a deep clone and creates a proxy from it |
| `findAll` | Fetch all of a resource type and watch for changes to the returned resources so that the list updates as it changes. | `$store.dispatch('<store type>/findAll', { type: <resource type> })` | Fetches all resources of the given type. Also, when applicable, will register the type for automatic updates. If the type has already been fetched return the local cached list instead |
| `find` | Fetch a resource by ID and watch for changes to that individual resource. | `$store.dispatch('<store type>/find', { type: <resource type>, id: <resource id> })` | Finds the resource matching the ID. If the type has already been fetched return the local cached instance. |
| `findMatching` | Fetch resources by label and watch for changes to the returned resources, a live array that updates as it changes. | `$store.dispatch('<store type>/findMatching', { type: <resource type>, selector: <label name:value map> })` | Fetches resources that have `metadata.labels` matching that of the name-value properties in the selector. Does not support match expressions. |
Once objects of most types are fetched they will be automatically updated. See [Watching Resources](#watching-resources) for more info. For some types this does not happen. For those cases, or when an immediate update is required, adding `force: true` to the `find` style actions will result in a fresh http request.
@ -285,22 +285,22 @@ The Create/Edit Yaml experience is controlled by `/components/ResourceYaml.vue`.
Special attention should be made of the `mode` and `as` params that's available via the `CreateEditView` mixin (as well as other helpful functionality). Changing these should change the behaviour of the resource details page (depending on the availability of resource type custom components).
For more information about CreateEditView and how to add new create/edit forms, see [Create/Edit Forms.](./forms-and-validation.md)
For more information about CreateEditView and how to add new create/edit forms, see [Create/Edit Forms.](../guide/forms-and-validation.md)
| `mode` | `as` | Content |
|------------|----------|-------|
| falsy | falsy | Shows the View YAML or Customised Detail component|
| falsy | `config` | Shows the View YAML or Customised Edit component (in read only mode)|
| `edit` | falsy | Shows the Customised Edit component|
| `edit` | `yaml` | Shows the Edit Yaml component|
| `mode` | `as` | Content |
| ------ | -------- | -------------------------------------------------------------------- |
| falsy | falsy | Shows the View YAML or Customised Detail component |
| falsy | `config` | Shows the View YAML or Customised Edit component (in read only mode) |
| `edit` | falsy | Shows the Customised Edit component |
| `edit` | `yaml` | Shows the Edit Yaml component |
In addition the Create process (assessable with the same url + `/create`) is also managed by the resource detail page with similar param options.
| `mode` | `as` | Content |
|------------|----------|-------|
| falsy | `yaml` | Show the Edit YAML component in create mode
| `edit` | falsy | Show the Customised Edit component in create mode
| `clone` | falsy | Shows the Customised Edit component in create mode pre-populated with an existing resource
| `mode` | `as` | Content |
| ------- | ------ | ------------------------------------------------------------------------------------------ |
| falsy | `yaml` | Show the Edit YAML component in create mode |
| `edit` | falsy | Show the Customised Edit component in create mode |
| `clone` | falsy | Shows the Customised Edit component in create mode pre-populated with an existing resource |
### Customising Resource Detail Pages

View File

@ -0,0 +1,67 @@
# Auth Providers
## Github
### Developer Set up
Follow the in-dashboard instructions when configuring a Github auth provider.
### Multiple GitHub auth configs
The auth system supports multiple GitHub auth URLs and using the appropriate one based on the Host header that a request comes in on. Configuring this is not exposed in the regular UI, but is particularly useful for development against a server that already has GitHub setup.
In `management.cattle.io.authconfig`, edit the `github` entry. Add a `hostnameToClientId` map of Host header value -> GitHub client ID:
```yaml
hostnameToClientId:
"localhost:8005": <your GitHub Client ID for localhost:8005>
```
In the `secret`, namespace `cattle-global-data`, edit `githubconfig-clientsecret`. Add GitHub client ID -> base64-encoded client secret to the `data` section:
```yaml
data:
clientsecret: <the normal client secret already configured>
<your client id>: <your base64-encoded client secret for localhost:8005>
```
## Keycloak
### Developer Set Up (SAML)
Use the steps below to set up a Keycloak instance for dev environments and configure an Auth Provider for it.
1. Bring up a local Keycloak instance in docker using the instructions at [here](https://www.keycloak.org/getting-started/getting-started-docker).
> Ensure that the admin user has a first name, last name and email. These fields are referenced in the Keycloak client's mappers which are then referenced in the Rancher's auth provider config.
> Double check the client has the correct checkboxes set, specifically the Mappers `group` entry.
1. Using either the Ember or Vue UI set up the Keycloak auth provider by follow the instructions at [here](https://rancher.com/docs/rancher/v2.6/en/admin-settings/authentication/keycloak-saml/)
| Field | Value |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Display Name Field | givenName |
| User Name Field | email |
| UID Field | email |
| Groups Field | member |
| Entity ID Field | Depending on Rancher API Url. For instance when running Dashboard locally `https://192.168.86.26:8005/v1-saml/keycloak/saml/metadata` |
| Rancher API Host | Depending on Rancher API Url. For instance when running Dashboard locally `https://192.168.86.26:8005/` |
| Private Key | For key and cert files, export the Client in the Keycloak UI via the `Clients` list page and extract & wrap the `saml.signing.certificate` and `saml.signing.private.key` as cert files (see [step 5](https://gist.github.com/PhilipSchmid/506b33cd74ddef4064d30fba50635c5b) for more info). |
| Certificate | See Private Key section above |
| Metadata | For the SAML Metadata, download as per Rancher docs. Be sure to follow the `NOTE` instructions regarding `EntitiesDescriptor` and `EntityDescriptor`. For a better set of instructions see [step 6](https://gist.github.com/PhilipSchmid/506b33cd74ddef4064d30fba50635c5b) |
### Developer Set Up (OIDC)
1. In Vue UI set up the Keycloak OIDC provider with the following values
| Field | Value |
| ---------------------- | ---------------------------------------------------------------------------- |
| Client ID | Find via the keycloak console |
| Client Secret | Find via the keycloak console (client's credentials tab) |
| Private Key (optional) | |
| Certificate (optional) | |
| Keycloak URL | URL of keycloak instance (no path) |
| Keycloak Realm | Find via the keycloak console (above menu on left or in path after /realms/) |
> The user used when enabling the provider must be an Admin or in a group

View File

@ -14,7 +14,7 @@ This process cannot be reversed, still admin password can be changed in the prof
Rancher uses the following cookies:
| Name | Description |
|======|=============|
|------|-------------|
| `R_SESS` | The logged in user's session token |
| `R_PCS` | The user's preferred color scheme, used for server side rendering. If it's auto, it's the user's system preference. |
| `CSRF` | Cross site request. The server sets this cookie as defined in the header of the request. For any request other than a GET request, we have to use Javascript to read this value and use it as a header in the request. That proves we are using our code on Rancher's domain and our header matches that cookie. |

View File

@ -52,8 +52,7 @@ There are three Kubernetes resources in the local Rancher server Kubernetes clus
Rancher maintains a list of management clusters to maintain a consistent API for tracking all kinds of Kubernetes clusters, including imported clusters. There is one management Cluster resource for every downstream cluster managed by Rancher. It is also the Steve API's representation of Norman/v1 clusters.
In the UI, the management Cluster resource is used in Cluster Explorer. like the provisioning and CAPI cluster resources. Its used to show info on Kubernetes version, cloud provider, node OSs, and resource usage. No, you cant edit them, theyre available to users who can work within clusters but cant necessarily provision clusters. RKE1 and RKE2 clusters both have management clusters; its like, a shared interface between all types of clusters so we can have a consistent experience within Cluster Explorer
* v1
In the UI, the management Cluster resource is used in Cluster Explorer. like the provisioning and CAPI cluster resources. Its used to show info on Kubernetes version, cloud provider, node OSs, and resource usage. No, you cant edit them, theyre available to users who can work within clusters but cant necessarily provision clusters. RKE1 and RKE2 clusters both have management clusters; its like, a shared interface between all types of clusters so we can have a consistent experience within Cluster Explorer * v1
The `kubectl` command to get the management Cluster resources is:

View File

@ -0,0 +1,28 @@
# Directory Structure
The directory structure is mostly flat, with each top level dir being for a different important thing (or just required by Nuxt to be there).
## Other commonly changed stuff
| Path | Used for |
| ---------- | ----------------------------------------------------------------------------------------------- |
| assets | CSS, fonts, images, translations, etc resources which get processed during build |
| components | All general components which don't have a separate special directory elsewhere |
| layouts | The outermost components for rendering different kinds of pages (Nuxt) |
| store | [Vuex](https://vuex.vuejs.org/) stores which maintain all the state for the life of a page load |
| utils | Misc parsers, utilities, and other (usually) standalone code that doesn't fit anywhere else |
## The rest
These are mostly standard Nuxt dirs that you won't need to go into very often.
| Path | Used for |
| ---------- | -------------------------------------------------------------------------------------------------------------- |
| static | Static files which get directly copied into the build with no processing |
| middleware | Hooks called on every page load |
| mixins | Code that is defined once and then applied to several different other components |
| pages | The structure in here defines the routes that are available, and what gets rendered when one is hit |
| plugins | Add-ons to modify vue/nuxt or load additional 3rd-party code. The "steve" API client also notably lives here. |
| scripts | Shell scripts for building and related tasks, used by CI and `npm run ...` commands |
| server | Server-side middleware and dev SSL cert |
| test | Unit tests (or lack thereof) |

View File

@ -10,16 +10,16 @@ The Rancher UI injects the following global values
into the Helm chart values of apps installed through Rancher.
It adds them under `values.global.cattle.`
| YAML Directive | Source |
|------|-------|
| `cattle.clusterId` | The management cluster ID |
| `cattle.clusterName` | The management cluster name |
| `systemDefaultRegistry` | The default registry, taken from the settings |
| `cattle.url` | The Rancher server URL, taken from the settings |
| `cattle.rkePathPrefix` | The prefix path defined in the management cluster at `spec.rancherKubernetesEngineConfig.prefixPath` |
| YAML Directive | Source |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `cattle.clusterId` | The management cluster ID |
| `cattle.clusterName` | The management cluster name |
| `systemDefaultRegistry` | The default registry, taken from the settings |
| `cattle.url` | The Rancher server URL, taken from the settings |
| `cattle.rkePathPrefix` | The prefix path defined in the management cluster at `spec.rancherKubernetesEngineConfig.prefixPath` |
| `cattle.rkeWindowsPathPrefix` | The Windows prefix path defined in the management cluster at `spec.rancherKubernetesEngineConfig.winPrefixPath` |
| `cattle.windows` | Included in global values if `workerOSs` on the management cluster contains Windows |
| `cattle.systemProjectId` | Taken from the ID of the project named System |
| `cattle.windows` | Included in global values if `workerOSs` on the management cluster contains Windows |
| `cattle.systemProjectId` | Taken from the ID of the project named System |
If there are two charts associated with an app, such as `rancher-monitoring` and `rancher-monitoring-crd`, Rancher injects the global values into the values of both charts.

View File

@ -13,13 +13,13 @@ To tell Rancher about a new driver, go to Cluster Management -> Drivers -> Node
For more advanced control, the machine driver custom resource supports several annotations:
| Key | Value |
| --------------------------|---------------------------------------------------------------------------------------------------|
| publicCredentialFields | Fields that are considered "public" information and ok to display in cleartext for detail screens |
| privateCredentialFields | Fields that are private information that should be stored as a secret |
| optionalCredentialFields | Fields that are related to the credential, but are optional for the user to fill out |
| passwordFields | Fields that should be displayed as type="password" bullets instead of cleartext |
| defaults | Default values to set which the user may override |
| Key | Value |
| ------------------------ | ------------------------------------------------------------------------------------------------- |
| publicCredentialFields | Fields that are considered "public" information and ok to display in cleartext for detail screens |
| privateCredentialFields | Fields that are private information that should be stored as a secret |
| optionalCredentialFields | Fields that are related to the credential, but are optional for the user to fill out |
| passwordFields | Fields that should be displayed as type="password" bullets instead of cleartext |
| defaults | Default values to set which the user may override |
Each has a value that is a comma-separated list of field names. `defaults` are comma-separated, then colon-separated for key and value (e.g. `foo:bar,baz:42`). The annotations become information in API schemas, which the generic UI support uses to show better information.
@ -31,7 +31,7 @@ Cloud Credentials store the username & password, or other similar information, n
The cloud credential component lives in the top-level `cloud-credential` directory in the repo. The file should be named the same as the driver, in all lowercase (e.g. `cloud-credential/digitalocean.vue`).
If there is a reason to rename it or map multiple drivers to the same credential, configure that in the [plugins store](`../../../../store/plugins.js`). There is also other info in there about how guesses are taken on what each field is for and how it should be displayed. These can be customized for your driver by importing and calling `configureCredential()` and `mapDriver()`.
If there is a reason to rename it or map multiple drivers to the same credential, configure that in the [plugins store](../../shell/store/plugins.js). There is also other info in there about how guesses are taken on what each field is for and how it should be displayed. These can be customized for your driver by importing and calling `configureCredential()` and `mapDriver()`.
Create a component which displays each field that is relevant to authentication and lets the user configure them. Only the actual auth fields themselves, the rest of configuring the name, description, save buttons, etc is handled outside of the credential component.

View File

@ -0,0 +1,28 @@
# Directory Structure
The directory structure is mostly flat, with each top level dir being for a different important thing (or just required by Nuxt to be there).
## Other commonly changed stuff
| Path | Used for |
| ---------- | ----------------------------------------------------------------------------------------------- |
| assets | CSS, fonts, images, translations, etc resources which get processed during build |
| components | All general components which don't have a separate special directory elsewhere |
| layouts | The outermost components for rendering different kinds of pages (Nuxt) |
| store | [Vuex](https://vuex.vuejs.org/) stores which maintain all the state for the life of a page load |
| utils | Misc parsers, utilities, and other (usually) standalone code that doesn't fit anywhere else |
## The rest
These are mostly standard Nuxt dirs that you won't need to go into very often.
| Path | Used for |
| ---------- | -------------------------------------------------------------------------------------------------------------- |
| static | Static files which get directly copied into the build with no processing |
| middleware | Hooks called on every page load |
| mixins | Code that is defined once and then applied to several different other components |
| pages | The structure in here defines the routes that are available, and what gets rendered when one is hit |
| plugins | Add-ons to modify vue/nuxt or load additional 3rd-party code. The "steve" API client also notably lives here. |
| scripts | Shell scripts for building and related tasks, used by CI and `npm run ...` commands |
| server | Server-side middleware and dev SSL cert |
| test | Unit tests (or lack thereof) |

View File

@ -1,6 +1,6 @@
# Development Environment
This is part of the developer [getting started guide](../../../README.md).
This is part of the developer [getting started guide](https://github.com/rancher/dashboard/blob/master/README.md).
## Stack
@ -8,17 +8,17 @@ A good base knowledge of Vue, Vuex and Nuxt should be reached before going throu
## Helpful links
Description | Link
-----| ---
Core Vue Docs | <https://vuejs.org/v2/guide>
Typescript in Vue | <https://vuejs.org/v2/guide/typescript.html>
Vue Template/Directive Shorthands | <https://vuejs.org/v2/guide/syntax.html>
Vue Conditional rendering | <https://vuejs.org/v2/guide/conditional.html>
Vuex Core Docs | <https://vuex.vuejs.org/>
Nuxt Get Started | <https://nuxtjs.org/docs/2.x/get-started/installation>
Nuxt Structure | <https://nuxtjs.org/docs/2.x/directory-structure>
Axios (HTTP Requests) | <https://axios.nuxtjs.org/options>
HTTP Proxy middleware | <https://github.com/nuxt-community/proxy-module> (<https://github.com/chimurai/http-proxy-middleware>)
| Description | Link |
| --------------------------------- | ------------------------------------------------------------------------------------------------------ |
| Core Vue Docs | <https://vuejs.org/v2/guide> |
| Typescript in Vue | <https://vuejs.org/v2/guide/typescript.html> |
| Vue Template/Directive Shorthands | <https://vuejs.org/v2/guide/syntax.html> |
| Vue Conditional rendering | <https://vuejs.org/v2/guide/conditional.html> |
| Vuex Core Docs | <https://vuex.vuejs.org/> |
| Nuxt Get Started | <https://nuxtjs.org/docs/2.x/get-started/installation> |
| Nuxt Structure | <https://nuxtjs.org/docs/2.x/directory-structure> |
| Axios (HTTP Requests) | <https://axios.nuxtjs.org/options> |
| HTTP Proxy middleware | <https://github.com/nuxt-community/proxy-module> (<https://github.com/chimurai/http-proxy-middleware>) |
## Platform
@ -87,7 +87,7 @@ Developers are free to use the IDE and modern browser of their choosing. Here's
### Running the Dashboard
See the [Running For Development](../../../README.md#running-for-development) section on how to bring up the Dashboard locally
See the [Running For Development](https://github.com/rancher/dashboard/blob/master/README.md#running-for-development) section on how to bring up the Dashboard locally
> Troubleshooting: Multiple `Could not freeze errors` in `yarn dev` terminal
>

View File

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 154 KiB

View File

Before

Width:  |  Height:  |  Size: 333 KiB

After

Width:  |  Height:  |  Size: 333 KiB

View File

Before

Width:  |  Height:  |  Size: 326 KiB

After

Width:  |  Height:  |  Size: 326 KiB

View File

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 166 KiB

View File

@ -161,4 +161,4 @@ Node templates make it more convenient to define node pools so that you don't ha
![RKE1 node templates](./screenshots/rke1-node-templates.png)
For technical reasons that are better explained in the section on [cluster management resources,](../cluster-management-resources.md) node templates are not a planned feature for V2 cluster provisioning. For V2 provinioning, you have to fill in the machine configuration details when you create or edit the Kubernetes cluster through Rancher.
For technical reasons that are better explained in the section on [cluster management resources,](../code-base-works/cluster-management-resources.md) node templates are not a planned feature for V2 cluster provisioning. For V2 provinioning, you have to fill in the machine configuration details when you create or edit the Kubernetes cluster through Rancher.

View File

@ -1,15 +1,19 @@
### Customizing how Kubernetes Resources are Presented
---
sidebar_label: Customizing Kubernetes Resources
---
# Customizing how Kubernetes Resources are Presented
These are where you do most of the daily work of customizing of how a particular k8s resource is presented.
Path | Used for
-----|---------
config | Configuration of how products look and work; constants for labels, types, cookies, query params, etc that are used
chart | Custom components to present when installing a Product chart
detail | Custom components to show as the detail view for a particular resource instance
edit | Custom components to show as the edit (or view config) view for a particular resource instance
list | Custom components to show as the list view for a resource type
models | Custom logic extending the standard resource class for each API type and model returned by the API
| Path | Used for |
| ------ | ------------------------------------------------------------------------------------------------------------------ |
| config | Configuration of how products look and work; constants for labels, types, cookies, query params, etc that are used |
| chart | Custom components to present when installing a Product chart |
| detail | Custom components to show as the detail view for a particular resource instance |
| edit | Custom components to show as the edit (or view config) view for a particular resource instance |
| list | Custom components to show as the list view for a resource type |
| models | Custom logic extending the standard resource class for each API type and model returned by the API |
There is one `Config` entry for each "product", which are the result of installing one of our helm charts to add a feature into Rancher such as Istio, monitoring, logging, CIS scans, etc. The config defines things like:
- The condition for when that product should appear (usually the presence of a type in a certain k8s API group)

View File

@ -50,8 +50,8 @@ Now, back in the original console window, run:
```
npm adduser --registry http://localhost:4873 (just add a user with username/password: admin/admin)
yarn publish ./shell/creators/app --patch
yarn publish ./shell/creators/pkg --patch
yarn publish ./shell/creators/app --patch --registry http://localhost:4873/
yarn publish ./shell/creators/pkg --patch --registry http://localhost:4873/
```
These steps will publish the two [creator](https://classic.yarnpkg.com/en/docs/cli/create) packages to the local npm registry. If you open a browser to `http://127.0.0.1:4873` you'll see the two packages there.
@ -59,12 +59,12 @@ These steps will publish the two [creator](https://classic.yarnpkg.com/en/docs/c
Now we'll publish the `shell` package.
```
yarn publish ./shell --patch
PUBLISH_ARGS="--registry http://localhost:4873/" yarn publish-shell
```
At this point, we've published 3 NPM packages:
- `@rancher/shell` - the core shell UI that can be incorporated into a new UI to give core funtionality
- `@rancher/shell` - the core shell UI that can be incorporated into a new UI to give core functionality
- `@rancher/create-app` - a simple package that can be used with the `yarn create` command to create a new UI
- `@rancher/create-pkg` - a simple package that can be used with the `yarn create` command to create a new UI Package
@ -95,7 +95,7 @@ yarn dev
You should be able to open a browser at `https://127.0.0.1:8005` and you'll get the Rancher Dashboard UI.
This illustrates being able to share the `shell` across UIs - in practice, we would extract the Rancher code from the shell, so instead of getting the Rancher
functionality in this example, you'd get an empty shell applciation with the side menu, UI chrome, avatar menu etc.
functionality in this example, you'd get an empty shell application with the side menu, UI chrome, avatar menu etc.
You would now add new functionality to this application by adding new UI Packages.
@ -259,7 +259,7 @@ Now we can add our UI package that we published to the local registry with:
yarn add testplugin
```
Now when we run the app with `yarn dev` and browse to https://127.0.0.1:8005 you'll see that our `testplugin` plugin ahas been included in the UI.
Now when we run the app with `yarn dev` and browse to https://127.0.0.1:8005 you'll see that our `testplugin` plugin has been included in the UI.
This supports the use case where we may be developing a UI Package that relies on another UI package - so we can create a skeleton app, add the dependencies we need via NPM and then develop our own plugin in the repository - so the only code we have is the code for out plugin, but we are able to test and run it in a UI locally with the other plugins that it needs.
@ -284,4 +284,4 @@ yarn link @rancher/shell
This link the package used by the app to the dashboard source code. We can make changes to the shell code in the Rancher Dashboard repository and the separate app will hot-reload.
This allows us to develop a new UI Application and be able to make changes to the Shell - in this use case, we're working against two git repositories, so we need to ensure we commit chanages accordingly.
This allows us to develop a new UI Application and be able to make changes to the Shell - in this use case, we're working against two git repositories, so we need to ensure we commit changes accordingly.

View File

@ -64,7 +64,7 @@ Localisation files can be found in `./assets/translations/en-us.yaml`.
Please follow precedents in file to determine where new translations should be place.
Form fields are conventionally defined in translations as <some prefix>.<field name>.{label,description,enum options if applicable} e.g.
Form fields are conventionally defined in translations as `<some prefix>`.`<field name>`.{label,description,enum options if applicable} e.g.
```yml
account:

View File

@ -1,4 +1,8 @@
## Server-Side-Rendering (SSR)
---
sidebar_label: Server-side Rendering
---
# Server-side-Rendering (SSR)
> Update: From Rancher 2.6.6 SSR mode is disabled for devs by default

View File

@ -1,19 +1,9 @@
# Style
# Style and format
## SCSS
SCSS Styles can be found in `assets/styles/`. It's recommended to browse through some of the common styles in `_helpers.scss` and `_mixins.scss`.
### Icons
Icons are font based and can be shown via the icon class
```
<i class="icon icon-fw icon-gear" /></a>
```
Icons can be browsed via `https://rancher.github.io/icons/`.
Additional icon styles can be found in via `assets/styles/fonts/_icons.scss`.
## Date
The Dashboard uses the [dayjs](https://day.js.org/) library to handle dates, times and date algebra. However when showing a date and time they should take into account the date and time format. Therefore it's advised to use a formatter such as `/components/formatter/Date.vue` to display them.

View File

@ -0,0 +1,19 @@
# Terminology
This is part of the developer [getting started guide](https://github.com/rancher/dashboard/blob/master/README.md).
The official Kubernetes [glossary](https://kubernetes.io/docs/reference/glossary/?fundamental=true) also explains Kubernetes-specific terminology.
| Term | Description |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Dashboard / Cluster Explorer / Vue UI | The application in this repository. It will slowly replace the older Ember UI |
| Manager / Cluster Manager / Ember UI | The old [Ember based UI](https://github.com/rancher/ui) |
| Norman | Old Rancher API which has mostly been superseded by Steve |
| Steve | New Rancher API |
| Rancher (Product) | A [Kubernetes Management Platform](https://rancher.com/products/rancher/). This product includes the Rancher API and UIs |
| RKE | [Rancher Kubernetes Engine](https://rancher.com/products/rke/) - A certified Kubernetes distribution |
| SSR | Server Side Rendering. Disabled by default when developing the Dashboard (enabled pre 2.6.6) |
| SPA | Single Page Application. Enabled by default in production |
| Vue | [A frontend client framework used by the Dashboard](https://vuejs.org/) |
| Vuex | [Frontend state management](https://vuex.vuejs.org/) |
| Nuxt | [Vue framework helper](https://nuxtjs.org/) |

View File

@ -0,0 +1,156 @@
# E2E Test
This repo is configured for end-to-end testing with [Cypress](https://docs.cypress.io/api/table-of-contents) and the CI will run using a blank state of Rancher executed locally. The aim is however to enable also tests using remote instances of Ranchers.
Because of this, we extend the [Cypress best practices](https://docs.cypress.io/guides/references/best-practices#How-It-Works), so be sure to read them before write any test.
## Initial Setup
For the cypress test runner to consume the UI, you should specify the environment variables:
- Local authentication credentials
- `TEST_USERNAME`, default `admin`
- `TEST_PASSWORD`, user password or custom during first Rancher run
- `CATTLE_BOOTSTRAP_PASSWORD`, initialization password which will also be used as `admin` password (do not pick `admin`)
- `TEST_BASE_URL` // URL used by Cypress to run the tests, default `https://localhost:8005`
- `TEST_SKIP_SETUP` // Avoid to execute bootstrap setup tests for already initialized Rancher instances
- Dashboard
- `TEST_PROJECT_ID` // Project ID used by Cypress/Sorry cypress to run the tests
- `TEST_RUN_ID` (optional) // Identifier for your dashboard run, default value is timestamp
## Development with watch/dev
While writing the tests, you can simply run Rancher dashboard and then open the Cypress dashboard with the commands
- `yarn dev`
- `yarn cy:open`
The Cypress dashboard will contain the options and the list of test suites. These will automatically re-run if they are altered (hot reloading).
For further information, consult [official documentation](https://docs.cypress.io/guides/guides/command-line#cypress-open).
## E2E Dashboard
### Self-hosted: Sorry Cypress
Link to the dashboard: http://139.59.134.103:8080/
E2E tests can be added and displayed in a dashboard by defining the project ID with the env var `TEST_PROJECT_ID`, then run the script:
```bash
yarn cy:run:sorry
```
### Cypress dashboard installation guide
The setup is done using a cloud hosting service and with its IP we configured the Sorry Cypress as indicated in the [guide](https://docs.sorry-cypress.dev/guide/dashboard-and-api). The process is straightforward, except for the IP which is required to be overwritten within `minio.yml` manifest as the default `http://localhost` value generate CORS issues.
### Cypress Dashboard
E2E tests can be displayed in Cypress dashboard by defining the project ID with the env var `TEST_PROJECT_ID`, then run the script by passing the parameters
```bash
yarn cy:run --record --key YOUR_RECORD_KEY_HERE
```
These values are provided when you create a new project within Cypress dashboard or within `Project settings`.
It's also possible to run a workflow in GitHub Actions E2E test using these values to record on personal dashboards.
## Local and CI/prod run
It is possible to start the project and run all the tests at once with a single command. There's however a difference between `dev` and `production` run. The first will not require an official certificate and will build the project in `.nuxt`, while the production will enable all the SSL configurations to run encrypted.
- `yarn e2e:pre-dev`, to optionally initialize Docker and build the project, if not already done
- `yarn e2e:dev`, single run local development
- `yarn e2e:pre-prod`, to optionally initialize Docker and build the project, required for GitHub Actions
- `yarn e2e:dev`, for production use case and CI, which will also restart Docker and build the project
## Custom Commands
As Cypress common practice, some custom commands have been created within `command.ts` file to simplify the development process. Please consult Cypress documentation for more details about when and how to use them.
Worth mentioning the `cy.getId()` and `cy.findId()` commands, as it is mainly used to select elements. This would require to add `data-testid` to your element inside the markup and optionally matchers.
## Writing tests
Test specs should be grouped logically, normally by page or area of the Dashboard but also by a specific feature or component.
Tests should make use of common Page Object (PO) components. These can be pages or individual components which expose a useful set of tools, but most importantly contain the selectors for the DOM elements that need to be used. These will ensure changes to the underlying components don't require a rewrite of many many tests. They also allow parent components to easily search for children (for example easily finding all anchors in a section instead of the whole page). Given that tests are typescript it should be easy to explore the functionality.
Some examples of PO functionality
```ts
HomePage.gotTo()
new HomePagePo().checkIsCurrentPage()
new BurgerMenuPo().clusters()
new AsyncButtonPO('[data-testid="my-button"]').isDisabled()
new LoginPagePo().username().set('admin')
```
POs all inherit a root `component.po`. Common component functionality can be added there. They also expose their core cypress (chainable) element.
There are a large number of pages and components in the Dashboard and only a small set of POs. These will be expanded as the tests grow.
Note: When selecting an element be sure to use the attribute `data-testid`, even in case of lists where elements are distinguished by an index suffix.
## Tips
The Cypress UI is very much your friend. There you can click pick tests to run, easily visually track the progress of the test, see the before/after state of each cypress command (specifically good for debugging failed steps), see https requests, etc.
Tests can also be restricted before cypress runs, or at run time, by prepending `.only` to the run.
```ts
describe.only('Burger Side Nav Menu', () => {
beforeEach
```
```ts
it.only('Opens and closes on menu icon click', () => {
```
## Data testid naming
While defining naming, always consider deterministic usage and rely on specific values. For cases where the content is required, e.g. select name specific elements as in cluster selection, consider use the `contain()` method. Further guideline and explanation in the official documentation related section.
In case of complex component, define a prefix for your `data-testid` with a the prop `componentTestid` and a default value. This will help you to define unique value and composable identifier in case of more elements, as well to avoid custom term for each test if not necessary, e.g. no multiple elements.
E.g. given the action menu:
```ts
/**
* Inherited global identifier prefix for tests
* Define a term based on the parent component to avoid conflicts on multiple components
*/
componentTestid: {
type: String,
default: 'action-menu'
}
```
```html
<li
v-for="(option, i) in options"
:key="opt.action"
:data-testid="componentTestid + '-' + i + '-item'"
>
```
## Debugging
To summarize what [defined in the documentation](https://docs.cypress.io/guides/guides/debugging), the following modalities of debugging are provided:
- `debugger` flag
- `.debug()` as chained command
- `cy.pause()` for analyzing the state of the test
- Inspect commands in the Cypress dashboard to view the logs
- `.then(console.log)` to append the log to the resolved promise
These values are provided when you create a new project within Cypress dashboard or within `Project settings`.
## Coverage
Both unit and E2E tests generate coverage respectively with Jest and NYC. These values are generated on both PR and push to `master` and `release` after merging. The service used to display the values is Codecov and can be found [here](https://app.codecov.io/gh/rancher/dashboard).
Special attention goes to the E2E as the code is instrumented with Babel and the configuration is set within Nuxt.js.

View File

@ -0,0 +1,206 @@
# Unit test
The dashboard is configured to run unit tests with Jest in combination of vue-test-utils, for Vue scoped cases.
Requirements to accept tests:
- JS and TS formats
- Suffix with `.test` or `.spec`
- Contained in any directory `__tests__`
Adopted commands:
- `yarn test`, run and watch every test
- `yarn test:ci`, script used for CI, which outputs a coverage report to `/coverage` folder
Example tests can be found in `/components/__tests__`. For more information about testing vue components, see the [vue test utils](https://vue-test-utils.vuejs.org/) and [jest](https://jestjs.io/docs/getting-started) docs.
## VSCode debugging tools
It is possible to use debugging tools within Jest via VSCode. To do so, open the debugger panel (Ctrl/Cmd+Shift+D) and select the `Debug Jest Tests` option from the dropdown. This will start a debug session with the Jest tests, allowing you to set breakpoint, inspect code and visualize variables on the panel itself. As usual it's possible to execute the tests by `F5` after selecting the right option.
## Style guide
On top of the recommendation provided by the [Vue documentation](https://vuejs.org/guide/scaling-up/testing.html), it is also encouraged to follow these patterns to create readable and aimed tests.
### Describe and test/it statement
To clearly state the scope of the test, it's convenient to define in the first `describe` always define with a noun the name of the function, method, or component being tested. Multiple assertions may be grouped together under a common statement in `describe` block, as it helps to avoid repetition and ensure a set of tests to be included. Each `test`/`it` block should then start with a verb related to what is the expectation.
```ts
describe('myfunction', () => {
describe('given the same parameter', () => {
it('should return the same result', () => {
// Test code
});
it('should return something else for a second parameter', () => {
// Test code
});
});
});
```
For further information, consult the [Jest API](https://jestjs.io/docs/api#describename-fn) documentation.
### Simple tests
Test with the highest readability and reliability should avoid logic, as this will increase line of code and have to be tested as well. Static data is then preferred over computation and should be declared always within the describe or test or as close as possible.
Don't:
```ts
test('define if is required to use this in our component from the response', () => {
const myData = externalFunction(externalData);
for(data in myData) {
data.key = 'something else'
}
expect(isRequired(myData)).toBe(true);
});
```
Do:
```ts
describe('FX: isRequired', () => {
test('should return true', () => {
const myData = { key: 'required case' };
const result = isRequired(myData);
expect(result).toBe(true);
});
});
```
### AAA pattern
Adoption of AAA format (arrange, act, assert) for tests.
- Arrange is where you prepare test, e.g. set properties to a component or declare variables
- Act is when an event or function is triggered
- Assertion correspond to the expectation of the test
Don't:
```ts
describe('FX: isRequired', () => {
test('should return true', () => {
let myData = { key: 'required case' };
expect(myData).toBeTruthy();
myData.key = 'something else';
const result = isRequired(myData);
expect(result).toBe(true);
myData['key2'] = 'another key/value';
expect(result).toBe(false);
});
});
```
Do:
```ts
describe('FX: isRequired', () => {
test('should return true', () => {
const myData = { key: 'required case' };
const result = isRequired(myData);
expect(result).toBe(true);
});
test('should return false if malformed data', () => {
const myData = {
key: 'required case',
key2: 'another key/value'
};
const result = isRequired(myData);
expect(result).toBe(false);
});
});
```
### Behaviors over implementations
As also defined in the Vue documentation for [component](https://vuejs.org/guide/scaling-up/testing.html#component-testing) and [composable testing](https://vuejs.org/guide/scaling-up/testing.html#testing-composables), it is recommended to test rendered elements over internal API of the component.
Following an input example as in the [documentation](https://v2.vuejs.org/v2/cookbook/unit-testing-vue-components.html?redirect=true#Base-Example).
Don't:
```ts
const wrapper = mount(YourComponent);
const inputWrapper = wrapper.find(`[data-testid=your-component]`;
inputWrapper.setValue(1);
expect(wrapper.emitted('input')[0][0]).toBe(1);
```
Do:
```ts
const wrapper = mount(YourComponent);
const inputWrapper = wrapper.find(`[data-testid=your-component]`;
inputWrapper.setValue(1);
expect(wrapper.text()).toContain('1')
```
### Parameterization
When multiple cases are required to be tested for the same component, it is recommended to avoid multiple actions and assertions or even worse logic, but rather rely on Jest functions to parametrize the test.
Don't:
```ts
describe('FX: isRequired', () => {
test('should return true', () => {
let myData = { key: 'required case' };
expect(myData).toBeTruthy();
myData.key = 'something else';
expect(result).toBe(true);
myData.key = 'another value';
expect(result).toBe(true);
});
});
```
Do:
```ts
describe('FX: isRequired', () => {
test.each([
'required case',
'something else',
'another value',
])('should return true', (key) => {
const myData = { key };
const result = isRequired(myData);
expect(result).toBe(true);
});
});
```
## Coverage
Both unit and E2E tests generate coverage respectively with Jest and NYC. These values are generated on both PR and push to `master` and `release` after merging. The service used to display the values is Codecov and can be found [here](https://app.codecov.io/gh/rancher/dashboard).
Special attention goes to the E2E as the code is instrumented with Babel and the configuration is set within Nuxt.js.

View File

@ -0,0 +1,93 @@
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion
const lightCodeTheme = require('prism-react-renderer/themes/github');
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'Rancher UI DevKit',
tagline: 'Rancher UI development kit',
url: 'https://rancher.github.io',
baseUrl: '/dashboard/',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
favicon: 'img/favicon.ico',
trailingSlash: false,
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: 'rancher', // Usually your GitHub org/user name.
projectName: 'dashboard', // Usually your repo name.
// Even if you don't use internalization, you can use this field to set useful
// metadata like html lang. For example, if your site is Chinese, you may want
// to replace "en" with "zh-Hans".
i18n: {
defaultLocale: 'en',
locales: ['en'],
},
presets: [
[
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
routeBasePath: '/',
sidebarPath: require.resolve('./sidebars.js'),
showLastUpdateTime: true,
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
},
blog: false,
theme: { customCss: require.resolve('./src/css/custom.css') },
}),
],
],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
navbar: {
title: 'UI DevKit',
logo: {
alt: 'Rancher Logo',
src: 'img/rancher-logo.svg',
},
items: [
{
type: 'doc',
docId: 'getting-started/concepts',
position: 'right',
label: 'Docs',
},
{
href: 'https://rancher.github.io/storybook/',
position: 'right',
label: 'Components & Design kit',
},
],
},
footer: {
style: 'dark',
links: [
{
label: 'Stack',
href: 'https://slack.rancher.io/',
},
{
label: 'Github',
href: 'https://github.com/rancher/',
},
],
copyright: `Copyright © ${ new Date().getFullYear() } Rancher. All rights reserved.`,
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
},
}),
};
module.exports = config;

43
docusaurus/package.json Normal file
View File

@ -0,0 +1,43 @@
{
"name": "rancher-ui-devkit",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "2.0.0-beta.22",
"@docusaurus/preset-classic": "2.0.0-beta.22",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.2.0",
"prism-react-renderer": "^1.3.5",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-beta.22"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"engines": {
"node": ">=16.14"
}
}

69
docusaurus/sidebars.js Normal file
View File

@ -0,0 +1,69 @@
/**
* Creating a sidebar enables you to:
- create an ordered group of docs
- render a sidebar for each doc of that group
- provide next/previous navigation
The sidebars can be generated from the filesystem, or explicitly defined here.
Create as many sidebars as you want.
*/
// @ts-check
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
// By default, Docusaurus generates a sidebar from the docs folder structure
// tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }],
// But you can create a sidebar manually
// Items name and page should be same.
// For eg. if you rename a page you should also change that page name in item attribute under the tutorialSidebar.
tutorialSidebar: [
{
type: 'category',
label: 'Getting Started',
items: ['getting-started/concepts', 'getting-started/development_environment', 'getting-started/ui-walkthrough'],
},
{
type: 'category',
label: 'Guide',
items: [
'guide/build-for-container-registry',
'guide/customising-how-k8s-resources-are-presented',
'guide/forms-and-validation',
'guide/package-management',
'guide/plugins'
],
},
{
type: 'category',
label: 'How code base works',
items: [
'code-base-works/api-resources-and-schemas',
'code-base-works/auth-providers',
'code-base-works/auth-sessions-and-tokens',
'code-base-works/cluster-management-resources',
'code-base-works/directory-structure',
'code-base-works/helm-chart-apps',
'code-base-works/keyboard-shortcuts',
'code-base-works/machine-drivers',
'code-base-works/performance',
],
},
'on-screen-text-and-translations',
'products-and-navigation',
'server-side-rendering',
'sortable-table',
'style',
'terminology',
{
type: 'category',
label: 'Testing',
items: ['testing/stress-test', 'testing/e2e-test', 'testing/unit-test'],
},
],
};
module.exports = sidebars;

View File

@ -0,0 +1,62 @@
import React from 'react';
import clsx from 'clsx';
import styles from './styles.module.css';
const FeatureList = [
{
title: 'Getting Started',
link: 'getting-started/concepts/',
target: '',
Svg: require('@site/static/img/documentation.svg').default,
description: (
<>
This section covers the basics of working with the Rancher dashboard. It will familiarize you with the development
environment, concepts, and new Rancher UI.
</>
),
},
{
title: 'Components & Design kit',
link: 'https://rancher.github.io/storybook/',
target: '_blank',
Svg: require('@site/static/img/storybook.svg').default,
description: (
<>
Rancher storybook is a collection of pre-built, reusable assetscomponents, patterns, and documentation guidance, to help
developers to build consistent UI experiences faster.
</>
),
},
];
function Feature( {
Svg, title, description, link, target
} ) {
return (
<div className={clsx('col col--6')}>
<a className="featureLink" href={link} target={target}>
<div className="text--center">
<Svg className={styles.featureSvg} role="img" />
</div>
<div className="text--center padding-horiz--md">
<h3>{title}</h3>
<p>{description}</p>
</div>
</a>
</div>
);
}
export default function HomepageFeatures() {
return (
<section className={styles.features}>
<div className="container">
<div className="row">
{FeatureList.map((props, idx) => (
<Feature key={idx} {...props} />
))}
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,11 @@
.features {
display: flex;
align-items: center;
padding: 2rem 0;
width: 100%;
}
.featureSvg {
height: 200px;
width: 200px;
}

View File

@ -0,0 +1,93 @@
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #3D98D3;
--ifm-color-primary-dark: #29784c;
--ifm-color-primary-darker: #277148;
--ifm-color-primary-darkest: #205d3b;
--ifm-color-primary-light: #33925d;
--ifm-color-primary-lighter: #359962;
--ifm-color-primary-lightest: #3cad6e;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
--ifm-color-body: #141419;
--ifm-menu-color: #141419;
}
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] {
--ifm-color-primary: #3D98D3;
--ifm-color-primary-dark: #21af90;
--ifm-color-primary-darker: #1fa588;
--ifm-color-primary-darkest: #1a8870;
--ifm-color-primary-light: #29d5b0;
--ifm-color-primary-lighter: #32d8b4;
--ifm-color-primary-lightest: #4fddbf;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
--ifm-color-body: #ffffff;
--ifm-menu-color: #ffffff;
}
body {
font-family: 'Lato', sans-serif;
color: var( --ifm-color-body);
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Poppins', sans-serif;
font-weight: normal;
}
.navbar__logo {
height: 1.5rem;
}
.homepage-banner {
text-align: left;
min-height: 300px;
background: url(@site/static/img/footer-bg.png) no-repeat right bottom;
background-size: 75%;
padding: 50px;
}
.homepage-banner p {
width: 50%;
}
.featureSvg_src-components-HomepageFeatures-styles-module {
max-width: 150px;
}
a.featureLink {
color: var(--ifm-color-body);
}
a.featureLink:hover {
color: var(--ifm-link-color);
text-decoration: none;
}
@media screen and (max-width: 996px) {
.homepage-banner {
padding: 30px 30px;
background-size: 100%;
}
.homepage-banner p {
width: 100%;
}
}
.featureSvg_GfXr {
width: 150px;
}

View File

@ -0,0 +1,33 @@
import React from 'react';
import clsx from 'clsx';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import Layout from '@theme/Layout';
import HomepageFeatures from '@site/src/components/HomepageFeatures';
import styles from './index.module.css';
function HomepageHeader() {
return (
<header className={clsx('hero hero--primary', styles.heroBanner)}>
<div className="container homepage-banner">
<h1 className="hero__title">Rancher UI DevKit</h1>
<p className="hero__subtitle">Rancher UI DevKit provides everything you need to start developing with the Rancher UI and plugins</p>
</div>
</header>
);
}
export default function Home() {
const { siteConfig } = useDocusaurusContext();
return (
<Layout
title={`${ siteConfig.title }`}
description="Description will go into a meta tag in <head />">
<HomepageHeader />
<main>
<HomepageFeatures />
</main>
</Layout>
);
}

View File

@ -0,0 +1,23 @@
/**
* CSS files with the .module.css suffix will be treated as CSS modules
* and scoped locally.
*/
.heroBanner {
padding: 0;
text-align: center;
position: relative;
overflow: hidden;
}
@media screen and (max-width: 996px) {
.heroBanner {
padding: 0 2rem;
}
}
.buttons {
display: flex;
align-items: center;
justify-content: center;
}

View File

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><g id="b"><path id="c" d="M474.96357,305.2449l-78.23344-106.21121v-18.68469l7.87221-7.86811c19.02467-18.59241,19.37507-49.08707,.78266-68.11173-2.60402-2.66455-5.5084-5.01797-8.65487-7.01305v-30.22404c0-4.67655-3.79109-8.46765-8.46764-8.46766h-94.9429l-35.06972-47.61928c-2.75724-3.76885-8.04769-4.58891-11.81654-1.83166l-.02257,.01658-67.08422,49.41794H96.02975c-4.67656,0-8.46767,3.79108-8.46767,8.46764l.00002,.01644v59.13398L11.1191,182.56653c-3.76468,2.7711-4.57164,8.06868-1.80277,11.83501l78.24576,106.2071v131.91395c0,4.67655,3.79111,8.46764,8.46766,8.46764h94.93467l35.06974,47.60697c2.7667,3.76471,8.06146,4.57379,11.82619,1.80707l.01703-.01253,67.0719-49.40151h83.31322c4.67655,0,8.46764-3.79109,8.46764-8.46764v-59.13399l76.43477-56.30868c3.76362-2.77204,4.56743-8.07025,1.79538-11.83387l-.00085-.00113m-78.22931-149.82254v-34.78639c6.88643,10.57599,6.88643,24.21861,0,34.7946M249.63848,27.90653l22.64751,30.74146h-64.39035l41.74284-30.74146ZM27.98871,191.17792l59.57338-43.88645v124.75628L27.98871,191.17792ZM234.64965,471.73169l-22.64749-30.74146h64.39032l-41.74283,30.74146Zm-130.15225-47.68088V75.58741H379.79481v97.73533l-58.5796,58.5796c-7.06461,6.92536-18.40571,6.81248-25.33109-.25213-6.82888-6.96618-6.82797-18.11595,.00204-25.08103l56.2594-56.25941c1.94922-1.87529,5.03187-1.87529,6.98108,0,1.92779,1.9211,1.93321,5.04122,.01209,6.96899l-.01209,.01211-51.14676,51.13857c-3.27825,3.33516-3.2321,8.69639,.10305,11.97463,3.29463,3.23843,8.57695,3.23843,11.87159,0l51.14679-51.14269c8.54118-8.53179,8.54879-22.37216,.01701-30.91334l-.01701-.01701c-8.6529-8.271-22.28155-8.271-30.93445,0l-56.2594,56.25941c-13.78496,13.43071-14.07215,35.49336-.64146,49.27832,13.43071,13.78496,35.49336,14.07215,49.27832,.64144,.21659-.21103,.43045-.42485,.64146-.64144l46.60904-46.58853v226.7706H104.4974Zm292.23273-71.69584v-124.76448l59.57339,80.89857-59.57339,43.86591Z"/><path id="d" d="M137.64117,147.04508h113.42227c4.67768,0,8.46971-3.79202,8.46971-8.46971s-3.79203-8.46971-8.46971-8.46971h-113.42227c-4.67769,0-8.46971,3.79202-8.46971,8.46971s3.79202,8.46971,8.46971,8.46971"/><path id="e" d="M137.64117,221.20483h113.42227c4.67768,0,8.46971-3.79203,8.46971-8.46971s-3.79203-8.46971-8.46971-8.46971h-113.42227c-4.67769,0-8.46971,3.79203-8.46971,8.46971s3.79202,8.46971,8.46971,8.46971"/><path id="f" d="M346.28145,278.42929H137.63707c-4.67769,0-8.46971,3.79203-8.46971,8.46971s3.79202,8.46971,8.46971,8.46971h208.64439c4.67768,0,8.46971-3.79203,8.46971-8.46971s-3.79203-8.46971-8.46971-8.46971"/><path id="g" d="M346.28145,352.60136H137.63707c-4.67769,0-8.46973,3.79203-8.46973,8.46974s3.79203,8.46971,8.46973,8.46971h208.64439c4.67768,0,8.46971-3.79203,8.46971-8.46971s-3.79203-8.46971-8.46971-8.46971"/></g></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

Some files were not shown because too many files have changed in this diff Show More