Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add install/uninstall buttons to clusters in the cluster list #366

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions dashboard/pkg/epinio/l10n/en-us.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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<br><br>Epinio can be installed to known clusters using the action menu to the right of the cluster
tableHeaders:
api: URL
version: Version
Expand Down
106 changes: 101 additions & 5 deletions dashboard/pkg/epinio/models/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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,
Expand All @@ -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;
Expand All @@ -33,18 +45,73 @@ 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

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() {
Expand Down Expand Up @@ -82,4 +149,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 }
});
}
}
2 changes: 1 addition & 1 deletion dashboard/pkg/epinio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "epinio",
"description": "Application Development Engine for Kubernetes",
"icon": "https://raw.githubusercontent.com/rancher/dashboard/0b6cbe93e9ed3292294da178f119a500cc494db9/pkg/epinio/assets/logo-epinio.svg",
"version": "1.11.0-2",
"version": "1.11.0-3",
"private": false,
"rancher": true,
"license": "Apache-2.0",
Expand Down
54 changes: 45 additions & 9 deletions dashboard/pkg/epinio/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,7 +22,7 @@ interface Data {
// Data, Methods, Computed, Props
export default Vue.extend<Data, any, any, any>({
components: {
AsyncButton, Loading, Link, ResourceTable, LoginDialog, Dialog
AsyncButton, Loading, Link, ResourceTable, LoginDialog, Dialog, Banner
},

layout: 'plain',
Expand All @@ -37,6 +39,7 @@ export default Vue.extend<Data, any, any, any>({
version: null,
infoUrl: EpinioInfoPath,
currentCluster: {},
UNINSTALLED: STATES_ENUM.UNINSTALLED,
};
},

Expand All @@ -63,6 +66,10 @@ export default Vue.extend<Data, any, any, any>({

clusters() {
return this.$store.getters[`${ EPINIO_MGMT_STORE }/all`](EPINIO_TYPES.CLUSTER);
},

installedClusters() {
return this.clusters.find((c: EpinioCluster) => c.installed);
}
},

Expand All @@ -85,6 +92,11 @@ export default Vue.extend<Data, any, any, any>({
},

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: {
Expand Down Expand Up @@ -145,19 +157,19 @@ export default Vue.extend<Data, any, any, any>({
v-if="$fetchState.pending"
mode="main"
/>
<div
v-else-if="clusters.length === 0"
class="root"
>
<h2>{{ t('epinio.instances.none.header') }}</h2>
<p>{{ t('epinio.instances.none.description') }}</p>
</div>
<div
v-else
class="root"
>
<div class="epinios-table">
<h2>{{ t('epinio.instances.header') }}</h2>
<div v-if="installedClusters.length === 0">
<Banner
class="none"
color="info"
:labelKey="'epinio.instances.none.description'"
/>
</div>
<ResourceTable
:rows="clusters"
:schema="clustersSchema"
Expand Down Expand Up @@ -185,8 +197,9 @@ export default Vue.extend<Data, any, any, any>({
</template>
<template #cell:api="{row}">
<div class="epinio-row">
<template v-if="row.state === UNINSTALLED" />
<Link
v-if="row.state !== 'available'"
v-else-if="row.state !== 'available'"
:row="row"
:value="{ text: row.api, url: row.infoUrl }"
/>
Expand All @@ -195,6 +208,15 @@ export default Vue.extend<Data, any, any, any>({
</template>
</div>
</template>
<template #cell:rancherCluster="{row}">
<div class="epinio-row">
<nuxt-link
:to="{ name: 'c-cluster-explorer', params: { cluster: row.id } }"
>
{{ row.nameDisplay }}
</nuxt-link>
</div>
</template>
</ResourceTable>
</div>
<Dialog
Expand All @@ -217,6 +239,20 @@ export default Vue.extend<Data, any, any, any>({
</div>
</template>

<style lang="scss">
div.root .epinios-table {
.banner__content {
span {
text-align: center;
}

align-items: center;
display: flex;
flex-direction: column;
}
}
</style>

<style lang="scss" scoped>

div.root {
Expand Down
15 changes: 12 additions & 3 deletions dashboard/pkg/epinio/utils/epinio-discovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,19 @@ class EpinioDiscovery {
namespace,
api: url,
loggedIn: !!loggedIn,
mgmtCluster: c
}, { rootGetters: store.getters }));
mgmtCluster: c,
installed: true,
}, { rootGetters: store.getters, $dispatch: store.dispatch }));
} catch (err) {
console.debug(`Skipping epinio discovery for ${ c.spec.displayName }:`, err); // eslint-disable-line no-console
epinioClusters.push(new EpinioCluster({
id: c.id,
name: c.spec.displayName,
namespace: '',
api: '',
loggedIn: false,
mgmtCluster: c,
installed: false,
}, { rootGetters: store.getters, $dispatch: store.dispatch }));
}
}

Expand Down
Loading