Merge pull request #3101 from richard-cox/v1-mon-warn

Add dynamic 'Uninstall V1' step to Monitoring V2 wizard
This commit is contained in:
Richard Cox 2021-06-03 10:23:24 +01:00 committed by GitHub
commit 4ebe78930d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 443 additions and 182 deletions

View File

@ -513,6 +513,10 @@ asyncButton:
action: Snapshot Now
waiting: Snapshotting…
success: Snapshot Creating
uninstall:
action: Uninstall
success: Uninstalled
waiting: Uninstalling…
update:
action: Update
success: Updated
@ -1763,7 +1767,6 @@ monitoring:
label: Prometheus Targets
subtitle: 'Powered By: <a href="https://github.com/coreos/prometheus-operator" target="_blank" rel="noopener noreferrer nofollow">Prometheus</a>'
title: Dashboard
v1Warning: 'Monitoring is currently deployed from Cluster Manager. If you are migrating from an older version of {vendor} with monitoring enabled, please disable monitoring in Cluster Manager before attempting to use monitoring in Cluster Explorer.'
prometheus:
config:
adminApi: Admin API
@ -1822,6 +1825,14 @@ monitoring:
repeatInterval: Repeat Interval
routesAndReceivers: Routes and Receivers
monitors: Monitors
installSteps:
uninstallV1:
stepTitle: Uninstall V1
stepSubtext: Uninstall Previous Monitoring
warning1: V1 Monitoring is currently deployed. This needs to be uninstalled before V2 monitoring can be installed.
warning2: <a target="blank" href="https://rancher.com/docs/rancher/v2.x/en/monitoring-alerting/v2.5/migrating/#migrating-from-monitoring-v1-to-monitoring-v2" target='_blank' rel='noopener nofollow'>Learn more</a> about migrating to V2 Monitoring.
success1: V1 monitoring successfully uninstalled.
success2: Press Next to continue
tabs:
alerting: Alerting
general: General
@ -4217,6 +4228,7 @@ action:
hide: Hide
copy: Copy
unassign: 'Unassign'
uninstall: Uninstall
unit:
sec: secs

View File

