diff --git a/docusaurus/docusaurus.config.js b/docusaurus/docusaurus.config.js index f1b03d651ad..ec5fbff6805 100644 --- a/docusaurus/docusaurus.config.js +++ b/docusaurus/docusaurus.config.js @@ -1,6 +1,4 @@ // @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'); @@ -16,13 +14,9 @@ const config = { 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. + organizationName: 'rancher', + projectName: 'dashboard', - // 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'], @@ -33,13 +27,7 @@ const config = { '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. - }, + docs: false, blog: { showReadingTime: true, blogTitle: 'Rancher UX/UI Blog', @@ -47,14 +35,43 @@ const config = { postsPerPage: 'ALL', blogSidebarCount: 'ALL', blogSidebarTitle: 'All Posts', - // Please change this to your repo. - // Remove this to remove the "edit this page" links. }, theme: { customCss: require.resolve('./src/css/custom.css') }, }), ], ], + plugins: [ + [ + '@docusaurus/plugin-content-docs', + { + id: 'extensions', + path: 'docs/extensions', + routeBasePath: 'extensions', + sidebarPath: require.resolve('./extensionSidebar.js'), + showLastUpdateTime: true, + // Enable versioning for extensions + lastVersion: 'current', + versions: { + current: { + label: 'Next', + path: 'next', + }, + }, + }, + ], + [ + '@docusaurus/plugin-content-docs', + { + id: 'internal', + path: 'docs/internal', + routeBasePath: 'internal', + sidebarPath: require.resolve('./internalSidebar.js'), + showLastUpdateTime: true + }, + ], + ], + themeConfig: /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ ({ @@ -67,10 +84,17 @@ const config = { }, items: [ { - type: 'doc', - docId: 'extensions/home', - position: 'right', - label: 'Docs', + type: 'docsVersionDropdown', + docsPluginId: 'extensions', + position: 'left', + dropdownActiveClassDisabled: true, + }, + { + type: 'doc', + docId: 'home', + docsPluginId: 'extensions', + position: 'right', + label: 'Docs', }, { to: '/blog', label: 'Blog', position: 'right' diff --git a/docusaurus/extensionSidebar.js b/docusaurus/extensionSidebar.js new file mode 100644 index 00000000000..aeef7db5c89 --- /dev/null +++ b/docusaurus/extensionSidebar.js @@ -0,0 +1,117 @@ +/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ +const sidebars = { + extensionsSidebar: [ + { + type: 'category', + label: 'Extensions', + link: { + type: 'doc', + id: 'home', + }, + items: [ + 'introduction', + { + type: 'category', + label: 'Changelog', + link: { + type: 'doc', + id: 'changelog', + }, + items: ['rancher-2.9-support', 'rancher-2.10-support'] + }, + 'support-matrix', + 'extensions-getting-started', + 'extensions-configuration', + { + type: 'category', + label: 'Extensions API', + link: { + type: 'doc', + id: 'api/overview', + }, + items: [ + 'api/concepts', + 'api/metadata', + { + type: 'category', + label: 'Navigation & Pages', + items: [ + 'api/nav/products', + 'api/nav/custom-page', + 'api/nav/resource-page', + 'api/nav/side-menu', + 'api/nav/routing', + ] + }, + 'api/actions', + 'api/cards', + 'api/panels', + 'api/tabs', + 'api/table-columns', + { + type: 'category', + label: 'Components', + link: { + type: 'doc', + id: 'api/components/components', + }, + items: [ + 'api/components/resources', + 'api/components/node-drivers', + 'api/components/auto-import', + ] + }, + 'api/common', + ] + }, + { + type: 'category', + label: 'Advanced', + items: [ + 'advanced/air-gapped-environments', + 'advanced/provisioning', + 'advanced/localization', + 'advanced/hooks', + 'advanced/stores', + 'advanced/version-compatibility', + 'advanced/safe-mode', + 'advanced/yarn-link', + ] + }, + 'publishing', + { + type: 'category', + label: 'Use cases/Examples', + link: { + type: 'doc', + id: 'usecases/overview', + }, + items: [ + 'usecases/top-level-product', + 'usecases/cluster-level-product', + { + type: 'category', + label: 'Node Driver', + link: { + type: 'doc', + id: 'usecases/node-driver/overview', + }, + items: [ + 'usecases/node-driver/about-drivers', + 'usecases/node-driver/cloud-credential', + 'usecases/node-driver/machine-config', + 'usecases/node-driver/node-driver-icon', + 'usecases/node-driver/advanced', + 'usecases/node-driver/proxying', + 'usecases/node-driver/about-example', + ] + } + ] + }, + 'known-issues', + ] + }, + ] +}; + +module.exports = sidebars; diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/air-gapped-environments.md b/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/air-gapped-environments.md new file mode 100644 index 00000000000..b5924217178 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/air-gapped-environments.md @@ -0,0 +1,110 @@ +# Air-gapped Environments + +In order to load an extension within an air-gapped instance of Rancher, you will need to import an Extension Catalog Image to provide the extension assets, which are then served within the "Available" tab of the Extensions page and can be installed as normal. + +The Extension Catalog Image (ECI) contains the assets of each extension which give the [ui-plugin-operator](https://github.com/rancher/ui-plugin-operator) the necessary files to load an extension into the Rancher Dashboard. + +We have implemented an action within the Extensions page to take care of the heavy lifting for you by creating the necessary resources. + +> Note: The ECI is comprised of a hardened [SLE BCI](https://registry.suse.com/bci/bci-base-15sp4/index.html) image with an [NGINX](https://nginx.org/en/) service which supplies the minified extension files. + +## Prerequisites + +Loading an extension into an air-gapped envrionment requires a few prerequisites, namely: + +- The Extension needs to be bundled into the ECI +- A registry to house the ECI +- Access to this registry within the air-gapped Cluster + +> Note: Any Secrets that are required to authenticate with the registry ***MUST*** be created in the `cattle-ui-plugin-system` namespace. + +## Building the Extension Catalog Image + +Currently there are two options available for building your extension into an ECI. You can use the predefined Github Workflow, if you plan on housing the extension within a Github repository, or you can manually build and publish your extension to a specified registry. + +In either case, the ECI will need to be published to a registry that is accessible from the air-gapped cluster. + +### Github Workflow + +If using the provided Github workflow with your extension, the extension will be built and published for each package version to the [Github Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) (`ghcr`). + +> Note: The extension image that is built will contain Helm charts for each subsequent package (i.e. `./pkg/`). In order to release a new version of a package, the root extension will need to be published with an updated tag within `./package.json`. + +Once the extension has been published you will then be able to pull, tag, and push the ECI into your desired registry. + +From a machine that has access to both the desired registry and `ghcr.io`, pull the image: + +```sh +docker pull ghcr.io//ui-extension-: +``` + +Then re-tag and push the image to your registry: + +```sh +docker tag ghcr.io//ui-extension-: //ui-extension-:TAG +docker push //ui-extension-:TAG +``` + +Proceed to the [Importing the Extension Catalog Image](#importing-the-catalog-image) step. + +### Manual Build + +The ECI can also be built manually using the `yarn publish-pkgs -c` command. + +___Building Prerequisites___ + +This method requires a few tools to be installed: + +- [make](https://www.gnu.org/software/make/) +- [docker](https://docs.docker.com/get-docker/) +- [go](https://go.dev/dl/) +- [nodejs](https://nodejs.org/en/download) ( >= `12.0.0` < `17.0.0` ) +- [yarn](https://yarnpkg.com/getting-started/install) +- [jq](https://stedolan.github.io/jq/) +- [yq](https://github.com/mikefarah/yq/#install) ( >= `4.0.0` ) +- [helm](https://helm.sh/docs/intro/install/) ( >= `3.0.0` ) + +___Running the Build Manually___ + +To build, simply run the following: + +```sh +yarn publish-pkgs -cp -r -o +``` + +| Option | Argument | Description | +| -- | ---- | -------- | +| `-c` | | specifies the container build | +| `-p` | | Option to push the built image into the registry | +| `-r` | `` | specifies the registry where the image will be housed | +| `-o` | `` | specifies the organization namespace for the registry | + +Reference the [Manually Publishing an Extension Catalog Image](../publishing#manually-publishing-an-extension-catalog-image) step in the Publishing section for more information. + +## Importing the Extension Catalog Image + +Importing the ECI is fairly straightforward, you will need the Catalog Image Reference from your registry and any secrets necessary to authenticate with the registry. + +> Note: Any Secrets that are required to authenticate with the registry ***MUST*** be created in the `cattle-ui-plugin-system` namespace. + +Within the Extensions page, select "Manage Extension Catalog" from the action menu in the top right. From here you will be able to see any Extension Catalog Images loaded previously along with their state, name, image used for the deployment, and cache state (used by the `ui-plugin-operator`). + +To import an ECI, click on the "Import Extension Catalog" button: + +![Manage Catalog Action](../screenshots/manage-catalog-action.png) + +Fill out the form within the modal with your Catalog Reference Image URI (for example: `//ui-extension-:TAG`) and any secrets necessary to pull the image. + +> Note: If the registry is not supplied within the URI, it will default to `hub.docker.io`. + +> Note: If the version of the image is omitted, this will default to `latest`. + +![Extension Catalog Import](../screenshots/import-catalog-dialog.png) + +The resources mentioned [above](#air-gapped-environments) will be created. You can navigate back to the main Extensions page by selecting breadcrumb link for "Extension" button from the header title in the top left of the screen. + +![Navigate Charts List](../screenshots/navigate-chart-list.png) + +Within the "Available", tab the newly imported extensions are now available to be installed normally. + +![Available Charts](../screenshots/available-charts.png) diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/hooks.md b/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/hooks.md new file mode 100644 index 00000000000..9ce09c6139c --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/hooks.md @@ -0,0 +1,48 @@ +# Hooks + +## Navigation In and Out of Extensions +Extensions can define `onEnter` and `onLeave` hooks in their index `addNavHooks` extension method, which will run when the authenticated middleware detects a package change by checking the route meta property. `onEnter` and `onLeave` accept the same props: the vuex store context and a config object containing: + +| Key | Type | Description | +|---|---|---| +|`clusterId`| String | The unique ID of the current cluster, determined by the route's `cluster` param | +|`product`| String | The name of the product being navigated to, also taken from the route | +|`oldProduct`| String | The name of the product being navigated away from | +|`oldIsExt`| Boolean | True if the previous product was in a package (note that this doesn't distinguish between inter- and intra-package product changes) | + +The `authenticated` middleware will: +* Connect to the management cluster (`loadManagement`) +* Apply product config, as determined from the route (`applyProducts`) +* If an old extension is loaded, run its `onLeave` hook +* Run `onEnter` hook for new extension +* Run `loadCluster` (which, as stated above, can run load and unload cluster actions defined in extensions) + +An example of the usage `onEnter` and `onLeave` using the `addNavHooks` extension method would be: + +```ts +import { importTypes } from '@rancher/auto-import'; +import { IPlugin, OnNavToPackage, OnNavAwayFromPackage } from '@shell/core/types'; + +const onEnter: OnNavToPackage = async(store, config) => { + // define any function needed here for `onEnter` +}; +const onLeave: OnNavAwayFromPackage = async(store, config) => { + // define any function needed here for `onLeave` +}; + +// Init the extension +export default function(plugin: IPlugin) { + // Auto-import model, detail, edit from the folders + importTypes(plugin); + + // Provide extension metadata from package.json + // it will grab information such as `name` and `description` + plugin.metadata = require('./package.json'); + + // Load a product + plugin.addProduct(require('./product')); + + // => => => Add hooks to Vue navigation world + plugin.addNavHooks(onEnter, onLeave); +} +``` \ No newline at end of file diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/localization.md b/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/localization.md new file mode 100644 index 00000000000..67ac5addd03 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/localization.md @@ -0,0 +1,129 @@ +# Localization + +Generating localizations in extensions is done per package via a translation YAML file found in the `./pkg//l10n` directory. If a translation is not included in the user's selected language, it will fall back to English. + +## Internationalization (i18n) + +Any code producing messages, labels, numbers, dates, times, and the like should use the `i18n` store and translation strings to generate and format them instead of hardcoding English or American-isms anywhere. Messages and number formatting uses [ICU templating](https://formatjs.io/docs/intl-messageformat) for very powerful pluralization and customizing. + +The `assets/translations` dir stores a YAML file with translations for each supported language. + - English is automatically used as the "fallback" if a particular key is missing from a non-English language. + - If you don't have a native translation for a particular key, just leave that key out of the language + - Do not duplicate the English string into other languages. + +Translations should be the largest phrase that makes sense as a single key, rather than several separate things rendered in a fixed order. + - For example "about 2 minutes remaining" should be a single translation: `About {n, number} {n, plural, one { minute }, other { minutes }} remaining`. + - Not one for `About`, one for `minute`, one for `minutes`, one for `remaining`, and some code picking and choosing which to concatenate. + +All on screen text should be localised and implemented in the default `en-US` locale. There are different ways to access localised text + +> `t` can be exposed via adding the i18n getter as a computed property with `...mapGetters({ t: 'i18n/t' })` + +In HTML + +```html + +{{ t("") }} +``` + +Many components will also accept a localisation path via a `value-key` property, instead of the translated text in `value`. + +In JS/TS + +```ts +this.t('') +``` + +A localisation can be checked with + +```ts +this.$store.getters['i18n/exists']('') + +this.$store.getters['i18n/withFallback']('', null, '')) +``` + +## Using Variables in i18n Paths + +In Javascript files, variables in localisation paths must be wrapped in quotation marks when the variable contains a slash. + +For example, if we want to dynamically fill in the description of a resource based on its type, we can use a `type` variable when referencing the localisation path to the description: + +```ts +{ + description: this.t(`secret.typeDescriptions.'${ type }'.description`), +} +``` + +In this case, the quotation marks are required because some Secret types, such as `kubernetes.io/basic-auth`, include a slash. + +## l10n + +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 ``.``.{label,description,enum options if applicable} e.g. + +```yml +account: + apiKey: + description: + label: Description + placeholder: Optionally enter a description to help you identify this API Key +``` + +If a translation is not included in the user's selected language, it will fall back to English. The only time the Rancher UI devs should modify a non-English translation is when a key is renamed. + + +## Checking for missing translation strings + +The script `scripts/check-i18n` can be used to check for missing translation strings. + +It uses a set of regular expressions to detect for translation strings being used in the code base. It will report any string references that it finds that it does not find a corresponding translation for - this is an indication of a missing translation string. + +This script is run as part of the dashboard Continuos Integration via GitHub actions. + +The script accepts two optional arguments: + +- -s Print out the details of translations strings found in the localisation files that don't appear to be used by the code +- -x Don't set the exit code if there are errors detected + +### Comments + +Since the script uses regular expressions, there are cases where it might: + +- Incorrectly detect a use of a translation string in the code that is not such +- Not be able to detect that a translation string has been used + +To address these issues, you need to use comments in your code to assist the i18n checker script. + +Comments can be placed at the start of the line, or as part of a line of code at the end. They can not be added to templates, so +comments should be added at the top of the code file for these. + +#### i18n-uses + +You can use a comment of the form: + +``` +// i18n-uses TRANSLATION +``` + +to indicate that the string `TRANSLATION` is used in the code. + +#### i18n-ignore + +You can use a comment of the form: + +``` +// i18n-ignore TRANSLATION +``` + +to indicate that the string `TRANSLATION` should be ignored and is not a reference to a translation string. + +#### Translation Strings + +With both `i18n-uses` and `i18n-ignore`, the `TRANSLATION` string can be: + +- A simple string, e.g. `area.subarea.name` +- A regular expression, e.g. `/area\.subarea\.name/` +- A simple string with wild-cards specified using `*`, e.g. `area.subarea.*` or `area.*.name` diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/provisioning.md b/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/provisioning.md new file mode 100644 index 00000000000..7f2a42d1be8 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/provisioning.md @@ -0,0 +1,111 @@ +# Cluster Provisioning (RKE2 / Custom) + +The UI provides a number of ways to customise the processes that creates RKE2/Custom clusters. This includes +- Adding additional Cluster Provisioner types +- Customising or replacing components used in the create process +- Additional tabs +- Hooks in to the processes that persist cluster resources +- Overrides that replace the process to persist cluster resources + +## Custom Components +Existing components that manage cloud credentials and machine configuration can be replaced as per [Custom Node Driver UI](../api/components/node-drivers.md). + +## Custom Cluster Provisioner +New cluster provisioners can be added that can tailor the create/edit experience for their own needs. + +### Resources +Creating a cluster revolves around two resources +- The machine configuration + - The machine configuration defines how the individual nodes within a node pool will be provisioned. For instance which region and size they may be + - These normally have an type of `rke-machine-config.cattle.io.config`, which matches the id of it's schema object +- The provisioning cluster + - The `provisioning.cattle.io.cluster` which, aside from machine configuration, contains all details of the cluster + - In the UI this is an instance of the `rancher/dashboard` `shell/models/provisioning.cattle.io.cluster.js` class + - This has lots of great helper functions, most importantly `save` + - Cluster provisioners should always create an instance of this class + +### Provisioner Class +To customise the process of creating or editing these resources the extension should register a provisioner class which implements the `IClusterProvisioner` interface. + +``` + plugin.register('provisioner', 'my-provisioner', ExampleProvisioner); +``` + +> Note that `register` allows us to register an arbitrary extension and we introduce the type `provisioner`. + +Below is outline for the functionality the class provides, for detail about the `IClusterProvisioner` interface see the inline documentation. + +The class provides a way to... +1. show a card for the new cluster type for the user to choose when selecting a provider +1. handle custom machine configs that haven't necessarily been provided by the usual node driver way +1. hooks to extend/override the cluster save process. Either.. + - Override all of the cluster save process + - Extend/Override parts of the cluster save process. This allows a lot of the boilerplate code to manage addons, member bindings, etc to run + - run code before the cluster resource is saved + - replace the code that saves the core cluster + - run code after the cluster resource is saved +1. show a custom label for the provider of the custom cluster + - This is done by setting the `ui.rancher/provider` annotation in the cluster + - It's used in the UI whenever showing the cluster's provider +1. show custom tabs on the Detail page of the custom cluster + +To make API calls from the UI to a different domain, Rancher includes a [proxy](https://github.com/rancher/rancher/blob/release/v2.6/pkg/httpproxy/proxy.go) that can be used to make requests to third-party domains (like a cloud provider's API) without requiring that the other end supports CORS or other browser shenanigans. Send requests to `/meta/proxy/example.com/whatever/path/you/want` and the request will be made from the Rancher server and proxied back to you. + +TLS and port 443 are assumed. Add a port after the hostname to change the port (`example.com:1234`). For plain HTTP, first stop and consider the chain of life decisions which have led you to this point. Then if you still think you need that, use `/meta/proxy/http:/example.com:1234` (note one slash after `http:`, not two). The hostname must be included in the whitelist defined in global settings, or in the configuration for an active node driver. If if isn't your request will be denied. (This prevents a malicious (non-admin) user from abusing the Rancher server as an arbitrary HTTP proxy or reach internal IPs/names that the server can reach directly but the user can't from the outside.) + +The rest of the path and query string are sent to the target host as you'd expect. + +Normal headers are copied from your request and sent to the target. There are some exceptions for sensitive fields like the user's rancher cookies or saved basic auth creds which will not be copied. If you send an `X-Api-Cookie-Header`, its value will be sent as the normal `Cookie` to the target. If you send `X-API-Auth-Header`, that will be sent out as the normal `Authorization`. + +But normally you want to make a request using a Cloud Credential as the authorization, without knowing what the secret values in that credential are. You ask for this by sending an `X-Api-CattleAuth-Header` header. The value of the header specifies what credential Id to use, and a [signer](https://github.com/rancher/rancher/blob/release/v2.6/pkg/httpproxy/sign.go) which describes how that credential should be injected into the request. Common options include `awsv4` (Amazon's complicated HMAC signatures), `bearer`, `basic`, and `digest`. For example if you send `X-Api-CattleAuth-Header: Basic credId=someCredentialId usernameField=user passwordField=pass`, Rancher will retrieve the credential with id `someCredentialId`, read the values of the `user` and `pass` fields from it and add the header `Authorization: Basic ` to the proxied request for you. + +### Components +When creating or editing a cluster the user can see the cloud credential and machine pool components. + +These can be provided as per the `Custom Components` section. + +``` + plugin.register('cloud-credential', 'my-provisioner', false); + plugin.register('machine-config', 'my-provisioner', () => import('./src/test.vue')); +``` + +> This example registers that no cloud credential is needed and registers a custom component to be used for Machine Configuration within a node/machine pool - this is the same as with Node Drivers - e.g. with the OpenStack node driver example. + +### Custom tabs in the Cluster's Cluster Configuration + +When creating or editing the cluster the user can see a set of `Cluster Configuration` tabs that contain configuration applicable to the entire cluster. + +Extensions can add additional tabs here. + +``` + plugin.addTab(TabLocation.CLUSTER_CREATE_RKE2, { + resource: ['provisioning.cattle.io.cluster'], + queryParam: { type: ExampleProvisioner.ID } + }, { + name: 'custom-cluster-config', + labelKey: 'exampleClusterConfigTab.tabLabel', + component: () => import('./src/example-cluster-config-tab.vue') + }); +``` +> Note we use the new `queryParam` property to allow us to target the tab only when the cluster is of our provider type. + +### Custom tabs in the Cluster's detail page + +When clicking on a created cluster in the UI the user is shown details for the cluster. This page has some tabs which may not be applicable to the custom provider. The provider class has a way to hide these. To add a new custom tab the following can be used + +``` + plugin.addTab(TabLocation.RESOURCE_DETAIL, { + resource: ['provisioning.cattle.io.cluster'], + context: { provider: ExampleProvisioner.ID } + }, { + name: 'custom', + label: 'Custom Tab', + component: () => import('./src/example-tab.vue') + }); +``` + +Note we use the new `context` property to allow us to target the tab only when the cluster is of our provider type. + +### Localisation + +The custom cluster type's label is defined as per any other extension text in `l10n/en-us.yaml` as `cluster.provider.`. \ No newline at end of file diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/safe-mode.md b/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/safe-mode.md new file mode 100644 index 00000000000..4ae52cb5e54 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/safe-mode.md @@ -0,0 +1,6 @@ +# Safe Mode + +If you have an extension installed which blocks the loading or navigating of Rancher Dashboard, you can use Safe Mode to disable the misbehaving extensions. To use Safe Mode, there's a query parameter called `safemode` which you can use. + +To enable Safe Mode, you must enter your Rancher Dashboard in the following way: +* `https://<- YOUR RANCHER BASE URL ->/<- ANY PAGE ->?safemode`. \ No newline at end of file diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/stores.md b/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/stores.md new file mode 100644 index 00000000000..65bdfe782e6 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/stores.md @@ -0,0 +1,91 @@ +# Custom VueX Stores + +Extensions may want to define their own custom VueX stores. + +## Initializing Extension Stores + +Extensions should explicitly register any store modules in their `index.ts` by using the `addDashboardStore` extension method. This will also add familiar [Vuex](https://vuex.vuejs.org/) actions for retrieving and classifying resources, details of which can be found in `shell/plugins/dashboard-store/index`. + +An example would be to define in the folder `store` of your extension a basic configuration on an `index.ts` file, such as: + +```ts +import { CoreStoreSpecifics, CoreStoreConfig } from '@shell/core/types'; +import getters from './getters'; // this would be your getters file on your extension /store folder +import mutations from './mutations'; // this would be your mutations file on your extension /store folder +import actions from './actions'; // this would be your actions file on your extension /store folder + +// to achieve naming consistency throughout the extension +// we recommend this to be defined on a config file and exported +// so that the developer can import it wherever it needs to be used +const YOUR_PRODUCT_NAME = 'the-name-of-your-product'; + +const yourExtensionFactory = (): CoreStoreSpecifics => { + return { + state() { + return { someStateVariable: '' }; + }, + + getters: { ...getters }, + + mutations: { ...mutations }, + + actions: { ...actions }, + }; +}; +const config: CoreStoreConfig = { namespace: YOUR_PRODUCT_NAME }; + +export default { + specifics: yourExtensionFactory(), + config +}; +``` + +In the `store` folder you just need to create the `getters.js`, `actions.js` and `mutations.js` files and write up your store code there, based on the convention of [Vuex](https://vuex.vuejs.org/). + +And on the `index.ts` on your root folder, where you define your extension configuration, you can just use the `addDashboardStore` extension method, such as: + +```ts +import { importTypes } from '@rancher/auto-import'; +import { IPlugin } from '@shell/core/types'; +import extensionStore from './store'; + +// Init the package +export default function(plugin: IPlugin) { + // Auto-import model, detail, edit from the folders + importTypes(plugin); + + // Provide extension metadata from package.json + // it will grab information such as `name` and `description` + plugin.metadata = require('./package.json'); + + // Load a product + plugin.addProduct(require('./product')); + + // => => => Add Vuex store + plugin.addDashboardStore(extensionStore.config.namespace, extensionStore.specifics, extensionStore.config); +} +``` + + +Extensions can optionally define their own cluster store module by setting `isClusterStore` in the store index, eg: +```ts +// to achieve naming consistency throughout the extension +// we recommend this to be defined on a config file and exported +// so that the developer can import it wherever it needs to be used +const YOUR_PRODUCT_NAME = 'the-name-of-your-product'; + +const config: CoreStoreConfig = { + namespace: YOUR_PRODUCT_NAME, + isClusterStore: true +}; + +export default { + specifics: harvesterFactory(), + config, + init: steveStoreInit +}; +``` + +This will cause the shell `loadCluster` action to run the extension's `loadCluster` action when entering a package, and the extension store's `unsubscribe` and `reset` when leaving. + + diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/version-compatibility.md b/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/version-compatibility.md new file mode 100644 index 00000000000..15247216f22 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/version-compatibility.md @@ -0,0 +1,23 @@ +# Version compatibility + +There are several ways to "control" an Extension version compatibility with several parameters such as Rancher version, Kubernetes version and Rancher UI/Dashboard version. +It can be achieved by using annotations on the `index.yaml` and `chart.yaml` files generated by the [publish](../publishing.md) procedure. + +![Index YAML annotations](../screenshots/index-yaml-annotations.png) + +Here are the annotations you can modify: + +| API | Rancher Version support (Minimum version)| Effect +| --- | --- | --- | +| `catalog.cattle.io/kube-version` | v2.7.0 | Defines a possible minimum and maximum Kubernetes version for the extension to work with. Prevents extension version from being loaded on the UI +| `catalog.cattle.io/rancher-version` | v2.7.0 | Defines a possible minimum and maximum Rancher version for the extension to work with. Prevents extension version from being loaded on the UI +| `catalog.cattle.io/host` | v2.7.0 | Defines the host for the extension, which should have the value `rancher-manager`. Prevents extension version from being loaded on the UI +| `catalog.cattle.io/ui-extensions-version` | v2.9.0 | Defines a possible minimum and maximum Extensions API version for the extension to work with. Prevents extension version from being loaded on the UI +| `catalog.cattle.io/ui-version` | v2.7.3 | Defines a possible minimum and maximum Rancher Dashboard version for the extension to work with. Extension version will be loaded but will appear as disabled + + +**NOTE: The annotation `catalog.cattle.io/ui-extensions-version` will become mandatory from Rancher 2.10 and onwards. If the annotation is not present on a given extension Helm Chart, the extension itself will not be loaded** + +All annotations will prevent the loading of an extension into Rancher apart from `catalog.cattle.io/ui-version`, which will have a sligthly different behaviour and allow for the extension version to be loaded, but will disable it on the Install, Upgrade and Rollback scenarios, where that given version will not appear on those dropdowns and also the extension version button on the side panel will be disabled and will show a tooltip on hover with the information, such as: + +![UI version annotation](../screenshots/ui-version-annotation.png) \ No newline at end of file diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/workflow-configuration.md b/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/workflow-configuration.md new file mode 100644 index 00000000000..40070e20f98 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/workflow-configuration.md @@ -0,0 +1,105 @@ +# Github Workflow Configuration + +## Extension Charts Workflow + +To build the charts needed to provide a Helm repository, use the reusable workflow job found [here](https://github.com/rancher/dashboard/blob/master/.github/workflows/build-extension-charts.yml). When published you will be able to target the Github repository as a Helm repository, which will serve the charts for installation within the Rancher Dashboard. + +## Workflow Permissions + +Each workflow requires permissions to be set correctly to complete the release processes, both builds have specific needs with some overlap: + +| Property | Extension Type | Permission | Description | +| -------- | :---: | :---: | ----------------- | +| `actions` | Charts/Catalog | `write` | Requires `write` to [cancel a workflow](https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#cancel-a-workflow-run). | +| `contents` | Charts*/Catalog | `write`*/`read` | Requires `read` for `actions/checkout`, and requires `write` (*only necessary in the Chart Build workflow) to `put` the contents of the built extension charts [into a branch](https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28#create-or-update-file-contents). | +| `deployments` | Charts | `write` | Requires `write` when [deploying `gh-pages`](https://docs.github.com/en/rest/deployments/deployments?apiVersion=2022-11-28#create-a-deployment). | +| `packages` | Catalog | `write` | Requires `write` when a catalog image is created to [create the package](https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#publishing-a-package-using-an-action). | +| `pages` | Charts | `write` | Requires write to [request and create page builds](https://docs.github.com/en/rest/pages/pages?apiVersion=2022-11-28#request-a-github-pages-build) for the deployment. | + +### Extension Chart Inputs + +| Property | Required | Description | +| -------- | :---: | -----------------| +| `permissions` | `true` | This gives the workflow permissions to checkout, build, and push to the repository. | +| `target_branch` | `true` | The Github branch target for the extension build assets | +| `tagged_release` | `false` | Specifies the tag name when triggering workflows by publishing tagged releases. (Requires alternate dispatch rules) | + +### Example usage + +```yml +... +jobs: + build-extension-charts: + name: Build and release Extension charts + uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@master + permissions: + actions: write + contents: write + deployments: write + pages: write + with: + target_branch: gh-pages +``` + +## Extension Catalog Image Workflow + +To build an Extension Catalog Image (ECI) for air-gapped/private repositories, use the workflow found [here](https://github.com/rancher/dashboard/blob/master/.github/workflows/build-extension-catalog.yml). This will build and push the container image push into the specified registry. + +### Extension Catalog Image Inputs + +| Property | Required | Description | +| -------- | :---: | -----------------| +| `permissions` | `true` | This gives the workflow permissions to checkout, build, and push to the registry. | +| `registry_target` | `true` | The container registry to publish the catalog image | +| `registry_user` | `true` | The username connected to the container registry | +| `tagged_release` | `false` | Specifies the tag name when triggering workflows by publishing tagged releases. (Requires alternate dispatch rules) | +| `registry_token` | `true` | The password or token used to authenticate with the registry | + +### Example Usage + +```yml +... +jobs: + build-extension-catalog: + name: Build and release Extension Catalog Image + uses: rancher/dashboard/.github/workflows/build-extension-catalog.yml@master + permissions: + actions: write + contents: read + packages: write + with: + registry_target: ghcr.io + registry_user: ${{ github.actor }} + secrets: + registry_token: ${{ secrets.GITHUB_TOKEN }} + +``` + +## Versioning + +Each workflow is targeting the `master` branch of [`rancher/dashboard`](https://github.com/rancher/dashboard) by default. Depending on your `@rancher/shell` and Rancher instance versions, you will need to target the branch per release. For example, if running a Rancher instance version `v2.7.7`, you will need to target the `release-2.7.7` branch: + +```yml +... +jobs: + build-extension-charts: + name: Build and release Extension charts + uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@release-2.7.7 +``` + +> **Warning:** The reusable workflow was created after Rancher `v2.7.5` - this means you will **NOT** be able to use these workflow files with this release or any previous releases. + +## Dispatching Configuration + +As mentioned in the `tagged_release` property description, in order to have the workflow triggered by published releases the dispatch will need to be updated. +This topic is covered in the [Publishing section](../publishing#triggering-a-github-workflow-on-tagged-release). To be concise, update the dispatch within the workflow file to execute the workflow on the `released` event of a `release` action:" + +```yml +on: + release: + types: [released] +``` + +This will ensure that the workflow is only triggered when the tagged release is published and is _not_ a draft release. + +Extensive information on how to trigger workflows can be found in the [Github documentation](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows). diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/yarn-link.md b/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/yarn-link.md new file mode 100644 index 00000000000..51b95f3fd7b --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/advanced/yarn-link.md @@ -0,0 +1,26 @@ +# Using yarn link + +You may want to develop your extension with the very latest dashboard code rather than the code published in the `@rancher/shell` npm module. + +Suppose we are creating a new UI - it will include the Rancher Shell code via its npm package, so if we needed to make changes to the shell, we'd have to make those changes, publish them as a new version of the package and update our UI to use it. + +We can `yarn link` to improve this workflow. + +With the Dashboard repository checked out, we can run: + +```sh +cd shell +yarn link +``` + +Then, in our other app's folder, we can: + +```sh +yarn link @rancher/shell +``` + +This will 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 changes accordingly. + +> Note: This feature is most useful for dashboard developers - generally we encourage the use of the published shell module diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/api/actions.md b/docusaurus/extensions_versioned_docs/version-2.0.x/api/actions.md new file mode 100644 index 00000000000..93cd4872249 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/api/actions.md @@ -0,0 +1,179 @@ +--- +toc_max_heading_level: 4 +--- + +# Actions + +An action is typically represented as a button or icon and can be invoked by a user to execute a particular function. + +Actions are added to Rancher via the `addAction` method. + +## addAction + +*(Rancher version v2.7.2)* + +This method adds a button/action to the UI. + +Method: + +```ts +plugin.addAction(where: String, when: LocationConfig, options: Object); +``` + +_Arguments_ + +`where` string parameter admissable values for this method: + +| Key | Type | Description | +|---|---|---| +|`ActionLocation.HEADER`| String | Location for an action on the Header of Rancher Dashboard | +|`ActionLocation.TABLE`| String | Location for an action on a List View Table of Rancher Dashboard | + +
+ +`when` Object admissable values: + +`LocationConfig` as described above for the [LocationConfig object](./common#locationconfig). + +
+
+ +### ActionLocation.HEADER options + +![Header Actions](../screenshots/header-actions.png) + +`options` config object. Admissable parameters for the `options` with `'ActionLocation.HEADER'` are: + +| Key | Type | Description | +|---|---|---| +|`tooltip`| String | Text for tooltip of button | +|`tooltipKey`| String | Same as "tooltip" but allows for translation. Will superseed "tooltip" | +|`shortcut`| String | Shortcut key bindings. Passed as a string (ex: m), for which the default will be `Ctrl+m` for Linux/Windows and `Meta+m` for Mac OS. Check examples below | +|`icon`| String | icon name (based on [rancher icons](https://rancher.github.io/icons/)) | +|`svg`| Function | icon based on a SVG file which can be included using `@require` | +|`enabled`| Function | Whether the action/button is enabled or not | +|`invoke`| Function | function executed when action/button is clicked | + +Usage example for `'ActionLocation.HEADER'`: + +```ts +plugin.addAction( + ActionLocation.HEADER, + {}, + { + tooltipKey: 'plugin-examples.header-action-one', + tooltip: 'Test Action1', + shortcut: 'm', + icon: 'icon-pipeline', + invoke(opts: any, resources: any) { + console.log('action executed 1', this); // eslint-disable-line no-console + console.log(opts); // eslint-disable-line no-console + console.log(resources); // eslint-disable-line no-console + } + } +); +``` + +```ts +plugin.addAction( + ActionLocation.HEADER, + {}, + { + tooltipKey: 'plugin-examples.header-action-two', + tooltip: 'Test Action2', + shortcut: 'b', + svg: require('@pkg/test-features/icons/rancher-desktop.svg'), + enabled(ctx: any) { + return true; + }, + invoke(opts: any, resources: any) { + console.log('action executed 2', this); // eslint-disable-line no-console + console.log(opts); // eslint-disable-line no-console + console.log(resources); // eslint-disable-line no-console + } + } +); +``` + +
+
+ +### ActionLocation.TABLE options + +_INLINE TABLE ACTION_ + +![inline table action](../screenshots/inline-table-action.png) + +_BULKABLE/GLOBAL TABLE ACTION_ + +![bulkable table action](../screenshots/inline-and-bulkable.png) + +`options` config object. Admissable parameters for the `options` with `'ActionLocation.TABLE'` are: + +| Key | Type | Description | +|---|---|---| +|`label`| String | Action label | +|`labelKey`| String | Same as "label" but allows for translation. Will superseed "label" | +|`icon`| String | icon name (based on [rancher icons](https://rancher.github.io/icons/)) | +|`svg`| Function | icon based on a SVG file which can be included using `@require` | +|`divider`| Boolean | Shows a line separator (divider) in actions menu | +|`multiple`| Boolean | Whether the action/button is bulkable (can be performed on multiple list items) | +|`enabled`| Function | Whether the action/button is enabled or not | +|`invoke`| Function | function executed when action/button is clicked | + + +Usage example for `'ActionLocation.TABLE'`: + +_RENDERING A SIMPLE DIVIDER_ + +```ts +plugin.addAction( + ActionLocation.TABLE, + { resource: ['catalog.cattle.io.clusterrepo'] }, + { divider: true }); +``` + + +_CONFIGURING A NON-BULKABLE ACTION (inline action)_ + +```ts +plugin.addAction( + ActionLocation.TABLE, + { resource: ['catalog.cattle.io.clusterrepo'] }, + { + label: 'some-extension-action', + labelKey: 'plugin-examples.table-action-one', + icon: 'icon-pipeline', + enabled(ctx: any) { + return true; + }, + invoke(opts: ActionOpts, values: any[]) { + console.log('table action executed 1', this, opts, values); // eslint-disable-line no-console + } + } +); +``` + + +_CONFIGURING AN INLINE AND BULKABLE ACTION_ + +```ts +plugin.addAction( + ActionLocation.TABLE, + { resource: ['catalog.cattle.io.clusterrepo'] }, + { + label: 'some-bulkable-action', + labelKey: 'plugin-examples.table-action-two', + svg: require('@pkg/test-features/icons/rancher-desktop.svg'), + multiple: true, + enabled(ctx: any) { + return true; + }, + invoke(opts: ActionOpts, values: any[]) { + console.log('table action executed 2', this); // eslint-disable-line no-console + console.log(opts); // eslint-disable-line no-console + console.log(values); // eslint-disable-line no-console + }, + } +); +``` diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/api/cards.md b/docusaurus/extensions_versioned_docs/version-2.0.x/api/cards.md new file mode 100644 index 00000000000..7a8aaa86bc9 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/api/cards.md @@ -0,0 +1,64 @@ +--- +toc_max_heading_level: 4 +--- + +# Cards + +Cards present information with a bordered box. + +Cards are added to Rancher via the `addCard` method. + +# addCard + +*(Rancher version v2.7.2)* + +This method adds a card element to the UI. + +Method: + +```ts +plugin.addCard(where: String, when: LocationConfig, options: Object); +``` + +_Arguments_ + +`where` string parameter admissable values for this method: + +| Key | Type | Description | +|---|---|---| +|`CardLocation.CLUSTER_DASHBOARD_CARD`| String | Location for a card on the Cluster Dashboard page | + +
+ +`when` Object admissable values: + +`LocationConfig` as described above for the [LocationConfig object](./common#locationconfig). + +
+
+ +## CardLocation.CLUSTER_DASHBOARD_CARD options + +![Cluster Dashboard Card](../screenshots/cluster-cards.png) + +`options` config object. Admissable parameters for the `options` with `'CardLocation.CLUSTER_DASHBOARD_CARD'` are: + +| Key | Type | Description | +|---|---|---| +|`label`| String | Card title | +|`labelKey`| String | Same as "label" but allows for translation. Will superseed "label" | +|`component`| Function | Component to be rendered aas content of a "Cluster Dashboard Card" | + +Usage example for `'CardLocation.CLUSTER_DASHBOARD_CARD'`: + +```ts +plugin.addCard( + CardLocation.CLUSTER_DASHBOARD_CARD, + { cluster: ['local'] }, + { + label: 'some-label', + labelKey: 'generic.comingSoon', + component: () => import('./MastheadDetailsComponent.vue') + } +); +``` \ No newline at end of file diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/api/common.md b/docusaurus/extensions_versioned_docs/version-2.0.x/api/common.md new file mode 100644 index 00000000000..189dd32bc88 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/api/common.md @@ -0,0 +1,118 @@ +# Common Types - LocationConfig + +## Where + +The `where` defines which area of the UI the extension method will apply to and they depend on which method they are applied to. This means that each method will only accept a given subset of the the following list (documented per each method). + +The admissable string values for the `where` are: + +| Key | Type | Description | +|---|---|---| +|`ActionLocation.HEADER`| String | Location for an action on the Header of Rancher Dashboard. Check [screenshot](./actions/#actionlocationheader-options) for location. | +|`ActionLocation.TABLE`| String | Location for an action on a List View Table of Rancher Dashboard. Check [screenshot](./actions/#actionlocationtable-options) for location. | +|`TabLocation.RESOURCE_DETAIL`| String | Location for a Tab on a Resource Detail page. Check [screenshot](./tabs/#tablocationresource_detail-options) for location. | +|`PanelLocation.DETAILS_MASTHEAD`| String | Location for a panel on the Details Masthead area of a Resource Detail page. Check [screenshot](./panels/#panellocationdetails_masthead-options) for location. | +|`PanelLocation.DETAIL_TOP`| String | Location for a panel on the Detail Top area of a Resource Detail page. Check [screenshot](./panels/#panellocationdetail_top-options) for location. | +|`PanelLocation.RESOURCE_LIST`| String | Location for a panel on a Resource List View page (above the table area). Check [screenshot](./panels#panellocationresource_list-options) for location. | +|`CardLocation.CLUSTER_DASHBOARD_CARD`| String | Location for a card on the Cluster Dashboard page. Check [screenshot](./cards/#cardlocationcluster_dashboard_card-options) for location. | +|`TableColumnLocation.RESOURCE`| String | Location for a table column on a Resource List View page. Check [screenshot](./table-columns/#tablecolumnlocationresource-options) for location. | + + +## LocationConfig + +The `LocationConfig` object defines **when** (product, resource, cluster...) these UI enhancement methods are applied on the UI. The **when** is based on the current routing system employed on Rancher Dashboard. Let's take on a simple example to try and understand the routing structure. + +Example URL: +``` +/dashboard/c/local/explorer/apps.deployment/cattle-system/rancher-webhook +``` + +How to recognize the URL structure on the example above: + +``` +/dashboard/c///// +``` + +**Note:** There are Kubernetes resources that aren't namespaced, such as `catalog.cattle.io.clusterrepo`, and in those cases the following structure applies: + +``` +/dashboard/c//// +``` + +There is another different routing pattern for "extensions as products" which follows a slightly different convention of the core Rancher Dashboard routes. An example of this would be: + +``` +/dashboard/elemental/c/local/elemental.cattle.io.machineinventory/nvxml-6mtga +``` + +which translates to: + +``` +/dashboard//c/// +``` + +With this it's then possible to easily identify the parameters needed to populate the `LocationConfig` and add the UI enhancements to the areas that you like. YES, it's also possible to enhance other extensions! + + +The admissible parameters for the `LocationConfig` object are: + +| Key | Compatible Version | Type | Description | +|---|---|---|---| +|`product`| `v2.7.2` | Array | Array of the product identifier. Ex: `fleet`, `manager` (Cluster Management), `harvesterManager` (Virtualization Management), `explorer` (Cluster Explorer) or `home` (Homepage) | +|`resource`| `v2.7.2` + `v2.8.0` | Array | Array of the identifier of the kubernetes resource to be bound to. Ex: `apps.deployment`, `storage.k8s.io.storageclass` or `secret` (v2.7.2). You can also define a wildcard, ex: `['*']`, which will match any resource page (v2.8.0) | +|`namespace`| `v2.7.2` | Array | Array of the namespace identifier. Ex: `kube-system`, `cattle-global-data` or `cattle-system` | +|`path`| `v2.7.7` | Array | Array of objects that does matching for the `path` part of the url. Admissable properties for the object are: `urlPath` (string), `exact` (boolean, default or omission: `true`, which defines the type of match it does) and `endsWith` (boolean, defaults to false) .Ex: { `urlPath`: '/c/local/explorer/projectsnamespaces', `exact`: true } or { `urlPath`: 'explorer/projectsnamespaces', `endsWith`: true } | +|`cluster`| `v2.7.2` | Array | Array of the cluster identifier. Ex: `local` | +|`id`| `v2.7.2` | Array | Array of the identifier for a given resource. Ex: `deployment-unt6xmz` | +|`mode`| `v2.7.2` + `v2.7.7` | Array | Array of modes which relates to the type of view on which the given enhancement should be applied. Admissible values are: `edit` (v2.7.2), `config` (v2.7.2), `detail` (v2.7.2), `list` (v2.7.2) and `create` (v2.7.7) | +|`context`| `v2.7.2`| Object | Requirements set by the context itself. This is a key value object that must match the object provided where the feature is used. For instance if a ResourceTab should only include a tab given specific information where the ResourceTab is used. Ex `{ provider: "digitalocean" }` | +| `queryParam`| `v2.7.2` | Object | This is a key value object that must match the url's query param key values | +|`hash`| `v2.8.0` | Array | Array of strings for url hash identifiers, commonly used in Tabs Ex: On a details view of a `provisioning.cattle.io.cluster`, you have several tabs identified in the hash portion of the url such as `node-pools`, `conditions` and `related` | + +### LocationConfig Examples + +Example 1: +```ts +{} +``` + +Passing an empty object as a `LocationObject` will apply a given extension enhancement to all locations where it can be apllied. + +Example 2: +```ts +{ product: ['home'] } +``` + +Extension enhancement will be applied on the homepage of rancher dashboard (if applicable). + +Example 3: +```ts +{ resource: ['pod'], id: ['pod-nxr5vm'] } +``` + +Extension enhancement will be applied on the resource `pod` with id `pod-nxr5vm` (if applicable). + +Example 4: +```ts +{ + cluster: ['local'], + resource: ['catalog.cattle.io.clusterrepo'], + mode: ['edit'] +} +``` + +Extension enhancement will be applied on the `edit` view/mode of the resource `catalog.cattle.io.clusterrepo` inside the `local` cluster (if applicable). + + + + + + + + + + + + + + diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/api/components/auto-import.md b/docusaurus/extensions_versioned_docs/version-2.0.x/api/components/auto-import.md new file mode 100644 index 00000000000..7078347cdfe --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/api/components/auto-import.md @@ -0,0 +1,39 @@ +# Auto-Import Folders + +Rancher simplifies the registration of components through auto-import folders. + +This must feature must be enabled in an extension, by calling the `importTypes` function in the extension's initialization function, for example: + +```ts +import { importTypes } from '@rancher/auto-import'; + +// Initialize the extension +export default function(plugin: IPlugin) { + // Auto-import model, detail, edit from the folders + importTypes(plugin); + + // ... +} +``` + +With this enabled, the extension build tooling will auto-generate component registration for you, based on folder names, without you having +to do this manually. + +The folder names match the `type` property of the `register` function. For example, to automatically register a `list` component, create a folder named `list` +in your extension and ensure the code above is added. + +The `ID` of the component is taken from the filename, so to automatically get the equivalent of: + +```ts +import NamespaceList from './NamespaceList.vue`; + +// Initialize the extension +export default function(plugin: IPlugin) { + + plugin.register('list', 'namespace', NamespaceList); + + // ... +} +``` + +you would instead create the component `namespace.vue` in the `list` folder. Here the filename `namespace.vue` matches the id `namespace` (the `.vue` extension is ignored). diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/api/components/components.md b/docusaurus/extensions_versioned_docs/version-2.0.x/api/components/components.md new file mode 100644 index 00000000000..d1533b07d21 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/api/components/components.md @@ -0,0 +1,40 @@ +--- +toc_max_heading_level: 4 +--- + +# Components + +Rancher uses dynamic components in the Dashboard UI. Extensions can register components for Rancher to discover and use. Components +are Vue components and similar to panels, but are used in a single specific context. + +Components are added to Rancher via the `register` method. + +## register + +This method registers a component. + +Method: + +```ts +plugin.register(type: String, id: String, component: Function); +``` + +_Arguments_ + +| Name | Type | Description | +|---|---|---| +|`type`| String | Type of the component to register (indicates where the component is used)| +|`id`| String | Unique id for the component| +|`component`| Function | Vue Component| + +### Component Types + +The Rancher Dashboard provides the following standard component types: + +| Type | Description | +|---|---| +|`detail`|Detail component for a given resource type| +|`edit`|Edit/Create/View component for a given resource type| +|`list`|List component for a given resource type| +|`cloud-credential`|Cloud credentials are components that add provider-specific UI to create cloud credentials, needed to provision clusters| +|`machine-config`|Machine configs components are used to add provider-specific UI to the rke2/k3s provisioning page| diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/api/components/node-drivers.md b/docusaurus/extensions_versioned_docs/version-2.0.x/api/components/node-drivers.md new file mode 100644 index 00000000000..a723777625b --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/api/components/node-drivers.md @@ -0,0 +1,44 @@ +# Custom Node Driver UI + +Rancher allows UI to be created for custom Node Drivers by registering components for the following two component types: + +- `cloud-credential` + - defines a custom component for collecting data for a cloud credential for a given node driver + - if cloud credentials are required, override any existing credential setting by setting the value to `true` + - if no cloud credentials are required, the extension can just set the component to `false` +- `machine-config` + - defined a custom component for the machine pool configuration for a cloud credential for a given node driver + +In both cases, the `ID` when registering should match the Node Driver name. + +## Cloud Credential + +Cloud Credentials store the username & password, or other similar information, needed to talk to a particular provider. There is typically a 1-to-1 mapping of cloud credentials to drivers. If one provider (e.g. Amazon) has both a *Machine* driver for RKE (using EC2) and a *Cluster* driver for Kontainer Engine (using EKS) then you can and should use a single shared type of credential (e.g. `aws`) for both. + +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 `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. + +Your component should emit a `validationChanged` event every time a value changes. It should also (but doesn't _have to_ implement a `test()` method. This may be asynchronous, and should make an API call or similar to see if the provided credentials work. Return `true` if they're ok and `false` otherwise. When provided, this is called before saving and the user won't be able to save until their input causes you return `true`. + +## Machine Config + +Similar to the Cloud Credential component, the Machine Config component should display just controls for the fields on the driver that are relevant to the configuration of the machine to be created. The machine pool name, saving, etc is handled outside of your component. You probably want to use `fetch()` to load some info from the provider's API (e.g. list of regions or instance types). + +It should live in the top-level `machine-config` directory, again named the same as the driver and lowercase (e.g. `machine-config/digitalocean.vue`). + +The selected cloud credential ID is available as a `credentialId` prop. You will always know that ID, and can use it to make API calls (see [#api-calls] below), but **must not** rely on being able to actually retrieve the cloud credential model corresponding to it. Users with lesser permissions may be able to edit a cluster, but not have permission to see the credential being used to manage it. + +### API Calls + +Rancher includes a [proxy](https://github.com/rancher/rancher/blob/release/v2.6/pkg/httpproxy/proxy.go) that can be used to make requests to third-party domains (like a cloud provider's API) without requiring that the other end supports CORS or other browser shenanigans. Send requests to `/meta/proxy/example.com/whatever/path/you/want` and the request will be made from the Rancher server and proxied back to you. + +TLS and port 443 are assumed. Add a port after the hostname to change the port (`example.com:1234`). For plain HTTP, first stop and consider the chain of life decisions which have led you to this point. Then if you still think you need that, use `/meta/proxy/http:/example.com:1234` (note one slash after `http:`, not two). The hostname must be included in the whitelist defined in global settings, or in the configuration for an active node driver. If if isn't your request will be denied. (This prevents a malicious (non-admin) user from abusing the Rancher server as an arbitrary HTTP proxy or reach internal IPs/names that the server can reach directly but the user can't from the outside.) + +The rest of the path and query string are sent to the target host as you'd expect. + +Normal headers are copied from your request and sent to the target. There are some exceptions for sensitive fields like the user's rancher cookies or saved basic auth creds which will not be copied. If you send an `X-Api-Cookie-Header`, its value will be sent as the normal `Cookie` to the target. If you send `X-API-Auth-Header`, that will be sent out as the normal `Authorization`. + +But normally you want to make a request using a Cloud Credential as the authorization, without knowing what the secret values in that credential are. You ask for this by sending an `X-Api-CattleAuth-Header` header. The value of the header specifies what credential Id to use, and a [signer](https://github.com/rancher/rancher/blob/release/v2.6/pkg/httpproxy/sign.go) which describes how that credential should be injected into the request. Common options include `awsv4` (Amazon's complicated HMAC signatures), `bearer`, `basic`, and `digest`. For example if you send `X-Api-CattleAuth-Header: Basic credId=someCredentialId usernameField=user passwordField=pass`, Rancher will retrieve the credential with id `someCredentialId`, read the values of the `user` and `pass` fields from it and add the header `Authorization: Basic ` to the proxied request for you. \ No newline at end of file diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/api/components/resources.md b/docusaurus/extensions_versioned_docs/version-2.0.x/api/components/resources.md new file mode 100644 index 00000000000..54426f50d18 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/api/components/resources.md @@ -0,0 +1,46 @@ +# Resource Views + +Rancher uses a standard mechanism for rendering views for Kubernetes Resources (such as Pods, Secrets as well as Custom Resources). + +Rancher will present standard views for Custom Resources without any custom UI, showing a list of resources with a basic column set +and the ability to view and edit the resource YAML. + +Extensions can register components to provide custom UI for a given resource type. + +## List View + +An extension can override the list view for a given resource type using the `list` component type. + +e.g. + +```ts + plugin.register('list', ID, VueComponent); +``` + +where `ID` is the unique id of the resource type and `VueComponent` is the component to use. + +## Detail View + +An extension can override the detail view for a given resource type using the `detail` component type. + +e.g. + +```ts + plugin.register('detail', ID, VueComponent); +``` + +where `ID` is the unique id of the resource type and `VueComponent` is the component to use. + +## Edit/Config View + +An extension can override the edit/config view for a given resource type using the `edit` component type. + +> Note: The edit view is used for both creating and editing the resource as well as for viewing the resource configuration. The component is passed a `mode` property which indicates to the component which mode (edit/create/view) the component is being used in. + +e.g. + +```ts + plugin.register('edit', ID, VueComponent); +``` + +where `ID` is the unique id of the resource type and `VueComponent` is the component to use. diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/api/concepts.md b/docusaurus/extensions_versioned_docs/version-2.0.x/api/concepts.md new file mode 100644 index 00000000000..87654994a04 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/api/concepts.md @@ -0,0 +1,78 @@ +# Concepts + +## Overview on routing structure for Rancher Dashboard + +To become familiar with routing on VueJS and route definition we recommend that you should give a read about the [Essentials on Vue Router](https://v3.router.vuejs.org/guide/) and also the definition of a [Vue Router route](https://v3.router.vuejs.org/api/#routes). + +Rancher Dashboard follows a specific route pattern that needs to be fulfilled in order for Extensions to work properly with the current overall logic of the application. That pattern needs on the url path to include which `product` we are trying to load and which `cluster` we are using. + +As example of an existing route, say the Fleet product, let's look at the current url structure for it: + +```ts +<-YOUR-RANCHER-INSTANCE-BASE-URL->/c/_/fleet +``` + +In terms of the route definition (Vue Router), we would translate this url to: + +```ts +const clusterManagerRoute = { + name: 'c-cluster-product', + path: 'c/:cluster/:product', + params: { + cluster: '_', + product: 'fleet' + }, + meta: { + cluster: '_', + product: 'fleet' + } +} +``` + +As we can see from the example above, we have defined on the `path` the wildcards for `cluster` and `product`. Also we can see the definition of `params` property, which is needed for internal app navigation and where we define the `cluster` value as `_` , which in terms of the app context this means that we are using a "blank cluster" which translates that the app doesn't need to care about the cluster context for the Fleet product to run. Also we are defining `product` value as `fleet`, which in turn tells the app what is the correct product to load. + +With this pattern of wildcards and `params` in mind, then how does the route structure should look like for a top-level Extension product? In short, it needs to follow this pattern: + +```ts +const YOUR_EXT_PRODUCT_NAME = 'myExtension'; + +const baseRouteForATopLevelProduct = { + name: `${ YOUR_EXT_PRODUCT_NAME }-c-cluster`, + path: `/${ YOUR_EXT_PRODUCT_NAME }/c/:cluster`, + params: { + cluster: '_', + product: YOUR_EXT_PRODUCT_NAME + }, + meta: { + cluster: '_', + product: YOUR_EXT_PRODUCT_NAME + } +} +``` + +As we can see we have dismissed the `product` wildcard on the `path` and replaced it with the Extension product name to make it unique. With the `product` param we make sure that the is taken to the correct product at all time. +This structure on the above example ensures that all the wiring needed for the Extension to work properly on Rancher Dashboard is done. There's even the case where the wildcard `resource` needs to be defined in order to display information about Kubernetes resources or custom CRDs. An example of a resource route in a top-level Extension product would be: + +```ts +const YOUR_EXT_PRODUCT_NAME = 'myExtension'; +const RESOURCE_NAME = 'my-resource-name'; + +const routeForATopLevelProductResource = { + name: `${ YOUR_EXT_PRODUCT_NAME }-c-cluster-resource`, + path: `/${ YOUR_EXT_PRODUCT_NAME }/c/:cluster/:resource`, + params: { + cluster: '_', + product: YOUR_EXT_PRODUCT_NAME + resource: RESOURCE_NAME + }, + meta: { + cluster: '_', + product: YOUR_EXT_PRODUCT_NAME + } +} +``` + +With this overview on how routing works in Rancher Dashboard, we should be ready to cover the registration of custom pages, resource pages and general route definition. For more detailed information on **top-level product routing**, check this page [here](./nav/routing.md#routes-definition-for-an-extension-as-a-top-level-product). + +If you are interested in **cluster-level product routing**, check this page [here](./nav/routing.md#routes-definition-for-an-extension-as-a-cluster-level-product). + diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/api/metadata.md b/docusaurus/extensions_versioned_docs/version-2.0.x/api/metadata.md new file mode 100644 index 00000000000..acc7a79c5a6 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/api/metadata.md @@ -0,0 +1,28 @@ +# Metadata + +Extensions need to provide metadata so that can it be displayed in the Rancher UI. + +This is done my setting the `metadata` property on the `plugin` object that is passed to the init function for an extension package. + +This should pull metadata from the extension's `pacakge.json` file, for example: + +```ts +import { importTypes } from '@rancher/auto-import'; +import { IPlugin } from '@shell/core/types'; + +// Init the package +export default function(plugin: IPlugin) { + // Auto-import model, detail, edit from the folders + // importTypes(plugin); + + // Provide extension metadata from package.json + plugin.metadata = require('./package.json'); + +} +``` + +In addition, if you add a `README.md` file to your extension root folder, it's content will be used to populate the `detail` portion of the product description. + +Example of extension metadata shown in the Rancher Extensions UI: + +![Product Information](../screenshots/product-information.png) diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/api/nav/custom-page.md b/docusaurus/extensions_versioned_docs/version-2.0.x/api/nav/custom-page.md new file mode 100644 index 00000000000..64ca2aa203a --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/api/nav/custom-page.md @@ -0,0 +1,65 @@ +# Custom page + +## Defining a custom page for an Extension (virtualType) +As we've seen from the previous chapter, a developer can register a top-level product with the `product` function. How about adding a custom page to your extension product? To do that, we can use the function `virtualType` coming from `$plugin.DSL`. As an example usage of that method, one could do the following: + +```ts +import { IPlugin } from '@shell/core/types'; + +// this is the definition of a "blank cluster" for Rancher Dashboard +// definition of a "blank cluster" in Rancher Dashboard +const BLANK_CLUSTER = '_'; + + +export function init($plugin: IPlugin, store: any) { + const YOUR_PRODUCT_NAME = 'myProductName'; + const CUSTOM_PAGE_NAME = 'page1'; + + const { + product, + virtualType + } = $plugin.DSL(store, YOUR_PRODUCT_NAME); + + // registering a top-level product + product({ + icon: 'gear', + inStore: 'management', + weight: 100, + to: { // this is the entry route for the Extension product, which is registered below + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME }`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + } + } + }); + + // => => => creating a custom page + virtualType({ + labelKey: 'some.translation.key', + name: CUSTOM_PAGE_NAME, + route: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME }`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + } + } + }); +} +``` + +With the route definition in the router (check the [Extension Routing](#routes-definition-for-an-extension-as-a-top-level-product)) chapter, you can define which Vue component will be loaded as a custom page. That will act as a "blank canvas" to render anything you want. + +The acceptable parameters for the `virtualType` function are defined here: + +| Key | Type | Description | +| --- | --- | --- | +|`name`| String | Page name (should be unique) | +|`label`| String | side-menu label for this page | +|`labelKey`| String | Same as "label" but allows for translation. Will superseed "label" | +| `icon` | [String | icon name (based on [rancher icons](https://rancher.github.io/icons/)) | +| `weight` | Int | Side menu ordering (bigger number on top) | +| `route` | [Vue Router route config](https://v3.router.vuejs.org/api/#routes) | Route for this custom page | + +> Note: If no `label` or `labelKey` is set, then the side-menu label will be the `name` field diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/api/nav/products.md b/docusaurus/extensions_versioned_docs/version-2.0.x/api/nav/products.md new file mode 100644 index 00000000000..30e4660dce0 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/api/nav/products.md @@ -0,0 +1,72 @@ +# Products + +A product is a top-level view in Rancher. A product typically adds a navigation entry into the +top-level slide-in menu in Rancher. When the user navigates to the link, the product renders +the entire view beneath the header bar. + +Products typically declare their navigation such that it is presented on the left-hand side, e.g. + +## Registering a Product + +Defining a product leverages the `addProduct` extension method, which should be defined on the `index.ts` on your root folder: + +```ts +import { IPlugin } from '@shell/core/types'; + +// Init the package +export default function(plugin: IPlugin) { + // .... + + // ... provide metadata + + // Load a product + plugin.addProduct(require('./product')); +} +``` + +The `addProduct` method registers a module which will be invoked by Rancher at the +appropriate point in its lifecycle to create the product. + +You can register more than one product in an extension. + +## Product Definition + +The module registered via `addProduct` must export an `init` method. This is invoked with two parameters; + +- The `$plugin` API +- The VueX store + +### Creating a product + +An example `init` function for creating a new product is shown below: + +```ts +import { IPlugin } from '@shell/core/types'; + +export function init($plugin: IPlugin, store: any) { + const YOUR_PRODUCT_NAME = 'myProductName'; + + const { product } = $plugin.DSL(store, YOUR_PRODUCT_NAME); + + product({ + icon: 'gear', + inStore: 'management', + weight: 100, + to: // this will the route path that will be your entry point for this product + }); +} +``` +The function `product` comes from `$plugin.DSL` will add your extension to the top-level slide-in menu. + +> Note: `plugin.DSL` is called with the store and your product name and returns a number of functions to add and configure products and navigation. The example above shows the use of the `product` function + + +The allowed parameters for the `product` function are: + +| Key | Type | Description | +| --- | --- | --- | +| `icon` | String | icon name (based on [rancher icons](https://rancher.github.io/icons/)) | +| `svg` | Module | SVG icon (alernative to above). Typically use the `require` method with a path of an SVG file| +| `inStore` | String | Which store should the product be registered on. Use `management` for a top-level product and `cluster` for a cluster-level product | +| `weight` | Int | Side menu ordering (bigger number on top) | +| `to` | [Vue Router route config](https://v3.router.vuejs.org/api/#routes) | Route to where the click on the product top-level menu should lead to | diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/api/nav/resource-page.md b/docusaurus/extensions_versioned_docs/version-2.0.x/api/nav/resource-page.md new file mode 100644 index 00000000000..67b8dad47d1 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/api/nav/resource-page.md @@ -0,0 +1,73 @@ +# Resource page + +## Defining a kubernetes resource as a page for an Extension (configureType) +One of the most common view types in Rancher Dashboard is the list view for a kubernetes resource. What if you wanted to include a similiar view on your Extension product for a given resource? For that we can use the function `configureType` coming from `$plugin.DSL`. As an example usage of that method, one could do the following: + +```ts +import { IPlugin } from '@shell/core/types'; + +// this is the definition of a "blank cluster" for Rancher Dashboard +// definition of a "blank cluster" in Rancher Dashboard +const BLANK_CLUSTER = '_'; + + +export function init($plugin: IPlugin, store: any) { + const YOUR_PRODUCT_NAME = 'myProductName'; + // example of using an existing k8s resource as a page + const YOUR_K8S_RESOURCE_NAME = 'provisioning.cattle.io.cluster'; + + const { + product, + configureType + } = $plugin.DSL(store, YOUR_PRODUCT_NAME); + + // registering a top-level product + product({ + icon: 'gear', + inStore: 'management', + weight: 100, + to: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-resource`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER, + resource: YOUR_K8S_RESOURCE_NAME + } + } + }); + + // => => => defining a k8s resource as page + configureType(YOUR_K8S_RESOURCE_NAME, { + displayName: 'some-custom-name-you-wish-to-assign-to-this-resource', + isCreatable: true, + isEditable: true, + isRemovable: true, + showAge: true, + showState: true, + canYaml: true, + customRoute: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-resource`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER, + resource: YOUR_K8S_RESOURCE_NAME + } + } + }); +} +``` + +> Note: We strongly encourange the usage of the `customRoute` to make sure we follow the same route structure as the other routes on the same Extension product. Check pattern [here](#overview-on-routing-structure-for-a-top-level-extension-product). + +The acceptable parameters for the `configureType` function are defined here: + +| Key | Type | Description | +| --- | --- | --- | +|`displayName`| String | Display name for the given resource. Defaults to `YOUR_K8S_RESOURCE_NAME` (based on example) if you haven't defined a `displayName` | +|`isCreatable`| Boolean | If the 'create' button is available on the list view | +|`isEditable`| Boolean | If a resource instance is editable | +|`isRemovable`| Boolean | If a resource instance is deletable | +|`showAge`| Boolean | If the 'age' column is available on the list view | +|`showState`| Boolean | If the 'state' column is available on the list view | +|`canYaml`| Boolean | If the k8s resource can be edited/created via YAML editor | +| `customRoute` | [Vue Router route config](https://v3.router.vuejs.org/api/#routes) | Route for this resource page | diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/api/nav/routing.md b/docusaurus/extensions_versioned_docs/version-2.0.x/api/nav/routing.md new file mode 100644 index 00000000000..0f1540407ab --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/api/nav/routing.md @@ -0,0 +1,530 @@ +# Routing + +## Routes definition for an Extension as a top-level product +Extensions should use a `pages` directory, as the shell currently does, but routing needs to be explicitly defined then added in the extension index using the extension `addRoutes` method. Extension routes can override existing dashboard routes: they'll be loaded on extension entry and unloaded (with old dashboard routes re-loaded...) on extension leave. + +As touched on above, cluster and product information used to connect to the cluster and define navigation is determined from the route. Consequently, while extensions have a lot of control over their own routing, anything tied into one kubernetes cluster should be nested in `pages/c/_cluster`. + +> Note: All of the routes defined when setting up your Extension product (`product.ts`) need to be defined as routes with the `addRoutes` method. + +Within the `index.ts` in your root folder, where you define your extension configuration, you can just use the `addRoutes` extension method, such as: + +```ts +import { importTypes } from '@rancher/auto-import'; +import { IPlugin } from '@shell/core/types'; +import extensionRouting from './routing/extension-routing'; + +// Init the package +export default function(plugin: IPlugin) { + // Auto-import model, detail, edit from the folders + importTypes(plugin); + + // Provide extension metadata from package.json + // it will grab information such as `name` and `description` + plugin.metadata = require('./package.json'); + + // Load a product + plugin.addProduct(require('./product')); + + // => => => Add Vue Routes + plugin.addRoutes(extensionRouting); +} +``` + +Let's then take into consideration the following example a of `product.ts` config: + +```ts +import { IPlugin } from '@shell/core/types'; + +// this is the definition of a "blank cluster" for Rancher Dashboard +// definition of a "blank cluster" in Rancher Dashboard +const BLANK_CLUSTER = '_'; + + +export function init($plugin: IPlugin, store: any) { + const YOUR_PRODUCT_NAME = 'myProductName'; + const YOUR_K8S_RESOURCE_NAME = 'provisioning.cattle.io.cluster'; + const CUSTOM_PAGE_NAME = 'page1'; + + const { + product, + configureType, + virtualType, + basicType + } = $plugin.DSL(store, YOUR_PRODUCT_NAME); + + // registering a top-level product + product({ + icon: 'gear', + inStore: 'management', + weight: 100, + to: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME }`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + }, + meta: { + product: YOUR_PRODUCT_NAME + } + } + }); + + // defining a k8s resource as page + configureType(YOUR_K8S_RESOURCE_NAME, { + displayName: 'some-custom-name-you-wish-to-assign-to-this-resource', + isCreatable: true, + isEditable: true, + isRemovable: true, + showAge: true, + showState: true, + canYaml: true, + customRoute: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-resource`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER, + resource: YOUR_K8S_RESOURCE_NAME + }, + meta: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + } + } + }); + + + + // creating a custom page + virtualType({ + labelKey: 'some.translation.key', + name: CUSTOM_PAGE_NAME, + route: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME }`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + }, + meta: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + } + } + }); + + // => => => registering the defined pages as side-menu entries + basicType([YOUR_K8S_RESOURCE_NAME, CUSTOM_PAGE_NAME]); +} +``` + +One above example we are registering 2 pages: a resource page called `YOUR_K8S_RESOURCE_NAME` and a custom page called `CUSTOM_PAGE_NAME`. These need to be reflected in the routes definition that is provided to the `addRoutes` method. + +Please note that for a Top-level product, routing follows a defined pattern: + +```js +route: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME }`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + }, + meta: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + } +} +``` + +Within the params property, we define **cluster** as `BLANK_CLUSTER`. `BLANK_CLUSTER` is a notion on which Rancher will ignore the context of the cluster the user is browsing, which is the desired effect when creating a product that is "above" the notion of a cluster. + +This pattern is a very important aspect for Top-level products that you should be mindful at all times when creating a product of this type. + +Considering the above, `/routing/extension-routing.ts` would then have to be defined like: + +```ts +// custom pages should be created as VueJS components. Usually stored on the /pages folder on the extension +// definition of a "blank cluster" in Rancher Dashboard +const BLANK_CLUSTER = '_'; + +import MyCustomPage from '../pages/myCustomPage.vue'; + +// to achieve naming consistency throughout the extension +// we recommend this to be defined on a config file and exported +// so that the developer can import it wherever it needs to be used +const YOUR_PRODUCT_NAME = 'myProductName'; +const CUSTOM_PAGE_NAME = 'page1'; + +const routes = [ + // this is an example of a custom page if you wanted to register one + { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME }`, + path: `/${ YOUR_PRODUCT_NAME }/c/:cluster/${ CUSTOM_PAGE_NAME }`, + component: MyCustomPage, + meta: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + }, + } +]; + +export default routes; +``` + +> Note: the `meta` parameter is mandatory in order for the routes to work properly! + +On the above example, we are registering the route for our custom page called `CUSTOM_PAGE_NAME`. At this point we are still missing the route for `YOUR_K8S_RESOURCE_NAME`, which we will cover next. + +Just to reinforce the message, it is imperative that the `name` and `path` follow this convention needed for Extension top-level products, which we cover on this [overview](../../api/concepts.md#overview-on-routing-structure-for-a-top-level-extension-product). + +As you can see, we've added a `meta` parameter with the product and cluster names. This is necessary to exist on the routes definition in order to ensure that all the wiring "under the hood" is handled correctly by Rancher Dashboard. + +Now, for a resource page like `YOUR_K8S_RESOURCE_NAME`, one can leverage the usage of the default components for a list/create/edit routes used on Rancher Dashboard in such a way: + +```ts +// definition of a "blank cluster" in Rancher Dashboard +const BLANK_CLUSTER = '_'; + +import MyCustomPage from '../pages/myCustomPage.vue'; +import ListResource from '@shell/pages/c/_cluster/_product/_resource/index.vue'; +import CreateResource from '@shell/pages/c/_cluster/_product/_resource/create.vue'; +import ViewResource from '@shell/pages/c/_cluster/_product/_resource/_id.vue'; +import ViewNamespacedResource from '@shell/pages/c/_cluster/_product/_resource/_namespace/_id.vue'; + +// to achieve naming consistency throughout the extension +// we recommend this to be defined on a config file and exported +// so that the developer can import it wherever it needs to be used +const YOUR_PRODUCT_NAME = 'myProductName'; +const CUSTOM_PAGE_NAME = 'page1'; + +const routes = [ + { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME }`, + path: `/${ YOUR_PRODUCT_NAME }/c/:cluster/${ CUSTOM_PAGE_NAME }`, + component: MyCustomPage, + meta: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + }, + }, + { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-resource`, + path: `/${ YOUR_PRODUCT_NAME }/c/:cluster/:resource`, + component: ListResource, + meta: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + }, + }, + { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-resource-create`, + path: `/${ YOUR_PRODUCT_NAME }/c/:cluster/:resource/create`, + component: CreateResource, + meta: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + }, + }, + { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-resource-id`, + path: `/${ YOUR_PRODUCT_NAME }/c/:cluster/:resource/:id`, + component: ViewResource, + meta: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + }, + }, + { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-resource-namespace-id`, + path: `/${ YOUR_PRODUCT_NAME }/c/:cluster/:resource/:namespace/:id`, + component: ViewNamespacedResource, + meta: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + }, + } +]; + +export default routes; +``` + +> Note: Notice that we didn't need to define the parameter `resource` under `meta`? Since it is a wildcard parameter on the path and it's not mandatory like `cluster` for a top-level product, we don't need to define it on the routes definition. + +On the above routes definition for `YOUR_K8S_RESOURCE_NAME` the user will get the default list view automatically wired in to display the list of `YOUR_K8S_RESOURCE_NAME` instances (`${ YOUR_PRODUCT_NAME }-c-cluster-resource`). + +The remaining routes will ensure that all the necessary connections are done for create/edit views, but they will not provide any interfaces for those view types! Those will have to be created by the developer and placed on folders with the correct naming in order to make them work. (`edit`, `detail` folders). + +Let's then look at an example of this: + +```ts +const YOUR_K8S_RESOURCE_NAME = 'your-custom-crd-name'; +``` + +If a user wishes to create custom views for the resource `your-custom-crd-name`, there are three types to consider: `list`, `detail`, and `edit`. + +For a `list` view, follow these steps: + +1. Create a folder named `list` inside your extension folder. +2. Inside the `list` folder, create a file named `your-custom-crd-name.vue` for the Vue component. + +For a `detail` view, follow these steps: + +1. Create a folder named `detail` inside your extension folder. +2. Inside the `detail` folder, create a file named `your-custom-crd-name.vue` for the Vue component. + +For an `edit` view (which can also serve as a create view), follow these steps: + +1. Create a folder named `edit` inside your extension folder. +2. Inside the `edit` folder, create a file named `your-custom-crd-name.vue` for the Vue component. + +> Note: The `edit` view can also be used as a `detail` view if you prefer not to duplicate it. + +By adhering to this pattern, Rancher Dashboard will automatically take care of the wiring for you, ensuring a seamless experience for all three view types. + +The routing definition on this example for `/routing/extension-routing.ts` is based on Vue Router. Don't forget to check the official documentation [here](https://router.vuejs.org/guide/). + + + +## Routes definitions for an Extension as a cluster-level product +Routes definitions start very similar as a top-level product with the `index.ts` in your root folder, where you define your extension configuration, you can just use the `addRoutes` extension method, such as: + +```ts +import { importTypes } from '@rancher/auto-import'; +import { IPlugin } from '@shell/core/types'; +import extensionRouting from './routing/extension-routing'; + +// Init the package +export default function(plugin: IPlugin) { + // Auto-import model, detail, edit from the folders + importTypes(plugin); + + // Provide extension metadata from package.json + // it will grab information such as `name` and `description` + plugin.metadata = require('./package.json'); + + // Load a product + plugin.addProduct(require('./product')); + + // => => => Add Vue Routes + plugin.addRoutes(extensionRouting); +} +``` + +For a Cluster-level product, let's then take into consideration the following example a of `product.ts` config: + +```ts +import { IPlugin } from '@shell/core/types'; + +export function init($plugin: IPlugin, store: any) { + const YOUR_PRODUCT_NAME = 'myProductName'; + const YOUR_K8S_RESOURCE_NAME = 'provisioning.cattle.io.cluster'; + const CUSTOM_PAGE_NAME = 'page1'; + + const { + product, + configureType, + virtualType, + basicType + } = $plugin.DSL(store, YOUR_PRODUCT_NAME); + + // registering a top-level product + product({ + icon: 'gear', + inStore: 'management', + weight: 100, + to: { + name: `c-cluster-${ YOUR_PRODUCT_NAME }-${ CUSTOM_PAGE_NAME }`, + params: { + product: YOUR_PRODUCT_NAME + }, + meta: { + product: YOUR_PRODUCT_NAME + } + } + }); + + // defining a k8s resource as page + configureType(YOUR_K8S_RESOURCE_NAME, { + displayName: 'some-custom-name-you-wish-to-assign-to-this-resource', + isCreatable: true, + isEditable: true, + isRemovable: true, + showAge: true, + showState: true, + canYaml: true, + customRoute: { + name: `c-cluster-${ YOUR_PRODUCT_NAME }-resource`, + params: { + product: YOUR_PRODUCT_NAME, + resource: YOUR_K8S_RESOURCE_NAME + }, + meta: { + product: YOUR_PRODUCT_NAME + } + } + }); + + + + // creating a custom page + virtualType({ + labelKey: 'some.translation.key', + name: CUSTOM_PAGE_NAME, + route: { + name: `c-cluster-${ YOUR_PRODUCT_NAME }-${ CUSTOM_PAGE_NAME }`, + params: { + product: YOUR_PRODUCT_NAME + }, + meta: { + product: YOUR_PRODUCT_NAME + } + } + }); + + // => => => registering the defined pages as side-menu entries + basicType([YOUR_K8S_RESOURCE_NAME, CUSTOM_PAGE_NAME]); +} +``` + +In the above example we are registering 2 pages: a resource page called `YOUR_K8S_RESOURCE_NAME` and a custom page called `CUSTOM_PAGE_NAME`. These need to be reflected in the routes definition that is provided to the `addRoutes` method. + +Please note that for a Cluster-level product, routing follows a defined pattern: + +```js +route: { + name: `c-cluster-${ YOUR_PRODUCT_NAME }-${ CUSTOM_PAGE_NAME }`, + params: { product: YOUR_PRODUCT_NAME } +} +``` + +Within the params property, we do not define the cluster parameter as this is provided by the context in which cluster the user is currently browsing. + +This pattern is a very important aspect for Cluster-level products that you should be mindful at all times when creating a product of this type. + +Considering the above, `/routing/extension-routing.ts` would then have to be defined like: + +```ts +// custom pages should be created as VueJS components. Usually stored on the /pages folder on the extension +import MyCustomPage from '../pages/myCustomPage.vue'; + +// to achieve naming consistency throughout the extension +// we recommend this to be defined on a config file and exported +// so that the developer can import it wherever it needs to be used +const YOUR_PRODUCT_NAME = 'myProductName'; +const CUSTOM_PAGE_NAME = 'page1'; + +const routes = [ + // this is an example of a custom page if you wanted to register one + { + name: `c-cluster-${ YOUR_PRODUCT_NAME }-${ CUSTOM_PAGE_NAME }`, + path: `/c/:cluster/${ YOUR_PRODUCT_NAME }/${ CUSTOM_PAGE_NAME }`, + component: MyCustomPage, + meta: { + product: YOUR_PRODUCT_NAME + } + } +]; + +export default routes; +``` + +> Note: the `meta` parameter is mandatory in order for the routes to work properly! + +On the above example, we are registering the route for our custom page called `CUSTOM_PAGE_NAME`. At this point we are still missing the route for `YOUR_K8S_RESOURCE_NAME`, which we will cover next. + +Just to reinforce the message, it is imperative that the `name` and `path` follow this convention needed for Extension cluster-level products as well. + +As you can see, we've added a `meta` parameter with the product name. This is necessary to exist on the routes definition in order to ensure that all the wiring "under the hood" is handled correctly by Rancher Dashboard. + +Now, for a resource page like `YOUR_K8S_RESOURCE_NAME`, one can leverage the usage of the default components for a list/create/edit routes used on Rancher Dashboard in such a way: + +```ts +import MyCustomPage from '../pages/myCustomPage.vue'; +import ListResource from '@shell/pages/c/_cluster/_product/_resource/index.vue'; +import CreateResource from '@shell/pages/c/_cluster/_product/_resource/create.vue'; +import ViewResource from '@shell/pages/c/_cluster/_product/_resource/_id.vue'; +import ViewNamespacedResource from '@shell/pages/c/_cluster/_product/_resource/_namespace/_id.vue'; + +// to achieve naming consistency throughout the extension +// we recommend this to be defined on a config file and exported +// so that the developer can import it wherever it needs to be used +const YOUR_PRODUCT_NAME = 'myProductName'; +const CUSTOM_PAGE_NAME = 'page1'; + +const routes = [ + { + name: `c-cluster-${ YOUR_PRODUCT_NAME }-${ CUSTOM_PAGE_NAME }`, + path: `/c/:cluster/${ YOUR_PRODUCT_NAME }/${ CUSTOM_PAGE_NAME }`, + component: MyCustomPage, + meta: { + product: YOUR_PRODUCT_NAME + }, + }, + { + name: `c-cluster-${ YOUR_PRODUCT_NAME }-resource`, + path: `/c/:cluster/${ YOUR_PRODUCT_NAME }/:resource`, + component: ListResource, + meta: { + product: YOUR_PRODUCT_NAME + }, + }, + { + name: `c-cluster-${ YOUR_PRODUCT_NAME }-resource-create`, + path: `/c/:cluster/${ YOUR_PRODUCT_NAME }/:resource/create`, + component: CreateResource, + meta: { + product: YOUR_PRODUCT_NAME + }, + }, + { + name: `c-cluster-${ YOUR_PRODUCT_NAME }-resource-id`, + path: `/c/:cluster/${ YOUR_PRODUCT_NAME }/:resource/:id`, + component: ViewResource, + meta: { + product: YOUR_PRODUCT_NAME + }, + }, + { + name: `c-cluster-${ YOUR_PRODUCT_NAME }-resource-namespace-id`, + path: `/c/:cluster/${ YOUR_PRODUCT_NAME }/:resource/:namespace/:id`, + component: ViewNamespacedResource, + meta: { + product: YOUR_PRODUCT_NAME + }, + } +]; + +export default routes; +``` + +On the above routes definition for `YOUR_K8S_RESOURCE_NAME` the user will get the default list view automatically wired in to display the list of `YOUR_K8S_RESOURCE_NAME` instances (`c-cluster-${ YOUR_PRODUCT_NAME }-resource`). + +The remaining routes will ensure that all the necessary connections are done for create/edit views, but they will not provide any interfaces for those view types! Those will have to be created by the developer and placed on folders with the correct naming in order to make them work. (`edit`, `detail` folders). + +Let's then look at an example of this: + +```ts +const YOUR_K8S_RESOURCE_NAME = 'your-custom-crd-name'; +``` + +If a user wishes to create custom views for the resource `your-custom-crd-name`, there are three types to consider: `list`, `detail`, and `edit`. + +For a `list` view, follow these steps: + +1. Create a folder named `list` inside your extension folder. +2. Inside the `list` folder, create a file named `your-custom-crd-name.vue` for the Vue component. + +For a `detail` view, follow these steps: + +1. Create a folder named `detail` inside your extension folder. +2. Inside the `detail` folder, create a file named `your-custom-crd-name.vue` for the Vue component. + +For an `edit` view (which can also serve as a create view), follow these steps: + +1. Create a folder named `edit` inside your extension folder. +2. Inside the `edit` folder, create a file named `your-custom-crd-name.vue` for the Vue component. + +> Note: The `edit` view can also be used as a `detail` view if you prefer not to duplicate it. + +By adhering to this pattern, Rancher Dashboard will automatically take care of the wiring for you, ensuring a seamless experience for all three view types. + +The routing definition on this example for `/routing/extension-routing.ts` is based on Vue Router. Don't forget to check the official documentation [here](https://router.vuejs.org/guide/). diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/api/nav/side-menu.md b/docusaurus/extensions_versioned_docs/version-2.0.x/api/nav/side-menu.md new file mode 100644 index 00000000000..aaed24f97e8 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/api/nav/side-menu.md @@ -0,0 +1,321 @@ +# Side menu + +## Defining a page as a side-menu entry (basicType) + +With the `virtualType` and `configureType` we have learned how to configure a page for your Extension product, but that won't make it appear on the side-menu. For that you need to use the function `basicType` coming from `$plugin.DSL`. As an example usage of that method, one could do the following: + +```ts +import { IPlugin } from '@shell/core/types'; + +// this is the definition of a "blank cluster" for Rancher Dashboard +// definition of a "blank cluster" in Rancher Dashboard +const BLANK_CLUSTER = '_'; + + +export function init($plugin: IPlugin, store: any) { + const YOUR_PRODUCT_NAME = 'myProductName'; + const YOUR_K8S_RESOURCE_NAME = 'provisioning.cattle.io.cluster'; + const CUSTOM_PAGE_NAME = 'page1'; + + const { + product, + configureType, + virtualType, + basicType + } = $plugin.DSL(store, YOUR_PRODUCT_NAME); + + // registering a top-level product + product({ + icon: 'gear', + inStore: 'management', + weight: 100, + to: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME }`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + } + } + }); + + // defining a k8s resource as page + configureType(YOUR_K8S_RESOURCE_NAME, { + displayName: 'some-custom-name-you-wish-to-assign-to-this-resource', + isCreatable: true, + isEditable: true, + isRemovable: true, + showAge: true, + showState: true, + canYaml: true, + customRoute: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-resource`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER, + resource: YOUR_K8S_RESOURCE_NAME + } + } + }); + + + + // creating a custom page + virtualType({ + labelKey: 'some.translation.key', + name: CUSTOM_PAGE_NAME, + route: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME }`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + } + } + }); + + // => => => registering the defined pages as side-menu entries + basicType([YOUR_K8S_RESOURCE_NAME, CUSTOM_PAGE_NAME]); +} +``` + +On the above example we are creating two side menu entries on a "root" level for your `YOUR_K8S_RESOURCE_NAME` and `CUSTOM_PAGE_NAME` pages. + +Menu entries can also be grouped under a common "folder/group" in the side menu. For that the `basicType` takes an additional parameter which will be the name for the folder/group" in the side-menu. An example of the grouping as a follow-up on the example above would be: + +```ts +// update of the function usage based on the example above + +// => => => registering the defined pages as side-menu entries as a group + basicType([YOUR_K8S_RESOURCE_NAME, CUSTOM_PAGE_NAME], 'my-custom-group-name'); +``` + +> NOTE: On the example above the label of the group on the side-menu will be `my-custom-group-name`. + +## Side menu ordering (weightType and weightGroup) + +How about if you wanted to change the side-menu ordering for your Extension product? That can be achieved by using the functions `weightType` and `weightGroup` coming from `$plugin.DSL`. Let's then look at the following example: + +```ts +import { IPlugin } from '@shell/core/types'; + +// this is the definition of a "blank cluster" for Rancher Dashboard +// definition of a "blank cluster" in Rancher Dashboard +const BLANK_CLUSTER = '_'; + +export function init($plugin: IPlugin, store: any) { + const YOUR_PRODUCT_NAME = 'myProductName'; + const YOUR_K8S_RESOURCE_NAME = 'provisioning.cattle.io.cluster'; + const CUSTOM_PAGE_NAME_1 = 'page1'; + const CUSTOM_PAGE_NAME_2 = 'page2'; + const CUSTOM_PAGE_NAME_3 = 'page3'; + + const { + product, + configureType, + virtualType, + basicType + } = $plugin.DSL(store, YOUR_PRODUCT_NAME); + // registering a top-level product + product({ + icon: 'gear', + inStore: 'management', + weight: 100, + to: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME_2 }`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + } + } + }); + // defining a k8s resource as page + configureType(YOUR_K8S_RESOURCE_NAME, { + displayName: 'some-custom-name-you-wish-to-assign-to-this-resource', + isCreatable: true, + isEditable: true, + isRemovable: true, + showAge: true, + showState: true, + canYaml: true, + customRoute: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-resource`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER, + resource: YOUR_K8S_RESOURCE_NAME + } + } + }); + // creating a custom page + virtualType({ + labelKey: 'some.translation.key', + name: CUSTOM_PAGE_NAME_1, + route: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME_1 }`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + } + } + }); + // creating yet another custom page + virtualType({ + labelKey: 'some.translation.key', + name: CUSTOM_PAGE_NAME_2, + route: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME_2 }`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + } + } + }); + virtualType({ + labelKey: 'some.translation.key', + name: CUSTOM_PAGE_NAME_3, + route: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME_3 }`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + } + } + }); + // registering some of the defined pages as side-menu entries in the root level + basicType([CUSTOM_PAGE_NAME_2, CUSTOM_PAGE_NAME_3]); + // registering some of the defined pages as side-menu entries in a group + basicType([YOUR_K8S_RESOURCE_NAME, CUSTOM_PAGE_NAME_1], 'myAdvancedGroup'); +} +``` + +> Note: All individual root elements (in the example would be `CUSTOM_PAGE_NAME_2` and `CUSTOM_PAGE_NAME_3`) are placed under a pseudo-group called `root`, which in turn has always a default weight of `1000`. +In the example provided above we are registering 4 pages: 1 is a "resource" page (`YOUR_K8S_RESOURCE_NAME`) and 3 are "custom" pages (`CUSTOM_PAGE_NAME_1`, `CUSTOM_PAGE_NAME_2` and `CUSTOM_PAGE_NAME_3`). + +These pages are set as side-menu entries being `YOUR_K8S_RESOURCE_NAME` and `CUSTOM_PAGE_NAME_1` in a group called `myAdvancedGroup` and 2 other pages(`CUSTOM_PAGE_NAME_2` and `CUSTOM_PAGE_NAME_3`) as a root level side-menu entry. + +The default ordering of these side-menu entries is the order on which you register them using `basicType`, taking also into consideration pseudo-group `root`, which in turn will always be above any other custom groups, provided the fact that the developer hasn't defined any custom group weight yet. + +In the above example the side-menu output would be something like: + +* CUSTOM_PAGE_NAME_2 +* CUSTOM_PAGE_NAME_3 +* myAdvancedGroup + - YOUR_K8S_RESOURCE_NAME + - CUSTOM_PAGE_NAME_1 + +If we wanted to define some custom ordering for these menu entries, we would need to use the functions `weightType` and `weightGroup`, like: + +```ts +import { IPlugin } from '@shell/core/types'; + +// this is the definition of a "blank cluster" for Rancher Dashboard +// definition of a "blank cluster" in Rancher Dashboard +const BLANK_CLUSTER = '_'; + +export function init($plugin: IPlugin, store: any) { + const YOUR_PRODUCT_NAME = 'myProductName'; + const YOUR_K8S_RESOURCE_NAME = 'provisioning.cattle.io.cluster'; + const CUSTOM_PAGE_NAME_1 = 'page1'; + const CUSTOM_PAGE_NAME_2 = 'page2'; + const CUSTOM_PAGE_NAME_3 = 'page3'; + + const { + product, + configureType, + virtualType, + basicType + } = $plugin.DSL(store, YOUR_PRODUCT_NAME); + // registering a top-level product + product({ + icon: 'gear', + inStore: 'management', + weight: 100, + to: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME_2 }`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + } + } + }); + // defining a k8s resource as page + configureType(YOUR_K8S_RESOURCE_NAME, { + displayName: 'some-custom-name-you-wish-to-assign-to-this-resource', + isCreatable: true, + isEditable: true, + isRemovable: true, + showAge: true, + showState: true, + canYaml: true, + customRoute: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-resource`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER, + resource: YOUR_K8S_RESOURCE_NAME + } + } + }); + // creating a custom page + virtualType({ + labelKey: 'some.translation.key', + name: CUSTOM_PAGE_NAME_1, + route: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME_1 }`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + } + } + }); + // creating yet another custom page + virtualType({ + labelKey: 'some.translation.key', + name: CUSTOM_PAGE_NAME_2, + route: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME_2 }`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + } + } + }); + virtualType({ + labelKey: 'some.translation.key', + name: CUSTOM_PAGE_NAME_3, + route: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME_3 }`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + } + } + }); + // registering some of the defined pages as side-menu entries in the root level + basicType([CUSTOM_PAGE_NAME_2, CUSTOM_PAGE_NAME_3]); + // registering some of the defined pages as side-menu entries in a group + basicType([YOUR_K8S_RESOURCE_NAME, CUSTOM_PAGE_NAME_1], 'myAdvancedGroup'); + // => => => individual ordering of each menu entry + weightType(CUSTOM_PAGE_NAME_1, 2, true); + weightType(YOUR_K8S_RESOURCE_NAME, 1, true); + weightType(CUSTOM_PAGE_NAME_3, 2, true); + weightType(CUSTOM_PAGE_NAME_2, 1, true); + // => => => ordering of the grouped entry + weightGroup('myAdvancedGroup', 1001, true); +} +``` + +Given the example provided above, what would be the output in terms of ordering of this side-menu? +* myAdvancedGroup + - CUSTOM_PAGE_NAME_1 + - YOUR_K8S_RESOURCE_NAME +* CUSTOM_PAGE_NAME_3 +* CUSTOM_PAGE_NAME_2 + +Interpreting the code on the example, it's easy to follow the ordering defined: +- We are setting 3 root level side-menu items: `CUSTOM_PAGE_NAME_2`, `CUSTOM_PAGE_NAME_3` and `myAdvancedGroup` +- Technically, as mentioned on the note above, `CUSTOM_PAGE_NAME_2` and `CUSTOM_PAGE_NAME_3` are placed under a group called `root` which has no label associated, hence why it's not perceived as "group" like `myAdvancedGroup` +- Since we are giving a weight of `1001` to `myAdvancedGroup` (the bigger, the higher it will sit on the menu ordering - higher than the default `1000` of `root`), the `myAdvancedGroup` menu will be above the `CUSTOM_PAGE_NAME_2` and `CUSTOM_PAGE_NAME_3` side-menu entries +- Inside the `myAdvancedGroup` group we are setting a specific order as well: weight of `2` to `CUSTOM_PAGE_NAME_1` and a weight of `1` to `YOUR_K8S_RESOURCE_NAME`.This will make the side-menu entry for `CUSTOM_PAGE_NAME_1` appear higher than `YOUR_K8S_RESOURCE_NAME` inside the group `myAdvancedGroup` +- As for the `CUSTOM_PAGE_NAME_2` and `CUSTOM_PAGE_NAME_3` they are done inside that virtual group called `root`. Since `CUSTOM_PAGE_NAME_3` is set a weight of `2` and `CUSTOM_PAGE_NAME_3` is set a weight of `1`, `CUSTOM_PAGE_NAME_3` will appear above `CUSTOM_PAGE_NAME_2` + +> NOTE: The last parameter for the `weightType` and `weightGroup` functions is a boolean that should be set to `true` at all times so that it works properly. \ No newline at end of file diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/api/overview.md b/docusaurus/extensions_versioned_docs/version-2.0.x/api/overview.md new file mode 100644 index 00000000000..82d64abbf0c --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/api/overview.md @@ -0,0 +1,7 @@ +# Extensions API + +Rancher provides a set of Extension hooks to developers. + +You can find installable extensions providing some usage examples of the Extensions API here: https://github.com/rancher/ui-plugin-examples. + +For information regarding the support of each Extension API hook available, check it's support matrix [here](../support-matrix#extension-api-support-matrix). \ No newline at end of file diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/api/panels.md b/docusaurus/extensions_versioned_docs/version-2.0.x/api/panels.md new file mode 100644 index 00000000000..7b6c170c7b8 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/api/panels.md @@ -0,0 +1,102 @@ +# Panels + +A Panel is a defined area in the Rancher UI where custom UI components can be shown. + +Panels are added to Rancher via the `addPanel` method. + +## addPanel + +*(Rancher version v2.7.2)* + +This method adds a panel/content to the UI. + +Method: + +```ts +plugin.addPanel(where: String, when: LocationConfig, options: Object); +``` + +_Arguments_ + +`where` string parameter admissable values for this method: + +| Key | Type | Description | +|---|---|---| +|`PanelLocation.DETAILS_MASTHEAD`| String | Location for a panel on the Details Masthead area of a Resource Detail page (only for modes `detail` (v2.7.2), `edit` (v2.7.2), `config` (v2.7.2) and `create` (v2.7.7)) | +|`PanelLocation.DETAIL_TOP`| String | Location for a panel on the Detail Top area of a Resource Detail page (only for modes `detail` (v2.7.2), `edit` (v2.7.2), `config` (v2.7.) and `create` (v2.7.7)) | +|`PanelLocation.RESOURCE_LIST`| String | Location for a panel on a Resource List View page (above the table area - only for mode `list` (v2.7.2)) | + +
+ +`when` Object admissable values: + +`LocationConfig` as described above for the [LocationConfig object](./common#locationconfig). + +
+
+ +### PanelLocation.DETAILS_MASTHEAD options + +![Details Masthead](../screenshots/masthead.png) + +`options` config object. Admissable parameters for the `options` with `'PanelLocation.DETAILS_MASTHEAD'` are: + +| Key | Type | Description | +|---|---|---| +|`component`| Function | Component to be rendered as content on the "detail view" Masthead component | + +Usage example for `'PanelLocation.DETAILS_MASTHEAD'`: + +```ts +plugin.addPanel( + PanelLocation.DETAILS_MASTHEAD + { resource: ['catalog.cattle.io.clusterrepo'] }, + { component: () => import('./MastheadDetailsComponent.vue') } +); +``` + +
+
+ +### PanelLocation.DETAIL_TOP options + +![DetailTop](../screenshots/detailtop.png) + +`options` config object. Admissable parameters for the `options` with `'PanelLocation.DETAIL_TOP'` are: + +| Key | Type | Description | +|---|---|---| +|`component`| Function | Component to be rendered as content on the "detail view" detailTop component | + +Usage example for `'PanelLocation.DETAIL_TOP'`: + +```ts +plugin.addPanel( + PanelLocation.DETAIL_TOP, + { resource: ['catalog.cattle.io.clusterrepo'] }, + { component: () => import('./DetailTopComponent.vue') } +); +``` + +
+
+ +### PanelLocation.RESOURCE_LIST options + +![List View](../screenshots/list-view.png) + +`options` config object. Admissable parameters for the `options` with `'PanelLocation.RESOURCE_LIST'` are: + +| Key | Type | Description | +|---|---|---| +|`component`| Function | Component to be rendered as content above a table on a "list view" | + +Usage example for `'PanelLocation.RESOURCE_LIST'`: + +```ts +plugin.addPanel( + PanelLocation.RESOURCE_LIST, + { resource: ['catalog.cattle.io.app'] }, + { component: () => import('./BannerComponent.vue') } +); +``` \ No newline at end of file diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/api/table-columns.md b/docusaurus/extensions_versioned_docs/version-2.0.x/api/table-columns.md new file mode 100644 index 00000000000..d3900b63ec4 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/api/table-columns.md @@ -0,0 +1,69 @@ +# Table Columns + +Table Columns are added to Rancher via the `addTableColumn` method. + +## addTableColumn + +*(Rancher version v2.7.2)* + +>**IMPORTANT NOTE:** on **Rancher version v2.8.0** we've introduced breaking changes to the behaviour of this extension enhancement (Table Columns). Previously, you would target the resource name of the table you were trying to extend, which was different from the usage of the [LocationConfig object](./common#locationconfig) in any of the other extension enhancements available. With these new changes, the [LocationConfig object](./common#locationconfig) will be used to target a specific page that contains a table and add it to that particular one, therefore having a better control of the new table column appearance. + +This method adds a table column to a `ResourceTable` element-based table on the UI. + +Method: + +```ts +plugin.addTableColumn(where: String, when: LocationConfig, options: Object); +``` + +_Arguments_ + +`where` string parameter admissable values for this method: + +| Key | Type | Description | +|---|---|---| +|`TableColumnLocation.RESOURCE`| String | Location for a table column on a Resource List View page | + +
+ +`when` Object admissable values: + +`LocationConfig` as described above for the [LocationConfig object](./common#locationconfig). + +
+
+ +### TableColumnLocation.RESOURCE options + +![Table Col](../screenshots/table-cols.png) + +`options` config object. Admissable parameters for the `options` with `'TableColumnLocation.RESOURCE'` are: + +| Key | Type | Description | +|---|---|---| +|`name`| String | Label for column | +|`labelKey`| String | Same as "name" but allows for translation. Will superseed "name" | +|`value`| String | Object property to obtain the value from | +|`getValue`| Fuction | Same as "value", but it can be a function. Will superseed "value" | +|`width`| Int | Column width (in `px`). Optional | +|`sort`| Array | Object properties to be bound to the table sorting. Optional | +|`search`| Array | Object properties to be bound to the table search. Optional | + +Usage example for `'TableColumnLocation.RESOURCE'`: + +```ts +plugin.addTableColumn( + TableColumnLocation.RESOURCE, + { resource: ['configmap'] }, + { + name: 'some-prop-col', + labelKey: 'generic.comingSoon', + getValue: (row: any) => { + return `${ row.id }-DEMO-COL-STRING-ADDED!`; + }, + width: 100, + sort: ['stateSort', 'nameSort'], + search: ['stateSort', 'nameSort'], + } +); +``` \ No newline at end of file diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/api/tabs.md b/docusaurus/extensions_versioned_docs/version-2.0.x/api/tabs.md new file mode 100644 index 00000000000..0fbd773dc13 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/api/tabs.md @@ -0,0 +1,69 @@ +# Tabs + +Tabs present custom content inside a new Tab of an existing Tabbed Area section +within the Rancher UI. + +Tabs are added to Rancher via the `addTab` method. + +## addTab + +*(Rancher version v2.7.2)* + +This method adds a tab to the UI. + +Method: + +```ts +plugin.addTab(where: String, when: LocationConfig, options: Object); +``` + +_Arguments_ + +`where` string parameter admissable values for this method: + +| Key | Type | Description | +|---|---|---| +|`TabLocation.RESOURCE_DETAIL`| String | Location for a Tab on a Resource Detail page | + +
+ +`when` Object admissable values: + +`LocationConfig` as described above for the [LocationConfig object](./common#locationconfig). + +
+
+ +### TabLocation.RESOURCE_DETAIL options + +![Tabs](../screenshots/add-tab.png) + +`options` config object. Admissable parameters for the `options` with `'TabLocation.RESOURCE_DETAIL'` are: + +| Key | Type | Description | +|---|---|---| +|`name`| String | Query param name used in url when tab is active/clicked | +|`label`| String | Text for the tab label | +|`labelKey`| String | Same as "label" but allows for translation. Will superseed "label" | +|`weight`| Int | Defines the order on which the tab is displayed in relation to other tabs in the component | +|`showHeader`| Boolean | Whether the tab header is displayed or not | +|`tooltip`| String | Tooltip message (on tab header) | +|`component`| Function | Component to be rendered as content on the tab | + +Usage example: + +```ts +plugin.addTab( + TabLocation.RESOURCE_DETAIL, + { resource: ['pod'] }, + { + name: 'some-name', + labelKey: 'plugin-examples.tab-label', + label: 'some-label', + weight: -5, + showHeader: true, + tooltip: 'this is a tooltip message', + component: () => import('./MyTabComponent.vue') + } +); +``` \ No newline at end of file diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/changelog.md b/docusaurus/extensions_versioned_docs/version-2.0.x/changelog.md new file mode 100644 index 00000000000..dfec0fefed7 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/changelog.md @@ -0,0 +1,18 @@ +# Changelog + +> **Rancher 2.10.0** will introduce the migration to Vue 3 - extensions will need to be updated to Vue 3 - read more [**here**](./rancher-2.10-support.md) + +> **Rancher 2.9.0** contains changes that may require updates to extensions - read more [**here**](./rancher-2.9-support.md) + + + +| Date | Version | Description | +|---|---|---| +| 09‑07‑2024 | 1.2.3 | Minor bug fixes | +| 09‑07‑2024 | [**2.0.1**](https://github.com/rancher/dashboard/releases/tag/shell-pkg-v2.0.1) | Minor bug fixes | +| 01‑07‑2024 | 1.2.2 | First release of a new `Shell` version, but codewise similar to `0.5.3` in order to keep extensions api versioning up to par with shell versioning | +| 01‑07‑2024 | [**2.0.0**](https://github.com/rancher/dashboard/releases/tag/shell-pkg-v2.0.0) | Various fixes to the `Shell` package in order to make it compliant with Rancher 2.9 | +| 15‑02‑2024 | [**0.5.3**](https://github.com/rancher/dashboard/releases/tag/shell-pkg-v0.5.3) | Inclusion of the replacement of `Vue.extend` to `defineComponent` with proper fix | +| 31‑01‑2024 | [**0.5.2**](https://github.com/rancher/dashboard/releases/tag/shell-pkg-v0.5.2) | Fix `parse tag name` check in workflows for publishing extensions | +| 29‑01‑2024 | [**0.5.1**](https://github.com/rancher/dashboard/releases/tag/shell-pkg-v0.5.1) | Revert changes to replacement of `Vue.extend` to `defineComponent` because of impact on extensions | +| 24‑01‑2024 | [**0.5.0**](https://github.com/rancher/dashboard/releases/tag/shell-pkg-v0.5.0) | Add support for Gitlab workflows for publishing extensions | diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/extensions-configuration.md b/docusaurus/extensions_versioned_docs/version-2.0.x/extensions-configuration.md new file mode 100644 index 00000000000..57e0266b29e --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/extensions-configuration.md @@ -0,0 +1,104 @@ +# Extensions configuration + +Follow instructions [here](./extensions-getting-started.md) to scaffold your extension. This will assist you in the creation of an extension as a top-level product inside Rancher Dashboard. + +## Folders Structure + +Once you've done so, there are some initialization steps specific to extensions. Beyond that, extensions largely work the same as the rest of the dashboard. There are a set of top-level folders that can be defined and used as they are in the dashboard: `chart`, `cloud-credential`, `content`, `detail`, `edit`, `list`, `machine-config`, `models`, `promptRemove`, `l10n`, `windowComponents`, `dialog`, and `formatters`. + + +### chart +Components in the chart folder are used to add custom UI to a helm install flow. The dashboard will look up a custom chart component for a given helm chart by checking two annotations: `'catalog.cattle.io/ui-component'` if set, otherwise `'catalog.cattle.io/release-name'`. + +### cloud-credential +Cloud credentials are components that add provider-specific UI to create cloud credentials, needed to provision clusters. + +### dialog +Components in the dialog folder are used within the `PromptModal` component. Dispatch the `promptModal` action from the dashboard store to open a modal. This action takes a few props: + +|Name |Type |Description| +|---|---|---| +|resources| Array or Object| Optional - Any resource(s) relevant to the custom modal component | +|componentProps| Object | Optional - additional props to pass to the custom modal component | +|component| String | Optional- the name of the custom modal component| +|modalWidth| String CSS Property | Desired width of the modal (default 600px)| +|modalSticky| Boolean | Whether or not to apply sticky positioning (default false)| + +### formatters +This is not a top-level folder in the shell, which uses `/components/formatter`, but a top-level `formatters` directory works the same way in an extension as the shell `formatter` directory does. Formatters are used to format data within tables. + +### machine-config +Machine configs are used to add provider-specific UI to the rke2/k3s provisioning page. + +### detail, edit, and list +`detail`, `edit`, and `list` folders are used to create custom CRUD views for kubernetes resources, and components in each should be given the same name as the targeted resource. + +### models +Kubernetes resources loaded through the dashboard store are, by default, instances of the resource class found here: `plugins/dashboard-store/resource-class.js`. Add a file with the name of the resource to the `models` directory to expand on that functionality. Generally, models should be an extension of the Steve class (Norman resources should not, but they are primarly used around auth functionality): +``` +import SteveModel from '@shell/plugins/steve/steve-class'; + +export default class extends SteveModel { +... +} +``` +Some common model properties to overwrite are: +* `availableActions`: list of resource actions that appear in kebab menus (include/alter default actions with `_availableActions`) +* `canDelete`: whether or not the current user should be able to delete a resource +* `detailLocation`: route for the detail view of one instance of the resource + +### promptRemove +Components in the PromptRemove folder are used to customize the removal prompt for specific resource types. Components added to this folder should have the same file name as the resource they're intended for. These components do not control the actual removal action - they are intended to allow the developer to supply additional information about consequences of removing a given resource, eg the Global Role removal prompt warns how many users are bound to that role. + +### l10n +Extension translation strings are merged with those already present in `shell/assets/translations`. Translation strings with duplicate keys of those present in the relevant shell translation file will overwrite those shell translation strings _across the app_: be mindful if adding translation strings that are not explicitly scoped to your extension. Read more about translations [here](../extensions/advanced/localization.md) + +### Extension Package Metadata + +Each extension package has the ability to customize certain aspects when it comes to compatibility with Rancher Manager/Kubernetes or displaying extension names. These are determined by the `rancher.annotations` object applied to the `package.json` of an extension package. + +These annotations allow you to specify compatibility with Kubernetes, Rancher Manager, the Extensions API, and the Rancher UI version by relying on [semver ranges](https://www.npmjs.com/package/semver/v/6.3.0#ranges). As well as version compatibility, you can also specify a Display Name for the Extension package as it appears on the "Extensions" page within the UI. + + +## Overlapping Model Names +It's possible that different products will use the same kubernetes resource, but need to add different model functionality (eg Harvester has a 'node' model). Files in a extension's `models` folder will overwrite any files in the `shell/models` directory across the application. To extend or overwrite model functionality for a given store, nest models within a subfolder with the same name as the vuex module's `namespace`. + +## Configurable Annotations + +| Annotation | Value | Description | +| ------ | :------: | --------------| +| `catalog.cattle.io/kube-version` | `Range` | Determines if the Kubernetes version that Rancher Manager is utilizing is compatible with the Extension package. | +| `catalog.cattle.io/rancher-version` | `Range` | Determines the compatibility of the installed Rancher Manager version with the Extension package. | +| `catalog.cattle.io/ui-extensions-version` | `Range` | Determines the Extensions API version that is compatible with the Extension package. | +| `catalog.cattle.io/ui-version` | `Range` | Determines the Rancher UI version that is compatible with the Extension package. | +| `catalog.cattle.io/display-name` | `String` | Specifies the Display Name for an Extension package's card on the "Extensions" page. | + +## Other configuration properties +| Property | Value | Description | +| ------ | :------: | --------------| +| `noAuth` | `Boolean` | If `noAuth` is set to `true` then the extension will be loaded even when the user is logged out. (Rancher 2.9 - Extensions API 2.0) | + +## Example Configuration + +Here's an example configuration of an extensions `package.json`: + +___`./pkg/my-package/package.json`___ +```json +{ + "name": "my-package", + "description": "my-package plugin description", + "version": "0.1.0", + "rancher": { + "annotations": { + "catalog.cattle.io/kube-version": ">= v1.26.0-0 < v1.29.0-0", + "catalog.cattle.io/rancher-version": ">= 2.7.7-0 < 2.9.0-0", + "catalog.cattle.io/ui-extensions-version": ">= 1.1.0", + "catalog.cattle.io/ui-version": ">= 2.7.7-0 < 2.9.0-0", + "catalog.cattle.io/display-name": "My Super Great Extension" + }, + "noAuth": true + }, + ... +} +``` + diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/extensions-getting-started.md b/docusaurus/extensions_versioned_docs/version-2.0.x/extensions-getting-started.md new file mode 100644 index 00000000000..9d3e7c68098 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/extensions-getting-started.md @@ -0,0 +1,375 @@ +# Getting Started + +This guide will walk through creating a new extension from scratch. + +## Prerequisites + +> Note: Extensions development is only currently supported on Mac and Linux. Windows is not currently supported. + +You will need a recent version of nodejs installed (Tested with node version: `v16.19.1`). + +You'll also need the yarn package manager installed, which can be done with `npm install -g yarn`. + +## Creating the Application + +To develop a new extension, you need an application UI to host it during development. Rancher provides a helper to create a skeleton application for you. This gives you a full version of the Rancher UI that can be used to develop and test your extension. + +Rancher now publishes a single npm package, `@rancher/extension`, to help bootstrap the creation of the application and extension. This replaces the previous separate creators (`@rancher/app` and `@rancher/pkg`). + +### Creating an Extension + +Create a new folder and run: + +```sh +yarn create @rancher/extension my-app [OPTIONS] +cd my-app +``` + +This command will create a new folder `my-app` and populate it with the minimum files needed. + +> Note: The skeleton application references the Rancher dashboard code via the `@rancher/shell` npm module. + +### Installing Rancher + +See . Note: Not all Linux distros and versions are supported. To make sure your OS is compatible with Rancher, see the support maintenance terms for the specific Rancher version that you are using: https://www.suse.com/suse-rancher/support-matrix/all-supported-versions + +The above linked installation docs cover two methods confirmed to work with the Dashboard: + +- [Single Docker Container](https://ranchermanager.docs.rancher.com/getting-started/installation-and-upgrade/other-installation-methods/rancher-on-a-single-node-with-docker) +- [Kube Cluster (via Helm)](https://ranchermanager.docs.rancher.com/getting-started/installation-and-upgrade/install-upgrade-on-a-kubernetes-cluster) + +To use the most recent version of Rancher that is actively in development, use the version tag `v2.10-head` when installing Rancher. For example, the Docker installation command would look like this: + +```bash +sudo docker run -d --restart=unless-stopped -p 80:80 -p 443:443 --privileged -e CATTLE_BOOTSTRAP_PASSWORD=OPTIONAL_PASSWORD_HERE rancher/rancher:v2.10-head +``` + +Dashboard provides convenience methods to start and stop Rancher in a single docker container + +```bash +yarn run docker:local:start +yarn run docker:local:stop // default user password as "password" +``` + +Note that for Rancher to provision and manage downstream clusters, the Rancher server URL must be accessible from the Internet. If you’re running Rancher in Docker Desktop, the Rancher server URL is `https://localhost`. To make Rancher accessible to downstream clusters for development, you can: + +- Use ngrok to test provisioning with a local rancher server +- Install Rancher on a virtual machine in Digital Ocean or Amazon EC2 +- Change the Rancher server URL using `c/local/settings/management.cattle.io.setting` + +Also for consideration: + +- [K3d](https://k3d.io/v4.4.8/#installation) lets you immediately install a Kubernetes cluster in a Docker container and interact with it with kubectl for development and testing purposes. + +You should be able to reach the older Ember UI by navigating to the Rancher API url. This same API Url will be used later when starting up the Dashboard. + +#### **_Extension Options_** + +There are a few options available to be passed as an argument to the `@rancher/extension` script: + +| Option | Description | +| :-----------------------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--update \| -u` | This will update all dependencies within the extension to match the declared versions in the shell. Update can be ran independently or along with creating an extension (e.g. `yarn create @rancher/extension my-extension --update` | +| `--app-name \| -a ` | Allows specifying a different name for the skeleton application instead of using the extension name. | +| `--skeleton-only \| -s` | Installs only the skeleton application without creating the extension package. | +| `-l` | This will automatically add the [`.gitlab-ci.yml`](https://github.com/rancher/dashboard/blob/master/creators/extension/app/files/.gitlab-ci.yml) pipeline file for integration with GitLab | +| `-w` | Does not add the Github workflow files [`build-extension-catalog.yml`, `build-extension-charts.yml`](https://github.com/rancher/dashboard/tree/master/creators/extension/app/files/.github/workflows) to be used as Github actions. These files will be added automatically by default. | +| `-t` | Does not add the template folders automatically into the Extension package. These folders will be added automatically by default | + +--- + +You can run the app with: + +```sh +yarn install +API= 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. Your skeleton application is a full Rancher UI - but referenced via `npm`. + +## Creating an Extension as a top-level-product + +The next step is to create an extension. As a Getting Started example, we'll demonstrate an extension for a [Top-level product](./usecases/top-level-product), but you also have the option to create an extension for a [Cluster-level product](./usecases/cluster-level-product). + +### Creating an Extension Package + +Rancher provides a helper to add an extension, allowing you to manage multiple extensions or a single extension within the parent folder. + +To create a new extension, run the following command: + +```sh +yarn create @rancher/extension my-app [OPTIONS] +``` + +This will create a new UI Package in the `./pkg/my-app` folder. + +#### Logic Behind the init Script + +When you run this command, the init script creates a skeleton application along with an extension package. By default, both the skeleton application and the extension package will have the same name (`my-app` in this case). + +- If you want the skeleton application to have a different name than the extension package, you can use the `--app-name` (or `-a`) option: + +```sh +yarn create @rancher/extension new-extension --app-name my-app +``` + +This will create a skeleton application named `my-app` and an extension package named `new-extension`. + +- If you are already within a skeleton application and want to create another extension package within the same application, simply run the same command: + +```sh +yarn create @rancher/extension another-extension +``` + +In this case, only a new extension package (`another-extension`) will be created under the existing skeleton application. No additional skeleton application will be generated. + +- If you only want to create the skeleton application without any extension package, you can use the `--skeleton-only` (or `-s`) option: + +```sh +yarn create @rancher/extension my-app --skeleton-only +``` + +This will create only the skeleton application, and you can later add extension packages as needed. + +This flexibility allows you to structure your development environment based on your specific needs, whether you're starting fresh or adding to an existing setup. + +### Configuring an Extension + +Replace the contents of the file `./pkg/my-app/index.ts` with: + +```ts +import { importTypes } from '@rancher/auto-import'; +import { IPlugin } from '@shell/core/types'; +import extensionRouting from './routing/extension-routing'; + +// Init the package +export default function (plugin: IPlugin) { + // Auto-import model, detail, edit from the folders + importTypes(plugin); + + // Provide extension metadata from package.json + // it will grab information such as `name` and `description` + plugin.metadata = require('./package.json'); + + // Load a product + plugin.addProduct(require('./product')); + + // Add Vue Routes + plugin.addRoutes(extensionRouting); +} +``` + +Next, create a new file `./pkg/my-app/product.ts` with this content: + +```ts +import { IPlugin } from '@shell/core/types'; + +export function init($plugin: IPlugin, store: any) { + const YOUR_PRODUCT_NAME = 'myProductName'; + const BLANK_CLUSTER = '_'; + + const { product } = $plugin.DSL(store, YOUR_PRODUCT_NAME); + + product({ + icon: 'gear', + inStore: 'management', + weight: 100, + to: { + name: `${YOUR_PRODUCT_NAME}-c-cluster`, + path: `/${YOUR_PRODUCT_NAME}/c/:cluster/dashboard`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER, + pkg: YOUR_PRODUCT_NAME, + }, + }, + }); +} +``` + +And finally create a file + folder `/routing/extension-routing.js` with the content: + +```js +// Don't forget to create a VueJS page called index.vue in the /pages folder!!! +import Dashboard from '../pages/index.vue'; + +const BLANK_CLUSTER = '_'; +const YOUR_PRODUCT_NAME = 'myProductName'; + +const routes = [ + { + name: `${YOUR_PRODUCT_NAME}-c-cluster`, + path: `/${YOUR_PRODUCT_NAME}/c/:cluster`, + component: Dashboard, + meta: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER, + pkg: YOUR_PRODUCT_NAME, + }, + }, +]; + +export default routes; +``` + +## Running the App + +We've created a bare bones extension and exposed a new 'product' that will appear in the top-level slide-in menu. At this stage, it does +nothing other than that! + +You should now be able to run the UI again with: + +```sh +API= yarn dev +``` + +Open a web browser to https://127.0.0.1:8005 and you'll see a new 'MyProductName' nav item in the top-level slide-in menu. + +
+ +![MyProductName Nav Item](./screenshots/global-nav.png) + +
+ +> Note: You should be able to make changes to the extension and the UI will hot-reload and update in the browser. + +To further develop a product, add new pages and add new types here's an [example](./usecases/top-level-product.md). + +## Building the Extension + +Up until now, we've run the extension inside of the skeleton application - this is the developer workflow. + +To build the extension so we can use it independently, run: + +```sh +yarn build-pkg my-app +``` + +This will build the extension as a Vue library and the built extension will be placed in the `dist-pkg` folder. + +## Loading Into Rancher + +### Prevent loading your extension in dev mode + +When we run `API= yarn dev`, our test extension will be automatically loaded into the application - this allows us to develop +the extension with hot-reloading. To test loading the extension dynamically, we can update configuration to tell Rancher not to include our extension. + +To do this, edit the file `vue.config.js` in the root `my-app` folder, and add the name of the package you want to exclude, such as: + +```js +const config = require('@rancher/shell/vue.config'); + +module.exports = config(__dirname, { + excludes: ['my-app'], +}); +``` + +Now restart your app by running the UI again with: + +```sh +API= yarn dev +``` + +Open a web browser to https://127.0.0.1:8005 and you'll see that the Example nav item is not present - since the extension was not loaded. + +> Note: You need to be an admin user to test Extensions in the Rancher UI + +### Test built extension by doing a Developer load + +To enable Developer load in the UI, you should go to the user avatar in the top-right and go to `Preferences`. Under `Advanced Features`, check the `Enable Extension developer features` checkbox. + +![Preferences](./screenshots/preferences.png) + +![Extension Developer Features](./screenshots/extension-developer-features.png) + +Now we need to serve the built package locally by running the following: + +```sh +yarn serve-pkgs +``` + +This will start a small web server (on port 4500) that serves up the contents of the `dist-pkg` folder. It will output which extensions are being served up - in our case you should see output like that below - it shows the URLs to use for each of the available extensions. + +```console +Serving catalog on http://127.0.0.1:4500 + +Serving packages: + + my-app-0.1.0 available at: http://127.0.0.1:4500/my-app-0.1.0/my-app-0.1.0.umd.min.js +``` + +Now jump back into the UI and bring in the slide-in menu (click on the hamburger menu in the top-left) and click on 'Extensions'. + +![Developer Load](./screenshots/dev-load.png) + +Go to the three dot menu and select 'Developer load' - you'll get a dialog allowing you to load the extension into the UI. + +![Developer Load Modal](./screenshots/dev-load-modal.png) + +In the top input box `Extension URL`, enter: + +``` +https://127.0.0.1:8005/pkg/my-app-0.1.0/my-app-0.1.0.umd.min.js +``` + +Press 'Load' and the extension will be loaded, you should see a notification telling you the extension was loaded and if you bring in the side menu again, you should see the Example nav item there now. + +This illustrates dynamically loading an extension. + +You'll notice that if you reload the Rancher UI, the extension is not persistent and will need to be added again. You can make it persistent by checking the `Persist extension by creating custom resource` checkbox in the Developer Load dialog. + +## Creating a Release + +Creating a Release for your extension is the official avenue for loading extensions into any Rancher instance. As mentioned in the [Introduction](./introduction.md), the extension can be packaged into a Helm chart and added as a Helm repository to be easily accessible from your Rancher Manager. + +We have created [workflows](https://github.com/rancher/dashboard/tree/master/creators/extension/app/files/.github/workflows) for [Github Actions](https://docs.github.com/en/actions) which will automatically build, package, and release your extension as a Helm chart for use within your Github repository, and an [Extension Catalog Image](./advanced/air-gapped-environments) (ECI) which is published into a specified container registry (`ghcr.io` by default). Depending on the use case, you can utilize the Github repository as a [Helm repository](https://helm.sh/docs/topics/chart_repository/) endpoint which we can use to consume the chart in Rancher, or you can import the ECI into the Extension Catalog list and serve the Helm charts locally. + +> **WARNING:** When using the provided Github workflows, the base skeleton application name (Found in the root level `package.json`) ___MUST___ be unique when compared with any extension packages found in `./pkg/*`. If an extension package name matches the base skeleton name the workflow will fail due to the "Parse Extension Name" step found in both the ["Build and Release Extension Charts"](https://github.com/rancher/dashboard/blob/422823e2b6868191b9bb33470e99e69ff058b72b/.github/workflows/build-extension-charts.yml#L59-L65) and ["Build and release Extension Catalog Image to registry"](https://github.com/rancher/dashboard/blob/422823e2b6868191b9bb33470e99e69ff058b72b/.github/workflows/build-extension-catalog.yml#L64-L70) workflows. + +> Note: GitLab support is offered through leverging the ECI build. For configuration instructions, follow the setps in the [Gitlab Integration](./publishing#gitlab-integration) section. + +> Note: If you wish to build and publish the Helm chart or the ECI manually or with specific configurations, you can follow the steps listed in the [Publishing an Extension](./publishing) section. + +### Release Prerequisites + +In order to have a Helm repository you will need to enable Github Pages on your Github repository. Just follow these steps: + +1. Create a branch called `gh-pages` on your Github repository for the extension + +2. Go to the repository of the extension and click the `Settings` tab in the top navigation bar. + +
+ +![Repo Settings Tab](./screenshots/repo-settings-tab.png) + +
+ +3. Then on the left navigation bar of the settings page click the `Pages` tab. + +
+ +![Repo Pages Tab](./screenshots/repo-pages-tab.png) + +
+ +4. Lastly, select `GitHub Actions` from the `Source` dropdown. + +
+ +![Repo Pages Dropdown](./screenshots/repo-pages-dropdown.png) + +
+ +### Consuming the Helm chart + +After releasing the Helm chart you will be able to consume this from the Rancher UI by adding your Helm repository's URL to the App -> Repository list. If you used the automated workflow to release the Helm chart, you can find the URL within your Github repository under the "github-pages" Environment. + +The URL should be listed as: `https://.github.io/` + +Once the URL has been added to the repository list, the extension should appear within the Extensions page. + +## Wrap-up + +This guide has showed you how to create a skeleton application that helps you develop and test one or more extensions. + +We showed how we can develop and test those with hot-reloading in the browser and how we can build our extensions into a package that we can dynamically load into Rancher at runtime. We also went over how to release our extensions as Helm charts using the automated workflow. diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/home.md b/docusaurus/extensions_versioned_docs/version-2.0.x/home.md new file mode 100644 index 00000000000..1367cd89c65 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/home.md @@ -0,0 +1,9 @@ +# Rancher Extensions + +Rancher Extensions provides a mechanism to add new functionality to the Dashboard UI at runtime. + +Extensions can be created independently of Rancher and their code can live in separate GitHub repositories. They are compiled as Vue libraries and packaged up with a simple Helm chart to allow easy installation into Rancher. + +You can find some example extensions here: https://github.com/rancher/ui-plugin-examples + +> Note: Rancher Extensions is in early development - support for Extensions was recently added to Rancher 2.7.0 and the internal Rancher team is building out extensions using the extensions framework. Over time, the extensions API will improve and the supporting documentation will be built out, to enable the wider developer community to build their own extensions. diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/introduction.md b/docusaurus/extensions_versioned_docs/version-2.0.x/introduction.md new file mode 100644 index 00000000000..fa984643036 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/introduction.md @@ -0,0 +1,18 @@ +# Introduction + +A Rancher Extension is a packaged Vue library that provides a set of functionality to extend and enhance the Rancher UI. + +Developers can author, release and maintain extensions independently of Rancher itself. + +Rancher defines a number of extension points - the [**Extensions API**](./api/overview.md) - which developers can take advantage of, to provide extra functionality, for example: + +- Add new UI screens to the top-level side navigation - [**Top-level product**](./usecases/top-level-product.md) +- Add new UI screens to the cluster-level side navigation - [**Cluster-level product**](./usecases/cluster-level-product.md) +- Add new UI screens to the navigation of the Cluster Explorer UI +- Add new UI for Kubernetes CRDs +- Extend existing views in Rancher Manager by adding panels, tabs and actions +- Customize the landing page + +> Note: More extension points will be added over time + +Once an extension has been authored, it can be packaged up into a Helm chart, added to a Helm repository and then easily installed into a running Rancher system. diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/known-issues.md b/docusaurus/extensions_versioned_docs/version-2.0.x/known-issues.md new file mode 100644 index 00000000000..81c395a8fa1 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/known-issues.md @@ -0,0 +1,19 @@ +# Known issues + +- Running `yarn install` might throw the following error: +``` +error glob@10.4.3: The engine "node" is incompatible with this module +``` + +To resolve this add the following `resolution` to the root application's `package.json`: +``` +{ + "name": "app-name", + "version": "0.1.0", + ... + resolutions": { + "glob": "7.2.3" + }, + ... +} +``` diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/migration.md b/docusaurus/extensions_versioned_docs/version-2.0.x/migration.md new file mode 100644 index 00000000000..a9a4f25722b --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/migration.md @@ -0,0 +1,19 @@ +# Migration to Vue3 + +Migration of Rancher plugins can be easily done using the migration script. This will ensure all the updates and highlights manual changes required to be done. + +- Run script `./node_modules/.bin/@rancher/dashboard migrate` to migrate files from Vue2 to Vue3 +- Update NVM/Node version to 20.0.0 (current pointed version) +- Reinstall the packages to fetch the newer versions with `yarn` +- Run unit test upgrade tool `npx vue-upgrade-tool --files 'shell/**/*.test.ts'` +- Run linter with auto-fix flag `yarn lint --fix` +- Manually review logged issues, the script is not aimed to convert 100% of the code + +At this point the plugin should be Vue3 compatible. + +Allowed flags for the migration script: + +- `--files`, list all the files +- `--log`, create a `stats.json` log file in the root +- `--dry`, run script without modify files, e.g. for log preview +- `--verbose`, print all the changes in console diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/moving-existing-code.md b/docusaurus/extensions_versioned_docs/version-2.0.x/moving-existing-code.md new file mode 100644 index 00000000000..a69602b52ab --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/moving-existing-code.md @@ -0,0 +1,19 @@ +# Moving Existing Code + +During the transition to the new folder structure in 2.6.5 required by the extension work ... +- Run the script `./scripts/rejig` to move folders to their new location in the `shell` folder and update the appropriate import statements + Use this to convert older PRs to the new format +- 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) + > IMPORTANT - This script contains a `git reset --hard` + +## Step 1 +The basis of this step 1 is to move the majority of the code under the `shell` folder. Additionally, the top-level `nuxt.config.js` is updated +to extend a base version now located within the `shell` folder. This includes updated nuxt and webpack configuration to get everything working with the +moved folders. + +Note that this represents the minimum to get things working - the next step would be to move the Rancher and Harvester code out from the `shell` folder into a number +of UI Package folders under the top-level `pkg` folder. This would then reduce the scope of what's in the `shell` folder to be the core common UI that we would +want to share across our UIs. So, bear in mind, that ultimately, we wouldn't just be moving all of the code under `shell`. + +The rework supports a number of use cases, which we will talk through below. diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/publishing.md b/docusaurus/extensions_versioned_docs/version-2.0.x/publishing.md new file mode 100644 index 00000000000..1cddca51d2d --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/publishing.md @@ -0,0 +1,228 @@ +# Publishing an Extension + +There are currently two options for building and publishing a extensions: + +1. Building the Helm charts and necessary assets of an extension that can be committed into a Github or Helm repository. +2. Building an [Extension Catalog Image](./advanced/air-gapped-environments) that can be pushed or mirrored into a container registry. + +As discussed in the [Getting Started](./extensions-getting-started#creating-a-release) section, we have established a [workflow](https://github.com/rancher/dashboard/tree/master/creators/extension/app/files/.github/workflows) using [Github reusable workflows](https://docs.github.com/en/actions/using-workflows/reusing-workflows), that automatically handles the build and publication of both the Helm charts and ECI into the Extension's GitHub repository. However, this workflow can be omitted for a more hands-on approach to publishing Extensions. + +> Note: An explanation on the workflow files can be found in the [Additional Release Configuration](#additional-release-configuration) section. + +## Automatic Approach - Triggering a Github Workflow on Tagged Release + +> **WARNING:** When using the provided Github workflows, the base skeleton application name (Found in the root level `package.json`) ___MUST___ be unique when compared with any extension packages found in `./pkg/*`. If an extension package name matches the base skeleton name the workflow will fail due to the "Parse Extension Name" step found in both the ["Build and Release Extension Charts"](https://github.com/rancher/dashboard/blob/422823e2b6868191b9bb33470e99e69ff058b72b/.github/workflows/build-extension-charts.yml#L59-L65) and ["Build and release Extension Catalog Image to registry"](https://github.com/rancher/dashboard/blob/422823e2b6868191b9bb33470e99e69ff058b72b/.github/workflows/build-extension-catalog.yml#L64-L70) workflows. + +If your extensions repository doesn't include a github workflow to automate the publishing procedure (ignore this step if you have it already), you can create one in `.github/workflows/build.yaml` on your extension folder, with the following content: + +```yaml +name: Build and Release Extension Charts + +on: + workflow_dispatch: + release: + types: [released] + +defaults: + run: + shell: bash + working-directory: ./ + +jobs: + build-extension-charts: + uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@master + permissions: + actions: write + contents: write + deployments: write + pages: write + with: + target_branch: gh-pages + tagged_release: ${{ github.ref_name }} +``` + +This will leverage the usage of our reusable workflow and give your Github extension repo the ability to publish to the `gh-pages` branch via a **Tagged Release**. + +It's mandatory that release type is **Tagged Release**, otherwise the `build-extension-charts.yml` workflow won't run properly, which is crucial for the success of the publish operation. + +When creating a Tagged Release, the tag name **_MUST_** match the proper Extension name depending on the type of build. As it was mentioned above, the Extension Charts (published in the specified branch) and ECI (published in the specified registry) are determined by different versions. + +### Proper Tagged Release naming scheme to build Extension Charts + +- The name **_MUST_** match the Extension Package folder name (`./pkg/<-YOUR-EXT-DIR->`), followed by the version (determined by the `version` property in `./pkg/<-YOUR-EXT-DIR->/package.json`). + + If the extension folder in named **my-awesome-extension** and the `version` property value in `./pkg/<-YOUR-EXT-DIR->/package.json` is **1.2.3**, then a correct tag to build the Extension Helm Chart would be: `my-awesome-extension-1.2.3` + +### Proper Tagged Release naming scheme to build Extension Catalog Image + +The name **_MUST_** match the main Extension name (determined by the `name` property in `./package.json`) followed by the version (determined by the `name` property in `./package.json`). + + If the `name` property in `./package.json` is **my-extension** and the `version` property is **8.0.1**, then a correct tag to build the Catalog Image would be: `my-extension-8.0.1` + +--- + + +## Manual Approach - Publish Script Usage + +**_Usage_** + +```console +Usage: yarn publish-pkgs [] [plugins] + options: + -s Specify destination GitHub repository (org/name) - defaults to the git origin + -b Specify destination GitHub branch + -t Specify the Github release tag to build when a release has been tagged and published + -f Force building the chart even if it already exists + -c Build as a container image rather than publishing to Github + -p Push container images on build + -r Specify destination container registry for built images + -o Specify destination container registry organization for built images + -i Specify prefix for the built container image (default: 'ui-extension-') + -l Specify Podman container build +``` + +## Manually Publishing the Extension Helm Charts + +> Note: Currently, we only support publishing Extension Helm charts into a **public** Github repository, if you inted to deploy an extension from a private repository/registry, we recommend utilizing the [Extension Catalog Image](#manually-publishing-an-extension-catalog-image) method. + +Building the Extension into Helm charts with the necessary assets can be accomplished with the `publish-pkgs` script declared in the extension's `package.json`. +Running this script will bundle each extension package as a node.js app, create Helm charts with metadata needed by the [`ui-plugin-operator`](https://github.com/rancher/ui-plugin-operator), and a [Helm repo index](https://helm.sh/docs/helm/helm_repo_index/) to populate the Extension marketplace for installation. + +After the Helm assets have been built, you will need to commit them into a publicly accessible Github repository. This repository will then need to be added into the Cluster Repositories list within a Rancher instance, that will then populate the Extension marketplace with the charts built from the previous step. + +### Prerequisites + +> Note: This has been tested on Linux and MacOS machines + +This method requires a few tools to be installed: + +- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +- [nodejs](https://nodejs.org/en/download) ( >= `12.0.0` < `17.0.0` ) +- [yarn](https://yarnpkg.com/getting-started/install) +- [jq](https://stedolan.github.io/jq/) +- [yq](https://github.com/mikefarah/yq/#install) ( >= `4.0.0` ) +- [helm](https://helm.sh/docs/intro/install/) ( >= `3.0.0` ) + +### Running the Publish Script to generate the extension Helm Chart + +To start the build, run the `publish-pkgs` script with two options: + +| Option | Argument | Description | +| ------ | -------------- | ----------------------------------------------------------------------------------- | +| `-s` | `` | Specifies the destination GitHub repository (org/name) - defaults to the git origin | +| `-b` | `` | Specifies destination GitHub branch - defaults to `main` | + +Example: + +```console +yarn publish-pkgs -s "my-organization/my-extension-repo" -b "production" +``` + +After running the script, a number of directories will be created: + +- `assets` - Contains the packaged Extension charts for each package from `./charts/*` (e.g. `./assets/my-pkg/my-pkg-0.1.0.tgz`), used by the `index.yaml` to obtain the Chart info. +- `charts` - The Extension Charts, includes the `Chart.yaml` and `values.yaml` among other files. +- `extensions` - Contains the minified javascript bundle for the node.js app, targeted by the `.spec.plugin.endpoint` which is used by the `ui-plugin-operator` to load and cache the minified javascript. +- `index.yaml` - The Helm repo index file used by Helm to gather Chart information - this contains the `urls` property that targets the `assets/*` for each package. +- `tmp` - Contains all of the directories and `index.yaml` mentioned which allows for simple aggregation when pushing charts to a repository. + +These files will need to be pushed into a publically available Github repository, for example: + +```console +git add ./tmp/* +git commit -m 'Adding extension charts' +git push origin production +``` + +Once the files are accessible on Github, add the repository within the Rancher "local" cluster - The charts will then be available on the Extensions page, under the "Available" tab. + +## Manually Publishing an Extension Catalog Image + +Publishing an ECI into the registry of your choice can also be accomplished with the `publish-pkgs` script declared in the extension's `package.json`. This script will build a Docker image for each extension package, as well as a Helm chart that can be used to deploy the images. Given the option, the script can automatically push the built images and chart into a specified registry. + +### Prerequisites + +Publishing an ECI has a few requirements, namely: + +- The Extension needs to be bundled into the ECI +- A registry to house the ECI +- Access to this registry within the Cluster + +This method also requires a few tools to be installed: + +- [make](https://www.gnu.org/software/make/) +- [docker](https://docs.docker.com/get-docker/) +- [go](https://go.dev/dl/) +- [nodejs](https://nodejs.org/en/download) ( >= `12.0.0` < `17.0.0` ) +- [yarn](https://yarnpkg.com/getting-started/install) +- [jq](https://stedolan.github.io/jq/) +- [yq](https://github.com/mikefarah/yq/#install) ( >= `4.0.0` ) +- [helm](https://helm.sh/docs/intro/install/) ( >= `3.0.0` ) + +### Running the Publish Script for a Catalog Image + +You can simply run the `publish-pkgs` script with three options: + +| Option | Argument | Description | +| ------ | ---------------- | ----------------------------------------------------- | +| `-c` | | specifies the container build | +| `-r` | `` | specifies the registry where the image will be housed | +| `-o` | `` | specifies the organization namespace for the registry | + +Example: + +```console +yarn publish-pkgs -c -r -o +``` + +Running any of the commands above will only build your images - in order to publish the images to your registry you must either push them manually or use the script with the `-p`` option. This will automatically push your images to the designated registry. + +```console +yarn publish-pkgs -c -p -r -o +``` + +## Additional Release Configuration + +Depending on your use case, there are multiple options on building and creating releases. When building an extension within a Github repository, you have the option of utilizing an action that triggers [prebuilt workflows](https://github.com/rancher/dashboard/tree/master/creators/extension/app/files/.github/workflows). When added to your extension, the `./github/workflows` directory will contain the files: `build-extension-charts.yml` and `build-extension-catalog.yml`. These workflows accomplish two seperate actions: + +- `build-extension-charts` - Builds the Helm charts and necessary assets that are then published to the specified branch (defaults to `gh-pages`). + - The versioning of these builds is determined by the Extension Package `version` property found in `./pkg//package.json` + - Find the resuable workflow file [here](https://github.com/rancher/dashboard/blob/master/.github/workflows/build-extension-charts.yml) +- `build-extension-catalog` - Builds and publishes the [Extension Catalog Image](./advanced/air-gapped-environments) (ECI) into the specified registry (defaults to `ghcr.io`), for use with private repositories or air-gapped builds. + - An ECI contains the Helm charts and assets within the image, and is determined by the main Extension `version` property found in `./package.json` + - Find the reusable workflow file [here](https://github.com/rancher/dashboard/blob/master/.github/workflows/build-extension-catalog.yml) + +By default, both of these actions are triggered by pushing into the `main` branch. This may not be your disired flow, and so you can modify the workflow file to fit your needs. + +> Note: Addition configuration information on the workflows can be found in the [Workflow Configuration](./advanced/workflow-configuration.md) section. + +> Note: For more information on events that trigger workflows, follow the [Github Documentation](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows). + +## GitLab Integration + +When building an extension that will be housed in a GitLab repository or hosted environment, there is only one option for publishing automatically - That is by utilizing the provided [GitLab Pipeline CI file](https://github.com/rancher/dashboard/blob/master/creators/extension/app/files/.gitlab-ci.yml) that is generated when [creating the skeleton app](extensions-getting-started#creating-the-skeleton-app). + +This pipeline will build an ECI and publish it to container registry (`registry.gitlab.com` by default) to allow for importing into Rancher Manager. +The actual pipeline jobs are defined in the [Dashboard repo](https://github.com/rancher/dashboard/blob/master/.gitlab/workflows/build-extension-catalog.gitlab-ci.yml) to allow for proper versioning and to apply any updates to the pipeline without any additional work from the Extension developer. + +### Pipeline Configuration + +There are a few pipeline configuration options, mostly tied to the container registry: + +| Variable | Default | Description | +| :-----------------: | :--------------------------------------: | ----------------------------------------------------------------------------- | +| `REGISTRY` | `$CI_REGISTRY` | The Container Registry address where the ECI will be published. | +| `REGISTRY_USER` | `$CI_REGISTRY_USER` | The username to push the ECI to the Container Registry. | +| `REGISTRY_PASSWORD` | `$CI_REGISTRY_PASSWORD` | The password to push the ECI to the Container Registry. | +| `IMAGE_NAMESPACE` | `$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME` | Refers to the unique location within the Container Registry to store the ECI. | + +> **_WARNING:_** The default values for this configuration will **only** be available if the Container Registry is enabled for the project. + +> **Note:** You can find a list of the Predefined Variables in the [GitLab Documentation](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html), this is where the default values used above can be found. + +### Sequence of Events + +The Pipeline will run automatically when a change to the root level `package.json` has been detected, which will first trigger the `check_version` stage to check for any version collisions within the specified Container Registry. If this stage is successful, the stage `build_catalog` will then build and release the ECI to the Container Registry. + +Once the ECI has been published to the Container Registry, it can then be imported into the Rancher UI by following the steps in the [Importing section](./advanced/air-gapped-environments#importing-the-extension-catalog-image). + diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/rancher-2.10-support.md b/docusaurus/extensions_versioned_docs/version-2.0.x/rancher-2.10-support.md new file mode 100644 index 00000000000..07ec31f16c7 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/rancher-2.10-support.md @@ -0,0 +1,11 @@ +# Rancher 2.10.0 update + +## Advance Notice + +The Rancher UI, the Shell and the UI Extensions mechanism is being updated from the Vue 2 framework to the Vue 3 framework. + +This is a breaking change that will require existing Rancher UI Extensions to be migrated to Vue 3, built and published. + +Our intent is to provide migration documentation to assist UI Extension authors in this migration. + +More information will be available as development progresses. diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/rancher-2.9-support.md b/docusaurus/extensions_versioned_docs/version-2.0.x/rancher-2.9-support.md new file mode 100644 index 00000000000..b6661bbdc58 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/rancher-2.9-support.md @@ -0,0 +1,83 @@ +# Rancher 2.9.0 update + +## Breaking Changes in 2.9.0 + +Some of the changes introduced in Rancher 2.9.0 may cause problems with existing UI Extensions. You should check that your UI Extension(s) continue to operate correctly, if they encounter issues, your Extension(s) are most likely affected by the changes below and you should follow the steps below to update them. + +Two key changes were made that can affect UI Extensions: + +- “Schema diet initiative” changes. The API that the UI uses to understand which schemas (resources) are available was changed such that it now only returns the top-level schema metadata. A new API was added to retrieve the full scheme definition for a given resource type. This will affect UI Extensions that take advantage of this directly or which use the YAML resource editing for a resource type. + +- “defineComponent” changes. The use of `defineComponent` was introduced in 2.9.0 as part of the migration to Vue 3. Due to a way in which Vue is exposed in older versions of Rancher, some UI Extensions may experience compatibility issues at load time. + +To support these changes we have made some important changes to Rancher Shell (our core JS package) which helps and protects version compatibility. + +## Shell Versioning + +Before Rancher 2.9.0 was released, the latest stable Shell version was `0.5.3`. From Rancher 2.9.0 we have updated the Shell versioning system in the following way: + +![Shell versioning 2.9.0](./screenshots/shell-update-2.9-diagram.png) + +Effectively, we have split Shell into two different versions: + +- `1.2.3` - which is compliant with any **pre-2.9.0** Rancher system (effectively should be the same as using `0.5.3`). + +- `2.0.1` - which is compliant and **needed** for a **2.9.0** Rancher system + +For future releases of your extension to **work on Rancher 2.9.0** you will need to build and release a new version of your extension using Shell `2.0.1`. + +> If your extension is using Shell `0.5.3` and you **don't need** to be compliant with Rancher 2.9.0, there's no update to do. + +These changes bring Shell versions in line with standard versioning patterns. Only major version updates are expected to contain breaking changes, minor and patch versions should not. + +## How to update your extension for Rancher 2.9.0 + +- In the root of your extension repository, update `package.json` `@rancher/shell` to the new `2.0.1` version and `yarn install` to fetch it. Then do a local build of your extensions and `Developer Load` them on the desired Rancher version to confirm everything works as expected. Check documentation about a Developer Load [here](./extensions-getting-started#test-built-extension-by-doing-a-developer-load). + +- Before publishing it, add annotation(s) to your extension `pkg/<-YOUR EXTENSION->/package.json` like: + +```json +{ + "name": "your-extension", + "description": "your-extension description", + "version": "1.2.1", + "rancher": { + "annotations": { + "catalog.cattle.io/rancher-version": ">= 2.9.0", + "catalog.cattle.io/ui-extensions-version": ">= 2.0.1" + } + }, + .... +} +``` + +> With these annotations your extension will be restricted to Rancher version greater or equal to `2.9.0` and will also be restricted to a UI Extensions version (Shell version) greater or equal to `2.0.1`. +These are not mandatory but highly recommended to ensure your extension versions reference Rancher / Shell versions they're compatible with. +For more information about the annotation we allow for, check the documentation [here](./extensions-configuration#configurable-annotations). + +- After the above steps, just publish a new version of the extension. That published version should now be compliant with Rancher 2.9.0. + +## How to maintain different extension versions + +In terms of development procedure, we do recommend that you branch your extension repository so that you can have one branch tracking the Rancher "pre-2.9" and the Rancher 2.9 “worlds”, if your extension is affected by the changes. + +You can check the example of `Elemental` where branch `main` is tracking the **Rancher 2.9 compliant version** and `release-2.8.x` branch is tracking everything Rancher "pre-2.9". + +The difference between the two branches is some annotations and of course the shell versions being used. + +`main` +- https://github.com/rancher/elemental-ui/blob/main/pkg/elemental/package.json#L6-L12 +- https://github.com/rancher/elemental-ui/blob/main/package.json#L11 + +vs `release-2.8.x` +- https://github.com/rancher/elemental-ui/blob/release-2.8.x/pkg/elemental/package.json#L6-L12 +- https://github.com/rancher/elemental-ui/blob/release-2.8.x/package.json#L11 + + +The workflow we offer for Github to build and release extensions to the `gh-pages` branch https://extensions.rancher.io/extensions/publishing#triggering-a-github-workflow-on-tagged-release will work just fine, but when doing a tagged release you’ll need to mindful of the target branch you want to release from. + +![Release target branch](./screenshots/target-branch.png) + +Picking up on the Elemental example, to release a Rancher pre-2.9 compliant version, we would need to target the `release-2.8.x` branch (with the appropriate tag format ex: `elemental-1.3.1-rc9` which is `extensionName-extensionVersion` ) and for Rancher 2.9 you would target the `main` branch as one would normally do in an extension release. We recommend that you don’t trigger both releases at the same time (let one finish first, then trigger the other release). + +To find out more about the support matrix for Shell versions in regards to Rancher versions, check the support matrix [here](./support-matrix#shell-support-matrix). diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/add-tab.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/add-tab.png new file mode 100644 index 00000000000..b7dfa0c4d26 Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/add-tab.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/available-charts.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/available-charts.png new file mode 100644 index 00000000000..c96f17f7e4b Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/available-charts.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/cluster-cards.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/cluster-cards.png new file mode 100644 index 00000000000..ab2ef5bea3d Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/cluster-cards.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/cluster-provisioning.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/cluster-provisioning.png new file mode 100644 index 00000000000..a09c641e9f4 Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/cluster-provisioning.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/detailtop.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/detailtop.png new file mode 100644 index 00000000000..f303bfa02a9 Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/detailtop.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/dev-load-modal.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/dev-load-modal.png new file mode 100644 index 00000000000..380a44874ce Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/dev-load-modal.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/dev-load.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/dev-load.png new file mode 100644 index 00000000000..f26ab973083 Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/dev-load.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/extension-developer-features.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/extension-developer-features.png new file mode 100644 index 00000000000..10dee134b7c Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/extension-developer-features.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/global-nav.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/global-nav.png new file mode 100644 index 00000000000..db1f7322a9b Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/global-nav.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/header-actions.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/header-actions.png new file mode 100644 index 00000000000..d751f5cace4 Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/header-actions.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/import-catalog-dialog.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/import-catalog-dialog.png new file mode 100644 index 00000000000..7df7a775281 Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/import-catalog-dialog.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/index-yaml-annotations.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/index-yaml-annotations.png new file mode 100644 index 00000000000..65127ca9de3 Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/index-yaml-annotations.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/inline-and-bulkable.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/inline-and-bulkable.png new file mode 100644 index 00000000000..7bc1860396e Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/inline-and-bulkable.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/inline-table-action.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/inline-table-action.png new file mode 100644 index 00000000000..227843da559 Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/inline-table-action.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/list-view.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/list-view.png new file mode 100644 index 00000000000..9ed136f60ff Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/list-view.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/manage-catalog-action.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/manage-catalog-action.png new file mode 100644 index 00000000000..968c8d241fa Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/manage-catalog-action.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/masthead.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/masthead.png new file mode 100644 index 00000000000..ff36b531d6e Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/masthead.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/navigate-chart-list.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/navigate-chart-list.png new file mode 100644 index 00000000000..8add7b24132 Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/navigate-chart-list.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/openstack-cloud-credential.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/openstack-cloud-credential.png new file mode 100644 index 00000000000..f1283d472e2 Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/openstack-cloud-credential.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/openstack-machine-config.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/openstack-machine-config.png new file mode 100644 index 00000000000..046d864a652 Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/openstack-machine-config.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/preferences.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/preferences.png new file mode 100644 index 00000000000..1e4dd01f8af Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/preferences.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/product-information.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/product-information.png new file mode 100644 index 00000000000..83606f7c94e Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/product-information.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/repo-pages-dropdown.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/repo-pages-dropdown.png new file mode 100644 index 00000000000..6d61f130f3c Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/repo-pages-dropdown.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/repo-pages-tab.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/repo-pages-tab.png new file mode 100644 index 00000000000..75088ffb360 Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/repo-pages-tab.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/repo-settings-tab.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/repo-settings-tab.png new file mode 100644 index 00000000000..1078b84e2ff Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/repo-settings-tab.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/shell-update-2.9-diagram.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/shell-update-2.9-diagram.png new file mode 100644 index 00000000000..81d66bf93c1 Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/shell-update-2.9-diagram.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/table-cols.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/table-cols.png new file mode 100644 index 00000000000..e525ff0a383 Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/table-cols.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/target-branch.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/target-branch.png new file mode 100644 index 00000000000..55b135f5e11 Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/target-branch.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/ui-version-annotation.png b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/ui-version-annotation.png new file mode 100644 index 00000000000..c87f1f482c3 Binary files /dev/null and b/docusaurus/extensions_versioned_docs/version-2.0.x/screenshots/ui-version-annotation.png differ diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/support-matrix.md b/docusaurus/extensions_versioned_docs/version-2.0.x/support-matrix.md new file mode 100644 index 00000000000..dc900df2b65 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/support-matrix.md @@ -0,0 +1,36 @@ +# Support matrixes + +## Shell support matrix + +The Shell package enables Extensions to integrate with Rancher. +It's important to know which version of the Shell package is compatible with each Rancher version: + +| | Rancher 2.7.x | Rancher 2.8.x
(Extensions API V1) | Rancher 2.9.x
(Extensions API V2) | Rancher 2.10.x
(Extensions API V3) | +|---|---|---|---|---| +|Shell **0.3.8**|Supported|Limited support|Not supported|Not supported| +|Shell 0.5.3/**1.2.3**|Limited support|Supported|Not supported|Not supported| +|Shell **2.0.1**|Not supported|Not supported|Supported|Not supported| +|Shell **3.0.0**|Not supported|Not supported|Not supported|Supported| + +To know more about the Shell package versioning take a look at the diagram [here](./rancher-2.9-support). + +## Extension API support matrix + +Here's the support matrix for every Extension API hook available in Rancher: + +| API | Rancher Version support (Minimum version)| +| --- | --- | +| [Metadata](./api/metadata) | v2.7.0 | +| [Products](./api/nav/products) | v2.7.0 | +| [Routes](./api/nav/routing) | v2.7.0 | +| [Actions](./api/actions) | v2.7.2 | +| [Cards](./api/cards) | v2.7.2 | +| [Panels](./api/panels) | v2.7.2 | +| [Tabs](./api/tabs) | v2.7.2 | +| [Table Columns](./api/table-columns) | v2.7.2 | +| [Components](./api/components) | v2.7.0 | + + +## LocationConfig support matrix + +The `LocationConfig` object is one of the keystones to define in which place in the UI a given Extension API hook should be applied to. For more information about it's usage and support matrix check the documentation **[here](./api/common#locationconfig)**. \ No newline at end of file diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/cluster-level-product.md b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/cluster-level-product.md new file mode 100644 index 00000000000..fe2185bcb69 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/cluster-level-product.md @@ -0,0 +1,151 @@ +# Extension as a cluster-level product +As a full example of an Extension as cluster-level product, let's start with the definition of `product.ts` config: + +```ts +// ./index.ts +import { importTypes } from '@rancher/auto-import'; +import { IPlugin } from '@shell/core/types'; +import extensionRouting from './routing/extension-routing'; + +// Init the package +export default function(plugin: IPlugin) { + // Auto-import model, detail, edit from the folders + importTypes(plugin); + + // Provide extension metadata from package.json + // it will grab information such as `name` and `description` + plugin.metadata = require('./package.json'); + + // Load a product + plugin.addProduct(require('./product')); + + // Add Vue Routes + plugin.addRoutes(extensionRouting); +} +``` + +The `product.ts` config will then define the product and which "pages/views" we want to add, such as: + +```ts +// ./product.ts +import { IPlugin } from '@shell/core/types'; + +export function init($plugin: IPlugin, store: any) { + const YOUR_PRODUCT_NAME = 'clusterLevelProduct'; + const YOUR_K8S_RESOURCE_NAME = 'provisioning.cattle.io.cluster'; + const CUSTOM_PAGE_NAME = 'page1'; + + const { + product, + configureType, + virtualType, + basicType + } = $plugin.DSL(store, YOUR_PRODUCT_NAME); + + // registering a cluster-level product + product({ + icon: 'gear', + inStore: 'cluster', // this is what defines the extension as a cluster-level product + weight: 100, + to: { + name: `c-cluster-${ YOUR_PRODUCT_NAME }-${ CUSTOM_PAGE_NAME }`, + params: { product: YOUR_PRODUCT_NAME } + } + }); + + // defining a k8s resource as page + configureType(YOUR_K8S_RESOURCE_NAME, { + displayName: 'some-custom-name-you-wish-to-assign-to-this-resource', + isCreatable: true, + isEditable: true, + isRemovable: true, + showAge: true, + showState: true, + canYaml: true, + customRoute: { + name: `c-cluster-${ YOUR_PRODUCT_NAME }-resource`, + params: { + product: YOUR_PRODUCT_NAME, + resource: YOUR_K8S_RESOURCE_NAME + } + } + }); + + // creating a custom page + virtualType({ + labelKey: 'some.translation.key', + name: CUSTOM_PAGE_NAME, + route: { + name: `c-cluster-${ YOUR_PRODUCT_NAME }-${ CUSTOM_PAGE_NAME }`, + params: { product: YOUR_PRODUCT_NAME } + } + }); + + // registering the defined pages as side-menu entries + basicType([YOUR_K8S_RESOURCE_NAME, CUSTOM_PAGE_NAME]); +} + +``` + +In the example above, we are registering 2 pages: a resource page called `YOUR_K8S_RESOURCE_NAME` and a custom page called `CUSTOM_PAGE_NAME`. These need to be reflected in the routes definition that is provided to the `addRoutes` method. + +> Note: For more information on routing for a Top-level-product, check [here](../api/nav/routing.md#routes-definition-for-an-extension-as-a-top-level-product) + +The `/routing/extension-routing.ts` would then be defined like: + +```ts +// ./routing/extension-routing.ts +import ListResource from '@shell/pages/c/_cluster/_product/_resource/index.vue'; +import CreateResource from '@shell/pages/c/_cluster/_product/_resource/create.vue'; +import ViewResource from '@shell/pages/c/_cluster/_product/_resource/_id.vue'; +import ViewNamespacedResource from '@shell/pages/c/_cluster/_product/_resource/_namespace/_id.vue'; +import MyCustomPage from '../pages/myCustomPage.vue'; + +// to achieve naming consistency throughout the extension +// we recommend this to be defined on a config file and exported +// so that the developer can import it wherever it needs to be used +const YOUR_PRODUCT_NAME = 'clusterLevelProduct'; +const CUSTOM_PAGE_NAME = 'page1'; + +const routes = [ + // this covers the "custom page" + { + name: `c-cluster-${ YOUR_PRODUCT_NAME }-${ CUSTOM_PAGE_NAME }`, + path: `/c/:cluster/${ YOUR_PRODUCT_NAME }/${ CUSTOM_PAGE_NAME }`, + component: MyCustomPage, + meta: { product: YOUR_PRODUCT_NAME }, + }, + // the following routes cover the "resource page" + // registering routes for list/edit/create views + { + name: `c-cluster-${ YOUR_PRODUCT_NAME }-resource`, + path: `/c/:cluster/${ YOUR_PRODUCT_NAME }/:resource`, + component: ListResource, + meta: { product: YOUR_PRODUCT_NAME }, + }, + { + name: `c-cluster-${ YOUR_PRODUCT_NAME }-resource-create`, + path: `/c/:cluster/${ YOUR_PRODUCT_NAME }/:resource/create`, + component: CreateResource, + meta: { product: YOUR_PRODUCT_NAME }, + }, + { + name: `c-cluster-${ YOUR_PRODUCT_NAME }-resource-id`, + path: `/c/:cluster/${ YOUR_PRODUCT_NAME }/:resource/:id`, + component: ViewResource, + meta: { product: YOUR_PRODUCT_NAME }, + }, + { + name: `c-cluster-${ YOUR_PRODUCT_NAME }-resource-namespace-id`, + path: `/:cluster/${ YOUR_PRODUCT_NAME }/:resource/:namespace/:id`, + component: ViewNamespacedResource, + meta: { product: YOUR_PRODUCT_NAME }, + } +]; + +export default routes; +``` + +> Note: Comparing with a [Top-level product](./top-level-product), we can see that the routes definition in `product.ts` and `/routing/extension-routing.ts` don't have the notion of a `BLANK CLUSTER`. This is on purpose, because a Cluster-level product needs the context of cluster where it's running when compared with a Top-level product, which is "above" all clusters. + +A full working example of this code, which can be deployed as an Extension on you Rancher Dashboard, can be found on the [Rancher examples repo](https://github.com/rancher/ui-plugin-examples). Just follow the instructions described on the [README](https://github.com/rancher/ui-plugin-examples#readme) on how to add the repo to Rancher Dasboard. \ No newline at end of file diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/about-drivers.md b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/about-drivers.md new file mode 100644 index 00000000000..1f14a19b03b --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/about-drivers.md @@ -0,0 +1,40 @@ +# About Node Drivers + +Custom Node Drivers can be created and registered with Rancher to allow it to provision nodes onto which RKE1/RKE2 or K3s can be installed. + +## Registering a Node Driver + +To tell Rancher about a new driver, go to: + +`Cluster Management -> Drivers -> Node Drivers -> Add Node Driver` + +You should: + +- Set the URL the binary should be downloaded from. +- If the UI will need to communicate with an API to show options (e.g getting data from `api.mycloudprovider.com`), add it to the list of Whitelist Domains. +- Click Create to save. + +Rancher will download the Node Driver binary and once activated, it will become available in the UI using all the generic driver support. + +This just lists all the fields that the driver says it has and makes some guesses about likely sounding names. The user ultimately has to figure out which ones are required or important and set those. + +To improve the experience creating a cluster using your custom Node Driver: + +- For RKE1, the legacy Ember UI is used and you can create an Ember extension and provide the URL to the Javascript package for it when you add the Node Driver to Rancher +- For RKE2 and K3s, you can create a Rancher Extension and add that to Rancher - this is the extension mechanism documented here + +## Driver binary + +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 | + +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. + +The standard drivers included in Rancher and their options are defined [here](https://github.com/rancher/rancher/blob/release/v2.6/pkg/data/management/machinedriver_data.go). \ No newline at end of file diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/about-example.md b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/about-example.md new file mode 100644 index 00000000000..6f171cbd482 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/about-example.md @@ -0,0 +1,42 @@ +# Node Driver Example + +An example node driver UI Extension has been created for OpenStack - the source code is available in this +GitHub repository - https://github.com/rancher/ui-plugin-examples + +This is a great starting point for creating your own extension for a Node Driver UI. + +## Caveats + +The OpenStack Node Driver provided by Rancher has a few limitations which are not generally encountered when you create a Node Driver extension - these are discussed below. + +### Allow-list + +Many node drivers communicate with cloud-based systems where the URL is constant and well-known - e.g. my-provider.com. The Node Driver added to Rancher will include this URL in its allow-list, which means the UI can proxy API calls to it. + +For providers like OpenStack, the URL is not constant - it depends on each deployment. + +The OpenStack UI Extension example catches `503` HTTP errors from the proxy API (which indicates that the proxy was not permitted) and offers to add the required URL to the allow-list for the node driver. Note that the user must have appropriate permissions in order to be able to do this. + +### Private/Public Fields + +The OpenStack node driver only declares `password` as a private field (see `privateCredentialFields` on the OpenStack `nodeDriver` custom resource). It does not define any public fields. + +This means we can only store the password field on this resource. + +For our OpenStack UI, we need to store additional fields, for example: + +- Authentication URL +- Domain Name +- Username + +To work around the node driver limitation, we store these additional fields as annotations. This is not encouraged and for a custom node driver, you should ensure that `privateCredentialFields` and `publicCredentialFields` are defined appropriately. + +### Proxying + +Rancher provides a proxying mechanism to get around CORS issues. This allows the UI to communicate with external APIs outside of Rancher. + +Additionally, the proxying mechanism can insert private credential metadata into a proxied request. This allows calls to be made to backend APIs that require authentication without having to retrieve the private data from the backend. + +This mechanism only supports adding credential fields into the auth header - for OpenStack, we need to send the password in a JSON body as part of a token request to the OpenStack API. + +The OpenStack example extension works around this limitation by retrieving the secret associated with the corresponding cloud credential. This is generally discouraged and has the limitation that the user must be able to access secrets in order to do this, which may not be the case where permissions have been applied. diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/advanced.md b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/advanced.md new file mode 100644 index 00000000000..82d88af7938 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/advanced.md @@ -0,0 +1,31 @@ +# Advanced + +## Machine Template Model + +Each cluster has one or more Machine Templates, which specify to create a particular number of machines using a specified Machine Config + Cloud Credential. Basic information about the template is shown later on detail screens, such as the machine size and location. This is done by providing a model class for your driver's template and overriding methods. + +Your model should be called `models/rke-machine.cattle.io.[your driver in lowercase]template.js` (corresponding to the schema that shows up once the driver is installed). It should extend the generic MachineTemplate and override methods as appropriate: + +```javascript +import MachineTemplate from '@shell/models/rke-machine.cattle.io.machinetemplate'; + +export default class MyDriverMachineTemplate extends MachineTemplate { + get provider() { + return 'mydriver'; + } + + get providerLocation() { + return this.spec.template.spec.zone; + } + + get providerSize() { + return this.spec.template.spec.instanceType; + } +} +``` + +## Using a Custom Store + +If you have several different API calls to make or expensive information that can be cached after it's retrieved once, consider making a `store` with getters and actions to handle making your API calls and managing the caching. Most of the standard built-in drivers have these. + +For more complicated providers (e.g. AWS) you can also consider importing their Javascript SDK and using their client to make calls. But unless there is an extension point to manipulate the request before they send it, you'll probably have to monkey patch their client to get the `X-Api-CattleAuth-Header` injected and the request sent through the proxy instead of direct to them. The SDK should also be dynamically `import('…')`ed as needed at runtime so it's not loaded all the time. Regular `import … as …;` at the top of the file will become part of the basic app bundle js that's always loaded and has to be downloaded before the page can render. `store/aws.js` has examples of all of this. diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/cloud-credential.md b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/cloud-credential.md new file mode 100644 index 00000000000..6b3d8f7481f --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/cloud-credential.md @@ -0,0 +1,21 @@ +# Cloud Credential + +Cloud Credentials store the username & password, or other similar information, needed to talk to a particular provider. There is typically a 1-to-1 mapping of cloud credentials to drivers. If one provider (e.g. Amazon) has both a *Machine* driver for RKE (using EC2) and a *Cluster* driver for Kontainer Engine (using EKS) then you can and should use a single shared type of credential (e.g. `aws`) for both. + +A cloud credential component for a given driver will be automatically registered when placed in the `cloud-credential` folder and named with the name of the driver (e.g. `openstack.vue`). + +> Note: Your extension's entry file must call `importTypes` for the automatic registration to work + +The component should be a standard Vue 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. + +Your component should emit a `validationChanged` event every time a value changes. It should also (but doesn't _have to_ implement a `test()` method. This may be asynchronous, and should make an API call or similar to see if the provided credentials work. Return `true` if they're ok and `false` otherwise. When provided, this is called before saving and the user won't be able to save until their input causes you return `true`. + +The `value` property of the component will be bound to the cloud credential resource. + +Other properties: + +- `mode` - String mode - can be `view`, `edit` or `create`. Controls should be disabled when in view mode. Controls should be populated from existing values for the view and edit modes. + +Example Cloud Credential UI: + +![Example Cloud Credential UI!](../../screenshots/openstack-cloud-credential.png) diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/machine-config.md b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/machine-config.md new file mode 100644 index 00000000000..6811a759c56 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/machine-config.md @@ -0,0 +1,25 @@ +# Machine Config + +Similar to the Cloud Credential component, the Machine Config component should display the controls for the fields of the node driver that are relevant to the configuration of the machine to be created. The machine pool name, saving, etc is handled outside of the component. You can use `fetch()` to load data from the provider's API (e.g. list of regions or instance types) as needed. + +A machine config component for a given driver will be automatically registered when placed in the `machine-config` folder and named with the name of the driver (e.g. `openstack.vue`). + +> Note: Your extension's entry file must call `importTypes` for the automatic registration to work + +The selected cloud credential ID is available as a `credentialId` prop. You will always know that ID, and can use it to make API calls, but **should not** rely on being able to actually retrieve the cloud credential model corresponding to it. Users with lesser permissions may be able to edit a cluster, but not have permission to see the credential being used to manage it. + +Your component can emit a `validationChanged` event every time a value changes to report validation status of the machine configuration. + +The `value` property of the component will be bound to the machine configuration resource. The fields available on that resource are determined by the corresponding node driver. + +Other properties: + +- `busy` - Boolean to indicate if the controls should be disabled while the UI is busy (typically during a save operation) +- `mode` - String mode - can be `view`, `edit` or `create`. Controls should be disabled when in view mode. Controls should be populated from existing values for the view and edit modes. + + +Example Machine Config UI: + +![Example Machine Config UI!](../../screenshots/openstack-machine-config.png) + +> Note: The UI provided by the custom component is shown in the blue box. The full Machine Pool Ui is shown to give context. diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/node-driver-icon.md b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/node-driver-icon.md new file mode 100644 index 00000000000..f2abaf39426 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/node-driver-icon.md @@ -0,0 +1,13 @@ +# Setting the node driver icon + +While in the Cluster Provisioning screen in Rancher UI, you've noticed that node drivers have icons for each provider: + +![Cluster Provisioning](../../screenshots/cluster-provisioning.png) + +In order to set the icon for your custom node driver in the cluster provisioning screen, you can use the following method: + +``` +plugin.register('image', 'providers/YOUR_PROVIDER_NAME.svg', require('./PATH_TO_YOUR_ICON/YOUR_ICON_NAME.svg')); +``` + +> **Note:** This feature should only be compatible with Shell version `2.0.1` (for compatibility with Rancher check our [Shell support matrix](../../support-matrix.md)) \ No newline at end of file diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/overview.md b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/overview.md new file mode 100644 index 00000000000..746484ce10f --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/overview.md @@ -0,0 +1,12 @@ +# Custom Node Driver UI + +Rancher supports custom node drivers to support additional node provisioners. Node drivers are used for both RKE1 and RKE2/k3s. RKE1 support is handled by the legacy ember UI. + +Rancher Extensions allows users to develop custom UI experiences for configuring node drivers for use with RKE3/k3s. + +Developing a custom node driver UI typically consists of developing two main components: + +- A **Cloud Credential** component that is used to allow a user to supply and verify their credentials for interacting with the node driver +- A **Machine Configuration** component that is used to allow users to supply the necessary machine pool data for the node driver - example being the node type (CPU, memory, disk etc), the base OS etc. + +An example custom Node Driver UI Extension that demonstrates a custom UI experience for Openstack is included in the Rancher Extensions example repository - see: https://github.com/rancher/ui-plugin-examples. diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/proxying.md b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/proxying.md new file mode 100644 index 00000000000..0d8ec1aadce --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/node-driver/proxying.md @@ -0,0 +1,32 @@ + +# Making API Calls + +Rancher includes a proxy that can be used to make requests to third-party domains (like a cloud provider's API) without requiring that the other end supports CORS or other browser shenanigans. Send requests to `/meta/proxy/example.com/whatever/path/you/want` and the request will be made from the Rancher server and proxied back to you. + +TLS and port 443 are assumed. Add a port after the hostname to change the port (`example.com:1234`). + +Plain HTTP (i.e. not HTTPS) calls can be made - but you should *carefully* consider doing so. For this, use `/meta/proxy/http:/example.com:1234` (note one slash after `http:`, not two). + +## Allow lists + +> **Important: Please be aware of the allow list restrictions below** + +API calls can ONLY be made to hostnames that have specifically been allowed in Rancher. This is done by including the hostname in the whitelist defined in global settings, or in the configuration for an active node driver. If if isn't your request will be denied. (This prevents a malicious (non-admin) user from abusing the Rancher server as an arbitrary HTTP proxy or reach internal IPs/names that the server can reach directly but the user can't from the outside.) Typically API requests to hosts not in the allow-list will return a 503 HTTP status code. + +The rest of the path and query string are sent to the target host as you'd expect. + +Normal headers are copied from your request and sent to the target. There are some exceptions for sensitive fields like the user's rancher cookies or saved basic auth creds which will not be copied. If you send an `X-Api-Cookie-Header`, its value will be sent as the normal `Cookie` to the target. If you send `X-API-Auth-Header`, that will be sent out as the normal `Authorization`. + +But normally you want to make a request using a Cloud Credential as the authorization, without knowing what the secret values in that credential are. You ask for this by sending an `X-Api-CattleAuth-Header` header. The value of the header specifies what credential Id to use, and a [signer](https://github.com/rancher/rancher/blob/release/v2.6/pkg/httpproxy/sign.go) which describes how that credential should be injected into the request. + +Common options include `awsv4` (Amazon's complicated HMAC signatures), `bearer`, `basic`, and `digest`. + +For example if you send: + +`X-Api-CattleAuth-Header: Basic credId=someCredentialId usernameField=user passwordField=pass` + +Rancher will retrieve the credential with id `someCredentialId`, read the values of the `user` and `pass` fields from it and add the header: + +`Authorization: Basic ` + +to the proxied request for you. diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/overview.md b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/overview.md new file mode 100644 index 00000000000..369babce4f5 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/overview.md @@ -0,0 +1,5 @@ +# Usecases/Examples + +In order to help developers with Rancher Extensions we've created this chapter where one can find some concise examples of key Entension integration types. + +We aim to provide full working example of these code examples, which can be deployed as an Extension on you Rancher Dashboard, can be found on the [Rancher examples repo](https://github.com/rancher/ui-plugin-examples). Just follow the instructions described on the [README](https://github.com/rancher/ui-plugin-examples#readme) on how to add the repo to Rancher Dasboard. \ No newline at end of file diff --git a/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/top-level-product.md b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/top-level-product.md new file mode 100644 index 00000000000..78acae0c5a4 --- /dev/null +++ b/docusaurus/extensions_versioned_docs/version-2.0.x/usecases/top-level-product.md @@ -0,0 +1,181 @@ +# Extension as a top-level product +As a full example of an Extension as top-level product, let's start with the definition of `product.ts` config: + +```ts +// ./index.ts +import { importTypes } from '@rancher/auto-import'; +import { IPlugin } from '@shell/core/types'; +import extensionRouting from './routing/extension-routing'; + +// Init the package +export default function(plugin: IPlugin) { + // Auto-import model, detail, edit from the folders + importTypes(plugin); + + // Provide extension metadata from package.json + // it will grab information such as `name` and `description` + plugin.metadata = require('./package.json'); + + // Load a product + plugin.addProduct(require('./product')); + + // Add Vue Routes + plugin.addRoutes(extensionRouting); +} +``` + +The `product.ts` config will then define the product and which "pages/views" we want to add, such as: + +```ts +// ./product.ts +import { IPlugin } from '@shell/core/types'; + +// this is the definition of a "blank cluster" for Rancher Dashboard +// definition of a "blank cluster" in Rancher Dashboard +const BLANK_CLUSTER = '_'; + + +export function init($plugin: IPlugin, store: any) { + const YOUR_PRODUCT_NAME = 'myProductName'; + const YOUR_K8S_RESOURCE_NAME = 'provisioning.cattle.io.cluster'; + const CUSTOM_PAGE_NAME = 'page1'; + + const { + product, + configureType, + virtualType, + basicType + } = $plugin.DSL(store, YOUR_PRODUCT_NAME); + + // registering a top-level product + product({ + icon: 'gear', + inStore: 'management', + weight: 100, + to: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME }`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + } + } + }); + + // defining a k8s resource as page + configureType(YOUR_K8S_RESOURCE_NAME, { + displayName: 'some-custom-name-you-wish-to-assign-to-this-resource', + isCreatable: true, + isEditable: true, + isRemovable: true, + showAge: true, + showState: true, + canYaml: true, + customRoute: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-resource`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER, + resource: YOUR_K8S_RESOURCE_NAME + } + } + }); + + + + // creating a custom page + virtualType({ + labelKey: 'some.translation.key', + name: CUSTOM_PAGE_NAME, + route: { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME }`, + params: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + } + } + }); + + // registering the defined pages as side-menu entries + basicType([YOUR_K8S_RESOURCE_NAME, CUSTOM_PAGE_NAME]); +} +``` + + +In the example above, we are registering 2 pages: a resource page called `YOUR_K8S_RESOURCE_NAME` and a custom page called `CUSTOM_PAGE_NAME`. These need to be reflected in the routes definition that is provided to the `addRoutes` method. + +> Note: For more information on routing for a Top-level-product, check [here](../api/nav/routing.md#routes-definition-for-an-extension-as-a-top-level-product) + +The `/routing/extension-routing.ts` would then be defined like: + +```ts +// ./routing/extension-routing.ts +// definition of a "blank cluster" in Rancher Dashboard +const BLANK_CLUSTER = '_'; + +import MyCustomPage from '../pages/myCustomPage.vue'; +import ListResource from '@shell/pages/c/_cluster/_product/_resource/index.vue'; +import CreateResource from '@shell/pages/c/_cluster/_product/_resource/create.vue'; +import ViewResource from '@shell/pages/c/_cluster/_product/_resource/_id.vue'; +import ViewNamespacedResource from '@shell/pages/c/_cluster/_product/_resource/_namespace/_id.vue'; + +// to achieve naming consistency throughout the extension +// we recommend this to be defined on a config file and exported +// so that the developer can import it wherever it needs to be used +const YOUR_PRODUCT_NAME = 'the-name-of-your-product'; +const CUSTOM_PAGE_NAME = 'page1'; + +const routes = [ + // this covers the "custom page" + { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME }`, + path: `/${ YOUR_PRODUCT_NAME }/c/:cluster/${ CUSTOM_PAGE_NAME }`, + component: MyCustomPage, + meta: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + }, + }, + // the following routes cover the "resource page" + // registering routes for list/edit/create views + { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-resource`, + path: `/${ YOUR_PRODUCT_NAME }/c/:cluster/:resource`, + component: ListResource, + meta: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + }, + }, + { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-resource-create`, + path: `/${ YOUR_PRODUCT_NAME }/c/:cluster/:resource/create`, + component: CreateResource, + meta: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + }, + }, + { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-resource-id`, + path: `/${ YOUR_PRODUCT_NAME }/c/:cluster/:resource/:id`, + component: ViewResource, + meta: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + }, + }, + { + name: `${ YOUR_PRODUCT_NAME }-c-cluster-resource-namespace-id`, + path: `/${ YOUR_PRODUCT_NAME }/:cluster/:resource/:namespace/:id`, + component: ViewNamespacedResource, + meta: { + product: YOUR_PRODUCT_NAME, + cluster: BLANK_CLUSTER + }, + } +]; + +export default routes; +``` + +A full working example of this code, which can be deployed as an Extension on you Rancher Dashboard, can be found on the [Rancher examples repo](https://github.com/rancher/ui-plugin-examples). Just follow the instructions described on the [README](https://github.com/rancher/ui-plugin-examples#readme) on how to add the repo to Rancher Dasboard. \ No newline at end of file diff --git a/docusaurus/extensions_versioned_sidebars/version-2.0.x-sidebars.json b/docusaurus/extensions_versioned_sidebars/version-2.0.x-sidebars.json new file mode 100644 index 00000000000..b07033ea444 --- /dev/null +++ b/docusaurus/extensions_versioned_sidebars/version-2.0.x-sidebars.json @@ -0,0 +1,117 @@ +{ + "extensionsSidebar": [ + { + "type": "category", + "label": "Extensions", + "link": { + "type": "doc", + "id": "home" + }, + "items": [ + "introduction", + { + "type": "category", + "label": "Changelog", + "link": { + "type": "doc", + "id": "changelog" + }, + "items": [ + "rancher-2.9-support", + "rancher-2.10-support" + ] + }, + "support-matrix", + "extensions-getting-started", + "extensions-configuration", + { + "type": "category", + "label": "Extensions API", + "link": { + "type": "doc", + "id": "api/overview" + }, + "items": [ + "api/concepts", + "api/metadata", + { + "type": "category", + "label": "Navigation & Pages", + "items": [ + "api/nav/products", + "api/nav/custom-page", + "api/nav/resource-page", + "api/nav/side-menu", + "api/nav/routing" + ] + }, + "api/actions", + "api/cards", + "api/panels", + "api/tabs", + "api/table-columns", + { + "type": "category", + "label": "Components", + "link": { + "type": "doc", + "id": "api/components/components" + }, + "items": [ + "api/components/resources", + "api/components/node-drivers", + "api/components/auto-import" + ] + }, + "api/common" + ] + }, + { + "type": "category", + "label": "Advanced", + "items": [ + "advanced/air-gapped-environments", + "advanced/provisioning", + "advanced/localization", + "advanced/hooks", + "advanced/stores", + "advanced/version-compatibility", + "advanced/safe-mode", + "advanced/yarn-link" + ] + }, + "publishing", + { + "type": "category", + "label": "Use cases/Examples", + "link": { + "type": "doc", + "id": "usecases/overview" + }, + "items": [ + "usecases/top-level-product", + "usecases/cluster-level-product", + { + "type": "category", + "label": "Node Driver", + "link": { + "type": "doc", + "id": "usecases/node-driver/overview" + }, + "items": [ + "usecases/node-driver/about-drivers", + "usecases/node-driver/cloud-credential", + "usecases/node-driver/machine-config", + "usecases/node-driver/node-driver-icon", + "usecases/node-driver/advanced", + "usecases/node-driver/proxying", + "usecases/node-driver/about-example" + ] + } + ] + }, + "known-issues" + ] + } + ] +} diff --git a/docusaurus/extensions_versions.json b/docusaurus/extensions_versions.json new file mode 100644 index 00000000000..22067a0d6d6 --- /dev/null +++ b/docusaurus/extensions_versions.json @@ -0,0 +1,3 @@ +[ + "2.0.x" +] diff --git a/docusaurus/internalSidebar.js b/docusaurus/internalSidebar.js new file mode 100644 index 00000000000..32c435f992f --- /dev/null +++ b/docusaurus/internalSidebar.js @@ -0,0 +1,63 @@ +/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ +const sidebars = { + internalSidebar: [ + 'docs', + { + type: 'category', + label: 'Getting Started', + items: [ + 'getting-started/quickstart', + 'getting-started/concepts', + 'getting-started/development_environment', + 'getting-started/ui-walkthrough' + ], + }, + { + type: 'category', + label: 'Guide', + items: [ + 'guide/package-management', + 'guide/auth-providers', + 'guide/custom-dev-build' + ], + }, + { + type: 'category', + label: 'How the Code Base Works', + items: [ + 'code-base-works/api-resources-and-schemas', + 'code-base-works/auth-sessions-and-tokens', + 'code-base-works/cluster-management-resources', + 'code-base-works/customising-how-k8s-resources-are-presented', + 'code-base-works/directory-structure', + 'code-base-works/products-and-navigation', + 'code-base-works/forms-and-validation', + 'code-base-works/helm-chart-apps', + 'code-base-works/keyboard-shortcuts', + 'code-base-works/kubernetes-resources-data-load', + 'code-base-works/routes', + 'code-base-works/middleware', + 'code-base-works/stores', + 'code-base-works/nuxt-plugins', + 'code-base-works/machine-drivers', + 'code-base-works/performance', + 'code-base-works/sortable-table', + 'code-base-works/on-screen-text-and-translations', + 'code-base-works/style', + ], + }, + 'storybook', + { + type: 'category', + label: 'Testing', + items: [ + 'testing/unit-test', + 'testing/e2e-test', + 'testing/stress-test', + ], + }, + 'terminology', + ], +}; + +module.exports = sidebars; diff --git a/docusaurus/sidebars.js b/docusaurus/sidebars.js deleted file mode 100644 index 05eae1f1e0a..00000000000 --- a/docusaurus/sidebars.js +++ /dev/null @@ -1,195 +0,0 @@ -/** - * 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. - - extensionsSidebar: [ - { - type: 'category', - label: 'Extensions', - link: { - type: 'doc', - id: 'extensions/home', - }, - items: [ - 'extensions/introduction', - { - type: 'category', - label: 'Changelog', - link: { - type: 'doc', - id: 'extensions/changelog', - }, - items: ['extensions/rancher-2.9-support', 'extensions/rancher-2.10-support'] - }, - 'extensions/support-matrix', - 'extensions/extensions-getting-started', - 'extensions/extensions-configuration', - { - type: 'category', - label: 'Extensions API', - link: { - type: 'doc', - id: 'extensions/api/overview', - }, - items: [ - 'extensions/api/concepts', - 'extensions/api/metadata', - { - type: 'category', - label: 'Navigation & Pages', - items: [ - 'extensions/api/nav/products', - 'extensions/api/nav/custom-page', - 'extensions/api/nav/resource-page', - 'extensions/api/nav/side-menu', - 'extensions/api/nav/routing', - ] - }, - 'extensions/api/actions', - 'extensions/api/cards', - 'extensions/api/panels', - 'extensions/api/tabs', - 'extensions/api/table-columns', - { - type: 'category', - label: 'Components', - link: { - type: 'doc', - id: 'extensions/api/components/components', - }, - items: [ - 'extensions/api/components/resources', - 'extensions/api/components/node-drivers', - 'extensions/api/components/auto-import', - ] - }, - 'extensions/api/common', - ] - }, - { - type: 'category', - label: 'Advanced', - items: [ - 'extensions/advanced/air-gapped-environments', - 'extensions/advanced/provisioning', - 'extensions/advanced/localization', - 'extensions/advanced/hooks', - 'extensions/advanced/stores', - 'extensions/advanced/version-compatibility', - 'extensions/advanced/safe-mode', - 'extensions/advanced/yarn-link', - ] - }, - 'extensions/publishing', - { - type: 'category', - label: 'Use cases/Examples', - link: { - type: 'doc', - id: 'extensions/usecases/overview', - }, - items: [ - 'extensions/usecases/top-level-product', - 'extensions/usecases/cluster-level-product', - { - type: 'category', - label: 'Node Driver', - link: { - type: 'doc', - id: 'extensions/usecases/node-driver/overview', - }, - items: [ - 'extensions/usecases/node-driver/about-drivers', - 'extensions/usecases/node-driver/cloud-credential', - 'extensions/usecases/node-driver/machine-config', - 'extensions/usecases/node-driver/node-driver-icon', - 'extensions/usecases/node-driver/advanced', - 'extensions/usecases/node-driver/proxying', - 'extensions/usecases/node-driver/about-example', - ] - } - ] - }, - 'extensions/known-issues', - ] - }, - ], - internalSidebar: [ - 'internal/docs', - { - type: 'category', - label: 'Getting Started', - items: [ - 'internal/getting-started/quickstart', - 'internal/getting-started/concepts', - 'internal/getting-started/development_environment', - 'internal/getting-started/ui-walkthrough' - ], - }, - { - type: 'category', - label: 'Guide', - items: [ - 'internal/guide/package-management', - 'internal/guide/auth-providers', - 'internal/guide/custom-dev-build' - ], - }, - { - type: 'category', - label: 'How the Code Base Works', - items: [ - 'internal/code-base-works/api-resources-and-schemas', - 'internal/code-base-works/auth-sessions-and-tokens', - 'internal/code-base-works/cluster-management-resources', - 'internal/code-base-works/customising-how-k8s-resources-are-presented', - 'internal/code-base-works/directory-structure', - 'internal/code-base-works/products-and-navigation', - 'internal/code-base-works/forms-and-validation', - 'internal/code-base-works/helm-chart-apps', - 'internal/code-base-works/keyboard-shortcuts', - 'internal/code-base-works/kubernetes-resources-data-load', - 'internal/code-base-works/routes', - 'internal/code-base-works/middleware', - 'internal/code-base-works/stores', - 'internal/code-base-works/nuxt-plugins', - 'internal/code-base-works/machine-drivers', - 'internal/code-base-works/performance', - 'internal/code-base-works/sortable-table', - 'internal/code-base-works/on-screen-text-and-translations', - 'internal/code-base-works/style', - ], - }, - 'internal/storybook', - { - type: 'category', - label: 'Testing', - items: [ - 'internal/testing/unit-test', - 'internal/testing/e2e-test', - 'internal/testing/stress-test', - ], - }, - 'internal/terminology', - ], -}; - -module.exports = sidebars; diff --git a/docusaurus/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.js b/docusaurus/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.js new file mode 100644 index 00000000000..87fd1945e71 --- /dev/null +++ b/docusaurus/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.js @@ -0,0 +1,19 @@ +import React from 'react'; +import { useLocation } from '@docusaurus/router'; + +import DocsVersionDropdownNavbarItem from '@theme-original/NavbarItem/DocsVersionDropdownNavbarItem'; + +export default function DocsVersionDropdownNavbarItemWrapper(props) { + const location = useLocation(); + + // Only show the version dropdown when on `/extensions` paths + const showVersionDropdown = location.pathname.startsWith('/extensions'); + + return ( + <> + {showVersionDropdown && ( + + )} + + ); +}