dashboard/pages/c/_cluster/explorer/tools/index.vue

396 lines
11 KiB
Vue

<script>
import { mapGetters } from 'vuex';
import Loading from '@/components/Loading';
import { _FLAGGED, DEPRECATED, HIDDEN } from '@/config/query-params';
import { filterAndArrangeCharts } from '@/store/catalog';
import { CATALOG } from '@/config/types';
import LazyImage from '@/components/LazyImage';
import AppSummaryGraph from '@/components/formatter/AppSummaryGraph';
import { sortBy } from '@/utils/sort';
import { LEGACY } from '@/store/features';
export default {
components: {
AppSummaryGraph, LazyImage, Loading
},
async fetch() {
await this.$store.dispatch('catalog/load');
const query = this.$route.query;
this.showDeprecated = query[DEPRECATED] === _FLAGGED;
this.showHidden = query[HIDDEN] === _FLAGGED;
this.allInstalled = await this.$store.dispatch('cluster/findAll', { type: CATALOG.APP });
// If legacy feature flag enabled
if (this.legacyEnabled) {
this.v1SystemCatalog = await this.$store.dispatch('cluster/find', {
type: 'management.cattle.io.catalog',
id: 'system-library',
});
}
},
data() {
const legacyEnabled = this.$store.getters['features/get'](LEGACY);
return {
allInstalled: null,
v1SystemCatalog: null,
legacyEnabled
};
},
computed: {
...mapGetters(['currentCluster']),
...mapGetters({ allCharts: 'catalog/charts', loadingErrors: 'catalog/errors' }),
...mapGetters({ t: 'i18n/t' }),
rancherCatalog() {
return this.$store.getters['catalog/repos'].find(x => x.isRancher);
},
installedApps() {
return this.allInstalled;
},
installedAppForChart() {
const out = {};
for ( const app of this.installedApps ) {
const matching = app.matchingChart();
if ( !matching ) {
continue;
}
out[matching.id] = app;
}
return out;
},
options() {
const clusterProvider = this.currentCluster.status.provider || 'other';
const enabledCharts = (this.allCharts || []);
let charts = filterAndArrangeCharts(enabledCharts, {
clusterProvider,
showDeprecated: this.showDeprecated,
showHidden: this.showHidden,
showRepos: [this.rancherCatalog._key],
});
// If legacy support is enabled, show V1 charts for some V1 Cluster tools
if (this.legacyEnabled) {
charts = charts.concat(this.legacyCharts);
charts = sortBy(charts, ['certifiedSort', 'chartDisplayName']);
}
const chartsWithApps = charts.map((chart) => {
const app = this.installedAppForChart[chart.id];
return {
chart,
app,
upgradeAvailable: app?.upgradeAvailable,
};
});
// V1 Legacy support
if (this.legacyEnabled) {
this.moveAppWhenLegacy(chartsWithApps, 'v1-monitoring', 'rancher-monitoring');
this.moveAppWhenLegacy(chartsWithApps, 'v1-logging', 'rancher-logging');
}
return chartsWithApps;
},
legacyCharts() {
// Fake charts for the legacy V1 tools
return [
this._legacyChart('monitoring'),
this._legacyChart('logging'),
];
}
},
mounted() {
window.c = this;
},
methods: {
_legacyChart(id) {
return {
certifiedSort: 1,
chartDisplayName: this.t(`v1ClusterTools.${ id }.label`),
chartDescription: this.t(`v1ClusterTools.${ id }.description`),
id: `v1-${ id }`,
chartName: `v1-${ id }`,
key: `v1-${ id }`,
versions: this.getLegacyVersions(`rancher-${ id }`),
repoKey: this.rancherCatalog._key,
legacy: true,
legacyPage: id,
iconName: `icon-${ id }`,
};
},
edit(app, version) {
app.goToUpgrade(version, true);
},
remove(app) {
app.promptRemove();
},
install(chart) {
chart.goToInstall(true);
},
openV1Tool(id) {
const cluster = this.$store.getters['currentCluster'];
const route = {
name: 'c-cluster-explorer-tools-pages-page',
params: {
cluster: cluster.id,
prodct: 'explorer',
page: id,
}
};
this.$router.replace(route);
},
getLegacyVersions(id) {
const versions = [];
const c = this.v1SystemCatalog?.status?.helmVersionCommits[id]?.Value;
if (c) {
Object.keys(c).forEach(v => versions.unshift({ version: v }));
}
return versions;
},
moveAppWhenLegacy(chartsWithApps, v1ChartName, v2ChartName) {
const v1 = chartsWithApps.find(a => a.chart.chartName === v1ChartName);
const v2 = chartsWithApps.find(a => a.chart.chartName === v2ChartName);
// Check app on v2
if (v1 && v2 && v2.app) {
const appVersion = v2.app.spec?.chart?.metadata?.version;
if (appVersion) {
const isV1Version = v1.chart.versions.find(v => v.version === appVersion);
if (isV1Version) {
// Move the app data to the v1 chart
v1.app = v2.app;
v2.app = undefined;
v2.blockedV1 = true;
// We don't show upgrade info for V1 features
v1.upgradeAvailable = false;
}
}
}
}
}
};
</script>
<style lang="scss" scoped>
$margin: 10px;
$logo: 50px;
.grid {
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
margin: 0 -1*$margin;
@media only screen and (min-width: map-get($breakpoints, '--viewport-4')) {
.item {
width: 100%;
}
}
@media only screen and (min-width: map-get($breakpoints, '--viewport-7')) {
.item {
width: 100%;
}
}
@media only screen and (min-width: map-get($breakpoints, '--viewport-9')) {
.item {
width: calc(50% - 2 * #{$margin});
}
}
@media only screen and (min-width: map-get($breakpoints, '--viewport-12')) {
.item {
width: calc(33.33333% - 2 * #{$margin});
}
}
.item {
display: grid;
grid-template-areas: "logo name-version name-version"
"description description description"
"state state action";
grid-template-columns: $logo auto min-content;
grid-template-rows: 50px 55px 35px;
row-gap: $margin;
column-gap: $margin;
margin: $margin;
padding: $margin;
position: relative;
border: 1px solid var(--border);
border-radius: calc( 1.5 * var(--border-radius));
.logo {
grid-area: logo;
text-align: center;
width: $logo;
height: $logo;
border-radius: calc(2 * var(--border-radius));
overflow: hidden;
background-color: white;
&.legacy {
background-color: transparent;
}
img {
width: $logo - 4px;
height: $logo - 4px;
object-fit: contain;
position: relative;
top: 2px;
}
> i {
background-color: var(--box-bg);
border-radius: 50%;
font-size: 32px;
line-height: 50px;
width: 50px;
}
}
.name-version {
grid-area: name-version;
padding: 10px 0 0 0;
}
.name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin: 0;
}
.version {
color: var(--muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 0.9em;
margin-top: 4px;
}
.description {
grid-area: description;
}
.description-content {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
line-clamp: 3;
overflow: hidden;
text-overflow: ellipsis;
color: var(--text-muted);
}
.state {
grid-area: state;
}
.action {
grid-area: action;
white-space: nowrap;
}
}
}
</style>
<template>
<Loading v-if="$fetchState.pending" />
<div v-else>
<h1 v-html="t('catalog.tools.header')" />
<div v-if="options.length" class="grid">
<div
v-for="opt in options"
:key="opt.chart.id"
class="item"
>
<div class="logo" :class="{'legacy': opt.chart.legacy}">
<i v-if="opt.chart.iconName" class="icon" :class="opt.chart.iconName" />
<LazyImage v-else :src="opt.chart.icon" />
</div>
<div class="name-version">
<h3 class="name">
{{ opt.chart.chartDisplayName }}
</h3>
<div class="version">
<template v-if="opt.app && opt.upgradeAvailable">
v{{ opt.app.currentVersion }} <b><i class="icon icon-chevron-right" /> v{{ opt.upgradeAvailable }}</b>
</template>
<template v-else-if="opt.app">
v{{ opt.app.currentVersion }}
</template>
<template v-else-if=" opt.chart.versions.length">
v{{ opt.chart.versions[0].version }}
</template>
</div>
</div>
<div class="description">
<div class="description-content" v-html="opt.chart.chartDescription" />
</div>
<div v-if="opt.app" class="state">
<AppSummaryGraph :row="opt.app" label-key="generic.resourceCount" :link-to="opt.app.detailLocation" />
</div>
<div class="action">
<template v-if="opt.blockedV1">
<button disabled="true" class="btn btn-sm role-primary" v-html="t('catalog.tools.action.install')" />
</template>
<template v-else-if="opt.app && opt.chart.legacy">
<button class="btn btn-sm role-secondary" @click="openV1Tool(opt.chart.legacyPage)" v-html="t('catalog.tools.action.manage')" />
</template>
<template v-else-if="opt.app && opt.upgradeAvailable">
<button class="btn btn-sm role-secondary" @click="remove(opt.app)">
<i class="icon icon-delete icon-lg" />
</button>
<button class="btn btn-sm role-secondary" @click="edit(opt.app, opt.upgradeAvailable)" v-html="t('catalog.tools.action.upgrade')" />
</template>
<template v-else-if="opt.app">
<button class="btn btn-sm role-secondary" @click="remove(opt.app)">
<i class="icon icon-delete icon-lg" />
</button>
<button class="btn btn-sm role-secondary" @click="edit(opt.app)" v-html="t('catalog.tools.action.edit')" />
</template>
<template v-else-if="opt.chart.legacy">
<button class="btn btn-sm role-primary" @click="openV1Tool(opt.chart.legacyPage)" v-html="t('catalog.tools.action.install')" />
</template>
<template v-else>
<button class="btn btn-sm role-primary" @click="install(opt.chart)" v-html="t('catalog.tools.action.install')" />
</template>
</div>
</div>
</div>
</div>
</template>