@ -16,8 +16,6 @@ import Tab from '@/components/Tabbed/Tab';
import { allHash } from '@/utils/promise';
import { STORAGE_CLASS, PVC, SECRET, WORKLOAD_TYPES } from '@/config/types';
const CATTLE_MONITORING_NAMESPACE = 'cattle-monitoring-system';
export default {
components: {
Alerting,
@ -54,32 +52,6 @@ export default {
async fetch() {
const { $store } = this;
await Promise.all(
Object.values(WORKLOAD_TYPES).map(type => this.$store.dispatch('cluster/findAll', { type })
)
);
this.workloads.forEach((workload) => {
if (
!isEmpty(workload?.spec?.template?.spec?.containers) &&
workload.spec.template.spec.containers.find(
c => c.image.includes('quay.io/coreos/prometheus-operator') ||
c.image.includes('rancher/coreos-prometheus-operator')
) &&
workload?.metadata?.namespace !== CATTLE_MONITORING_NAMESPACE
) {
if (!this.v1Installed) {
this.v1Installed = true;
}
}
});
if (this.v1Installed) {
this.$emit('warn', this.t('monitoring.v1Warning', {}, true));
return;
}
const hash = await allHash({
namespaces: $store.getters['namespaces'](),
pvcs: $store.dispatch('cluster/findAll', { type: PVC }),
@ -130,7 +102,6 @@ export default {
secrets: [],
storageClasses: [],
targetNamespace: null,
v1Installed: false,
};
},

View File

@ -0,0 +1,126 @@
<script>
import { haveV1Monitoring, haveV1MonitoringWorkloads } from '@/utils/monitoring';
import AsyncButton from '@/components/AsyncButton';
import IconMessage from '@/components/IconMessage';
function delay(t, v) {
return new Promise((resolve) => {
setTimeout(resolve.bind(null, v), t);
});
}
export default {
label: 'monitoring.installSteps.uninstallV1.stepTitle',
subtext: 'monitoring.installSteps.uninstallV1.stepSubtext',
weight: 100,
components: {
AsyncButton,
IconMessage
},
data() {
return {
haveV1Monitoring: false,
error: null,
};
},
mounted() {
this.haveV1Monitoring = haveV1Monitoring(this.$store.getters);
this.$emit('update', {
loading: false,
ready: false,
hidden: !this.haveV1Monitoring,
});
},
methods: {
uninstall(buttonCb) {
Promise.resolve()
.then(async() => {
await this.$store.getters['currentCluster'].doAction('disableMonitoring');
for (let index = 0; index < 30; index++) {
// Wait 30 seconds for the containers to go
const hasV1Monitoring = haveV1Monitoring(this.$store.getters);
const hasV1MonitoringWorkloads = await haveV1MonitoringWorkloads(this.$store);
if ((!hasV1Monitoring && !hasV1MonitoringWorkloads)) {
this.$emit('update', { ready: true, hidden: true });
buttonCb(true);
this.haveV1Monitoring = false;
return;
}
await delay(1000);
}
this.$emit('errors', [`Failed to uninstall: timed out`]);
buttonCb(false);
})
.catch((e) => {
this.$emit('errors', [`Failed to uninstall: ${ e }`]);
buttonCb(false);
});
},
}
};
</script>
<template>
<div class="v1-monitoring">
<template v-if="haveV1Monitoring">
<IconMessage
class="mt-40 mb-20"
icon="icon-warning"
:vertical="true"
icon-state="warning"
>
<template #message>
<p>
{{ t('monitoring.installSteps.uninstallV1.warning1') }}
</p>
<p class="mt-10" v-html="t('monitoring.installSteps.uninstallV1.warning2', {}, true)">
</p>
</template>
</IconMessage>
<AsyncButton
mode="uninstall"
:delay="2000"
@click="uninstall"
/>
</template>
<IconMessage
v-else
class="mt-40"
icon="icon-checkmark"
:vertical="true"
icon-state="success"
>
<template #message>
<p class="">
{{ t('monitoring.installSteps.uninstallV1.success1') }}
</p>
<p class="mt-10" v-html="t('monitoring.installSteps.uninstallV1.success2')">
</p>
</template>
</IconMessage>
</div>
</template>
<style lang='scss' scoped>
.v1-monitoring {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
p {
max-width: 900px;
}
.btn {
min-width: 200px;
}
}
</style>

View File

@ -1,10 +1,18 @@
<script>
export default {
props: {
vertical: {
type: Boolean,
default: false,
},
icon: {
type: String,
required: true,
},
iconState: {
type: String,
default: null
},
message: {
type: String,
default: null
@ -18,28 +26,52 @@ export default {
</script>
<template>
<div class="icon-message">
<i class="icon" :class="icon" />
<div class="icon-message" :class="{'vertical': vertical}">
<i class="icon" :class="{ [icon]: true, [iconState]: !!iconState}" />
<div class="message">
<template v-if="messageKey">
{{ t(messageKey) }}
</template>
<template v-else>
{{ message }}
</template>
<slot name="message">
<template v-if="messageKey">
{{ t(messageKey) }}
</template>
<template v-else>
{{ message }}
</template>
</slot>
</div>
</div>
</template>
<style lang="scss">
<style lang="scss" scoped>
.vertical {
flex-direction: column;
width: 100%;
}
.icon-message {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
> I {
font-size: 64px;
margin-bottom: 20px;
&.info {
color: var(--primary);
}
&.error {
color: var(--error);
}
&.warning {
color: var(--warning);
}
&.success {
color: var(--success);
}
}
> .message {
@ -49,5 +81,6 @@ export default {
text-align: center;
line-height: 30px;
}
}
</style>

View File

@ -2,6 +2,7 @@
import AsyncButton from '@/components/AsyncButton';
import Banner from '@/components/Banner';
import Loading from '@/components/Loading';
import { stringify } from '@/utils/error';
/*
@ -20,7 +21,8 @@ Wizard will emit these events:
export default {
components: {
AsyncButton,
Banner
Banner,
Loading,
},
props: {
@ -31,6 +33,8 @@ export default {
subtext: String (optional) - If defined, appears below the step number in the banner. If blank, label is used
ready: Boolean - whether or not the step is completed/wizard is able to go to next step
if a step has ready=true, the wizard also allows navigation *back* to it
hidden: Don't show step, though include in DOM (dynamic steps must be in DOM to determine if they will include themselves in wizard)
loading: Wizard will block until all steps are not loading
}
*/
steps: {
@ -93,7 +97,7 @@ export default {
},
data() {
return { activeStep: this.steps[this.initStepIndex] };
return { activeStep: null };
},
computed: {
@ -102,23 +106,41 @@ export default {
},
activeStepIndex() {
return this.steps.indexOf(this.activeStep);
return this.visibleSteps.indexOf(this.activeStep);
},
canNext() {
return (this.activeStepIndex < this.steps.length - 1) && this.activeStep.ready;
return (this.activeStepIndex < this.visibleSteps.length - 1) && this.activeStep.ready;
},
readySteps() {
return this.steps.filter(step => step.ready);
return this.visibleSteps.filter(step => step.ready);
},
showSteps() {
return this.activeStep.showSteps !== false;
},
stepsLoaded() {
return !this.steps.some(step => step.loading === true);
},
visibleSteps() {
return this.steps.filter(step => !step.hidden);
}
},
watch: {
stepsLoaded(neu, old) {
if (!old && neu) {
this.activeStep = this.visibleSteps[this.initStepIndex];
this.goToStep(this.activeStepIndex + 1);
}
}
},
created() {
this.activeStep = this.visibleSteps[this.initStepIndex];
this.goToStep(this.activeStepIndex + 1);
},
@ -133,7 +155,7 @@ export default {
return;
}
const selected = this.steps[number - 1];
const selected = this.visibleSteps[number - 1];
if ( !selected || (!this.isAvailable(selected) && number !== 1)) {
return;
@ -166,14 +188,14 @@ export default {
return false;
}
const idx = this.steps.indexOf(step);
const idx = this.visibleSteps.indexOf(step);
if (idx === 0 && !this.editFirstStep) {
return false;
}
for (let i = 0; i < idx; i++) {
if ( this.steps[i].ready === false ) {
if ( this.visibleSteps[i].ready === false ) {
return false;
}
}
@ -185,109 +207,112 @@ export default {
</script>
<template>
<div class="container">
<div class="header">
<div class="title">
<div v-if="showBanner" class="top choice-banner">
<div v-show="initialTitle || activeStepIndex > 0" class="title">
<!-- Logo -->
<slot name="bannerTitleImage">
<div v-if="bannerImage" class="round-image">
<LazyImage :src="bannerImage" class="logo" />
<!-- <img :src="bannerImage" /> -->
<div class="outer-container">
<Loading v-if="!stepsLoaded" mode="relative" />
<!-- Note - Don't v-else this.... the steps need to be included in order to update 'stepsLoaded' -->
<div class="outer-container" :class="{'hide': !stepsLoaded}">
<div class="header">
<div class="title">
<div v-if="showBanner" class="top choice-banner">
<div v-show="initialTitle || activeStepIndex > 0" class="title">
<!-- Logo -->
<slot name="bannerTitleImage">
<div v-if="bannerImage" class="round-image">
<LazyImage :src="bannerImage" class="logo" />
</div>
</slot>
<!-- Title with subtext -->
<div class="subtitle">
<h2 v-if="bannerTitle">
{{ bannerTitle }}
</h2>
<span v-if="bannerTitleSubtext" class="subtext">{{ bannerTitleSubtext }}</span>
</div>
</slot>
<!-- Title with subtext -->
<div class="subtitle">
<h2 v-if="bannerTitle">
{{ bannerTitle }}
</h2>
<span v-if="bannerTitleSubtext" class="subtext">{{ bannerTitleSubtext }}</span>
</div>
<!-- Step number with subtext -->
<div v-if="activeStep" class="subtitle">
<h2>{{ t(`asyncButton.${finishMode}.action`) }}: {{ t('wizard.step', {number:activeStepIndex+1}) }}</h2>
<slot name="bannerSubtext">
<span class="subtext">{{ activeStep.subtext || activeStep.label }}</span>
</slot>
</div>
</div>
<!-- Step number with subtext -->
<div class="subtitle">
<h2>{{ t(`asyncButton.${finishMode}.action`) }}: {{ t('wizard.step', {number:activeStepIndex+1}) }}</h2>
<slot name="bannerSubtext">
<span class="subtext">{{ activeStep.subtext || activeStep.label }}</span>
</div>
<div class="step-sequence">
<ul
v-if="showSteps"
class="steps"
tabindex="0"
@keyup.right.stop="selectNext(1)"
@keyup.left.stop="selectNext(-1)"
>
<template v-for="(step, idx ) in visibleSteps">
<li
:id="step.name"
:key="step.name+'li'"
:class="{step: true, active: step === activeStep, disabled: !isAvailable(step)}"
role="presentation"
>
<span
:aria-controls="'step' + idx+1"
:aria-selected="step === activeStep"
role="tab"
class="controls"
@click.prevent="goToStep(idx+1, true)"
>
<span class="icon icon-lg" :class="{'icon-dot': step === activeStep, 'icon-dot-open':step !== activeStep}" />
<span>
{{ step.label }}
</span>
</span>
</li>
<div v-if="idx!==visibleSteps.length-1" :key="step.name" class="divider" />
</template>
</ul>
</div>
</div>
<div class="step-container">
<template v-for="step in steps">
<div v-if="step === activeStep || step.hidden" :key="step.name" class="step-container__step" :class="{'hide': step !== activeStep && step.hidden}">
<slot :step="step" :name="step.name" />
</div>
</template>
</div>
<div class="controls-container">
<div v-for="(err,idx) in errorStrings" :key="idx">
<Banner color="error" :label="err" :closable="true" @close="errors.splice(idx, 1)" />
</div>
<div class="controls-row pt-20">
<slot name="cancel" :cancel="cancel">
<button type="button" class="btn role-secondary" @click="cancel">
<t k="generic.cancel" />
</button>
</slot>
<div class="controls-steps">
<slot v-if="activeStepIndex!==0" name="back" :back="back">
<button :disabled="!editFirstStep && activeStepIndex===1" type="button" class="btn role-secondary" @click="back()">
<t k="wizard.previous" />
</button>
</slot>
<slot v-if="activeStepIndex === visibleSteps.length-1" name="finish" :finish="finish">
<AsyncButton
:disabled="!activeStep.ready"
:mode="finishMode"
@click="finish"
/>
</slot>
<slot v-else name="next" :next="next">
<button :disabled="!canNext" type="button" class="btn role-primary" @click="next()">
<t k="wizard.next" />
</button>
</slot>
</div>
</div>
</div>
<div class="step-sequence">
<ul
v-if="showSteps"
class="steps"
tabindex="0"
@keyup.right.stop="selectNext(1)"
@keyup.left.stop="selectNext(-1)"
>
<template v-for="(step, idx ) in steps">
<li
:id="step.name"
:key="step.name+'li'"
:class="{step: true, active: step === activeStep, disabled: !isAvailable(step)}"
role="presentation"
>
<span
:aria-controls="'step' + idx+1"
:aria-selected="step === activeStep"
role="tab"
class="controls"
@click.prevent="goToStep(idx+1, true)"
>
<span class="icon icon-lg" :class="{'icon-dot': step === activeStep, 'icon-dot-open':step !== activeStep}" />
<span>
{{ step.label }}
</span>
</span>
</li>
<div v-if="idx!==steps.length-1" :key="step.name" class="divider" />
</template>
</ul>
</div>
</div>
<div class="step-container">
<template v-for="step in steps">
<div v-if="step === activeStep" :key="step.name" class="step-container__step">
<slot :step="step" :name="step.name" />
</div>
</template>
</div>
<div class="controls-container">
<div v-for="(err,idx) in errorStrings" :key="idx">
<Banner color="error" :label="err" :closable="true" @close="errors.splice(idx, 1)" />
</div>
<div class="controls-row pt-20">
<slot name="cancel" :cancel="cancel">
<button type="button" class="btn role-secondary" @click="cancel">
<t k="generic.cancel" />
</button>
</slot>
<div class="controls-steps">
<slot v-if="activeStepIndex!==0" name="back" :back="back">
<button :disabled="!editFirstStep && activeStepIndex===1" type="button" class="btn role-secondary" @click="back()">
<t k="wizard.previous" />
</button>
</slot>
<slot v-if="activeStepIndex === steps.length-1" name="finish" :finish="finish">
<AsyncButton
:disabled="!activeStep.ready"
:mode="finishMode"
@click="finish"
/>
</slot>
<slot v-else name="next" :next="next">
<button :disabled="!canNext" type="button" class="btn role-primary" @click="next()">
<t k="wizard.next" />
</button>
</slot>
</div>
</div>
</div>
</div>
</template>
@ -295,7 +320,7 @@ export default {
<style lang='scss' scoped>
$spacer: 10px;
.container {
.outer-container {
display: flex;
flex-direction: column;
flex: 1;

View File

@ -17,18 +17,20 @@ import Tabbed from '@/components/Tabbed';
import UnitInput from '@/components/form/UnitInput';
import YamlEditor, { EDITOR_MODES } from '@/components/YamlEditor';
import Wizard from '@/components/Wizard';
import ChartMixin from '@/pages/c/_cluster/apps/chart_mixin';
import ChildHook, { BEFORE_SAVE_HOOKS, AFTER_SAVE_HOOKS } from '@/mixins/child-hook';
import { CATALOG, MANAGEMENT } from '@/config/types';
import {
CHART, FROM_TOOLS, NAMESPACE, REPO, REPO_TYPE, VERSION, _FLAGGED
} from '@/config/query-params';
import { CATALOG as CATALOG_ANNOTATIONS, DESCRIPTION as DESCRIPTION_ANNOTATION, PROJECT } from '@/config/labels-annotations';
import { exceptionToErrorsArray } from '@/utils/error';
import { clone, diff, get, set } from '@/utils/object';
import { findBy, insertAt } from '@/utils/array';
import ChildHook, { BEFORE_SAVE_HOOKS, AFTER_SAVE_HOOKS } from '@/mixins/child-hook';
import ChartMixin from '@/pages/c/_cluster/apps/chart_mixin';
import isEqual from 'lodash/isEqual';
import Vue from 'vue';
const VALUES_STATE = {
FORM: 'FORM',
@ -131,6 +133,8 @@ export default {
await this.loadValuesComponent();
}
await this.loadChartSteps();
if ( !this.loadedVersion || this.loadedVersion !== this.version.key ) {
let userValues;
@ -224,19 +228,26 @@ export default {
label: this.t('catalog.install.steps.basics.label'),
subtext: this.t('catalog.install.steps.basics.subtext'),
ready: true,
weight: 30
},
stepValues: {
name: 'helmValues',
label: this.t('catalog.install.steps.helmValues.label'),
subtext: this.t('catalog.install.steps.helmValues.subtext'),
ready: true,
weight: 20
},
stepCommands: {
name: 'helmCli',
label: this.t('catalog.install.steps.helmCli.label'),
subtext: this.t('catalog.install.steps.helmCli.subtext'),
ready: true,
}
weight: 10
},
customSteps: [
]
};
},
@ -410,14 +421,17 @@ export default {
},
steps() {
const steps = [this.stepBasic];
const steps = [
this.stepBasic,
this.stepValues,
...this.customSteps
];
steps.push(this.stepValues);
if (this.showCommandStep) {
steps.push(this.stepCommands);
}
return steps;
return steps.sort((a, b) => (b.weight || 0) - (a.weight || 0));
},
cmdOptions() {
@ -503,6 +517,8 @@ export default {
async mounted() {
await this.loadValuesComponent();
await this.loadChartSteps();
window.scrollTop = 0;
// For easy access debugging...
@ -543,6 +559,32 @@ export default {
}
},
async loadChartSteps() {
const component = this.version?.annotations?.[CATALOG_ANNOTATIONS.COMPONENT] || this.version?.annotations?.[CATALOG_ANNOTATIONS.RELEASE_NAME];
if ( component ) {
const steps = await this.$store.getters['catalog/chartSteps'](component);
this.customSteps = await Promise.all( steps.map(cs => this.loadChartStep(cs)));
}
},
async loadChartStep(customStep) {
const loaded = await customStep.component();
const withFallBack = this.$store.getters['i18n/withFallback'];
return {
name: customStep.name,
label: withFallBack(loaded?.default?.label, null, customStep.name),
subtext: withFallBack(loaded?.default?.subtext, null, ''),
weight: loaded?.default?.weight,
ready: false,
hidden: true,
loading: true,
component: customStep.component,
};
},
selectChart(chart) {
if ( !chart ) {
return;
@ -684,7 +726,7 @@ export default {
}
}
if ( values.global?.cattle.windows && !Object.keys(values.global.cattle.windows).length ) {
if ( values.global?.cattle?.windows && !Object.keys(values.global.cattle.windows).length ) {
delete values.global.cattle.windows;
}
@ -847,6 +889,16 @@ export default {
component: 'ChartReadme',
attrs: { versionInfo: this.versionInfo }
}, { root: true });
},
updateStep(stepName, update) {
const step = this.steps.find(step => step.name === stepName);
if (step) {
for (const prop in update) {
Vue.set(step, prop, update[prop]);
}
}
}
},
};
@ -867,6 +919,14 @@ export default {
@cancel="cancel"
@finish="finish"
>
<template v-for="customStep of customSteps" v-slot:[customStep.name]>
<component
:is="customStep.component"
:key="customStep.name"
@update="updateStep(customStep.name, $event)"
@errors="e=>errors.push(...e)"
/>
</template>
<template #bannerTitleImage>
<div class="logo-bg">
<LazyImage :src="chart ? chart.icon : ''" class="logo" />
@ -1111,6 +1171,7 @@ export default {
<style lang="scss" scoped>
$title-height: 50px;
$padding: 5px;
$slideout-width: 35%;
.install-steps {
position: relative; overflow: hidden;

View File

@ -4,13 +4,14 @@ import isEmpty from 'lodash/isEmpty';
import InstallRedirect from '@/utils/install-redirect';
import AlertTable from '@/components/AlertTable';
import { NAME, CHART_NAME } from '@/config/product/monitoring';
import { ENDPOINTS, MONITORING, WORKLOAD_TYPES } from '@/config/types';
import { ENDPOINTS, MONITORING } from '@/config/types';
import { allHash } from '@/utils/promise';
import { findBy } from '@/utils/array';
import Banner from '@/components/Banner';
import LazyImage from '@/components/LazyImage';
import SimpleBox from '@/components/SimpleBox';
import { haveV1MonitoringWorkloads } from '@/utils/monitoring';
const CATTLE_MONITORING_NAMESPACE = 'cattle-monitoring-system';
@ -94,25 +95,7 @@ export default {
async fetchDeps() {
const { $store, externalLinks } = this;
const workloads = await Promise.all(
Object.values(WORKLOAD_TYPES).map(type => this.$store.dispatch('cluster/findAll', { type })
)
);
workloads.flat().forEach((workload) => {
if (
!isEmpty(workload?.spec?.template?.spec?.containers) &&
workload.spec.template.spec.containers.find(
c => c.image.includes('quay.io/coreos/prometheus-operator') ||
c.image.includes('rancher/coreos-prometheus-operator')
) &&
workload?.metadata?.namespace !== CATTLE_MONITORING_NAMESPACE
) {
if (!this.v1Installed) {
this.v1Installed = true;
}
}
});
this.v1Installed = await haveV1MonitoringWorkloads($store);
const hash = await allHash({ endpoints: $store.dispatch('cluster/findAll', { type: ENDPOINTS }) });

View File

@ -236,6 +236,31 @@ export const getters = {
return importChart(name);
};
},
chartSteps(state, getters) {
return (name) => {
const steps = [];
const stepsPath = `./${ name }/steps/`;
// require.context only takes literals, so find all candidate step files and filter out
const allPaths = require.context('@/chart', true, /\.vue$/).keys();
allPaths
.filter(path => path.startsWith(stepsPath))
.forEach((path) => {
try {
steps.push({
name: path.replace(stepsPath, ''),
component: importChart(path.substr(2, path.length)),
});
} catch (e) {
console.warn(`Failed to load step component ${ path } for chart ${ name }`, e); // eslint-disable-line no-console
}
});
return steps;
};
}
};
export const mutations = {

View File

@ -3,7 +3,7 @@
// an import with a variable in the path.
export function importCloudCredential(name) {
if ( !name ) {
if (!name) {
throw new Error('Name required');
}
@ -11,7 +11,7 @@ export function importCloudCredential(name) {
}
export function importMachineConfig(name) {
if ( !name ) {
if (!name) {
throw new Error('Name required');
}
@ -19,7 +19,7 @@ export function importMachineConfig(name) {
}
export function importLogin(name) {
if ( !name ) {
if (!name) {
throw new Error('Name required');
}
@ -27,7 +27,7 @@ export function importLogin(name) {
}
export function importChart(name) {
if ( !name ) {
if (!name) {
throw new Error('Name required');
}
@ -35,7 +35,7 @@ export function importChart(name) {
}
export function importList(name) {
if ( !name ) {
if (!name) {
throw new Error('Name required');
}
@ -43,7 +43,7 @@ export function importList(name) {
}
export function importDetail(name) {
if ( !name ) {
if (!name) {
throw new Error('Name required');
}
@ -51,7 +51,7 @@ export function importDetail(name) {
}
export function importEdit(name) {
if ( !name ) {
if (!name) {
throw new Error('Name required');
}
@ -59,10 +59,10 @@ export function importEdit(name) {
}
export function loadProduct(name) {
if ( !name ) {
if (!name) {
throw new Error('Name required');
}
// Note: directly returns the import, not a function
return import(/* webpackChunkName: "product" */ `@/config/product/${ name }`);
return import(/* webpackChunkName: "product" */ `@/config/product/${name}`);
}

View File

@ -1,7 +1,9 @@
// Helpers for determining if V2 or v1 Monitoring are installed
import { MONITORING, SCHEMA } from '@/config/types';
import { SCHEMA, MONITORING, WORKLOAD_TYPES } from '@/config/types';
import { normalizeType } from '@/plugins/steve/normalize';
import { findBy } from '@/utils/array';
import { isEmpty } from '@/utils/object';
// Can be used inside a components' computed property
export function monitoringStatus() {
@ -39,6 +41,29 @@ export function haveV1Monitoring(getters) {
return !!cluster?.status?.monitoringStatus;
}
const CATTLE_MONITORING_NAMESPACE = 'cattle-monitoring-system';
export async function haveV1MonitoringWorkloads(store) {
const workloadsByType = await Promise.all(
Object.values(WORKLOAD_TYPES).map(type => store.dispatch('cluster/findAll', { type })
)
);
const workloads = workloadsByType.flat();
for (let i = 0; i < workloads.length; i++) {
const workload = workloads[i];
if (!isEmpty(workload?.spec?.template?.spec?.containers) &&
workload.spec.template.spec.containers.find(c => c.image?.includes('quay.io/coreos/prometheus-operator') ||
c.image?.includes('rancher/coreos-prometheus-operator')) &&
workload?.metadata?.namespace !== CATTLE_MONITORING_NAMESPACE) {
return Promise.resolve(true);
}
return Promise.resolve(false);
}
}
// Other ways we check for monitoring:
// (1) Using counts (requires RBAC permissinons)