mirror of https://github.com/rancher/dashboard.git
403 lines
8.4 KiB
Vue
403 lines
8.4 KiB
Vue
<script>
|
|
import CodeMirror from './CodeMirror';
|
|
import FileDiff from './FileDiff';
|
|
import AsyncButton from './AsyncButton';
|
|
|
|
import {
|
|
MODE,
|
|
_CREATE,
|
|
_VIEW,
|
|
_EDIT,
|
|
_PREVIEW,
|
|
EDIT_YAML
|
|
} from '@/config/query-params';
|
|
|
|
import { mapPref, DIFF } from '@/store/prefs';
|
|
|
|
export default {
|
|
components: {
|
|
CodeMirror,
|
|
FileDiff,
|
|
AsyncButton
|
|
},
|
|
|
|
props: {
|
|
forCreate: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
obj: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
|
|
value: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
|
|
doneRoute: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
|
|
parentRoute: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
|
|
hasEditAsForm: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
|
|
parentParams: {
|
|
type: Object,
|
|
default: null,
|
|
},
|
|
doneOverride: {
|
|
type: Function,
|
|
default: null
|
|
},
|
|
showHeader: {
|
|
type: Boolean,
|
|
default: true
|
|
}
|
|
},
|
|
|
|
data() {
|
|
let mode;
|
|
|
|
if ( this.forCreate ) {
|
|
mode = _CREATE;
|
|
} else {
|
|
mode = this.$route.query.mode || _VIEW;
|
|
}
|
|
|
|
if ( mode === _PREVIEW ) {
|
|
mode = _EDIT;
|
|
}
|
|
|
|
return {
|
|
currentValue: this.value,
|
|
mode,
|
|
errors: null
|
|
};
|
|
},
|
|
|
|
computed: {
|
|
schema() {
|
|
return this.$store.getters['cluster/schemaFor'](this.obj.type);
|
|
},
|
|
|
|
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 )
|
|
};
|
|
},
|
|
|
|
isCreate() {
|
|
return this.mode === _CREATE;
|
|
},
|
|
isView() {
|
|
return this.mode === _VIEW;
|
|
},
|
|
isEdit() {
|
|
return this.mode === _EDIT;
|
|
},
|
|
isPreview() {
|
|
return this.mode === _PREVIEW;
|
|
},
|
|
|
|
showEditAsForm() {
|
|
return this.isEdit && this.hasEditAsForm;
|
|
},
|
|
|
|
canEdit() {
|
|
return this.obj.hasLink('update');
|
|
},
|
|
|
|
canDelete() {
|
|
return this.obj.hasLink('remove');
|
|
},
|
|
|
|
diffMode: mapPref(DIFF),
|
|
|
|
parentLink() {
|
|
const name = this.parentRoute || 'c-cluster-resource';
|
|
const params = this.parentParams || {
|
|
cluster: this.$store.state.clusterId,
|
|
resource: this.obj.type
|
|
};
|
|
|
|
const out = this.$router.resolve({ name, params }).href;
|
|
|
|
return out;
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
onInput(value) {
|
|
this.currentValue = value;
|
|
},
|
|
|
|
onReady(cm) {
|
|
cm.getMode().fold = 'yaml';
|
|
|
|
if ( this.isCreate ) {
|
|
cm.execCommand('foldAll');
|
|
}
|
|
},
|
|
|
|
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;
|
|
}
|
|
},
|
|
|
|
edit() {
|
|
this.mode = _EDIT;
|
|
this.$router.applyQuery({ [MODE]: this.mode });
|
|
},
|
|
|
|
preview() {
|
|
this.mode = _PREVIEW;
|
|
this.$router.applyQuery({ [MODE]: this.mode });
|
|
},
|
|
|
|
cancel() {
|
|
if (this.mode === _CREATE) {
|
|
return this.done();
|
|
}
|
|
this.mode = _VIEW;
|
|
this.$router.applyQuery({ [MODE]: this.mode });
|
|
this.currentValue = this.value;
|
|
},
|
|
|
|
navigateToEditAsForm() {
|
|
this.$router.applyQuery({ [EDIT_YAML]: undefined });
|
|
},
|
|
|
|
async save(buttonDone) {
|
|
try {
|
|
if ( this.isCreate ) {
|
|
await this.schema.followLink('collection', {
|
|
method: 'POST',
|
|
headers: {
|
|
'content-type': 'application/yaml',
|
|
accept: 'application/json',
|
|
},
|
|
data: this.currentValue,
|
|
});
|
|
} else {
|
|
const link = this.obj.hasLink('rioupdate') ? 'rioupdate' : 'update';
|
|
|
|
await this.obj.followLink(link, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'content-type': 'application/yaml',
|
|
accept: 'application/json',
|
|
},
|
|
data: this.currentValue,
|
|
});
|
|
}
|
|
|
|
buttonDone(true);
|
|
this.done();
|
|
} catch (err) {
|
|
if ( err && err.response && err.response.data ) {
|
|
const body = err.response.data;
|
|
|
|
if ( body && body.message ) {
|
|
this.errors = [body.message];
|
|
} else {
|
|
this.errors = [err];
|
|
}
|
|
} else {
|
|
this.errors = [err];
|
|
}
|
|
|
|
buttonDone(false);
|
|
}
|
|
},
|
|
async remove(buttonDone) {
|
|
await this.obj.remove();
|
|
buttonDone(true);
|
|
this.done();
|
|
},
|
|
|
|
done() {
|
|
if (this.doneOverride) {
|
|
return this.doneOverride();
|
|
}
|
|
|
|
if ( !this.doneRoute ) {
|
|
return;
|
|
}
|
|
|
|
this.$router.replace({
|
|
name: this.doneRoute,
|
|
params: { resource: this.obj.type }
|
|
});
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="root">
|
|
<header>
|
|
<h1 v-if="showHeader">
|
|
<span v-if="isCreate">Create {{ schema.attributes.kind }}</span>
|
|
<span v-else>{{ schema.attributes.kind }}: {{ obj.id }}</span>
|
|
</h1>
|
|
<div class="actions">
|
|
<AsyncButton
|
|
v-if="canDelete && isView"
|
|
key="delete"
|
|
mode="delete"
|
|
action-color="bg-error"
|
|
waiting-color="bg-error"
|
|
@click="remove"
|
|
/>
|
|
<button
|
|
v-if="canEdit && (isView || isPreview)"
|
|
type="button"
|
|
class="btn bg-primary"
|
|
@click="edit"
|
|
>
|
|
Edit
|
|
</button>
|
|
<span v-if="showEditAsForm">
|
|
<button class="btn bg-primary" @click="navigateToEditAsForm">Edit as form</button>
|
|
</span>
|
|
</div>
|
|
<div v-for="(err, idx) in errors" :key="idx" class="text-error">
|
|
{{ err }}
|
|
</div>
|
|
</header>
|
|
<div class="text-right">
|
|
<span v-if="isPreview" v-trim-whitespace class="btn-group btn-sm diff-mode">
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm bg-default"
|
|
:class="{'active': diffMode !== 'split'}"
|
|
@click="diffMode='unified'"
|
|
>Unified</button>
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm bg-default"
|
|
:class="{'active': diffMode === 'split'}"
|
|
@click="diffMode='split'"
|
|
>Split</button>
|
|
</span>
|
|
</div>
|
|
|
|
<CodeMirror
|
|
v-if="!isPreview"
|
|
:value="currentValue"
|
|
:options="cmOptions"
|
|
:footer-space="71"
|
|
@onInput="onInput"
|
|
@onReady="onReady"
|
|
@onChanges="onChanges"
|
|
/>
|
|
<FileDiff
|
|
v-if="isPreview"
|
|
:filename="obj.id + '.yaml'"
|
|
:side-by-side="diffMode === 'split'"
|
|
:orig="value"
|
|
:neu="currentValue"
|
|
:footer-space="71"
|
|
/>
|
|
<footer>
|
|
<div class="actions">
|
|
<button v-if="!isView" type="button" class="btn bg-transparent" @click="cancel">
|
|
Cancel
|
|
</button>
|
|
<button v-if="isEdit" type="button" class="btn bg-transparent" @click="preview">
|
|
Preview
|
|
</button>
|
|
<AsyncButton v-if="isEdit || isPreview" key="apply" mode="apply" @click="save" />
|
|
<AsyncButton v-if="isCreate" key="create" mode="create" @click="save" />
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
@import "~assets/styles/base/_variables.scss";
|
|
@import "~assets/styles/base/_functions.scss";
|
|
@import "~assets/styles/base/_mixins.scss";
|
|
|
|
.diff-mode {
|
|
position: absolute;
|
|
top: 95px;
|
|
right: 21px;
|
|
z-index: z-index("overContent");
|
|
}
|
|
|
|
footer .actions {
|
|
text-align: center;
|
|
}
|
|
</style>
|