use model dirty tracking to track changes

This commit is contained in:
Matthew Irish 2018-10-15 09:38:05 -05:00
parent e5ff92962d
commit 077f366954
9 changed files with 65 additions and 41 deletions

View File

@ -74,13 +74,6 @@ export default Component.extend(FocusOnInsertMixin, {
} }
}, },
willDestroyElement() {
this._super(...arguments);
if (this.model.isError && !this.model.isDestroyed) {
this.model.rollbackAttributes();
}
},
waitForKeyUp: task(function*() { waitForKeyUp: task(function*() {
while (true) { while (true) {
let event = yield waitForEvent(document.body, 'keyup'); let event = yield waitForEvent(document.body, 'keyup');
@ -115,6 +108,25 @@ export default Component.extend(FocusOnInsertMixin, {
canDelete: alias('updatePath.canDelete'), canDelete: alias('updatePath.canDelete'),
canEdit: alias('updatePath.canUpdate'), canEdit: alias('updatePath.canUpdate'),
v2UpdatePath: queryRecord(
'capabilities',
context => {
if (context.mode === 'create' || context.isV2 === false) {
return {};
}
let backend = context.model.belongsTo('engine').id;
let id = context.model.id;
return {
id: `${backend}/metadata/${id}`,
};
},
'isV2',
'model',
'model.id',
'mode'
),
canEditV2Secret: alias('updatePath.canUpdate'),
requestInFlight: or('model.isLoading', 'model.isReloading', 'model.isSaving'), requestInFlight: or('model.isLoading', 'model.isReloading', 'model.isSaving'),
buttonDisabled: or( buttonDisabled: or(
@ -164,7 +176,9 @@ export default Component.extend(FocusOnInsertMixin, {
// successCallback is called in the context of the component // successCallback is called in the context of the component
persistKey(successCallback) { persistKey(successCallback) {
let secret = this.model;
let model = this.modelForData; let model = this.modelForData;
let isV2 = this.isV2;
let key = model.get('path') || model.id; let key = model.get('path') || model.id;
if (key.startsWith('/')) { if (key.startsWith('/')) {
@ -174,12 +188,27 @@ export default Component.extend(FocusOnInsertMixin, {
return model.save().then(() => { return model.save().then(() => {
if (!model.isError) { if (!model.isError) {
if (isV2 && Object.keys(secret.changedAttributes()).length) {
// save secret metadata
secret
.save()
.then(() => {
this.saveComplete(successCallback, key);
})
.catch(e => {
this.set(e, e.errors.join(' '));
});
} else {
this.saveComplete(successCallback, key);
}
}
});
},
saveComplete(callback, key) {
if (this.wizard.featureState === 'secret') { if (this.wizard.featureState === 'secret') {
this.wizard.transitionFeatureMachine('secret', 'CONTINUE'); this.wizard.transitionFeatureMachine('secret', 'CONTINUE');
} }
successCallback(key); callback(key);
}
});
}, },
checkRows() { checkRows() {
@ -203,14 +232,11 @@ export default Component.extend(FocusOnInsertMixin, {
handleChange() { handleChange() {
this.set('codemirrorString', this.secretData.toJSONString(true)); this.set('codemirrorString', this.secretData.toJSONString(true));
this.modelForData.set('secretData', this.secretData.toJSON());
}, },
createOrUpdateKey(type, event) { createOrUpdateKey(type, event) {
event.preventDefault(); event.preventDefault();
const newData = this.secretData.toJSON();
let model = this.modelForData;
model.set('secretData', newData);
// prevent from submitting if there's no key // prevent from submitting if there's no key
// maybe do something fancier later // maybe do something fancier later
if (type === 'create' && isBlank(model.get('path') || model.id)) { if (type === 'create' && isBlank(model.get('path') || model.id)) {
@ -241,7 +267,7 @@ export default Component.extend(FocusOnInsertMixin, {
const data = this.secretData; const data = this.secretData;
if (isNone(data.findBy('name', ''))) { if (isNone(data.findBy('name', ''))) {
data.pushObject({ name: '', value: '' }); data.pushObject({ name: '', value: '' });
this.set('codemirrorString', data.toJSONString(true)); this.send('handleChange');
} }
this.checkRows(); this.checkRows();
}, },
@ -254,7 +280,7 @@ export default Component.extend(FocusOnInsertMixin, {
} }
data.removeObject(item); data.removeObject(item);
this.checkRows(); this.checkRows();
this.set('codemirrorString', data.toJSONString(true)); this.send('handleChange');
}, },
toggleAdvanced(bool) { toggleAdvanced(bool) {

View File

@ -12,9 +12,5 @@ export default Controller.extend(BackendCrumbMixin, {
// so we have to manually bubble here // so we have to manually bubble here
this.send('refreshModel'); this.send('refreshModel');
}, },
hasChanges(hasChanges) {
this.send('hasDataChanges', hasChanges);
},
}, },
}); });

View File

@ -11,9 +11,6 @@ export default Controller.extend(BackendCrumbMixin, {
refresh: function() { refresh: function() {
this.send('refreshModel'); this.send('refreshModel');
}, },
hasChanges(hasChanges) {
this.send('hasDataChanges', hasChanges);
},
toggleAdvancedEdit(bool) { toggleAdvancedEdit(bool) {
this.set('preferAdvancedEdit', bool); this.set('preferAdvancedEdit', bool);
this.get('backendController').set('preferAdvancedEdit', bool); this.get('backendController').set('preferAdvancedEdit', bool);

View File

@ -15,10 +15,6 @@ export default Controller.extend(BackendCrumbMixin, {
this.send('refreshModel'); this.send('refreshModel');
}, },
hasChanges(hasChanges) {
this.send('hasDataChanges', hasChanges);
},
toggleAdvancedEdit(bool) { toggleAdvancedEdit(bool) {
this.set('preferAdvancedEdit', bool); this.set('preferAdvancedEdit', bool);
this.get('backendController').set('preferAdvancedEdit', bool); this.get('backendController').set('preferAdvancedEdit', bool);

View File

@ -17,10 +17,6 @@ export default Controller.extend(BackendCrumbMixin, {
this.send('refreshModel'); this.send('refreshModel');
}, },
hasChanges(hasChanges) {
this.send('hasDataChanges', hasChanges);
},
toggleAdvancedEdit(bool) { toggleAdvancedEdit(bool) {
this.set('preferAdvancedEdit', bool); this.set('preferAdvancedEdit', bool);
this.get('backendController').set('preferAdvancedEdit', bool); this.get('backendController').set('preferAdvancedEdit', bool);

View File

@ -1,6 +1,6 @@
import Secret from './secret'; import Secret from './secret';
import DS from 'ember-data'; import DS from 'ember-data';
import { bool } from '@ember/object/computed'; import { alias, bool } from '@ember/object/computed';
const { attr, belongsTo } = DS; const { attr, belongsTo } = DS;

View File

@ -1,5 +1,7 @@
import DS from 'ember-data'; import DS from 'ember-data';
import { computed } from '@ember/object';
import { match } from '@ember/object/computed'; import { match } from '@ember/object/computed';
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
const { attr, hasMany, belongsTo, Model } = DS; const { attr, hasMany, belongsTo, Model } = DS;
@ -11,7 +13,18 @@ export default Model.extend({
updatedTime: attr(), updatedTime: attr(),
currentVersion: attr('number'), currentVersion: attr('number'),
oldestVersion: attr('number'), oldestVersion: attr('number'),
maxVersions: attr('number'), maxVersions: attr('number', {
casRequired: attr('boolean'), defaultValue: 10,
label: 'Maximum Number of Versions',
}),
casRequired: attr('boolean', {
defaultValue: false,
label: 'Require Check and Set',
helpText:
'Writes will only be allowed if the keys current version matches the version specified in the cas parameter',
}),
isFolder: match('id', /\/$/), isFolder: match('id', /\/$/),
fields: computed(function() {
return expandAttributeMeta(this, ['maxVersions', 'casRequired']);
}),
}); });

View File

@ -73,6 +73,7 @@ export default Route.extend(UnloadModelRoute, {
return hash({ return hash({
secret: this.store.queryRecord(modelType, { id: secret, backend }).then(resp => { secret: this.store.queryRecord(modelType, { id: secret, backend }).then(resp => {
if (modelType === 'secret-v2') { if (modelType === 'secret-v2') {
let backendModel = this.modelFor('vault.cluster.secrets.backend', backend);
let targetVersion = parseInt(params.version || resp.currentVersion, 10); let targetVersion = parseInt(params.version || resp.currentVersion, 10);
let version = resp.versions.findBy('version', targetVersion); let version = resp.versions.findBy('version', targetVersion);
// 404 if there's no version // 404 if there's no version
@ -81,6 +82,7 @@ export default Route.extend(UnloadModelRoute, {
set(error, 'httpStatus', 404); set(error, 'httpStatus', 404);
throw error; throw error;
} }
resp.set('engine', backendModel);
return version.reload().then(() => { return version.reload().then(() => {
resp.set('selectedVersion', version); resp.set('selectedVersion', version);
@ -136,14 +138,17 @@ export default Route.extend(UnloadModelRoute, {
}, },
willTransition(transition) { willTransition(transition) {
if (this.get('hasChanges')) { let model = this.controller.model;
let version = model.get('selectedVersion');
debugger; //eslint-disable-line
if (model.hasDirtyAttributes || (version && version.hasDirtyAttributes)) {
if ( if (
window.confirm( window.confirm(
'You have unsaved changes. Navigating away will discard these changes. Are you sure you want to discard your changes?' 'You have unsaved changes. Navigating away will discard these changes. Are you sure you want to discard your changes?'
) )
) { ) {
version && version.rollbackAttributes();
this.unloadModel(); this.unloadModel();
this.set('hasChanges', false);
return true; return true;
} else { } else {
transition.abort(); transition.abort();
@ -152,9 +157,5 @@ export default Route.extend(UnloadModelRoute, {
} }
return this._super(...arguments); return this._super(...arguments);
}, },
hasDataChanges(hasChanges) {
this.set('hasChanges', hasChanges);
},
}, },
}); });

View File

@ -5,7 +5,6 @@
mode=mode mode=mode
root=backendCrumb root=backendCrumb
capabilities=capabilities capabilities=capabilities
onDataChange=(action "hasChanges")
onRefresh=(action "refresh") onRefresh=(action "refresh")
onToggleAdvancedEdit=(action "toggleAdvancedEdit") onToggleAdvancedEdit=(action "toggleAdvancedEdit")
initialKey=initialKey initialKey=initialKey