mirror of https://github.com/rancher/dashboard.git
470 lines
12 KiB
Vue
470 lines
12 KiB
Vue
<script>
|
|
import jsyaml from 'js-yaml';
|
|
import AsyncButton from '@/components/AsyncButton';
|
|
import Footer from '@/components/form/Footer';
|
|
import InfoBox from '@/components/InfoBox';
|
|
import { NAMESPACE } from '@/config/types';
|
|
import { _VIEW, _EDIT } from '@/config/query-params';
|
|
import { findBy } from '@/utils/array';
|
|
import CodeMirror from './CodeMirror';
|
|
|
|
export default {
|
|
name: 'GatekeeperConfig',
|
|
|
|
components: {
|
|
AsyncButton,
|
|
CodeMirror,
|
|
InfoBox,
|
|
Footer,
|
|
},
|
|
|
|
props: {
|
|
/**
|
|
* The gatekeeper config if enabled.
|
|
* @model
|
|
*/
|
|
config: {
|
|
type: Object,
|
|
default: null,
|
|
},
|
|
|
|
/**
|
|
* Page mode
|
|
* @values view, create
|
|
*/
|
|
mode: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
|
|
/**
|
|
* All namespaces
|
|
*/
|
|
namespaces: {
|
|
type: Array,
|
|
default: null,
|
|
},
|
|
},
|
|
|
|
data() {
|
|
let gatekeeperEnabled = false;
|
|
let showYamlEditor = false;
|
|
|
|
if (this.config && this.config.id) {
|
|
gatekeeperEnabled = true;
|
|
}
|
|
|
|
if (this.mode === _EDIT) {
|
|
showYamlEditor = true;
|
|
}
|
|
|
|
return {
|
|
gatekeeperEnabled,
|
|
showYamlEditor,
|
|
errors: [],
|
|
saving: false,
|
|
};
|
|
},
|
|
|
|
computed: {
|
|
appVersion() {
|
|
const externalId = this.config?.spec?.externalId;
|
|
|
|
if (!externalId) {
|
|
return null;
|
|
}
|
|
|
|
const version = externalId.split('&').find(e => e.includes('version')).split('=').pop() || null;
|
|
|
|
return version;
|
|
},
|
|
|
|
gatekeeperSystemNamespace() {
|
|
const { namespaces } = this;
|
|
|
|
return namespaces.find(ns => ns.metadata.name === 'gatekeeper-system');
|
|
},
|
|
|
|
cmOptions() {
|
|
const readOnly = this.mode === _VIEW;
|
|
const gutters = ['CodeMirror-lint-markers'];
|
|
|
|
if ( !readOnly ) {
|
|
gutters.push('CodeMirror-foldgutter');
|
|
}
|
|
|
|
return {
|
|
readOnly,
|
|
gutters,
|
|
mode: 'yaml',
|
|
lint: true,
|
|
lineNumbers: !readOnly,
|
|
extraKeys: { 'Ctrl-Space': 'autocomplete' },
|
|
cursorBlinkRate: ( readOnly ? -1 : 530)
|
|
};
|
|
},
|
|
|
|
parsedValuesYaml() {
|
|
const str = this.config?.spec?.valuesYaml;
|
|
let values = null;
|
|
|
|
try {
|
|
values = jsyaml.safeLoad(str);
|
|
|
|
return values;
|
|
} catch (e) {
|
|
console.error('Unable to parse valuesYaml', str, e); // eslint-disable-line no-console
|
|
}
|
|
|
|
return null;
|
|
},
|
|
},
|
|
|
|
watch: {
|
|
mode() {
|
|
if (this.mode === _EDIT) {
|
|
this.showYamlEditor = true;
|
|
} else {
|
|
this.showYamlEditor = false;
|
|
}
|
|
},
|
|
|
|
config: {
|
|
deep: true,
|
|
handler() {
|
|
const gatekeeper = this.config || {};
|
|
const meta = gatekeeper?.metadata;
|
|
|
|
// this doesn't seeem right but the only way I can see to check that it was removed before the object goes away
|
|
if (meta && Object.prototype.hasOwnProperty.call(meta, 'deletionTimestamp')) {
|
|
this.gatekeeperEnabled = false;
|
|
this.$emit('gatekeeperEnabled', this.gatekeeperEnabled);
|
|
}
|
|
|
|
if (!this.gatekeeperEnabled && this.saving && gatekeeper.hasCondition('Deployed', 'True')) { // we can get here if waitForCondition takes too long
|
|
if (this.showYamlEditor) {
|
|
this.showYamlEditor = false;
|
|
}
|
|
|
|
this.$emit('gatekeeperEnabled', this.gatekeeperEnabled = true);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
async ensureNamespace() {
|
|
if ( findBy(this.namespaces, 'metadata.name', 'gatekeeper-system') ) {
|
|
return;
|
|
}
|
|
|
|
const newSystemNs = await this.$store.dispatch('cluster/create', {
|
|
type: NAMESPACE,
|
|
metadata: {
|
|
name: 'gatekeeper-system',
|
|
annotations: { 'field.cattle.io/projectId': this.config.spec.projectName },
|
|
labels: { 'field.cattle.io/projectId': this.config.metadata.namespace },
|
|
},
|
|
});
|
|
|
|
await newSystemNs.save();
|
|
await newSystemNs.waitForState('active');
|
|
},
|
|
|
|
showActions() {
|
|
this.$store.commit('action-menu/show', {
|
|
resources: this.config,
|
|
elem: this.$refs.actions,
|
|
});
|
|
},
|
|
/**
|
|
* Gets called when the user clicks on the button
|
|
* Checks for the system namespace and creates that first if it does not exist
|
|
* Creates gatekeeper app deployment
|
|
*
|
|
* @param {buttonCb} Callback to be called on success or fail
|
|
*/
|
|
async enableGatekeeper(buttonCb) {
|
|
try {
|
|
this.saving = true;
|
|
await this.ensureNamespace();
|
|
await this.config.save();
|
|
await this.config.waitForCondition('Deployed', 'True', 300000);
|
|
this.showYamlEditor = false;
|
|
this.saving = false;
|
|
this.$emit('gatekeeperEnabled', this.gatekeeperEnabled = true);
|
|
buttonCb(true);
|
|
} catch (err) {
|
|
if (this?.config.hasCondition('Deployed', 'True')) { // we can hit this if waitForCondition above fails, in that case the config observer will enable
|
|
buttonCb(true); // dont want to see red button error in this case
|
|
} else {
|
|
this.gatekeeperEnabled = false;
|
|
this.saving = false;
|
|
if (err?.message) {
|
|
this.errors = [err.message];
|
|
} else {
|
|
this.errors = [err];
|
|
}
|
|
buttonCb(false);
|
|
}
|
|
}
|
|
|
|
this.saving = false;
|
|
},
|
|
|
|
/**
|
|
* Gets called when the user selects advanced configuration
|
|
*
|
|
*/
|
|
openYamlEditor() {
|
|
if (this.showYamlEditor) {
|
|
if (this.mode === _EDIT) {
|
|
this.$router.push({ name: this.$route.name });
|
|
} else {
|
|
this.showYamlEditor = false;
|
|
}
|
|
} else {
|
|
this.showYamlEditor = true;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Gets called when the user clicks on the disable button
|
|
*
|
|
* @param {buttonCb} Callback to be called on success or fail
|
|
*/
|
|
async disable(buttonCb) {
|
|
try {
|
|
await this.config.remove();
|
|
|
|
this.gatekeeperEnabled = false;
|
|
|
|
buttonCb(true);
|
|
} catch (err) {
|
|
this.errors = [err];
|
|
|
|
buttonCb(false);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* An event handler that will be called whenever keydown fires in the CodeMirror input.
|
|
*
|
|
* @param {value} Yaml string
|
|
*/
|
|
onInput(value) {
|
|
this.config.spec.valuesYaml = value;
|
|
},
|
|
|
|
/**
|
|
* Gets called when CodeMirror is ready and folds
|
|
*
|
|
*/
|
|
onReady(cm) {
|
|
cm.getMode().fold = 'yaml';
|
|
|
|
cm.execCommand('foldAll');
|
|
},
|
|
|
|
/**
|
|
* An event handler that will be called whenever CodeMirror onChange event fires.
|
|
*
|
|
* @param {cm} CodeMirror instance
|
|
* @param {changes} Changes from CodeMirror
|
|
*/
|
|
onChanges(cm, changes) {
|
|
if ( changes.length !== 1 ) {
|
|
return;
|
|
}
|
|
|
|
const change = changes[0];
|
|
|
|
if ( change.from.line !== change.to.line ) {
|
|
return;
|
|
}
|
|
|
|
let line = change.from.line;
|
|
let str = cm.getLine(line);
|
|
let maxIndent = indentChars(str);
|
|
|
|
if ( maxIndent === null ) {
|
|
return;
|
|
}
|
|
|
|
cm.replaceRange('', { line, ch: 0 }, { line, ch: 1 }, '+input');
|
|
|
|
while ( line > 0 ) {
|
|
line--;
|
|
str = cm.getLine(line);
|
|
const indent = indentChars(str);
|
|
|
|
if ( indent === null ) {
|
|
break;
|
|
}
|
|
|
|
if ( indent < maxIndent ) {
|
|
cm.replaceRange('', { line, ch: 0 }, { line, ch: 1 }, '+input');
|
|
|
|
if ( indent === 0 ) {
|
|
break;
|
|
}
|
|
|
|
maxIndent = indent;
|
|
}
|
|
}
|
|
|
|
function indentChars(str) {
|
|
const match = str.match(/^#(\s+)/);
|
|
|
|
if ( match ) {
|
|
return match[1].length;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="gatekeeper-config">
|
|
<header>
|
|
<h1>
|
|
<t k="gatekeeperConfig.header" />
|
|
</h1>
|
|
<div v-if="gatekeeperEnabled" class="actions">
|
|
<button
|
|
ref="actions"
|
|
aria-haspopup="true"
|
|
type="button"
|
|
class="btn btn-sm role-multi-action actions"
|
|
@click="showActions"
|
|
>
|
|
<i class="icon icon-actions" />
|
|
</button>
|
|
</div>
|
|
</header>
|
|
<div v-if="gatekeeperEnabled" class="mt-20">
|
|
<InfoBox
|
|
v-if="parsedValuesYaml && !showYamlEditor"
|
|
class="row"
|
|
>
|
|
<div class="col span-6 info-column">
|
|
<div class="info-row">
|
|
<label><t k="gatekeeperConfig.infoBox.auditFromCache" />: </label>
|
|
{{ parsedValuesYaml.auditFromCache }}
|
|
</div>
|
|
<div class="info-row">
|
|
<label><t k="gatekeeperConfig.infoBox.auditInterval" />: </label>
|
|
{{ parsedValuesYaml.auditInterval }}s
|
|
</div>
|
|
<div class="info-row">
|
|
<label><t k="gatekeeperConfig.infoBox.constraintViolationsLimit" />: </label>
|
|
{{ parsedValuesYaml.constraintViolationsLimit }}
|
|
</div>
|
|
</div>
|
|
<div class="col span-6 info-column">
|
|
<div class="info-row">
|
|
<label><t k="gatekeeperConfig.infoBox.imageRepository" />: </label>
|
|
{{ parsedValuesYaml.image.repository }}
|
|
</div>
|
|
<div class="info-row">
|
|
<label><t k="gatekeeperConfig.infoBox.replicas" />: </label>
|
|
{{ parsedValuesYaml.replicas }}
|
|
</div>
|
|
<div class="info-row">
|
|
<label><t k="gatekeeperConfig.infoBox.imageTag" />: </label>
|
|
{{ parsedValuesYaml.image.tag }}
|
|
</div>
|
|
</div>
|
|
</InfoBox>
|
|
</div>
|
|
<div v-else class="mt-20 mb-20">
|
|
<div class="row">
|
|
<div class="col span-6">
|
|
<h3><t k="gatekeeperConfig.configure.description" /></h3>
|
|
<ul>
|
|
<li><t k="gatekeeperConfig.configure.helpText.listItem1" /></li>
|
|
<li><t k="gatekeeperConfig.configure.helpText.listItem2" /></li>
|
|
<li><t k="gatekeeperConfig.configure.helpText.listItem3" /> <a href="https://www.openpolicyagent.org/docs/latest/kubernetes-introduction/" target="blank"><t k="gatekeeperConfig.configure.helpText.listItem4" /></a></li>
|
|
</ul>
|
|
</div>
|
|
<div class="col span-6">
|
|
<h3><t k="gatekeeperConfig.configure.requirements.header" /></h3>
|
|
<ul>
|
|
<li><t k="gatekeeperConfig.configure.requirements.helpText.listItem1" /></li>
|
|
<li><t k="gatekeeperConfig.configure.requirements.helpText.listItem2" /></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="spacer"></div>
|
|
<div v-if="!showYamlEditor" class="text-center">
|
|
<button
|
|
type="button"
|
|
class="btn role-secondary"
|
|
:class="{ disabled: saving }"
|
|
:disable="saving"
|
|
@click="openYamlEditor"
|
|
>
|
|
<t k="generic.customize" />
|
|
</button>
|
|
<AsyncButton
|
|
mode="enable"
|
|
:disabled="showYamlEditor"
|
|
v-bind="$attrs"
|
|
@click="enableGatekeeper"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<section v-if="showYamlEditor">
|
|
<CodeMirror
|
|
class="code-mirror"
|
|
:value="config.spec.valuesYaml"
|
|
:options="cmOptions"
|
|
@onInput="onInput"
|
|
@onReady="onReady"
|
|
@onChanges="onChanges"
|
|
/>
|
|
<Footer
|
|
mode="enable"
|
|
@errors="errors"
|
|
@save="enableGatekeeper"
|
|
@done="openYamlEditor"
|
|
/>
|
|
</section>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss">
|
|
.gatekeeper-config {
|
|
.code-mirror {
|
|
min-height: 200px;
|
|
}
|
|
}
|
|
|
|
article {
|
|
font-size: .8em;
|
|
&.info {
|
|
padding: 10px 0;
|
|
|
|
p {
|
|
padding-bottom: 16px;
|
|
}
|
|
}
|
|
}
|
|
|
|
.action-group {
|
|
padding-top: 20px;
|
|
.col {
|
|
text-align: center;
|
|
flex: 1 1;
|
|
}
|
|
.col:first-of-type {
|
|
border-right: 1px solid;
|
|
}
|
|
}
|
|
</style>
|