From 9b97c06598e6e0cdad6a996b6c3a649c9224c58f Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Mon, 20 Nov 2023 17:26:15 +0000 Subject: [PATCH 1/3] Add install/uninstall buttons to clusters in the cluster list - we now show all clusters - for clusters which we've found epinio on... and they can... show uninstall action - for cluster with no epinio on... and they can... show install button --- dashboard/pkg/epinio/config/epinio.ts | 6 - dashboard/pkg/epinio/l10n/en-us.yaml | 3 +- dashboard/pkg/epinio/models/cluster.ts | 107 +++++++++++++++++- dashboard/pkg/epinio/pages/index.vue | 45 ++++++-- .../pkg/epinio/utils/epinio-discovery.ts | 15 ++- 5 files changed, 151 insertions(+), 25 deletions(-) diff --git a/dashboard/pkg/epinio/config/epinio.ts b/dashboard/pkg/epinio/config/epinio.ts index 18896a0..6e88026 100644 --- a/dashboard/pkg/epinio/config/epinio.ts +++ b/dashboard/pkg/epinio/config/epinio.ts @@ -300,12 +300,6 @@ export function init($plugin: any, store: any) { labelKey: 'epinio.instances.tableHeaders.api', sort: ['api'], }, - { - name: 'rancherCluster', - labelKey: 'epinio.instances.tableHeaders.cluster', - sort: ['mgmtCluster.nameDisplay'], - value: 'mgmtCluster.nameDisplay' - }, ]); headers(EPINIO_TYPES.CONFIGURATION, [ diff --git a/dashboard/pkg/epinio/l10n/en-us.yaml b/dashboard/pkg/epinio/l10n/en-us.yaml index 9ed006e..2318f25 100644 --- a/dashboard/pkg/epinio/l10n/en-us.yaml +++ b/dashboard/pkg/epinio/l10n/en-us.yaml @@ -95,8 +95,7 @@ epinio: instances: header: Epinio instances none: - header: No instances of Epinio were found - description: To view an Epinio cluster be sure to import a Cluster where one is installed + description: No instances were found

