adjust secret-edit component and associated templates to work for v1 and v2

This commit is contained in:
Matthew Irish 2018-10-05 22:05:53 -05:00
parent 46773189d5
commit 56d2852acf
5 changed files with 79 additions and 107 deletions

View File

@ -1,9 +1,9 @@
import { or } from '@ember/object/computed';
import { isBlank, isNone } from '@ember/utils';
import $ from 'jquery';
import { inject as service } from '@ember/service';
import Component from '@ember/component';
import { computed, get } from '@ember/object';
import { task, waitForEvent } from 'ember-concurrency';
import FocusOnInsertMixin from 'vault/mixins/focus-on-insert';
import keys from 'vault/lib/keycodes';
import KVObject from 'vault/lib/kv-object';
@ -18,6 +18,7 @@ export default Component.extend(FocusOnInsertMixin, {
// a key model
key: null,
model: null,
// a value to pre-fill the key input - this is populated by the corresponding
// 'initialKey' queryParam
@ -44,10 +45,15 @@ export default Component.extend(FocusOnInsertMixin, {
codemirrorString: null,
hasLintError: false,
isV2: false,
init() {
this._super(...arguments);
const secrets = this.get('key.secretData');
let secrets = this.model.secretData;
if (!secrets && this.model.selectedVersion) {
this.set('isV2', true);
secrets = this.model.belongsTo('selectedVersion').value().secretData;
}
const data = KVObject.create({ content: [] }).fromJSON(secrets);
this.set('secretData', data);
this.set('codemirrorString', data.toJSONString());
@ -56,81 +62,75 @@ export default Component.extend(FocusOnInsertMixin, {
}
this.checkRows();
if (this.get('wizard.featureState') === 'details' && this.get('mode') === 'create') {
let engine = this.get('key').backend.includes('kv') ? 'kv' : this.get('key').backend;
let engine = this.get('model').backend.includes('kv') ? 'kv' : this.get('model').backend;
this.get('wizard').transitionFeatureMachine('details', 'CONTINUE', engine);
}
if (this.get('mode') === 'edit') {
if (this.mode === 'edit') {
this.send('addRow');
}
},
didInsertElement() {
this._super(...arguments);
$(document).on('keyup.keyEdit', this.onEscape.bind(this));
},
willDestroyElement() {
this._super(...arguments);
const key = this.get('key');
if (get(key, 'isError') && !key.isDestroyed) {
key.rollbackAttributes();
if (this.model.isError && !this.model.isDestroyed) {
model.rollbackAttributes();
}
$(document).off('keyup.keyEdit');
},
waitForKeyUp: task(function*() {
while (true) {
let event = yield waitForEvent(document.body, 'keyup');
this.onEscape(event);
}
})
.on('didInsertElement')
.cancelOn('willDestroyElement'),
partialName: computed('mode', function() {
return `partials/secret-form-${this.get('mode')}`;
}),
showPrefix: or('key.initialParentKey', 'key.parentKey'),
requestInFlight: or('key.isLoading', 'key.isReloading', 'key.isSaving'),
requestInFlight: or('model.isLoading', 'model.isReloading', 'model.isSaving'),
buttonDisabled: or(
'requestInFlight',
'key.isFolder',
'key.isError',
'key.flagsIsInvalid',
'model.isFolder',
'model.isError',
'model.flagsIsInvalid',
'hasLintError',
'error'
),
modelForData: computed('isV2', 'model', function() {
return this.isV2 ? this.model.belongsTo('selectedVersion').value() : this.model;
}),
basicModeDisabled: computed('secretDataIsAdvanced', 'showAdvancedMode', function() {
return this.get('secretDataIsAdvanced') || this.get('showAdvancedMode') === false;
return this.secretDataIsAdvanced || this.showAdvancedMode === false;
}),
secretDataAsJSON: computed('secretData', 'secretData.[]', function() {
return this.get('secretData').toJSON();
return this.secretData.toJSON();
}),
secretDataIsAdvanced: computed('secretData', 'secretData.[]', function() {
return this.get('secretData').isAdvanced();
return this.secretData.isAdvanced();
}),
hasDataChanges() {
const keyDataString = this.get('key.dataAsJSONString');
const sameData = this.get('secretData').toJSONString() === keyDataString;
if (sameData === false) {
this.set('lastChange', Date.now());
}
this.get('onDataChange')(!sameData);
},
showAdvancedMode: computed('preferAdvancedEdit', 'secretDataIsAdvanced', 'lastChange', function() {
return this.get('secretDataIsAdvanced') || this.get('preferAdvancedEdit');
return this.secretDataIsAdvanced || this.preferAdvancedEdit;
}),
transitionToRoute() {
this.get('router').transitionTo(...arguments);
this.router.transitionTo(...arguments);
},
onEscape(e) {
if (e.keyCode !== keys.ESC || this.get('mode') !== 'show') {
if (e.keyCode !== keys.ESC || this.mode !== 'show') {
return;
}
const parentKey = this.get('key.parentKey');
const parentKey = this.model.parentKey;
if (parentKey) {
this.transitionToRoute(LIST_ROUTE, parentKey);
} else {
@ -139,25 +139,19 @@ export default Component.extend(FocusOnInsertMixin, {
},
// successCallback is called in the context of the component
persistKey(method, successCallback, isCreate) {
let model = this.get('key');
let key = model.get('id');
persistKey(successCallback) {
let model = this.modelForData;
let key = model.get('path') || model.id;
if (key.startsWith('/')) {
key = key.replace(/^\/+/g, '');
model.set('id', key);
model.set(model.pathAttr, key);
}
if (isCreate && typeof model.createRecord === 'function') {
// create an ember data model from the proxy
model = model.createRecord(model.get('backend'));
this.set('key', model);
}
return model[method]().then(() => {
if (!get(model, 'isError')) {
if (this.get('wizard.featureState') === 'secret') {
this.get('wizard').transitionFeatureMachine('secret', 'CONTINUE');
return model.save().then(() => {
if (!model.isError) {
if (this.wizard.featureState === 'secret') {
this.wizard.transitionFeatureMachine('secret', 'CONTINUE');
}
successCallback(key);
}
@ -165,85 +159,78 @@ export default Component.extend(FocusOnInsertMixin, {
},
checkRows() {
if (this.get('secretData').get('length') === 0) {
if (this.secretData.length === 0) {
this.send('addRow');
}
},
actions: {
//submit on shift + enter
handleKeyDown(e) {
e.stopPropagation();
if (!(e.keyCode === keys.ENTER && e.metaKey)) {
return;
}
let $form = this.$('form');
let $form = this.element.querySelector('form');
console.log('form is: ', $form);
if ($form.length) {
$form.submit();
}
$form = null;
},
handleChange() {
this.set('codemirrorString', this.get('secretData').toJSONString(true));
this.hasDataChanges();
this.set('codemirrorString', this.secretData.toJSONString(true));
},
createOrUpdateKey(type, event) {
event.preventDefault();
const newData = this.get('secretData').toJSON();
this.get('key').set('secretData', newData);
const newData = this.secretData.toJSON();
let model = this.modelForData;
model.set('secretData', newData);
// prevent from submitting if there's no key
// maybe do something fancier later
if (type === 'create' && isBlank(this.get('key.id'))) {
if (type === 'create' && isBlank(model.get('path') || model.id)) {
return;
}
this.persistKey(
'save',
key => {
this.hasDataChanges();
this.transitionToRoute(SHOW_ROUTE, key);
},
type === 'create'
);
this.persistKey(key => {
this.transitionToRoute(SHOW_ROUTE, key);
});
},
deleteKey() {
this.persistKey('destroyRecord', () => {
this.model.destroyRecord().then(() => {
this.transitionToRoute(LIST_ROOT_ROUTE);
});
},
refresh() {
this.get('onRefresh')();
this.onRefresh();
},
addRow() {
const data = this.get('secretData');
const data = this.secretData;
if (isNone(data.findBy('name', ''))) {
data.pushObject({ name: '', value: '' });
this.set('codemirrorString', data.toJSONString(true));
}
this.checkRows();
this.hasDataChanges();
},
deleteRow(name) {
const data = this.get('secretData');
const data = this.secretData;
const item = data.findBy('name', name);
if (isBlank(item.name)) {
return;
}
data.removeObject(item);
this.checkRows();
this.hasDataChanges();
this.set('codemirrorString', data.toJSONString(true));
this.rerender();
},
toggleAdvanced(bool) {
this.get('onToggleAdvancedEdit')(bool);
this.onToggleAdvancedEdit(bool);
},
codemirrorUpdated(val, codemirror) {
@ -252,7 +239,7 @@ export default Component.extend(FocusOnInsertMixin, {
const noErrors = codemirror.state.lint.marked.length === 0;
if (noErrors) {
try {
this.get('secretData').fromJSONString(val);
this.secretData.fromJSONString(val);
} catch (e) {
this.set('error', e.message);
}
@ -262,7 +249,7 @@ export default Component.extend(FocusOnInsertMixin, {
},
formatJSON() {
this.set('codemirrorString', this.get('secretData').toJSONString(true));
this.set('codemirrorString', this.secretData.toJSONString(true));
},
},
});

View File

@ -76,7 +76,10 @@ export default Route.extend(UnloadModelRoute, {
if (modelType === 'secret-v2') {
// TODO, find by query param to enable viewing versions
let version = resp.versions.findBy('version', resp.currentVersion);
version.reload();
return version.reload().then(() => {
resp.set('selectedVersion', version);
return resp;
});
}
return resp;
}),
@ -127,6 +130,8 @@ export default Route.extend(UnloadModelRoute, {
},
willTransition(transition) {
console.log(this.hasChanges);
console.log(this.controller.model.hasDirtyAttributes);
if (this.get('hasChanges')) {
if (
window.confirm(

View File

@ -1,4 +1,3 @@
{{#unless key.isFolder}}
{{#if showAdvancedMode}}
{{json-editor
value=codemirrorString
@ -44,5 +43,4 @@
</div>
</div>
{{/each}}
{{/if}}
{{/unless}}
{{/if}}

View File

@ -1,30 +1,12 @@
<form class="{{if showAdvancedMode 'advanced-edit' 'simple-edit'}}" onsubmit={{action "createOrUpdateKey" "create"}} onchange={{action "handleChange"}}>
<form class="{{if showAdvancedMode 'advanced-edit' 'simple-edit'}}" onsubmit={{action "createOrUpdateKey" "create"}}>
<div class="field box is-fullwidth is-sideless is-marginless">
<NamespaceReminder @mode="create" @noun="secret" />
{{message-error model=key errorMessage=error}}
<MessageError @model={{model}} @errorMessage={{error}} />
<label class="label is-font-weight-normal" for="kv-key">Path for this secret</label>
<div class="field has-addons">
{{#if (not-eq key.initialParentKey '') }}
{{! need this to prevent a shift in the layout before we transition when saving }}
{{#if key.isCreating}}
<p class="control is-no-flex-grow">
<button type="button" class="button is-static has-background-grey-lighter has-text-grey has-default-border">
{{key.initialParentKey}}
</button>
</p>
{{else}}
<p class="control is-no-flex-grow">
<button type="button" class="button is-static">
{{key.parentKey}}
</button>
</p>
{{/if}}
{{/if}}
<p class="control is-expanded">
{{input data-test-secret-path=true id="kv-key" class="input" value=key.keyWithoutParent}}
</p>
</div>
{{#if key.isFolder}}
<p class="control is-expanded">
{{input data-test-secret-path=true id="kv-key" class="input" value=(get modelForData modelForData.pathAttr)}}
</p>
{{#if modelForData.isFolder}}
<p class="help is-danger">
The secret path may not end in <code>/</code>
</p>
@ -47,7 +29,7 @@
<div class="control">
{{#secret-link
mode="list"
secret=key.initialParentKey
secret=model.parentKey
class="button"
}}
Cancel

View File

@ -1,6 +1,6 @@
{{#if showAdvancedMode}}
{{json-editor
value=key.dataAsJSONString
value=modelForData.dataAsJSONString
options=(hash
readOnly=true
)
@ -16,7 +16,7 @@
</div>
</div>
</div>
{{#each-in key.secretData as |key value|}}
{{#each-in modelForData.secretData as |key value|}}
{{#info-table-row label=key value=value alwaysRender=true}}
{{masked-input value=value displayOnly=true}}
{{/info-table-row}}