mirror of https://github.com/rancher/dashboard.git
Merge pull request #3101 from richard-cox/v1-mon-warn
Add dynamic 'Uninstall V1' step to Monitoring V2 wizard
This commit is contained in:
commit
4ebe78930d
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 }) });
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue