ui/lib/shared/addon/components/new-catalog/component.js

418 lines
13 KiB
JavaScript

import { get, set, computed, setProperties, observer} from '@ember/object';
import { scheduleOnce } from '@ember/runloop';
import { alias, notEmpty } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import Component from '@ember/component';
import NewOrEdit from 'shared/mixins/new-or-edit';
import C from 'ui/utils/constants';
import Util from 'ui/utils/util';
import { compare as compareVersion } from 'ui/utils/parse-version';
import { task } from 'ember-concurrency';
import YAML from 'npm:yamljs';
import layout from './template';
import { stringifyAnswer } from 'shared/utils/evaluate';
export default Component.extend(NewOrEdit, {
layout,
catalog: service(),
intl: service(),
scope: service(),
router: service(),
settings: service(),
globalStore: service(),
isGKE: alias('scope.currentCluster.isGKE'),
namespaceErrors: null,
allTemplates: null,
templateResource: null,
namespaceResource: null,
versionsArray: null,
versionsLinks: null,
actuallySave: true,
showHeader: true,
showPreview: true,
customizeNamespace: false,
titleAdd: 'newCatalog.titleAdd',
titleUpgrade: 'newCatalog.titleUpgrade',
selectVersionAdd: 'newCatalog.selectVersionAdd',
selectVersionUpgrade: 'newCatalog.selectVersionUpgrade',
saveUpgrade: 'newCatalog.saveUpgrade',
saveNew: 'newCatalog.saveNew',
sectionClass: 'box mb-20',
showDefaultVersionOption: false,
classNames: ['launch-catalog'],
catalogApp: null,
primaryResource: alias('namespaceResource'),
editing: notEmpty('catalogApp.id'),
decoded: false,
srcSet: false,
detailExpanded: false,
previewOpen: false,
previewTab: null,
questionsArray: null,
selectedTemplateUrl: null,
selectedTemplateModel: null,
readmeContent: null,
appReadmeContent: null,
pastedAnswers: null,
noAppReadme: null,
actions: {
toogleDetailedDescriptions: function () {
set(this, 'detailExpanded', true);
},
toogleNamespace: function () {
set(this, 'customizeNamespace', true);
},
cancel: function() {
this.sendAction('cancel');
},
togglePreview: function() {
if (!get(this, 'previewOpen') && !get(this, 'decoded')) {
this.decodeFiles();
set(this, 'decoded', true);
}
this.toggleProperty('previewOpen');
},
selectPreviewTab: function(tab) {
set(this, 'previewTab', tab);
},
},
init() {
this._super(...arguments);
set(this, 'selectedTemplateModel', null);
if (get(this, 'previewOpen')) {
this.decodeFiles();
}
scheduleOnce('afterRender', () => {
if ( get(this, 'selectedTemplateUrl') ) {
this.templateChanged();
} else {
var def = get(this, 'templateResource.defaultVersion');
var links = get(this, 'versionLinks');
var app = get(this, 'catalogApp');
if (get(app, 'id') && !get(this, 'upgrade')) {
def = get(app, 'externalIdInfo.version');
}
if (links[def]) {
set(this, 'selectedTemplateUrl', links[def]);
} else {
set(this, 'selectedTemplateUrl', null);
}
}
});
},
didRender() {
if (!this.get('srcSet')) {
this.set('srcSet', true);
const $icon = this.$('img');
$icon.attr('src', $icon.data('src'));
this.$('img').on('error', () => {
$icon.attr('src', `${this.get('app.baseAssets')}assets/images/generic-catalog.svg`);
});
}
},
updateReadme: function() {
let model = get(this, 'selectedTemplateModel');
set(this, 'readmeContent', null);
set(this, 'appReadmeContent', null);
set(this, 'noAppReadme', false);
if ( model && model.hasLink('readme') ) {
model.followLink('readme').then((response) => {
if (this.isDestroyed || this.isDestroying) {
return;
}
set(this, 'readmeContent', response);
});
}
if ( model && model.hasLink('app-readme') ) {
set(this, 'noAppReadme', false);
model.followLink('app-readme').then((response) => {
if (this.isDestroyed || this.isDestroying) {
return;
}
set(this, 'appReadmeContent', response);
if ( !response ) {
set(this, 'noAppReadme', true);
}
});
} else {
set(this, 'noAppReadme', true);
}
},
filenames: computed('selectedTemplateModel', 'selectedTemplateModel.filesAsArray.[]', function(){
let files = get(this, 'selectedTemplateModel.filesAsArray').map( file => {
return {
label: file.name,
value: file.name
}
});
files.addObject({label: 'answers.yaml', value: 'answers'});
return files.sortBy('label');
}),
sortedVersions: computed('versionsArray','templateResource.defaultVersion', function() {
let out = get(this, 'versionsArray').sort((a,b) => {
if ( a.sortVersion && b.sortVersion ) {
return compareVersion(a.sortVersion, b.sortVersion);
} else {
return compareVersion(a.version, b.version);
}
});
let def = get(this, 'templateResource.defaultVersion');
if ( get(this, 'showDefaultVersionOption') && get(this, 'defaultUrl') ) {
out.unshift({version: get(this, 'intl').t('newCatalog.version.default', {version: def}), link: 'default'});
}
return out;
}),
defaultUrl: computed('templateResource.defaultVersion','versionLinks', function() {
var defaultVersion = get(this, 'templateResource.defaultVersion');
var versionLinks = get(this, 'versionLinks');
if ( defaultVersion && versionLinks && versionLinks[defaultVersion] ) {
return versionLinks[defaultVersion];
}
return null;
}),
decodeFiles: function() {
let files = (get(this, 'selectedTemplateModel.files')||[]);
if (get(this, 'templateKind') === 'helm' && Object.keys(files).length) {
Object.keys(files).forEach( f => {
files[f] = atob(files[f]);
// console.log('decoded file: ', files[f]);
});
}
},
getTemplate: task(function * () {
var url = get(this, 'selectedTemplateUrl');
if ( url === 'default' ) {
let defaultUrl = get(this, 'defaultUrl');
if ( defaultUrl ) {
url = defaultUrl;
} else {
url = null;
}
}
if (url) {
var version = get(this, 'settings.rancherVersion');
if ( version ) {
url = Util.addQueryParam(url, 'rancherVersion', version);
}
var current = get(this, 'catalogApp.answers');
if ( !current ) {
current = {};
set(this, 'catalogApp.answers', current);
}
var selectedTemplateModel = yield get(this, 'catalog').fetchByUrl(url).then((response) => {
if (response.questions) {
const questions = [];
response.questions.forEach((q) => {
questions.push(q);
const subquestions = get(q, 'subquestions');
if ( subquestions ) {
questions.pushObjects(subquestions);
}
});
questions.forEach((item) => {
// This will be the component that is rendered to edit this answer
item.inputComponent = 'schema/input-'+item.type;
// Only types marked supported will show the component, Ember will explode if the component doesn't exist
item.supported = C.SUPPORTED_SCHEMA_INPUTS.indexOf(item.type) >= 0;
if (typeof current[item.variable] !== 'undefined') {
// If there's an existing value, use it (for upgrade)
item.answer = current[item.variable];
} else if (item.type === 'service' || item.type === 'certificate') {
// Loaded async and then the component picks the default
} else if ( item.type === 'boolean' ) {
// Coerce booleans
item.answer = (item.default === 'true' || item.default === true);
} else {
// Everything else
item.answer = item.default||null;
}
});
}
return response;
});
set(this, 'selectedTemplateModel', selectedTemplateModel);
set(this, 'previewTab', Object.keys(selectedTemplateModel.get('files')||[])[0]);
} else {
set(this, 'selectedTemplateModel', null);
set(this, 'readmeContent', null);
set(this, 'appReadmeContent', null);
set(this, 'noAppReadme', false);
}
this.updateReadme();
}),
templateChanged: observer('selectedTemplateUrl','templateResource.defaultVersion', function() {
get(this, 'getTemplate').perform();
}),
answers: computed('selectedTemplateModel.questions.@each.{variable,answer}', function() {
var out = {};
(get(this, 'selectedTemplateModel.questions') || []).forEach((item) => {
out[item.variable] = stringifyAnswer(item.answer);
(get(item, 'subquestions') || []).forEach((sub) => {
out[sub.variable] = stringifyAnswer(sub.answer);
});
});
return out;
}),
answersArray: computed('selectedTemplateModel.questions', 'catalogApp.answers', function() {
let model = get(this, 'selectedTemplateModel');
if (get(model, 'questions')) {
const questions = [];
(get(this, 'selectedTemplateModel.questions') || []).forEach((q) => {
questions.push(q);
const subquestions = get(q, 'subquestions');
if ( subquestions ) {
questions.pushObjects(subquestions);
}
});
return questions;
} else {
return get(this, 'catalogApp.answers');
}
}),
answersString: computed('answersArray.@each.{variable,answer}', function() {
let model = get(this, 'selectedTemplateModel');
if (get(model, 'questions')) {
let neu = {};
(get(this, 'answersArray')||[]).forEach((a) => {
neu[a.variable] = a.answer || a.default;
});
return YAML.stringify(neu);
} else {
return JSON.stringify(get(this, 'answersArray'));
}
}),
validate() {
var errors = [];
errors.pushObjects(get(this, 'namespaceErrors')||[]);
if (get(this, 'selectedTemplateModel.questions')) {
get(this, 'selectedTemplateModel.questions').forEach((item) => {
if (item.required && item.type !== 'boolean' && !item.answer) {
errors.push(`${item.label} is required`);
}
});
}
if (errors.length) {
set(this, 'errors', errors.uniq());
return false;
}
return true;
},
newExternalId: computed('selectedTemplateModel.id', function() {
return C.EXTERNAL_ID.KIND_CATALOG + C.EXTERNAL_ID.KIND_SEPARATOR + get(this, 'selectedTemplateModel.id');
}),
willSave() {
set(this, 'errors', null);
var ok = this.validate();
if (!ok) {
// Validation failed
return false;
}
if ( get(this, 'actuallySave') ) {
if (get(this, 'selectedTemplateModel.questions')) {
set(get(this, 'catalogApp'), 'answers', get(this, 'answers'));
}
return true;
} else {
// TODO 2.0 this is part of the volumes stuff so we need to investigate if this still works
// let versionId = null;
// if ( get(this, 'selectedTemplateUrl') !== 'default' && get(this, 'selectedTemplateModel') ) {
// versionId = get(this, 'selectedTemplateModel.id');
// }
// this.sendAction('doSave', {
// answers: get(this, 'answers'),
// externalId: get(this, 'newExternalId'),
// templateId: get(this, 'templateResource.id'),
// templateVersionId: versionId,
// });
return false;
}
},
didSave(neu) {
let app = get(this, 'catalogApp');
if (get(app, 'id')) {
return app.doAction('upgrade', {
externalId: get(this, 'selectedTemplateModel.externalId'),
answers: get(app, 'answers'),
}).then(resp => {
return resp;
}).catch(err => {
return err;
});
} else {
setProperties(app, {
targetNamespace: neu.name,
externalId: get(this, 'selectedTemplateModel.externalId'),
projectId: get(neu, 'projectId'),
});
return app.save().then(() => {
return get(this, 'primaryResource');
});
}
},
doneSaving() {
var projectId = get(this, 'scope.currentProject.id');
return get(this, 'router').transitionTo('apps-tab.index', projectId);
}
});