Epinio can be installed to known clusters using the action menu to the right of the cluster tableHeaders: api: URL version: Version diff --git a/dashboard/pkg/epinio/models/cluster.ts b/dashboard/pkg/epinio/models/cluster.ts index b974cc6..cdd7b04 100644 --- a/dashboard/pkg/epinio/models/cluster.ts +++ b/dashboard/pkg/epinio/models/cluster.ts @@ -2,9 +2,17 @@ import Resource from '@shell/plugins/dashboard-store/resource-class'; import { EPINIO_TYPES } from '../types'; import epinioAuth, { EpinioAuthConfig, EpinioAuthLocalConfig, EpinioAuthTypes } from '../utils/auth'; import { dashboardUrl } from '../utils/embedded-helpers'; +import { NAME as APP } from '@shell/config/product/apps'; +import { CATALOG } from '@shell/config/types'; +import Vue from 'vue'; export const EpinioInfoPath = `/api/v1/info`; +const defaultEpinioChart = { + repo: 'rancher-charts', + name: 'epinio', +}; + export default class EpinioCluster extends Resource { type = EPINIO_TYPES.CLUSTER; @@ -17,6 +25,9 @@ export default class EpinioCluster extends Resource { api: string; mgmtCluster: any; oidcEnabled: boolean = false; + installed: boolean = false; + canInstall: boolean = false; + canUninstall: boolean = false; constructor(data: { id: string, @@ -25,6 +36,7 @@ export default class EpinioCluster extends Resource { loggedIn: boolean, api: string, mgmtCluster: any, + installed: boolean, }, ctx: any) { super(data, ctx); this.id = data.id; @@ -33,18 +45,74 @@ export default class EpinioCluster extends Resource { this.api = data.api; this.loggedIn = data.loggedIn; this.mgmtCluster = data.mgmtCluster; + this.installed = data.installed; + + if (this.installed) { + // Can they uninstall? + // Try to find the installed helm app and check permissions on it + + debugger; + const url = `/k8s/clusters/${ data.mgmtCluster.id }/v1/catalog.cattle.io.apps/${ data.namespace }/${ defaultEpinioChart.name }?exclude=metadata.managedFields`; + + ctx.$dispatch(`cluster/request`, { url }, { root: true }) + .then((app: any) => { + Vue.set(this, 'canUninstall', !!app?.actions?.uninstall); + }) + .catch(() => { + Vue.set(this, 'canUninstall', false); + }); + } else { + // Can they install? + + // Can they install charts in target repo + const url = `/k8s/clusters/${ data.mgmtCluster.id }/v1/catalog.cattle.io.clusterrepos/${ defaultEpinioChart.repo }?exclude=metadata.managedFields`; + + ctx.$dispatch(`cluster/request`, { url }, { root: true }) + .then((repo: any) => { + Vue.set(this, 'canInstall', !!repo?.actions?.install); + }) + .catch(() => { + Vue.set(this, 'canInstall', false); + }); + + // Ideally we would also check if they can see the target chart to install, but lets go on the assumption epinio app will always be there + } } get availableActions() { - return [ - { + const actions: any[] = []; + + if (this.loggedIn) { + actions.push({ action: 'logOut', - enabled: this.loggedIn, icon: 'icon icon-fw icon-chevron-right', label: this.t('nav.userMenu.logOut'), disabled: false, - }, - ]; + }); + + actions.push({ divider: true }); + } + + if (this.installed) { + if (this.canUninstall) { + actions.push({ + action: 'uninstall', + icon: 'icon icon-fw icon-minus', + label: this.t('asyncButton.uninstall.action'), + }); + } + } else { + if (this.canInstall) { + actions.push({ + action: 'install', + icon: 'icon icon-fw icon-plus', + label: this.t('asyncButton.install.action'), + disabled: !this.canInstall, + }); + } + } + + return actions; } get infoUrl() { @@ -82,4 +150,33 @@ export default class EpinioCluster extends Resource { return config; } + + install() { + // Take them to the default helm chart's detail page + this.currentRouter().push({ + name: `c-cluster-apps-charts-chart`, + params: { + product: APP, + cluster: this.mgmtCluster.id, + }, + query: { + 'repo-type': 'cluster', + repo: defaultEpinioChart.repo, + chart: defaultEpinioChart.name, + } + }); + } + + uninstall() { + // Uninstall is an action from the apps list, so do our best to get the user there + this.currentRouter().push({ + name: `c-cluster-product-resource`, + params: { + product: APP, + cluster: this.mgmtCluster.id, + resource: CATALOG.APP, + }, + query: { q: defaultEpinioChart.name } + }); + } } diff --git a/dashboard/pkg/epinio/pages/index.vue b/dashboard/pkg/epinio/pages/index.vue index b89858f..c9c9948 100644 --- a/dashboard/pkg/epinio/pages/index.vue +++ b/dashboard/pkg/epinio/pages/index.vue @@ -12,6 +12,8 @@ import epinioAuth, { EpinioAuthTypes } from '../utils/auth'; import EpinioCluster, { EpinioInfoPath } from '../models/cluster'; import LoginDialog from '../components/LoginDialog.vue'; import Dialog from '@shell/components/Dialog.vue'; +import { STATES_ENUM } from '@shell/plugins/dashboard-store/resource-class'; +import { Banner } from '@components/Banner'; interface Data { clustersSchema: any; @@ -20,7 +22,7 @@ interface Data { // Data, Methods, Computed, Props export default Vue.extend({ components: { - AsyncButton, Loading, Link, ResourceTable, LoginDialog, Dialog + AsyncButton, Loading, Link, ResourceTable, LoginDialog, Dialog, Banner }, layout: 'plain', @@ -37,6 +39,7 @@ export default Vue.extend({ version: null, infoUrl: EpinioInfoPath, currentCluster: {}, + UNINSTALLED: STATES_ENUM.UNINSTALLED, }; }, @@ -63,6 +66,10 @@ export default Vue.extend({ clusters() { return this.$store.getters[`${ EPINIO_MGMT_STORE }/all`](EPINIO_TYPES.CLUSTER); + }, + + installedClusters() { + return this.clusters.find((c: EpinioCluster) => c.installed); } }, @@ -85,6 +92,11 @@ export default Vue.extend({ }, testCluster(c: EpinioCluster) { + if (!c.installed) { + this.setClusterState(c, STATES_ENUM.UNINSTALLED, { state: { transitioning: false } }); + + return; + } // Call '/ready' on each cluster. If there's a network error there's a good chance the user has to permit an invalid cert this.setClusterState(c, 'updating', { state: { @@ -145,19 +157,19 @@ export default Vue.extend({ v-if="$fetchState.pending" mode="main" /> -
-

{{ t('epinio.instances.none.header') }}

-

{{ t('epinio.instances.none.description') }}

-

{{ t('epinio.instances.header') }}

+
+ +
({