From 01a842883f9d1391d302a981801f36f8e37a0501 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Tue, 17 Jul 2018 09:20:27 -0500 Subject: [PATCH 001/124] add xstate --- ui/package.json | 1 + ui/yarn.lock | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/ui/package.json b/ui/package.json index 9c0697c16a4..396a10c3871 100644 --- a/ui/package.json +++ b/ui/package.json @@ -105,6 +105,7 @@ "string.prototype.endswith": "^0.2.0", "string.prototype.startswith": "^0.2.0", "text-encoder-lite": "1.0.0", + "xstate": "^3.3.3", "yargs-parser": "^10.0.0" }, "engines": { diff --git a/ui/yarn.lock b/ui/yarn.lock index 1114633ae34..73b621ae69e 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -10139,6 +10139,10 @@ xmlhttprequest-ssl@1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d" +xstate@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/xstate/-/xstate-3.3.3.tgz#64177cd4473d4c2424b3df7d2434d835404b09a9" + xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" From c9e3d7afd11c1e21cd8511e67d411cecaef8792e Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Wed, 18 Jul 2018 14:58:26 -0500 Subject: [PATCH 002/124] wizard-view --- ui/app/components/wizard-view.js | 9 ++++ ui/app/services/wizard.js | 46 +++++++++++++++++++++ ui/app/templates/components/wizard-view.hbs | 1 + ui/app/templates/vault/cluster.hbs | 1 + 4 files changed, 57 insertions(+) create mode 100644 ui/app/components/wizard-view.js create mode 100644 ui/app/services/wizard.js create mode 100644 ui/app/templates/components/wizard-view.hbs diff --git a/ui/app/components/wizard-view.js b/ui/app/components/wizard-view.js new file mode 100644 index 00000000000..3b442d7ad8b --- /dev/null +++ b/ui/app/components/wizard-view.js @@ -0,0 +1,9 @@ +import Ember from 'ember'; + +const { inject, computed } = Ember; + +export default Ember.Component.extend({ + wizard: inject.service(), + + currentState: computed.alias('wizard.currentState'), +}); diff --git a/ui/app/services/wizard.js b/ui/app/services/wizard.js new file mode 100644 index 00000000000..1a75afe53ed --- /dev/null +++ b/ui/app/services/wizard.js @@ -0,0 +1,46 @@ +import Ember from 'ember'; +import { Machine } from 'xstate'; + +const { Service } = Ember; + +const CubbyholeMachine = Machine({ + key: 'cubbyhole', + initial: 'idle', + states: { + idle: { + on: { INTERACTION: 'active' }, + }, + active: { + on: { DISMISS: 'idle' }, + key: 'feature', + initial: 'create', + states: { + create: { + on: { EDIT: 'edit' }, + onEntry: [{ type: 'render', component: 'cubbyHoleHelp' }], + }, + edit: { + on: { SAVE: 'details' }, + onEntry: [{ type: 'render', component: 'cubbyHoleEditHelp' }], + }, + details: { + onEntry: [{ type: 'render', component: 'cubbyHoleSuccess' }], + }, + }, + }, + }, +}); + +export default Service.extend({ + currentState: null, + + init() { + this._super(...arguments); + this.set('currentState', CubbyholeMachine.initialState); + }, + + transitionMachine(intendedState, extendedState) { + let { actions, value } = CubbyholeMachine.transition(state); + this.set('currentState', value); + }, +}); diff --git a/ui/app/templates/components/wizard-view.hbs b/ui/app/templates/components/wizard-view.hbs new file mode 100644 index 00000000000..08fe578308e --- /dev/null +++ b/ui/app/templates/components/wizard-view.hbs @@ -0,0 +1 @@ +{{stringify currentState}} diff --git a/ui/app/templates/vault/cluster.hbs b/ui/app/templates/vault/cluster.hbs index 699cd4d3486..e462faedb96 100644 --- a/ui/app/templates/vault/cluster.hbs +++ b/ui/app/templates/vault/cluster.hbs @@ -1,3 +1,4 @@ + {{#if showNav}} From 2f97694d079d3a3925222d003133be8a332645c1 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Wed, 18 Jul 2018 22:03:40 -0500 Subject: [PATCH 003/124] tweak the statechart --- ui/app/services/wizard.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ui/app/services/wizard.js b/ui/app/services/wizard.js index 1a75afe53ed..8b02497eaab 100644 --- a/ui/app/services/wizard.js +++ b/ui/app/services/wizard.js @@ -8,10 +8,13 @@ const CubbyholeMachine = Machine({ initial: 'idle', states: { idle: { - on: { INTERACTION: 'active' }, + on: { + DISMISS: 'dismissed', + INTERACTION: 'active', + }, }, active: { - on: { DISMISS: 'idle' }, + on: { DISMISS: 'dismissed' }, key: 'feature', initial: 'create', states: { @@ -25,9 +28,14 @@ const CubbyholeMachine = Machine({ }, details: { onEntry: [{ type: 'render', component: 'cubbyHoleSuccess' }], + on: { RESET: 'create' }, }, }, }, + dismissed: { + on: { RESET: 'idle' }, + onEntry: ['saveState'], + }, }, }); From c7aa2c9ce99aa6a4bd81481e91ec6178bcfeb904 Mon Sep 17 00:00:00 2001 From: Madalyn Parker Date: Tue, 7 Aug 2018 11:24:51 -0400 Subject: [PATCH 004/124] preliminary functions and states for main wizard and secrets wizard --- ui/app/components/wizard-view.js | 7 +- ui/app/machines/secrets-machine.js | 67 ++++++++++ ui/app/machines/tutorial-machine.js | 36 ++++++ ui/app/services/wizard.js | 134 ++++++++++++++------ ui/app/templates/components/wizard-view.hbs | 5 +- 5 files changed, 207 insertions(+), 42 deletions(-) create mode 100644 ui/app/machines/secrets-machine.js create mode 100644 ui/app/machines/tutorial-machine.js diff --git a/ui/app/components/wizard-view.js b/ui/app/components/wizard-view.js index 3b442d7ad8b..690d40f2039 100644 --- a/ui/app/components/wizard-view.js +++ b/ui/app/components/wizard-view.js @@ -4,6 +4,11 @@ const { inject, computed } = Ember; export default Ember.Component.extend({ wizard: inject.service(), - currentState: computed.alias('wizard.currentState'), + isActive: computed('currentState', () => this.get('currentState') === 'active'), + isDismissed: computed('currentState', () => this.get('currentState') === 'dismissed'), + + dismissWizard() { + this.get('wizard').transitionMachine(this.get('currentState'), 'DISMISS'); + }, }); diff --git a/ui/app/machines/secrets-machine.js b/ui/app/machines/secrets-machine.js new file mode 100644 index 00000000000..4d9258ad585 --- /dev/null +++ b/ui/app/machines/secrets-machine.js @@ -0,0 +1,67 @@ +export default { + key: 'secrets', + initial: 'idle', + states: { + idle: { + on: { + CONTINUE: { + aws: { + cond: (extState, event) => event.selected === 'aws', + }, + cubbyhole: { + cond: (extState, event) => event.selected === 'ch', + }, + kv: { + cond: (extState, event) => event.selected === 'kv', + }, + }, + }, + }, + aws: { + on: { + RESET: 'idle', + DONE: 'complete', + }, + key: 'aws', + initial: 'credentials', + states: { + credentials: { + on: { + CONTINUE: 'role', + }, + }, + role: { + on: { + REPEAT: 'role', + }, + }, + }, + }, + cubbyhole: { + on: { + RESET: 'idle', + DONE: 'complete', + }, + key: 'ch', + initial: 'role', + states: { + role: { + on: { + REPEAT: 'role', + }, + }, + }, + }, + kv: { + on: { + RESET: 'idle', + DONE: 'complete', + }, + }, + complete: { + on: { + RESET: 'idle', + }, + }, + }, +}; diff --git a/ui/app/machines/tutorial-machine.js b/ui/app/machines/tutorial-machine.js new file mode 100644 index 00000000000..101ff4106b5 --- /dev/null +++ b/ui/app/machines/tutorial-machine.js @@ -0,0 +1,36 @@ +export default { + key: 'tutorial', + initial: 'idle', + states: { + active: { + on: { DISMISS: 'dismissed' }, + key: 'feature', + initial: 'select', + states: { + select: { + on: { + CONTINUE: { + onExit: ['saveFeatures'], + }, + }, + }, + }, + }, + idle: { + on: { + DISMISS: 'dismissed', + INTERACTION: 'active', + }, + }, + dismissed: { + on: { RESET: 'idle' }, + onEntry: ['saveState'], + }, + complete: { + on: { + RESET: 'idle', + DISMISS: 'dismissed', + }, + }, + }, +}; diff --git a/ui/app/services/wizard.js b/ui/app/services/wizard.js index 8b02497eaab..62d38006fa4 100644 --- a/ui/app/services/wizard.js +++ b/ui/app/services/wizard.js @@ -1,54 +1,108 @@ import Ember from 'ember'; import { Machine } from 'xstate'; -const { Service } = Ember; - -const CubbyholeMachine = Machine({ - key: 'cubbyhole', - initial: 'idle', - states: { - idle: { - on: { - DISMISS: 'dismissed', - INTERACTION: 'active', - }, - }, - active: { - on: { DISMISS: 'dismissed' }, - key: 'feature', - initial: 'create', - states: { - create: { - on: { EDIT: 'edit' }, - onEntry: [{ type: 'render', component: 'cubbyHoleHelp' }], - }, - edit: { - on: { SAVE: 'details' }, - onEntry: [{ type: 'render', component: 'cubbyHoleEditHelp' }], - }, - details: { - onEntry: [{ type: 'render', component: 'cubbyHoleSuccess' }], - on: { RESET: 'create' }, - }, - }, - }, - dismissed: { - on: { RESET: 'idle' }, - onEntry: ['saveState'], - }, - }, -}); +const { Service, getOwner } = Ember; + +import getStorage from 'vault/lib/token-storage'; +const machinesDir = 'vault/machines/'; + +import TutorialMachineConfig from 'vault/machines/tutorial-machine'; + +const TutorialMachine = Machine(TutorialMachineConfig); +let FeatureMachine = null; export default Service.extend({ currentState: null, + featureList: null, + featureState: null, + currentMachine: null, init() { this._super(...arguments); - this.set('currentState', CubbyholeMachine.initialState); + if (!this.storageHasKey('vault-tutorial-state')) { + this.saveExtState('vault-tutorial-state', 'idle'); + this.set('currentState', TutorialMachine.initialState); + } else { + this.set('currentState', this.getExtState('vault-tutorial-state')); + if (this.storageHasKey('vault-feature-list')) { + this.set('featureList', this.getExtState('vault-feature-list')); + if (this.storageHasKey('vault-feature-state')) { + this.set('featureState', this.getExtState('vault-feature-state')); + } else { + if (FeatureMachine !== null) { + this.set('featureState', FeatureMachine.initialState); + } else { + this.buildFeatureMachine(); + } + } + } + } }, - transitionMachine(intendedState, extendedState) { - let { actions, value } = CubbyholeMachine.transition(state); + transitionMachine(currentState, event) { + let { actions, value } = TutorialMachine.transition(currentState, event); this.set('currentState', value); + for (let action in actions) { + this.executeAction(action, event); + } + }, + + saveExtState(key, value) { + this.storage().setItem(key, value); + }, + + getExtState(key) { + return this.storage().getItem(key); + }, + + storageHasKey(key) { + return this.storage().keys().includes(key); + }, + + executeAction(action, event) { + switch (action) { + case 'saveFeatures': + this.saveFeatures(event.features); + break; + case 'completeFeature': + this.completeFeature(); + break; + default: + break; + } + }, + + saveFeatures(features) { + this.set('featuresList', features); + this.saveExtState('vault-feature-list', this.get('featuresList')); + this.buildFeatureMachine(); + }, + + buildFeatureMachine() { + const FeatureMachineConfig = getOwner(this).lookup( + `machine:${this.get('featuresList').objectAt(0)}-machine` + ); + FeatureMachine = Machine(FeatureMachineConfig); + this.set('currentMachine', this.get('featuresList').objectAt(0)); + this.set('featureState', FeatureMachine.initialState); + }, + + completeFeature() { + let features = this.get('featuresList'); + features.pop(); + this.saveExtState('vault-feature-list', this.get('featuresList')); + if (features.length > 0) { + FeatureMachine = Machine(JSON.loads(`${machinesDir}/${features.objectAt(0)}-machine`)); + this.set('currentMachine', features.objectAt(0)); + this.set('featureState', FeatureMachine.initialState); + } else { + this.completeTutorial(); + FeatureMachine = null; + TutorialMachine.transition(this.get('currentState'), 'DONE'); + } + }, + + storage() { + return getStorage(); }, }); diff --git a/ui/app/templates/components/wizard-view.hbs b/ui/app/templates/components/wizard-view.hbs index 08fe578308e..65cd5ef1dd6 100644 --- a/ui/app/templates/components/wizard-view.hbs +++ b/ui/app/templates/components/wizard-view.hbs @@ -1 +1,4 @@ -{{stringify currentState}} +
+

Current State: {{currentState}}

+ +
From e9cbc3a4541f79beed4cdfac6f18a37fa086557a Mon Sep 17 00:00:00 2001 From: Madalyn Parker Date: Wed, 8 Aug 2018 14:03:35 -0400 Subject: [PATCH 005/124] transition into feature machine properly, save selected features --- ui/app/components/wizard-view.js | 25 +++++- .../components/wizard/features-selection.js | 41 ++++++++++ ui/app/machines/tutorial-machine.js | 11 ++- ui/app/services/wizard.js | 82 +++++++++++++------ ui/app/templates/components/wizard-view.hbs | 22 ++++- .../components/wizard/features-selection.hbs | 7 ++ 6 files changed, 153 insertions(+), 35 deletions(-) create mode 100644 ui/app/components/wizard/features-selection.js create mode 100644 ui/app/templates/components/wizard/features-selection.hbs diff --git a/ui/app/components/wizard-view.js b/ui/app/components/wizard-view.js index 690d40f2039..0ec62cab84d 100644 --- a/ui/app/components/wizard-view.js +++ b/ui/app/components/wizard-view.js @@ -5,10 +5,29 @@ const { inject, computed } = Ember; export default Ember.Component.extend({ wizard: inject.service(), currentState: computed.alias('wizard.currentState'), - isActive: computed('currentState', () => this.get('currentState') === 'active'), - isDismissed: computed('currentState', () => this.get('currentState') === 'dismissed'), + featureState: computed.alias('wizard.featureState'), + currentMachine: computed('wizard.featureList', function() { + if (this.get('wizard.featureList') !== null) { + let machine = this.get('wizard.featureList').toArray().objectAt(0); + return machine.charAt(0).toUpperCase() + machine.slice(1); + } + return 'None'; + }), + isActive: computed('currentState', function() { + return this.get('currentState').indexOf('active') == 0; + }), + isDismissed: computed('currentState', function() { + return this.get('currentState') === 'dismissed'; + }), + isSelecting: computed('currentState', 'isActive', function() { + return this.get('isActive') && this.get('currentState').indexOf('select') > 0; + }), dismissWizard() { - this.get('wizard').transitionMachine(this.get('currentState'), 'DISMISS'); + this.get('wizard').transitionTutorialMachine(this.get('currentState'), 'DISMISS'); + }, + + advanceWizard() { + this.get('wizard').transitionTutorialMachine(this.get('currentState'), 'CONTINUE'); }, }); diff --git a/ui/app/components/wizard/features-selection.js b/ui/app/components/wizard/features-selection.js new file mode 100644 index 00000000000..cf9f7cc49c5 --- /dev/null +++ b/ui/app/components/wizard/features-selection.js @@ -0,0 +1,41 @@ +import Ember from 'ember'; + +const { inject, computed } = Ember; + +export default Ember.Component.extend({ + wizard: inject.service(), + allFeatures: ['secrets', 'authentication', 'policies', 'replication', 'tools'], + selectedFeatures: null, + hasFeatures: computed('selectedFeatures', function() { + return this.get('selectedFeatures') !== null && this.get('selectedFeatures').length > 0; + }), + finalFeatures: computed('allFeatures', 'selectedFeatures', function() { + let features = []; + let selected = this.get('selectedFeatures'); + this.get('allFeatures').forEach(function(feature) { + if (selected.includes(feature)) { + features.push(feature); + } + }); + return features; + }), + + actions: { + saveFeatures() { + this.get('wizard').saveFeatures(this.get('finalFeatures')); + this.get('wizard').transitionTutorialMachine('active.select', 'CONTINUE'); + }, + toggleFeature(event) { + if (this.get('selectedFeatures') === null) { + this.set('selectedFeatures', [event.target.value]); + } else { + if (this.get('selectedFeatures').includes(event.target.value)) { + let selected = this.get('selectedFeatures').without(event.target.value); + this.set('selectedFeatures', selected); + } else { + this.set('selectedFeatures', this.get('selectedFeatures').toArray().addObject(event.target.value)); + } + } + }, + }, +}); diff --git a/ui/app/machines/tutorial-machine.js b/ui/app/machines/tutorial-machine.js index 101ff4106b5..ed84178f1b1 100644 --- a/ui/app/machines/tutorial-machine.js +++ b/ui/app/machines/tutorial-machine.js @@ -9,26 +9,25 @@ export default { states: { select: { on: { - CONTINUE: { - onExit: ['saveFeatures'], - }, + CONTINUE: 'feature', }, }, + feature: {}, }, }, idle: { on: { DISMISS: 'dismissed', - INTERACTION: 'active', + CONTINUE: 'active', }, }, dismissed: { - on: { RESET: 'idle' }, + on: { CONTINUE: 'idle' }, onEntry: ['saveState'], }, complete: { on: { - RESET: 'idle', + CONTINUE: 'idle', DISMISS: 'dismissed', }, }, diff --git a/ui/app/services/wizard.js b/ui/app/services/wizard.js index 62d38006fa4..48085ce4a9e 100644 --- a/ui/app/services/wizard.js +++ b/ui/app/services/wizard.js @@ -1,15 +1,19 @@ import Ember from 'ember'; import { Machine } from 'xstate'; -const { Service, getOwner } = Ember; +const { Service } = Ember; import getStorage from 'vault/lib/token-storage'; -const machinesDir = 'vault/machines/'; import TutorialMachineConfig from 'vault/machines/tutorial-machine'; +import SecretsMachineConfig from 'vault/machines/secrets-machine'; const TutorialMachine = Machine(TutorialMachineConfig); let FeatureMachine = null; +const TUTORIAL_STATE = 'vault-tutorial-state'; +const FEATURE_LIST = 'vault-feature-list'; +const FEATURE_STATE = 'vault-feature-state'; +const MACHINES = { secrets: SecretsMachineConfig }; export default Service.extend({ currentState: null, @@ -19,18 +23,19 @@ export default Service.extend({ init() { this._super(...arguments); - if (!this.storageHasKey('vault-tutorial-state')) { - this.saveExtState('vault-tutorial-state', 'idle'); - this.set('currentState', TutorialMachine.initialState); + if (!this.storageHasKey(TUTORIAL_STATE)) { + this.saveState('currentState', TutorialMachine.initialState); + this.saveExtState(TUTORIAL_STATE, this.get('currentState')); } else { - this.set('currentState', this.getExtState('vault-tutorial-state')); - if (this.storageHasKey('vault-feature-list')) { - this.set('featureList', this.getExtState('vault-feature-list')); - if (this.storageHasKey('vault-feature-state')) { - this.set('featureState', this.getExtState('vault-feature-state')); + this.saveState('currentState', this.getExtState(TUTORIAL_STATE)); + if (this.storageHasKey(FEATURE_LIST)) { + this.set('featureList', this.getExtState(FEATURE_LIST)); + if (this.storageHasKey(FEATURE_STATE)) { + this.saveState('featureState', this.getExtState(FEATURE_STATE)); } else { if (FeatureMachine !== null) { - this.set('featureState', FeatureMachine.initialState); + this.saveState('featureState', FeatureMachine.initialState); + this.saveExtState(FEATURE_STATE, this.get('featureState')); } else { this.buildFeatureMachine(); } @@ -39,9 +44,34 @@ export default Service.extend({ } }, - transitionMachine(currentState, event) { + saveState(stateType, state) { + if (state.value) { + state = state.value; + } + + let stateKey = ''; + while (Ember.typeOf(state) === 'object') { + let newState = Object.keys(state); + stateKey += newState + '.'; + state = state[newState]; + } + stateKey += state; + this.set(stateType, stateKey); + }, + + transitionTutorialMachine(currentState, event) { let { actions, value } = TutorialMachine.transition(currentState, event); - this.set('currentState', value); + this.saveState('currentState', value); + this.saveExtState(TUTORIAL_STATE, this.get('currentState')); + for (let action in actions) { + this.executeAction(action, event); + } + }, + + transitionFeatureMachine(currentState, event) { + let { actions, value } = FeatureMachine.transition(currentState, event); + this.saveState('featureState', value); + this.saveExtState(FEATURE_STATE, value); for (let action in actions) { this.executeAction(action, event); } @@ -73,28 +103,32 @@ export default Service.extend({ }, saveFeatures(features) { - this.set('featuresList', features); - this.saveExtState('vault-feature-list', this.get('featuresList')); + this.set('featureList', features); + this.saveExtState(FEATURE_LIST, this.get('featureList')); this.buildFeatureMachine(); }, buildFeatureMachine() { - const FeatureMachineConfig = getOwner(this).lookup( - `machine:${this.get('featuresList').objectAt(0)}-machine` - ); + if (this.get('featureList') === null) { + return; + } + const FeatureMachineConfig = MACHINES[this.get('featureList').objectAt(0)]; FeatureMachine = Machine(FeatureMachineConfig); - this.set('currentMachine', this.get('featuresList').objectAt(0)); - this.set('featureState', FeatureMachine.initialState); + this.set('currentMachine', this.get('featureList').objectAt(0)); + this.saveState('featureState', FeatureMachine.initialState); + this.saveExtState(FEATURE_STATE, this.get('featureState')); }, completeFeature() { - let features = this.get('featuresList'); + let features = this.get('featureList'); features.pop(); - this.saveExtState('vault-feature-list', this.get('featuresList')); + this.saveExtState(FEATURE_LIST, this.get('featureList')); if (features.length > 0) { - FeatureMachine = Machine(JSON.loads(`${machinesDir}/${features.objectAt(0)}-machine`)); + const FeatureMachineConfig = MACHINES[this.get('featureList').objectAt(0)]; + FeatureMachine = Machine(FeatureMachineConfig); this.set('currentMachine', features.objectAt(0)); - this.set('featureState', FeatureMachine.initialState); + this.saveState('featureState', FeatureMachine.initialState); + this.saveExtState(FEATURE_STATE, this.get('featureState')); } else { this.completeTutorial(); FeatureMachine = null; diff --git a/ui/app/templates/components/wizard-view.hbs b/ui/app/templates/components/wizard-view.hbs index 65cd5ef1dd6..d8abecb4ae2 100644 --- a/ui/app/templates/components/wizard-view.hbs +++ b/ui/app/templates/components/wizard-view.hbs @@ -1,4 +1,22 @@ -
+
+ {{#unless isDismissed}} + + {{/unless}}

Current State: {{currentState}}

- +

Current Feature: {{currentMachine}}

+

Feature State: {{featureState}}

+ {{#unless isActive}} + + {{/unless}} + {{#if isActive}} + {{#if isSelecting}} + {{wizard/features-selection}} + {{/if}} + {{/if}}
diff --git a/ui/app/templates/components/wizard/features-selection.hbs b/ui/app/templates/components/wizard/features-selection.hbs new file mode 100644 index 00000000000..83ecbb2e0cb --- /dev/null +++ b/ui/app/templates/components/wizard/features-selection.hbs @@ -0,0 +1,7 @@ +
+ {{#each allFeatures as |feature|}} + + + {{/each}} + +
\ No newline at end of file From da96f158eef00b0e877868d86175e2d321f994b5 Mon Sep 17 00:00:00 2001 From: Madalyn Parker Date: Fri, 10 Aug 2018 18:06:49 -0400 Subject: [PATCH 006/124] add some styles, flesh out secrets --- .../{wizard-view.js => ui-wizard.js} | 14 +++ .../components/wizard/features-selection.js | 34 ++++++- ui/app/components/wizard/secrets-machine.js | 61 ++++++++++++ ui/app/components/wizard/setup-machine.js | 0 .../cluster/settings/mount-secret-backend.js | 3 + ui/app/machines/secrets-machine.js | 8 +- ui/app/machines/tutorial-machine.js | 5 +- ui/app/services/wizard.js | 29 +++++- ui/app/styles/components/ui-wizard.scss | 94 +++++++++++++++++++ ui/app/styles/core.scss | 1 + ui/app/templates/components/ui-wizard.hbs | 34 +++++++ ui/app/templates/components/wizard-view.hbs | 22 ----- .../components/wizard/features-selection.hbs | 21 ++++- .../components/wizard/secrets-machine.hbs | 15 +++ ui/app/templates/vault/cluster.hbs | 2 +- 15 files changed, 306 insertions(+), 37 deletions(-) rename ui/app/components/{wizard-view.js => ui-wizard.js} (70%) create mode 100644 ui/app/components/wizard/secrets-machine.js create mode 100644 ui/app/components/wizard/setup-machine.js create mode 100644 ui/app/styles/components/ui-wizard.scss create mode 100644 ui/app/templates/components/ui-wizard.hbs delete mode 100644 ui/app/templates/components/wizard-view.hbs create mode 100644 ui/app/templates/components/wizard/secrets-machine.hbs diff --git a/ui/app/components/wizard-view.js b/ui/app/components/ui-wizard.js similarity index 70% rename from ui/app/components/wizard-view.js rename to ui/app/components/ui-wizard.js index 0ec62cab84d..c4e674b28ac 100644 --- a/ui/app/components/wizard-view.js +++ b/ui/app/components/ui-wizard.js @@ -22,6 +22,16 @@ export default Ember.Component.extend({ isSelecting: computed('currentState', 'isActive', function() { return this.get('isActive') && this.get('currentState').indexOf('select') > 0; }), + isFeature: computed('currentMachine', function() { + return this.get('currentMachine') !== 'None'; + }), + isPaused: computed('currentState', function() { + return this.get('currentState') === 'paused'; + }), + isIdle: computed('currentState', function() { + return this.get('currentState') === 'idle'; + }), + isCollapsed: computed.or('isPaused', 'isIdle'), dismissWizard() { this.get('wizard').transitionTutorialMachine(this.get('currentState'), 'DISMISS'); @@ -30,4 +40,8 @@ export default Ember.Component.extend({ advanceWizard() { this.get('wizard').transitionTutorialMachine(this.get('currentState'), 'CONTINUE'); }, + + pauseWizard() { + this.get('wizard').transitionTutorialMachine(this.get('currentState'), 'PAUSE'); + }, }); diff --git a/ui/app/components/wizard/features-selection.js b/ui/app/components/wizard/features-selection.js index cf9f7cc49c5..ff6c89bc21d 100644 --- a/ui/app/components/wizard/features-selection.js +++ b/ui/app/components/wizard/features-selection.js @@ -4,17 +4,43 @@ const { inject, computed } = Ember; export default Ember.Component.extend({ wizard: inject.service(), - allFeatures: ['secrets', 'authentication', 'policies', 'replication', 'tools'], + allFeatures: [ + { + key: 'secrets', + name: 'Secrets', + steps: ['Enabling a secrets engine', 'Entering secrets method details', 'Adding a secret'], + }, + { + key: 'authentication', + name: 'Authentication', + steps: ['Enabling an auth method', 'Entering auth method details', 'Adding a user'], + }, + { + key: 'policies', + name: 'Policies', + steps: [], + }, + { + key: 'replication', + name: 'Replication', + steps: [], + }, + { + key: 'tools', + name: 'Tools', + steps: [], + }, + ], selectedFeatures: null, hasFeatures: computed('selectedFeatures', function() { return this.get('selectedFeatures') !== null && this.get('selectedFeatures').length > 0; }), - finalFeatures: computed('allFeatures', 'selectedFeatures', function() { + finalFeatures: computed('selectedFeatures', function() { let features = []; let selected = this.get('selectedFeatures'); this.get('allFeatures').forEach(function(feature) { - if (selected.includes(feature)) { - features.push(feature); + if (selected !== null && selected.includes(feature.key)) { + features.push(feature.key); } }); return features; diff --git a/ui/app/components/wizard/secrets-machine.js b/ui/app/components/wizard/secrets-machine.js new file mode 100644 index 00000000000..6f6cf81be73 --- /dev/null +++ b/ui/app/components/wizard/secrets-machine.js @@ -0,0 +1,61 @@ +import Ember from 'ember'; + +const { inject, computed } = Ember; + +export default Ember.Component.extend({ + wizard: inject.service(), + currentState: computed.alias('wizard.currentState'), + featureState: computed.alias('wizard.featureState'), + selectedEngine: computed.alias('wizard.potentialSelection'), + secretsEngines: [ + { + key: 'secrets.aws', + name: 'AWS', + description: + 'The AWS secrets engine generates AWS access credentials dynamically based on IAM policies. This generally makes working with AWS IAM easier, since it does not involve clicking in the web UI. Additionally, the process is codified and mapped to internal auth methods (such as LDAP). The AWS IAM credentials are time-based and are automatically revoked when the Vault lease expires.', + }, + { + key: 'secrets.ch', + name: 'Cubbyhole', + description: + "The cubbyhole secrets engine is used to store arbitrary secrets within the configured physical storage for Vault namespaced to a token. In cubbyhole, paths are scoped per token. No token can access another token's cubbyhole. When the token expires, its cubbyhole is destroyed.", + }, + { + key: 'secrets.kv', + name: 'Key/Value', + description: + 'The kv secrets engine is used to store arbitrary secrets within the configured physical storage for Vault. This backend can be run in one of two modes. It can be a generic Key-Value store that stores one value for a key. Versioning can be enabled and a configurable number of versions for each key will be stored.', + }, + ], + isIdle: computed('featureState', function() { + return this.get('featureState') === 'idle'; + }), + currentStepName: computed('featureState', function() { + if (this.get('isIdle')) { + return 'Enabling a secrets engine'; + } + }), + currentStepText: computed('featureState', function() { + if (this.get('isIdle')) { + return "Vault is all about managing secrets, so let's set up your first secrets engine. You can use a static engine to store your secrets locally in Vault, or connect to a cloud backend with one of the dynamic engines."; + } + }), + engineDescription: computed('selectedEngine', function() { + return this.get('secretsEngines') + .filter(engine => engine.key === `secrets.${this.get('selectedEngine')}`) + .objectAt(0).description; + }), + engineName: computed('selectedEngine', function() { + return this.get('secretsEngines') + .filter(engine => engine.key === `secrets.${this.get('selectedEngine')}`) + .objectAt(0).name; + }), + + dismissWizard() { + this.get('wizard').transitionTutorialMachine(this.get('currentState'), 'DISMISS'); + }, + + advanceWizard() { + this.get('wizard').transitionFeatureMachine(this.get('featureState'), 'CONTINUE'); + }, +}); diff --git a/ui/app/components/wizard/setup-machine.js b/ui/app/components/wizard/setup-machine.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ui/app/controllers/vault/cluster/settings/mount-secret-backend.js b/ui/app/controllers/vault/cluster/settings/mount-secret-backend.js index 973d0544e96..244b10ceb66 100644 --- a/ui/app/controllers/vault/cluster/settings/mount-secret-backend.js +++ b/ui/app/controllers/vault/cluster/settings/mount-secret-backend.js @@ -41,6 +41,7 @@ export default Ember.Controller.extend({ }), flashMessages: Ember.inject.service(), + wizard: Ember.inject.service(), reset() { const defaultBackend = this.get('mountTypes.firstObject.value'); @@ -64,7 +65,9 @@ export default Ember.Controller.extend({ actions: { onTypeChange(val) { + debugger; const { selectedPath, selectedType } = this.getProperties('selectedPath', 'selectedType'); + this.get('wizard').setPotentialSelection(val); this.set('selectedType', val); if (selectedPath === selectedType) { this.set('selectedPath', val); diff --git a/ui/app/machines/secrets-machine.js b/ui/app/machines/secrets-machine.js index 4d9258ad585..4a4661c53a9 100644 --- a/ui/app/machines/secrets-machine.js +++ b/ui/app/machines/secrets-machine.js @@ -21,6 +21,7 @@ export default { on: { RESET: 'idle', DONE: 'complete', + PAUSE: 'paused', }, key: 'aws', initial: 'credentials', @@ -41,6 +42,7 @@ export default { on: { RESET: 'idle', DONE: 'complete', + PAUSE: 'paused', }, key: 'ch', initial: 'role', @@ -59,9 +61,9 @@ export default { }, }, complete: { - on: { - RESET: 'idle', - }, + onEntry: ['completeFeature'], + on: { RESET: 'idle' }, }, + paused: {}, }, }; diff --git a/ui/app/machines/tutorial-machine.js b/ui/app/machines/tutorial-machine.js index ed84178f1b1..0c065190738 100644 --- a/ui/app/machines/tutorial-machine.js +++ b/ui/app/machines/tutorial-machine.js @@ -23,7 +23,10 @@ export default { }, dismissed: { on: { CONTINUE: 'idle' }, - onEntry: ['saveState'], + onEntry: ['handleDismissed'], + }, + paused: { + on: { CONTINUE: ['handlePause'] }, }, complete: { on: { diff --git a/ui/app/services/wizard.js b/ui/app/services/wizard.js index 48085ce4a9e..c9fe9849405 100644 --- a/ui/app/services/wizard.js +++ b/ui/app/services/wizard.js @@ -13,6 +13,7 @@ let FeatureMachine = null; const TUTORIAL_STATE = 'vault-tutorial-state'; const FEATURE_LIST = 'vault-feature-list'; const FEATURE_STATE = 'vault-feature-state'; +const COMPLETED_FEATURES = 'vault-completed-list'; const MACHINES = { secrets: SecretsMachineConfig }; export default Service.extend({ @@ -20,6 +21,7 @@ export default Service.extend({ featureList: null, featureState: null, currentMachine: null, + potentialSelection: null, init() { this._super(...arguments); @@ -48,7 +50,6 @@ export default Service.extend({ if (state.value) { state = state.value; } - let stateKey = ''; while (Ember.typeOf(state) === 'object') { let newState = Object.keys(state); @@ -97,11 +98,21 @@ export default Service.extend({ case 'completeFeature': this.completeFeature(); break; + case 'handleDismissed': + this.handleDismissed(); + break; default: break; } }, + handleDismissed() { + this.storage().removeItem(FEATURE_STATE); + this.storage().removeItem(FEATURE_LIST); + this.storage().removeItem(MACHINES); + this.storage().removeItem(COMPLETED_FEATURES); + }, + saveFeatures(features) { this.set('featureList', features); this.saveExtState(FEATURE_LIST, this.get('featureList')); @@ -121,7 +132,17 @@ export default Service.extend({ completeFeature() { let features = this.get('featureList'); - features.pop(); + let completed = features.pop(); + + if (!this.getExtState(COMPLETED_FEATURES)) { + this.saveExtState([completed]); + } else { + this.saveExtState( + COMPLETED_FEATURES, + this.getExtState(COMPLETED_FEATURES).toArray().addObject(completed) + ); + } + this.saveExtState(FEATURE_LIST, this.get('featureList')); if (features.length > 0) { const FeatureMachineConfig = MACHINES[this.get('featureList').objectAt(0)]; @@ -136,6 +157,10 @@ export default Service.extend({ } }, + setPotentialSelection(key) { + this.set('potentialSelection', key); + }, + storage() { return getStorage(); }, diff --git a/ui/app/styles/components/ui-wizard.scss b/ui/app/styles/components/ui-wizard.scss new file mode 100644 index 00000000000..b47a66def4d --- /dev/null +++ b/ui/app/styles/components/ui-wizard.scss @@ -0,0 +1,94 @@ +.ui-wizard { + background: #ffffff; + color: #000000; + width: 300px; + padding: 16px; + display: block; + float: right; + margin: 9px; +} + +.ui-wizard.collapsed { + background: #000000; + color: #ffffff; +} + +.ui-wizard .wizard-header h1 { + width: 146px; + height: 24px; + font-size: 16px; + font-weight: 600; + line-height: 24px; +} + +.ui-wizard h2 { + font-size: 13px; + font-weight: 600; +} + +.form-label { + color: #677887; + font-size: 12px; + font-weight: 600; +} + +.ui-wizard .wizard-header p { + margin-top: 9px; + margin-bottom: 9px; + font-size: 14px; + font-weight: normal; + line-height: 18px; +} + +button { + border: 0; + padding: 0; + font-family: inherit; + font-size: 100%; + background: white; + color: black; + font-weight: 600; + cursor: pointer; +} + +.feature-row { + border-radius: 2px 2px 2px 2px; + border: 1px solid #bac1cc; + padding: 8px 8px; + margin-top: 8px; +} + +.feature-row label { + color: #000000; + font-size: 12px; + font-weight: 600; + line-height: 12px; +} + +.feature-row input { + width: 14px; + height: 14px; + border: 1px solid #b9c4d2; + border-radius: 2px 2px 2px 2px; + margin-right: 2px; +} + +ul.feature-steps { + margin-left: 25px; + list-style-type: disc; +} + +.feature-steps li { + color: #6a7786; + font-size: 12px; + font-weight: normal; +} + +.collapsed button { + background: black; + color: white; +} + +.ui-wizard button.dismiss-button { + float: right; +} diff --git a/ui/app/styles/core.scss b/ui/app/styles/core.scss index 0b8b08739b8..209c4399a62 100644 --- a/ui/app/styles/core.scss +++ b/ui/app/styles/core.scss @@ -77,4 +77,5 @@ @import "./components/tool-tip"; @import "./components/unseal-warning"; @import "./components/upgrade-overlay"; +@import "./components/ui-wizard"; @import "./components/vault-loading"; diff --git a/ui/app/templates/components/ui-wizard.hbs b/ui/app/templates/components/ui-wizard.hbs new file mode 100644 index 00000000000..ac8a293412d --- /dev/null +++ b/ui/app/templates/components/ui-wizard.hbs @@ -0,0 +1,34 @@ +{{#unless isDismissed}} +
+ + {{#unless isActive}} +
+ {{#if isPaused}} +

Vault Web UI Guide

+

Feel free to explore Vault. Click below to get back to the guide or close this window.

+ {{else}} +

Welcome to Vault

+

Want a tour? Our helpful guide will introduce you to the Vault Web UI.

+ {{/if}} +
+ + {{/unless}} + {{#unless isPaused}} + {{#if isActive}} + {{#if isSelecting}} + {{wizard/features-selection}} + {{else}} + {{#if isFeature}} + {{component "wizard/secrets-machine"}} + {{/if}} + {{/if}} + {{/if}} + {{/unless}} +
+{{/unless}} diff --git a/ui/app/templates/components/wizard-view.hbs b/ui/app/templates/components/wizard-view.hbs deleted file mode 100644 index d8abecb4ae2..00000000000 --- a/ui/app/templates/components/wizard-view.hbs +++ /dev/null @@ -1,22 +0,0 @@ -
- {{#unless isDismissed}} - - {{/unless}} -

Current State: {{currentState}}

-

Current Feature: {{currentMachine}}

-

Feature State: {{featureState}}

- {{#unless isActive}} - - {{/unless}} - {{#if isActive}} - {{#if isSelecting}} - {{wizard/features-selection}} - {{/if}} - {{/if}} -
diff --git a/ui/app/templates/components/wizard/features-selection.hbs b/ui/app/templates/components/wizard/features-selection.hbs index 83ecbb2e0cb..3fd0d033d9b 100644 --- a/ui/app/templates/components/wizard/features-selection.hbs +++ b/ui/app/templates/components/wizard/features-selection.hbs @@ -1,7 +1,20 @@ -
+
+

Vault Web UI

+

Choosing where to go

+

You did it! You now have access to your Vault and can start entering your data. We can help you get started with any of the options below

+
+ + {{#each allFeatures as |feature|}} - - +
+ + +
    + {{#each feature.steps as |step|}} +
  • {{step}}
  • + {{/each}} +
+
{{/each}} - +
\ No newline at end of file diff --git a/ui/app/templates/components/wizard/secrets-machine.hbs b/ui/app/templates/components/wizard/secrets-machine.hbs new file mode 100644 index 00000000000..d30d301d5cd --- /dev/null +++ b/ui/app/templates/components/wizard/secrets-machine.hbs @@ -0,0 +1,15 @@ +{{#unless isDismissed}} +
+

Secrets

+

{{currentStepName}}

+
+
+ {{currentStepText}} +
+ {{#if selectedEngine}} +
+

{{engineName}}

+ {{engineDescription}} +
+ {{/if}} +{{/unless}} \ No newline at end of file diff --git a/ui/app/templates/vault/cluster.hbs b/ui/app/templates/vault/cluster.hbs index e462faedb96..0e1048ce9de 100644 --- a/ui/app/templates/vault/cluster.hbs +++ b/ui/app/templates/vault/cluster.hbs @@ -1,4 +1,4 @@ - + {{#if showNav}} From 4fe492cdbf9f4f7a90c8992fcaab878f902bb0c5 Mon Sep 17 00:00:00 2001 From: Madalyn Parker Date: Mon, 13 Aug 2018 16:08:08 -0400 Subject: [PATCH 007/124] update guard methods on secrets machine, add init steps --- ui/app/components/wizard/secrets-machine.js | 53 +++++++++++++++++++ .../cluster/settings/mount-secret-backend.js | 2 +- ui/app/machines/auth-machine.js | 29 ++++++++++ ui/app/machines/secrets-machine.js | 11 ++-- ui/app/machines/tutorial-machine.js | 19 ++++++- ui/app/services/wizard.js | 8 +-- 6 files changed, 112 insertions(+), 10 deletions(-) create mode 100644 ui/app/machines/auth-machine.js diff --git a/ui/app/components/wizard/secrets-machine.js b/ui/app/components/wizard/secrets-machine.js index 6f6cf81be73..7abe4887505 100644 --- a/ui/app/components/wizard/secrets-machine.js +++ b/ui/app/components/wizard/secrets-machine.js @@ -8,24 +8,77 @@ export default Ember.Component.extend({ featureState: computed.alias('wizard.featureState'), selectedEngine: computed.alias('wizard.potentialSelection'), secretsEngines: [ + { + key: 'secrets.ad', + name: 'Active Directory', + description: + 'The AD secrets engine rotates AD passwords dynamically, and is designed for a high-load environment where many instances may be accessing a shared password simultaneously.', + }, { key: 'secrets.aws', name: 'AWS', description: 'The AWS secrets engine generates AWS access credentials dynamically based on IAM policies. This generally makes working with AWS IAM easier, since it does not involve clicking in the web UI. Additionally, the process is codified and mapped to internal auth methods (such as LDAP). The AWS IAM credentials are time-based and are automatically revoked when the Vault lease expires.', }, + { + key: 'secrets.consul', + name: 'Consul', + description: + 'The Consul secrets engine generates Consul API tokens dynamically based on Consul ACL policies', + }, { key: 'secrets.ch', name: 'Cubbyhole', description: "The cubbyhole secrets engine is used to store arbitrary secrets within the configured physical storage for Vault namespaced to a token. In cubbyhole, paths are scoped per token. No token can access another token's cubbyhole. When the token expires, its cubbyhole is destroyed.", }, + { + key: 'secrets.gcp', + name: 'Google Cloud', + description: + 'The Google Cloud Vault secrets engine dynamically generates Google Cloud service account keys and OAuth tokens based on IAM policies. This enables users to gain access to Google Cloud resources without needing to create or manage a dedicated service account.', + }, { key: 'secrets.kv', name: 'Key/Value', description: 'The kv secrets engine is used to store arbitrary secrets within the configured physical storage for Vault. This backend can be run in one of two modes. It can be a generic Key-Value store that stores one value for a key. Versioning can be enabled and a configurable number of versions for each key will be stored.', }, + { + key: 'secrets.nomad', + name: 'Nomad', + description: + 'The Nomad secret backend for Vault generates Nomad API tokens dynamically based on pre-existing Nomad ACL policies.', + }, + { + key: 'secrets.pki', + name: 'PKI', + description: + 'The PKI secrets engine generates dynamic X.509 certificates. With this secrets engine, services can get certificates without going through the usual manual process of generating a private key and CSR, submitting to a CA, and waiting for a verification and signing process to complete.', + }, + { + key: 'secrets.rabbitmq', + name: 'RabbitMQ', + description: + 'The RabbitMQ secrets engine generates user credentials dynamically based on configured permissions and virtual hosts. This means that services that need to access a virtual host no longer need to hardcode credentials.', + }, + { + key: 'secrets.ssh', + name: 'SSH', + description: + 'The Vault SSH secrets engine provides secure authentication and authorization for access to machines via the SSH protocol. The Vault SSH secrets engine helps manage access to machine infrastructure, providing several ways to issue SSH credentials.', + }, + { + key: 'secrets.totp', + name: 'TOTP', + description: 'The TOTP secrets engine generates time-based credentials according to the TOTP standard.', + }, + { + key: 'secrets.transit', + name: 'Transit', + description: + 'The transit secrets engine handles cryptographic functions on data in-transit. Vault does not store the data sent to the secrets engine. It can also be viewed as "cryptography as a service" or "encryption as a service".', + }, ], isIdle: computed('featureState', function() { return this.get('featureState') === 'idle'; diff --git a/ui/app/controllers/vault/cluster/settings/mount-secret-backend.js b/ui/app/controllers/vault/cluster/settings/mount-secret-backend.js index 244b10ceb66..7ba44ef8957 100644 --- a/ui/app/controllers/vault/cluster/settings/mount-secret-backend.js +++ b/ui/app/controllers/vault/cluster/settings/mount-secret-backend.js @@ -65,7 +65,6 @@ export default Ember.Controller.extend({ actions: { onTypeChange(val) { - debugger; const { selectedPath, selectedType } = this.getProperties('selectedPath', 'selectedType'); this.get('wizard').setPotentialSelection(val); this.set('selectedType', val); @@ -135,6 +134,7 @@ export default Ember.Controller.extend({ transition = this.transitionToRoute('vault.cluster.secrets.backends'); } transition.followRedirects().then(() => { + this.get('wizard').transitionFeatureMachine(this.get('wizard.featureState'), 'CONTINUE', type); this.get('flashMessages').success(`Successfully mounted '${type}' at '${path}'!`); }); }); diff --git a/ui/app/machines/auth-machine.js b/ui/app/machines/auth-machine.js new file mode 100644 index 00000000000..74d48f6012e --- /dev/null +++ b/ui/app/machines/auth-machine.js @@ -0,0 +1,29 @@ +export default { + key: 'auth', + initial: 'enable', + states: { + enable: { + on: { + CONTINUE: { + appRole: { + cond: (extState, event) => event.selected === 'appRole', + }, + }, + }, + }, + appRole: { + key: 'appRole', + initial: 'details', + states: { + details: { + on: { CONTINUE: 'complete' }, + }, + }, + }, + complete: { + onEntry: ['completeFeature'], + on: { RESET: 'idle' }, + }, + paused: {}, + }, +}; diff --git a/ui/app/machines/secrets-machine.js b/ui/app/machines/secrets-machine.js index 4a4661c53a9..6cb1a2ac261 100644 --- a/ui/app/machines/secrets-machine.js +++ b/ui/app/machines/secrets-machine.js @@ -6,13 +6,13 @@ export default { on: { CONTINUE: { aws: { - cond: (extState, event) => event.selected === 'aws', + cond: type => type === 'aws', }, cubbyhole: { - cond: (extState, event) => event.selected === 'ch', + cond: type => type === 'cubbyhole', }, kv: { - cond: (extState, event) => event.selected === 'kv', + cond: type => type === 'kv', }, }, }, @@ -33,9 +33,12 @@ export default { }, role: { on: { - REPEAT: 'role', + CONTINUE: 'display', }, }, + display: { + REPEAT: 'role', + }, }, }, cubbyhole: { diff --git a/ui/app/machines/tutorial-machine.js b/ui/app/machines/tutorial-machine.js index 0c065190738..1ce01840023 100644 --- a/ui/app/machines/tutorial-machine.js +++ b/ui/app/machines/tutorial-machine.js @@ -3,7 +3,10 @@ export default { initial: 'idle', states: { active: { - on: { DISMISS: 'dismissed' }, + on: { + DISMISS: 'dismissed', + AUTH: 'select', + }, key: 'feature', initial: 'select', states: { @@ -13,6 +16,20 @@ export default { }, }, feature: {}, + init: { + key: 'init', + initial: 'setup', + on: { DONE: 'select' }, + states: { + setup: { + on: { CONTINUE: 'save' }, + }, + save: { + on: { CONTINUE: 'unseal' }, + }, + unseal: {}, + }, + }, }, }, idle: { diff --git a/ui/app/services/wizard.js b/ui/app/services/wizard.js index c9fe9849405..95206194b40 100644 --- a/ui/app/services/wizard.js +++ b/ui/app/services/wizard.js @@ -38,10 +38,9 @@ export default Service.extend({ if (FeatureMachine !== null) { this.saveState('featureState', FeatureMachine.initialState); this.saveExtState(FEATURE_STATE, this.get('featureState')); - } else { - this.buildFeatureMachine(); } } + this.buildFeatureMachine(); } } }, @@ -69,8 +68,9 @@ export default Service.extend({ } }, - transitionFeatureMachine(currentState, event) { - let { actions, value } = FeatureMachine.transition(currentState, event); + transitionFeatureMachine(currentState, event, extendedState) { + debugger; + let { actions, value } = FeatureMachine.transition(currentState, event, extendedState); this.saveState('featureState', value); this.saveExtState(FEATURE_STATE, value); for (let action in actions) { From 9f6f17d20301f55ddc073b72f38dfe1e64555524 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Mon, 13 Aug 2018 16:05:14 -0500 Subject: [PATCH 008/124] add routing action and handle it in the service --- ui/app/machines/secrets-machine.js | 1 + ui/app/services/wizard.js | 23 +++++++++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/ui/app/machines/secrets-machine.js b/ui/app/machines/secrets-machine.js index 6cb1a2ac261..71b552bc6f5 100644 --- a/ui/app/machines/secrets-machine.js +++ b/ui/app/machines/secrets-machine.js @@ -3,6 +3,7 @@ export default { initial: 'idle', states: { idle: { + onEntry: [{ type: 'routeTransition', params: ['vault.cluster.settings.mount-secret-backend'] }], on: { CONTINUE: { aws: { diff --git a/ui/app/services/wizard.js b/ui/app/services/wizard.js index 95206194b40..018d44a1f41 100644 --- a/ui/app/services/wizard.js +++ b/ui/app/services/wizard.js @@ -1,7 +1,7 @@ import Ember from 'ember'; import { Machine } from 'xstate'; -const { Service } = Ember; +const { Service, inject } = Ember; import getStorage from 'vault/lib/token-storage'; @@ -17,6 +17,7 @@ const COMPLETED_FEATURES = 'vault-completed-list'; const MACHINES = { secrets: SecretsMachineConfig }; export default Service.extend({ + router: inject.service(), currentState: null, featureList: null, featureState: null, @@ -35,7 +36,7 @@ export default Service.extend({ if (this.storageHasKey(FEATURE_STATE)) { this.saveState('featureState', this.getExtState(FEATURE_STATE)); } else { - if (FeatureMachine !== null) { + if (FeatureMachine != null) { this.saveState('featureState', FeatureMachine.initialState); this.saveExtState(FEATURE_STATE, this.get('featureState')); } @@ -63,7 +64,7 @@ export default Service.extend({ let { actions, value } = TutorialMachine.transition(currentState, event); this.saveState('currentState', value); this.saveExtState(TUTORIAL_STATE, this.get('currentState')); - for (let action in actions) { + for (let action of actions) { this.executeAction(action, event); } }, @@ -73,7 +74,7 @@ export default Service.extend({ let { actions, value } = FeatureMachine.transition(currentState, event, extendedState); this.saveState('featureState', value); this.saveExtState(FEATURE_STATE, value); - for (let action in actions) { + for (let action of actions) { this.executeAction(action, event); } }, @@ -87,11 +88,18 @@ export default Service.extend({ }, storageHasKey(key) { - return this.storage().keys().includes(key); + return Boolean(this.getExtState(key)); }, executeAction(action, event) { - switch (action) { + let type = action; + if (action.type) { + type = action.type; + } + switch (type) { + case 'routeTransition': + this.get('router').transitionTo(...action.params); + break; case 'saveFeatures': this.saveFeatures(event.features); break; @@ -128,6 +136,9 @@ export default Service.extend({ this.set('currentMachine', this.get('featureList').objectAt(0)); this.saveState('featureState', FeatureMachine.initialState); this.saveExtState(FEATURE_STATE, this.get('featureState')); + for (let action of FeatureMachine.initialState.actions) { + this.executeAction(action); + } }, completeFeature() { From 35895bc186b7f420bd0d2235a30c1775842d5ae5 Mon Sep 17 00:00:00 2001 From: Madalyn Parker Date: Thu, 16 Aug 2018 19:50:46 -0400 Subject: [PATCH 009/124] start moving init stuff into separate template, add logic to determine unseal description string --- ui/app/components/shamir-flow.js | 8 +- ui/app/components/ui-wizard.js | 3 + ui/app/components/wizard/init-steps.js | 82 +++++++++++++++++++ ui/app/controllers/vault/cluster/init.js | 3 + ui/app/controllers/vault/cluster/unseal.js | 6 +- ui/app/machines/tutorial-machine.js | 7 +- ui/app/routes/vault/cluster/unseal.js | 21 ++++- ui/app/services/wizard.js | 8 +- ui/app/templates/components/ui-wizard.hbs | 8 +- .../components/wizard/init-steps.hbs | 5 ++ 10 files changed, 142 insertions(+), 9 deletions(-) create mode 100644 ui/app/components/wizard/init-steps.js create mode 100644 ui/app/templates/components/wizard/init-steps.hbs diff --git a/ui/app/components/shamir-flow.js b/ui/app/components/shamir-flow.js index 7b0647b1c8d..507a9347602 100644 --- a/ui/app/components/shamir-flow.js +++ b/ui/app/components/shamir-flow.js @@ -21,17 +21,23 @@ const DEFAULTS = { export default Component.extend(DEFAULTS, { tagName: '', store: inject.service(), + wizard: inject.service(), formText: null, fetchOnInit: false, buttonText: 'Submit', thresholdPath: 'required', generateAction: false, - encoded_token: null, init() { if (this.get('fetchOnInit')) { this.attemptProgress(); } + if (this.get('action') === 'unseal') { + this.get('wizard').transitionTutorialMachine(this.get('wizard.currentState'), 'NOOP', { + threshold: this.get('threshold'), + progress: this.get('progress'), + }); + } return this._super(...arguments); }, diff --git a/ui/app/components/ui-wizard.js b/ui/app/components/ui-wizard.js index c4e674b28ac..c1646bde8e6 100644 --- a/ui/app/components/ui-wizard.js +++ b/ui/app/components/ui-wizard.js @@ -22,6 +22,9 @@ export default Ember.Component.extend({ isSelecting: computed('currentState', 'isActive', function() { return this.get('isActive') && this.get('currentState').indexOf('select') > 0; }), + isInitializing: computed('currentState', 'isActive', function() { + return this.get('isActive') && this.get('currentState').indexOf('init') > 0; + }), isFeature: computed('currentMachine', function() { return this.get('currentMachine') !== 'None'; }), diff --git a/ui/app/components/wizard/init-steps.js b/ui/app/components/wizard/init-steps.js new file mode 100644 index 00000000000..1f39ae29780 --- /dev/null +++ b/ui/app/components/wizard/init-steps.js @@ -0,0 +1,82 @@ +import Ember from 'ember'; + +const { inject, computed } = Ember; + +export default Ember.Component.extend({ + wizard: inject.service(), + currentState: computed.alias('wizard.currentState'), + featureState: computed.alias('wizard.featureState'), + componentState: computed.alias('wizard.componentState'), + initializationSteps: [ + { + key: 'setup', + title: 'Setting up your master keys', + description: + 'This is the very first step of setting up a Vault server. Vault comes with an important security feature called "seal", which lets you shut down and secure your Vault installation if there is a security breach. This is the default state of Vault, so it is currently sealed since it was just installed. To unseal the vault, you will need to provide the key(s) that you generate here.', + docs: { + link: 'https://www.vaultproject.io/intro/getting-started/deploy.html#initializing-the-vault', + text: 'Docs: Initialization', + }, + }, + { + key: 'save', + title: 'Saving your keys', + description: + "Now that Vault is initialized, you'll want to save your root token and master key portions in a safe place. Distribute your keys to responsible people on your team. If these keys are lost, you may not be able to access your data again. Keep them safe!", + docs: { + link: 'https://www.vaultproject.io/intro/getting-started/deploy.html#initializing-the-vault', + text: 'Docs: Initialization', + }, + }, + { + key: 'unseal', + title: 'Unsealing your vault', + description: '', + docs: { + link: 'https://www.vaultproject.io/docs/concepts/seal.html', + text: 'Docs: Unseal', + }, + }, + { + key: 'login', + title: 'Sign in to your vault', + description: 'Congrats! Now that your vault is all set up you can sign in!', + }, + ], + inSetup: computed('currentState', function() { + return this.get('currentState').indexOf('init.setup') > 0; + }), + isSaving: computed('currentState', function() { + return this.get('currentState').indexOf('init.save') > 0; + }), + isUnsealing: computed('currentState', function() { + return this.get('currentState').indexOf('init.unseal') > 0; + }), + inLogin: computed('currentState', function() { + return this.get('currentState').indexOf('init.lgin') > 0; + }), + currentStep: computed('currentState', 'componentState', function() { + const stateParts = this.get('currentState').split('.'); + let currentStep = this.get('initializationSteps') + .filter(step => step.key === stateParts[stateParts.length - 1]) + .objectAt(0); + if (this.get('isUnsealing')) { + if (this.get('componentState')) { + const keyWord = this.get('componentState.threshold') > 1 ? 'keys' : 'key'; + const providedWord = this.get('componentState.progress') > 1 ? 'have' : 'has'; + const keysLeft = this.get('componentState.threshold') - this.get('componentState.progress'); + const leftWord = keysLeft > 1 ? 'keys' : 'key'; + Ember.set( + currentStep, + 'description', + `Now we will provide the ${keyWord} that you copied or downloaded to unseal the vault so that we can get started using it. You'll need ${this.get( + 'componentState.threshold' + )} ${keyWord} total, and ${this.get( + 'componentState.progress' + )} ${providedWord} already been provided. Please provide ${keysLeft} more ${leftWord} to unseal.` + ); + } + } + return currentStep; + }), +}); diff --git a/ui/app/controllers/vault/cluster/init.js b/ui/app/controllers/vault/cluster/init.js index c04dd306dcd..d1841695dd9 100644 --- a/ui/app/controllers/vault/cluster/init.js +++ b/ui/app/controllers/vault/cluster/init.js @@ -10,6 +10,8 @@ const DEFAULTS = { }; export default Ember.Controller.extend(DEFAULTS, { + wizard: Ember.inject.service(), + reset() { this.setProperties(DEFAULTS); }, @@ -17,6 +19,7 @@ export default Ember.Controller.extend(DEFAULTS, { initSuccess(resp) { this.set('loading', false); this.set('keyData', resp); + this.get('wizard').transitionTutorialMachine(this.get('wizard.currentState'), 'CONTINUE'); }, initError(e) { diff --git a/ui/app/controllers/vault/cluster/unseal.js b/ui/app/controllers/vault/cluster/unseal.js index d20b3812511..15f9b9050c7 100644 --- a/ui/app/controllers/vault/cluster/unseal.js +++ b/ui/app/controllers/vault/cluster/unseal.js @@ -1,9 +1,13 @@ import Ember from 'ember'; export default Ember.Controller.extend({ + wizard: Ember.inject.service(), + actions: { - transitionToCluster() { + transitionToCluster(resp) { + debugger; return this.get('model').reload().then(() => { + this.get('wizard').transitionTutorialMachine(this.get('wizard.currentState'), 'CONTINUE', resp); return this.transitionToRoute('vault.cluster', this.get('model.name')); }); }, diff --git a/ui/app/machines/tutorial-machine.js b/ui/app/machines/tutorial-machine.js index 1ce01840023..446d56a0581 100644 --- a/ui/app/machines/tutorial-machine.js +++ b/ui/app/machines/tutorial-machine.js @@ -8,7 +8,7 @@ export default { AUTH: 'select', }, key: 'feature', - initial: 'select', + initial: 'init', states: { select: { on: { @@ -27,7 +27,10 @@ export default { save: { on: { CONTINUE: 'unseal' }, }, - unseal: {}, + unseal: { + on: { CONTINUE: 'login' }, + }, + login: {}, }, }, }, diff --git a/ui/app/routes/vault/cluster/unseal.js b/ui/app/routes/vault/cluster/unseal.js index 875583f717c..153e8bae5d3 100644 --- a/ui/app/routes/vault/cluster/unseal.js +++ b/ui/app/routes/vault/cluster/unseal.js @@ -1 +1,20 @@ -export { default } from './cluster-route-base'; +import ClusterRouteBase from './cluster-route-base'; +import Ember from 'ember'; + +const { inject } = Ember; + +export default ClusterRouteBase.extend({ + wizard: inject.service(), + + beforeModel() { + this._super(...arguments); + debugger; + if (this.get('wizard.currentState') === 'active.init.save') { + this.get('wizard').transitionTutorialMachine(this.get('wizard.currentState'), 'CONTINUE'); + } + }, + afterModel() { + this._super(...arguments); + debugger; + }, +}); diff --git a/ui/app/services/wizard.js b/ui/app/services/wizard.js index 018d44a1f41..dcc3447c5d7 100644 --- a/ui/app/services/wizard.js +++ b/ui/app/services/wizard.js @@ -23,6 +23,7 @@ export default Service.extend({ featureState: null, currentMachine: null, potentialSelection: null, + componentState: null, init() { this._super(...arguments); @@ -60,7 +61,11 @@ export default Service.extend({ this.set(stateType, stateKey); }, - transitionTutorialMachine(currentState, event) { + transitionTutorialMachine(currentState, event, extendedState) { + debugger; + if (extendedState) { + this.set('componentState', extendedState); + } let { actions, value } = TutorialMachine.transition(currentState, event); this.saveState('currentState', value); this.saveExtState(TUTORIAL_STATE, this.get('currentState')); @@ -70,7 +75,6 @@ export default Service.extend({ }, transitionFeatureMachine(currentState, event, extendedState) { - debugger; let { actions, value } = FeatureMachine.transition(currentState, event, extendedState); this.saveState('featureState', value); this.saveExtState(FEATURE_STATE, value); diff --git a/ui/app/templates/components/ui-wizard.hbs b/ui/app/templates/components/ui-wizard.hbs index ac8a293412d..45a6fd743fb 100644 --- a/ui/app/templates/components/ui-wizard.hbs +++ b/ui/app/templates/components/ui-wizard.hbs @@ -24,8 +24,12 @@ {{#if isSelecting}} {{wizard/features-selection}} {{else}} - {{#if isFeature}} - {{component "wizard/secrets-machine"}} + {{#if isInitializing}} + {{component "wizard/init-steps"}} + {{else}} + {{#if isFeature}} + {{component "wizard/secrets-machine"}} + {{/if}} {{/if}} {{/if}} {{/if}} diff --git a/ui/app/templates/components/wizard/init-steps.hbs b/ui/app/templates/components/wizard/init-steps.hbs new file mode 100644 index 00000000000..7d9d15d06d3 --- /dev/null +++ b/ui/app/templates/components/wizard/init-steps.hbs @@ -0,0 +1,5 @@ +
+

{{currentStep.title}}

+

{{currentStep.description}}

+ {{currentStep.docs.text}} +
\ No newline at end of file From aa5632a7585d15cde265b28b5e3f5977fb97fd9a Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Fri, 17 Aug 2018 17:44:24 -0500 Subject: [PATCH 010/124] add hooks for unseal and setting component state on the service --- ui/app/components/shamir-flow.js | 31 +++++++++++++--------- ui/app/controllers/vault/cluster/unseal.js | 6 ++++- ui/app/routes/vault/cluster/unseal.js | 21 +-------------- ui/app/services/wizard.js | 25 +++++++++-------- ui/app/templates/vault/cluster/unseal.hbs | 1 + 5 files changed, 37 insertions(+), 47 deletions(-) diff --git a/ui/app/components/shamir-flow.js b/ui/app/components/shamir-flow.js index 507a9347602..801b9dd37ee 100644 --- a/ui/app/components/shamir-flow.js +++ b/ui/app/components/shamir-flow.js @@ -21,7 +21,6 @@ const DEFAULTS = { export default Component.extend(DEFAULTS, { tagName: '', store: inject.service(), - wizard: inject.service(), formText: null, fetchOnInit: false, buttonText: 'Submit', @@ -29,18 +28,18 @@ export default Component.extend(DEFAULTS, { generateAction: false, init() { + this._super(...arguments); if (this.get('fetchOnInit')) { this.attemptProgress(); } - if (this.get('action') === 'unseal') { - this.get('wizard').transitionTutorialMachine(this.get('wizard.currentState'), 'NOOP', { - threshold: this.get('threshold'), - progress: this.get('progress'), - }); - } - return this._super(...arguments); }, + didInsertElement() { + this._super(...arguments); + this.onUpdate(this.getProperties(Object.keys(DEFAULTS))); + }, + + onUpdate() {}, onShamirSuccess() {}, // can be overridden w/an attr isComplete(data) { @@ -62,17 +61,23 @@ export default Component.extend(DEFAULTS, { hasProgress: computed.gt('progress', 0), actionSuccess(resp) { - const { isComplete, onShamirSuccess, thresholdPath } = this.getProperties( + let { onActionSuccess, isComplete, onShamirSuccess, thresholdPath } = this.getProperties( + 'onActionSuccess', 'isComplete', 'onShamirSuccess', 'thresholdPath' ); + let threshold = get(resp, thresholdPath); + let props = { + ...resp, + threshold, + }; this.stopLoading(); - this.set('threshold', get(resp, thresholdPath)); - this.setProperties(resp); - if (isComplete(resp)) { + this.setProperties(props); + onUpdate(props); + if (isComplete(props)) { this.reset(); - onShamirSuccess(resp); + onShamirSuccess(props); } }, diff --git a/ui/app/controllers/vault/cluster/unseal.js b/ui/app/controllers/vault/cluster/unseal.js index 15f9b9050c7..5abcc4a8ce7 100644 --- a/ui/app/controllers/vault/cluster/unseal.js +++ b/ui/app/controllers/vault/cluster/unseal.js @@ -5,12 +5,16 @@ export default Ember.Controller.extend({ actions: { transitionToCluster(resp) { - debugger; return this.get('model').reload().then(() => { this.get('wizard').transitionTutorialMachine(this.get('wizard.currentState'), 'CONTINUE', resp); return this.transitionToRoute('vault.cluster', this.get('model.name')); }); }, + + setUnsealState(resp) { + this.get('wizard').set('componentState', resp); + }, + isUnsealed(data) { return data.sealed === false; }, diff --git a/ui/app/routes/vault/cluster/unseal.js b/ui/app/routes/vault/cluster/unseal.js index 153e8bae5d3..875583f717c 100644 --- a/ui/app/routes/vault/cluster/unseal.js +++ b/ui/app/routes/vault/cluster/unseal.js @@ -1,20 +1 @@ -import ClusterRouteBase from './cluster-route-base'; -import Ember from 'ember'; - -const { inject } = Ember; - -export default ClusterRouteBase.extend({ - wizard: inject.service(), - - beforeModel() { - this._super(...arguments); - debugger; - if (this.get('wizard.currentState') === 'active.init.save') { - this.get('wizard').transitionTutorialMachine(this.get('wizard.currentState'), 'CONTINUE'); - } - }, - afterModel() { - this._super(...arguments); - debugger; - }, -}); +export { default } from './cluster-route-base'; diff --git a/ui/app/services/wizard.js b/ui/app/services/wizard.js index dcc3447c5d7..7abfe415df6 100644 --- a/ui/app/services/wizard.js +++ b/ui/app/services/wizard.js @@ -30,20 +30,20 @@ export default Service.extend({ if (!this.storageHasKey(TUTORIAL_STATE)) { this.saveState('currentState', TutorialMachine.initialState); this.saveExtState(TUTORIAL_STATE, this.get('currentState')); - } else { - this.saveState('currentState', this.getExtState(TUTORIAL_STATE)); - if (this.storageHasKey(FEATURE_LIST)) { - this.set('featureList', this.getExtState(FEATURE_LIST)); - if (this.storageHasKey(FEATURE_STATE)) { - this.saveState('featureState', this.getExtState(FEATURE_STATE)); - } else { - if (FeatureMachine != null) { - this.saveState('featureState', FeatureMachine.initialState); - this.saveExtState(FEATURE_STATE, this.get('featureState')); - } + return; + } + this.saveState('currentState', this.getExtState(TUTORIAL_STATE)); + if (this.storageHasKey(FEATURE_LIST)) { + this.set('featureList', this.getExtState(FEATURE_LIST)); + if (this.storageHasKey(FEATURE_STATE)) { + this.saveState('featureState', this.getExtState(FEATURE_STATE)); + } else { + if (FeatureMachine != null) { + this.saveState('featureState', FeatureMachine.initialState); + this.saveExtState(FEATURE_STATE, this.get('featureState')); } - this.buildFeatureMachine(); } + this.buildFeatureMachine(); } }, @@ -62,7 +62,6 @@ export default Service.extend({ }, transitionTutorialMachine(currentState, event, extendedState) { - debugger; if (extendedState) { this.set('componentState', extendedState); } diff --git a/ui/app/templates/vault/cluster/unseal.hbs b/ui/app/templates/vault/cluster/unseal.hbs index 422d0641d78..88a8cdd3324 100644 --- a/ui/app/templates/vault/cluster/unseal.hbs +++ b/ui/app/templates/vault/cluster/unseal.hbs @@ -10,6 +10,7 @@ Date: Mon, 20 Aug 2018 23:12:24 -0500 Subject: [PATCH 011/124] use components for each machine state --- ui/app/components/doc-link.js | 2 +- ui/app/components/shamir-flow.js | 4 +- ui/app/components/wizard/init-steps.js | 82 ------------------- ui/app/machines/tutorial-machine.js | 7 +- ui/app/services/wizard.js | 4 + ui/app/templates/components/ui-wizard.hbs | 2 +- .../components/wizard/init-save-keys.hbs | 12 +++ .../components/wizard/init-setup.hbs | 16 ++++ .../components/wizard/init-steps.hbs | 5 -- .../components/wizard/init-unseal.hbs | 15 ++++ .../cluster/settings/mount-secret-backend.hbs | 2 +- 11 files changed, 58 insertions(+), 93 deletions(-) delete mode 100644 ui/app/components/wizard/init-steps.js create mode 100644 ui/app/templates/components/wizard/init-save-keys.hbs create mode 100644 ui/app/templates/components/wizard/init-setup.hbs delete mode 100644 ui/app/templates/components/wizard/init-steps.hbs create mode 100644 ui/app/templates/components/wizard/init-unseal.hbs diff --git a/ui/app/components/doc-link.js b/ui/app/components/doc-link.js index 892cb2ffe06..51381582c67 100644 --- a/ui/app/components/doc-link.js +++ b/ui/app/components/doc-link.js @@ -14,6 +14,6 @@ export default Component.extend({ path: '/', href: computed('path', function() { - return `https://www.vaultproject.io/docs${this.get('path')}`; + return `https://www.vaultproject.io${this.get('path')}`; }), }); diff --git a/ui/app/components/shamir-flow.js b/ui/app/components/shamir-flow.js index 801b9dd37ee..48cd5ab12e7 100644 --- a/ui/app/components/shamir-flow.js +++ b/ui/app/components/shamir-flow.js @@ -61,8 +61,8 @@ export default Component.extend(DEFAULTS, { hasProgress: computed.gt('progress', 0), actionSuccess(resp) { - let { onActionSuccess, isComplete, onShamirSuccess, thresholdPath } = this.getProperties( - 'onActionSuccess', + let { onUpdate, isComplete, onShamirSuccess, thresholdPath } = this.getProperties( + 'onUpdate', 'isComplete', 'onShamirSuccess', 'thresholdPath' diff --git a/ui/app/components/wizard/init-steps.js b/ui/app/components/wizard/init-steps.js deleted file mode 100644 index 1f39ae29780..00000000000 --- a/ui/app/components/wizard/init-steps.js +++ /dev/null @@ -1,82 +0,0 @@ -import Ember from 'ember'; - -const { inject, computed } = Ember; - -export default Ember.Component.extend({ - wizard: inject.service(), - currentState: computed.alias('wizard.currentState'), - featureState: computed.alias('wizard.featureState'), - componentState: computed.alias('wizard.componentState'), - initializationSteps: [ - { - key: 'setup', - title: 'Setting up your master keys', - description: - 'This is the very first step of setting up a Vault server. Vault comes with an important security feature called "seal", which lets you shut down and secure your Vault installation if there is a security breach. This is the default state of Vault, so it is currently sealed since it was just installed. To unseal the vault, you will need to provide the key(s) that you generate here.', - docs: { - link: 'https://www.vaultproject.io/intro/getting-started/deploy.html#initializing-the-vault', - text: 'Docs: Initialization', - }, - }, - { - key: 'save', - title: 'Saving your keys', - description: - "Now that Vault is initialized, you'll want to save your root token and master key portions in a safe place. Distribute your keys to responsible people on your team. If these keys are lost, you may not be able to access your data again. Keep them safe!", - docs: { - link: 'https://www.vaultproject.io/intro/getting-started/deploy.html#initializing-the-vault', - text: 'Docs: Initialization', - }, - }, - { - key: 'unseal', - title: 'Unsealing your vault', - description: '', - docs: { - link: 'https://www.vaultproject.io/docs/concepts/seal.html', - text: 'Docs: Unseal', - }, - }, - { - key: 'login', - title: 'Sign in to your vault', - description: 'Congrats! Now that your vault is all set up you can sign in!', - }, - ], - inSetup: computed('currentState', function() { - return this.get('currentState').indexOf('init.setup') > 0; - }), - isSaving: computed('currentState', function() { - return this.get('currentState').indexOf('init.save') > 0; - }), - isUnsealing: computed('currentState', function() { - return this.get('currentState').indexOf('init.unseal') > 0; - }), - inLogin: computed('currentState', function() { - return this.get('currentState').indexOf('init.lgin') > 0; - }), - currentStep: computed('currentState', 'componentState', function() { - const stateParts = this.get('currentState').split('.'); - let currentStep = this.get('initializationSteps') - .filter(step => step.key === stateParts[stateParts.length - 1]) - .objectAt(0); - if (this.get('isUnsealing')) { - if (this.get('componentState')) { - const keyWord = this.get('componentState.threshold') > 1 ? 'keys' : 'key'; - const providedWord = this.get('componentState.progress') > 1 ? 'have' : 'has'; - const keysLeft = this.get('componentState.threshold') - this.get('componentState.progress'); - const leftWord = keysLeft > 1 ? 'keys' : 'key'; - Ember.set( - currentStep, - 'description', - `Now we will provide the ${keyWord} that you copied or downloaded to unseal the vault so that we can get started using it. You'll need ${this.get( - 'componentState.threshold' - )} ${keyWord} total, and ${this.get( - 'componentState.progress' - )} ${providedWord} already been provided. Please provide ${keysLeft} more ${leftWord} to unseal.` - ); - } - } - return currentStep; - }), -}); diff --git a/ui/app/machines/tutorial-machine.js b/ui/app/machines/tutorial-machine.js index 446d56a0581..26232100ec3 100644 --- a/ui/app/machines/tutorial-machine.js +++ b/ui/app/machines/tutorial-machine.js @@ -23,14 +23,19 @@ export default { states: { setup: { on: { CONTINUE: 'save' }, + onEntry: { type: 'render', component: 'wizard/init-setup' }, }, save: { on: { CONTINUE: 'unseal' }, + onEntry: { type: 'render', component: 'wizard/init-save-keys' }, }, unseal: { on: { CONTINUE: 'login' }, + onEntry: { type: 'render', component: 'wizard/init-unseal' }, + }, + login: { + onEntry: { type: 'render', component: 'wizard/init-login' }, }, - login: {}, }, }, }, diff --git a/ui/app/services/wizard.js b/ui/app/services/wizard.js index 7abfe415df6..5f626a124e0 100644 --- a/ui/app/services/wizard.js +++ b/ui/app/services/wizard.js @@ -100,6 +100,10 @@ export default Service.extend({ type = action.type; } switch (type) { + case 'render': + this.set('featureComponent', action.component); + break; + case 'routeTransition': this.get('router').transitionTo(...action.params); break; diff --git a/ui/app/templates/components/ui-wizard.hbs b/ui/app/templates/components/ui-wizard.hbs index 45a6fd743fb..d07f4a378ff 100644 --- a/ui/app/templates/components/ui-wizard.hbs +++ b/ui/app/templates/components/ui-wizard.hbs @@ -25,7 +25,7 @@ {{wizard/features-selection}} {{else}} {{#if isInitializing}} - {{component "wizard/init-steps"}} + {{component featureComponent componentState=componentState}} {{else}} {{#if isFeature}} {{component "wizard/secrets-machine"}} diff --git a/ui/app/templates/components/wizard/init-save-keys.hbs b/ui/app/templates/components/wizard/init-save-keys.hbs new file mode 100644 index 00000000000..bc8731cfaf6 --- /dev/null +++ b/ui/app/templates/components/wizard/init-save-keys.hbs @@ -0,0 +1,12 @@ +
+

Saving your keys

+

+ Now that Vault is initialized, you'll want to save your root token and + master key portions in a safe place. Distribute your keys to responsible + people on your team. If these keys are lost, you may not be able to access + your data again. Keep them safe! +

+ + Docs: Initialization + +
diff --git a/ui/app/templates/components/wizard/init-setup.hbs b/ui/app/templates/components/wizard/init-setup.hbs new file mode 100644 index 00000000000..f4ac7f903c5 --- /dev/null +++ b/ui/app/templates/components/wizard/init-setup.hbs @@ -0,0 +1,16 @@ +
+

Setting up your master keys

+

+ This is the very first step of setting + a Vault server. Vault comes with an important + security feature called "seal", which lets you + shut down and secure your Vault installation if + there is a security breach. This is the default + state of Vault, so it is currently sealed since + it was just installed. To unseal the vault, you will + need to provide the key(s) that you generate here. +

+ + Docs: Initialization + +
diff --git a/ui/app/templates/components/wizard/init-steps.hbs b/ui/app/templates/components/wizard/init-steps.hbs deleted file mode 100644 index 7d9d15d06d3..00000000000 --- a/ui/app/templates/components/wizard/init-steps.hbs +++ /dev/null @@ -1,5 +0,0 @@ -
-

{{currentStep.title}}

-

{{currentStep.description}}

- {{currentStep.docs.text}} -
\ No newline at end of file diff --git a/ui/app/templates/components/wizard/init-unseal.hbs b/ui/app/templates/components/wizard/init-unseal.hbs new file mode 100644 index 00000000000..9610edeb2ba --- /dev/null +++ b/ui/app/templates/components/wizard/init-unseal.hbs @@ -0,0 +1,15 @@ +
+

Setting up your master keys

+

+ Now we will provide the {{pluralize 'key' componentState.threshold}} that + you copied or downloaded to unseal the vault so that we can get started + using it. You'll need {{pluralize 'key' componentState.threshold}} total, + and {{pluralize 'key has' componentState.progress}} already been provided. + Please provide + {{pluralize 'more key ' (dec componentState.progress componentState.threshold)}} to unseal. +

+ + Docs: Initialization + +
+ diff --git a/ui/app/templates/vault/cluster/settings/mount-secret-backend.hbs b/ui/app/templates/vault/cluster/settings/mount-secret-backend.hbs index 616b593c53c..19d0657e026 100644 --- a/ui/app/templates/vault/cluster/settings/mount-secret-backend.hbs +++ b/ui/app/templates/vault/cluster/settings/mount-secret-backend.hbs @@ -31,7 +31,7 @@ {{#if selection.deprecated}}
- The {{selection.label}} backend is deprecated! If you are using a SQL database backend, use the general purpose {{#doc-link path="/secrets/databases/index.html"}}Databases{{/doc-link}} backend instead. + The {{selection.label}} backend is deprecated! If you are using a SQL database backend, use the general purpose {{#doc-link path="/docs/secrets/databases/index.html"}}Databases{{/doc-link}} backend instead.
{{/if}} From d6fd20277e63c7d5e984542f4cc27e46d758c6a2 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Tue, 21 Aug 2018 12:44:52 -0500 Subject: [PATCH 012/124] move more things into templates --- ui/app/components/ui-wizard.js | 3 ++ ui/app/machines/tutorial-machine.js | 14 ++++--- ui/app/routes/vault/cluster/init.js | 13 +++++- ui/app/services/wizard.js | 13 ++++-- ui/app/templates/components/ui-wizard.hbs | 41 ++----------------- .../components/wizard/tutorial-active.hbs | 6 +++ .../components/wizard/tutorial-idle.hbs | 12 ++++++ .../components/wizard/tutorial-paused.hbs | 12 ++++++ 8 files changed, 66 insertions(+), 48 deletions(-) create mode 100644 ui/app/templates/components/wizard/tutorial-active.hbs create mode 100644 ui/app/templates/components/wizard/tutorial-idle.hbs create mode 100644 ui/app/templates/components/wizard/tutorial-paused.hbs diff --git a/ui/app/components/ui-wizard.js b/ui/app/components/ui-wizard.js index c1646bde8e6..1fc87ce6d42 100644 --- a/ui/app/components/ui-wizard.js +++ b/ui/app/components/ui-wizard.js @@ -6,6 +6,9 @@ export default Ember.Component.extend({ wizard: inject.service(), currentState: computed.alias('wizard.currentState'), featureState: computed.alias('wizard.featureState'), + featureComponent: computed.alias('wizard.featureComponent'), + tutorialComponent: computed.alias('wizard.tutorialComponent'), + componentState: computed.alias('wizard.componentState'), currentMachine: computed('wizard.featureList', function() { if (this.get('wizard.featureList') !== null) { let machine = this.get('wizard.featureList').toArray().objectAt(0); diff --git a/ui/app/machines/tutorial-machine.js b/ui/app/machines/tutorial-machine.js index 26232100ec3..60bdb117867 100644 --- a/ui/app/machines/tutorial-machine.js +++ b/ui/app/machines/tutorial-machine.js @@ -7,6 +7,7 @@ export default { DISMISS: 'dismissed', AUTH: 'select', }, + onEntry: { type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' }, key: 'feature', initial: 'init', states: { @@ -14,6 +15,7 @@ export default { on: { CONTINUE: 'feature', }, + onEntry: { type: 'render', level: 'feature', component: 'wizard/feature-selection' }, }, feature: {}, init: { @@ -23,18 +25,18 @@ export default { states: { setup: { on: { CONTINUE: 'save' }, - onEntry: { type: 'render', component: 'wizard/init-setup' }, + onEntry: { type: 'render', level: 'feature', component: 'wizard/init-setup' }, }, save: { on: { CONTINUE: 'unseal' }, - onEntry: { type: 'render', component: 'wizard/init-save-keys' }, + onEntry: { type: 'render', level: 'feature', component: 'wizard/init-save-keys' }, }, unseal: { on: { CONTINUE: 'login' }, - onEntry: { type: 'render', component: 'wizard/init-unseal' }, + onEntry: { type: 'render', level: 'feature', component: 'wizard/init-unseal' }, }, login: { - onEntry: { type: 'render', component: 'wizard/init-login' }, + onEntry: { type: 'render', level: 'feature', component: 'wizard/init-login' }, }, }, }, @@ -45,13 +47,15 @@ export default { DISMISS: 'dismissed', CONTINUE: 'active', }, + onEntry: { type: 'render', level: 'tutorial', component: 'wizard/tutorial-idle' }, }, dismissed: { on: { CONTINUE: 'idle' }, - onEntry: ['handleDismissed'], + onEntry: [{ type: 'render', level: 'tutorial', component: null }, 'handleDismissed'], }, paused: { on: { CONTINUE: ['handlePause'] }, + onEntry: { type: 'render', level: 'tutorial', component: 'wizard/tutorial-paused' }, }, complete: { on: { diff --git a/ui/app/routes/vault/cluster/init.js b/ui/app/routes/vault/cluster/init.js index 875583f717c..ba2e6a5e53b 100644 --- a/ui/app/routes/vault/cluster/init.js +++ b/ui/app/routes/vault/cluster/init.js @@ -1 +1,12 @@ -export { default } from './cluster-route-base'; +import Ember from 'ember'; +import ClusterRoute from './cluster-route-base'; + +const { inject } = Ember; + +export default ClusterRoute.extend({ + wizard: inject.service(), + + activate() { + this.get('wizard').set('currentState', 'idle'); + }, +}); diff --git a/ui/app/services/wizard.js b/ui/app/services/wizard.js index 5f626a124e0..2433c405f77 100644 --- a/ui/app/services/wizard.js +++ b/ui/app/services/wizard.js @@ -23,13 +23,19 @@ export default Service.extend({ featureState: null, currentMachine: null, potentialSelection: null, + tutorialComponent: null, + featureComponent: null, componentState: null, init() { this._super(...arguments); if (!this.storageHasKey(TUTORIAL_STATE)) { - this.saveState('currentState', TutorialMachine.initialState); - this.saveExtState(TUTORIAL_STATE, this.get('currentState')); + let state = TutorialMachine.initialState; + this.saveState('currentState', state); + this.saveExtState(TUTORIAL_STATE, state.value); + for (let action of state.actions) { + this.executeAction(action, event); + } return; } this.saveState('currentState', this.getExtState(TUTORIAL_STATE)); @@ -101,9 +107,8 @@ export default Service.extend({ } switch (type) { case 'render': - this.set('featureComponent', action.component); + this.set(`${action.level}Component`, action.component); break; - case 'routeTransition': this.get('router').transitionTo(...action.params); break; diff --git a/ui/app/templates/components/ui-wizard.hbs b/ui/app/templates/components/ui-wizard.hbs index d07f4a378ff..04f60521370 100644 --- a/ui/app/templates/components/ui-wizard.hbs +++ b/ui/app/templates/components/ui-wizard.hbs @@ -1,38 +1,3 @@ -{{#unless isDismissed}} -
- - {{#unless isActive}} -
- {{#if isPaused}} -

Vault Web UI Guide

-

Feel free to explore Vault. Click below to get back to the guide or close this window.

- {{else}} -

Welcome to Vault

-

Want a tour? Our helpful guide will introduce you to the Vault Web UI.

- {{/if}} -
- - {{/unless}} - {{#unless isPaused}} - {{#if isActive}} - {{#if isSelecting}} - {{wizard/features-selection}} - {{else}} - {{#if isInitializing}} - {{component featureComponent componentState=componentState}} - {{else}} - {{#if isFeature}} - {{component "wizard/secrets-machine"}} - {{/if}} - {{/if}} - {{/if}} - {{/if}} - {{/unless}} -
-{{/unless}} +{{#component tutorialComponent onDismiss=dismissWizard onAdvance=advanceWizard}} + {{component featureComponent componentState=componentState}} +{{/component}} diff --git a/ui/app/templates/components/wizard/tutorial-active.hbs b/ui/app/templates/components/wizard/tutorial-active.hbs new file mode 100644 index 00000000000..84e10f160c6 --- /dev/null +++ b/ui/app/templates/components/wizard/tutorial-active.hbs @@ -0,0 +1,6 @@ +
+ + {{yield}} +
diff --git a/ui/app/templates/components/wizard/tutorial-idle.hbs b/ui/app/templates/components/wizard/tutorial-idle.hbs new file mode 100644 index 00000000000..70713caa833 --- /dev/null +++ b/ui/app/templates/components/wizard/tutorial-idle.hbs @@ -0,0 +1,12 @@ + diff --git a/ui/app/templates/components/wizard/tutorial-paused.hbs b/ui/app/templates/components/wizard/tutorial-paused.hbs new file mode 100644 index 00000000000..def007c0e6f --- /dev/null +++ b/ui/app/templates/components/wizard/tutorial-paused.hbs @@ -0,0 +1,12 @@ + From 8fac4b57df19a172d402324a661e3732495988dc Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Tue, 21 Aug 2018 16:46:45 -0500 Subject: [PATCH 013/124] apply onEntry for all states on init so template rendering works, and add auth event for idle --- ui/app/components/ui-wizard.js | 49 ++++------------ ui/app/machines/tutorial-machine.js | 8 +-- ui/app/services/wizard.js | 69 +++++++++++------------ ui/app/templates/components/ui-wizard.hbs | 2 +- 4 files changed, 49 insertions(+), 79 deletions(-) diff --git a/ui/app/components/ui-wizard.js b/ui/app/components/ui-wizard.js index 1fc87ce6d42..50fdebc1eeb 100644 --- a/ui/app/components/ui-wizard.js +++ b/ui/app/components/ui-wizard.js @@ -4,50 +4,25 @@ const { inject, computed } = Ember; export default Ember.Component.extend({ wizard: inject.service(), + auth: inject.service(), currentState: computed.alias('wizard.currentState'), featureState: computed.alias('wizard.featureState'), featureComponent: computed.alias('wizard.featureComponent'), tutorialComponent: computed.alias('wizard.tutorialComponent'), componentState: computed.alias('wizard.componentState'), - currentMachine: computed('wizard.featureList', function() { - if (this.get('wizard.featureList') !== null) { - let machine = this.get('wizard.featureList').toArray().objectAt(0); - return machine.charAt(0).toUpperCase() + machine.slice(1); - } - return 'None'; - }), - isActive: computed('currentState', function() { - return this.get('currentState').indexOf('active') == 0; - }), - isDismissed: computed('currentState', function() { - return this.get('currentState') === 'dismissed'; - }), - isSelecting: computed('currentState', 'isActive', function() { - return this.get('isActive') && this.get('currentState').indexOf('select') > 0; - }), - isInitializing: computed('currentState', 'isActive', function() { - return this.get('isActive') && this.get('currentState').indexOf('init') > 0; - }), - isFeature: computed('currentMachine', function() { - return this.get('currentMachine') !== 'None'; - }), - isPaused: computed('currentState', function() { - return this.get('currentState') === 'paused'; - }), - isIdle: computed('currentState', function() { - return this.get('currentState') === 'idle'; - }), - isCollapsed: computed.or('isPaused', 'isIdle'), - dismissWizard() { - this.get('wizard').transitionTutorialMachine(this.get('currentState'), 'DISMISS'); - }, + actions: { + dismissWizard() { + this.get('wizard').transitionTutorialMachine(this.get('currentState'), 'DISMISS'); + }, - advanceWizard() { - this.get('wizard').transitionTutorialMachine(this.get('currentState'), 'CONTINUE'); - }, + advanceWizard() { + let event = this.get('auth.currentToken') ? 'AUTH' : 'CONTINUE'; + this.get('wizard').transitionTutorialMachine(this.get('currentState'), event); + }, - pauseWizard() { - this.get('wizard').transitionTutorialMachine(this.get('currentState'), 'PAUSE'); + pauseWizard() { + this.get('wizard').transitionTutorialMachine(this.get('currentState'), 'PAUSE'); + }, }, }); diff --git a/ui/app/machines/tutorial-machine.js b/ui/app/machines/tutorial-machine.js index 60bdb117867..525c9904444 100644 --- a/ui/app/machines/tutorial-machine.js +++ b/ui/app/machines/tutorial-machine.js @@ -3,19 +3,18 @@ export default { initial: 'idle', states: { active: { + key: 'feature', + initial: 'init', on: { DISMISS: 'dismissed', - AUTH: 'select', }, onEntry: { type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' }, - key: 'feature', - initial: 'init', states: { select: { on: { CONTINUE: 'feature', }, - onEntry: { type: 'render', level: 'feature', component: 'wizard/feature-selection' }, + onEntry: { type: 'render', level: 'feature', component: 'wizard/features-selection' }, }, feature: {}, init: { @@ -46,6 +45,7 @@ export default { on: { DISMISS: 'dismissed', CONTINUE: 'active', + AUTH: 'active.select', }, onEntry: { type: 'render', level: 'tutorial', component: 'wizard/tutorial-idle' }, }, diff --git a/ui/app/services/wizard.js b/ui/app/services/wizard.js index 2433c405f77..dbffdad1c9c 100644 --- a/ui/app/services/wizard.js +++ b/ui/app/services/wizard.js @@ -31,14 +31,13 @@ export default Service.extend({ this._super(...arguments); if (!this.storageHasKey(TUTORIAL_STATE)) { let state = TutorialMachine.initialState; - this.saveState('currentState', state); + this.saveState('currentState', state.value); this.saveExtState(TUTORIAL_STATE, state.value); - for (let action of state.actions) { - this.executeAction(action, event); - } - return; } this.saveState('currentState', this.getExtState(TUTORIAL_STATE)); + + let stateNodes = TutorialMachine.getStateNodes(this.get('currentState')); + this.executeActions(stateNodes.reduce((acc, node) => acc.concat(node.onEntry), [])); if (this.storageHasKey(FEATURE_LIST)) { this.set('featureList', this.getExtState(FEATURE_LIST)); if (this.storageHasKey(FEATURE_STATE)) { @@ -74,18 +73,14 @@ export default Service.extend({ let { actions, value } = TutorialMachine.transition(currentState, event); this.saveState('currentState', value); this.saveExtState(TUTORIAL_STATE, this.get('currentState')); - for (let action of actions) { - this.executeAction(action, event); - } + this.executeActions(actions, event); }, transitionFeatureMachine(currentState, event, extendedState) { let { actions, value } = FeatureMachine.transition(currentState, event, extendedState); this.saveState('featureState', value); this.saveExtState(FEATURE_STATE, value); - for (let action of actions) { - this.executeAction(action, event); - } + this.executeActions(actions, event); }, saveExtState(key, value) { @@ -100,29 +95,31 @@ export default Service.extend({ return Boolean(this.getExtState(key)); }, - executeAction(action, event) { - let type = action; - if (action.type) { - type = action.type; - } - switch (type) { - case 'render': - this.set(`${action.level}Component`, action.component); - break; - case 'routeTransition': - this.get('router').transitionTo(...action.params); - break; - case 'saveFeatures': - this.saveFeatures(event.features); - break; - case 'completeFeature': - this.completeFeature(); - break; - case 'handleDismissed': - this.handleDismissed(); - break; - default: - break; + executeActions(actions, event) { + for (let action of actions) { + let type = action; + if (action.type) { + type = action.type; + } + switch (type) { + case 'render': + this.set(`${action.level}Component`, action.component); + break; + case 'routeTransition': + this.get('router').transitionTo(...action.params); + break; + case 'saveFeatures': + this.saveFeatures(event.features); + break; + case 'completeFeature': + this.completeFeature(); + break; + case 'handleDismissed': + this.handleDismissed(); + break; + default: + break; + } } }, @@ -148,9 +145,7 @@ export default Service.extend({ this.set('currentMachine', this.get('featureList').objectAt(0)); this.saveState('featureState', FeatureMachine.initialState); this.saveExtState(FEATURE_STATE, this.get('featureState')); - for (let action of FeatureMachine.initialState.actions) { - this.executeAction(action); - } + this.executeActions(FeatureMachine.initialState.actions); }, completeFeature() { diff --git a/ui/app/templates/components/ui-wizard.hbs b/ui/app/templates/components/ui-wizard.hbs index 04f60521370..71db3fa5af2 100644 --- a/ui/app/templates/components/ui-wizard.hbs +++ b/ui/app/templates/components/ui-wizard.hbs @@ -1,3 +1,3 @@ -{{#component tutorialComponent onDismiss=dismissWizard onAdvance=advanceWizard}} +{{#component tutorialComponent onDismiss=(action "dismissWizard") onAdvance=(action "advanceWizard")}} {{component featureComponent componentState=componentState}} {{/component}} From a28a23a8f2acb3f9f9043ceab81eaab3f40743f1 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Wed, 22 Aug 2018 08:59:23 -0500 Subject: [PATCH 014/124] more events to go to nested states --- ui/app/machines/tutorial-machine.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/app/machines/tutorial-machine.js b/ui/app/machines/tutorial-machine.js index 525c9904444..5aa58643d82 100644 --- a/ui/app/machines/tutorial-machine.js +++ b/ui/app/machines/tutorial-machine.js @@ -46,6 +46,8 @@ export default { DISMISS: 'dismissed', CONTINUE: 'active', AUTH: 'active.select', + UNSEAL: 'active.unseal', + LOGIN: 'active.login', }, onEntry: { type: 'render', level: 'tutorial', component: 'wizard/tutorial-idle' }, }, From b20d653771407c77b205322428216fca704d026c Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Wed, 22 Aug 2018 10:09:17 -0500 Subject: [PATCH 015/124] fix state transitions from idle for init --- ui/app/machines/tutorial-machine.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/app/machines/tutorial-machine.js b/ui/app/machines/tutorial-machine.js index 5aa58643d82..c15e8e6fbe9 100644 --- a/ui/app/machines/tutorial-machine.js +++ b/ui/app/machines/tutorial-machine.js @@ -21,6 +21,8 @@ export default { key: 'init', initial: 'setup', on: { DONE: 'select' }, + onExit: ['showTutorialWhenAuthenticated'], + onEntry: ['showTutorialAlways'], states: { setup: { on: { CONTINUE: 'save' }, @@ -46,8 +48,8 @@ export default { DISMISS: 'dismissed', CONTINUE: 'active', AUTH: 'active.select', - UNSEAL: 'active.unseal', - LOGIN: 'active.login', + UNSEAL: 'active.init.unseal', + LOGIN: 'active.init.login', }, onEntry: { type: 'render', level: 'tutorial', component: 'wizard/tutorial-idle' }, }, From d3f068cb03d848bc408df2dc5ce97612cc466a3f Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Wed, 22 Aug 2018 16:56:23 -0500 Subject: [PATCH 016/124] initial positioning of UI wizard --- ui/app/styles/components/ui-wizard.scss | 16 +++++++++++++--- ui/app/templates/vault/cluster.hbs | 5 ++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/ui/app/styles/components/ui-wizard.scss b/ui/app/styles/components/ui-wizard.scss index b47a66def4d..7df5b33ba54 100644 --- a/ui/app/styles/components/ui-wizard.scss +++ b/ui/app/styles/components/ui-wizard.scss @@ -1,14 +1,24 @@ +.ui-wizard-container { + display: flex; + flex-direction: row; +} +.ui-wizard-container .app-content { + flex-grow: 1; +} .ui-wizard { + width: 300px; background: #ffffff; color: #000000; - width: 300px; padding: 16px; - display: block; - float: right; margin: 9px; + height: 80vh; } .ui-wizard.collapsed { + position: fixed; + top: 4rem; + right: 2rem; + height: 11rem; background: #000000; color: #ffffff; } diff --git a/ui/app/templates/vault/cluster.hbs b/ui/app/templates/vault/cluster.hbs index 0e1048ce9de..a9b5cb4a769 100644 --- a/ui/app/templates/vault/cluster.hbs +++ b/ui/app/templates/vault/cluster.hbs @@ -1,4 +1,3 @@ - {{#if showNav}} @@ -132,11 +131,15 @@ (is-after (now interval=1000) auth.tokenExpirationDate) ) }} + {{outlet}} + {{/unless}}
{{else}} + {{outlet}} + {{/if}} From adf1dc50d59e6d9c2dfe2d0ddfe614016e7c3c16 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Wed, 22 Aug 2018 16:56:55 -0500 Subject: [PATCH 017/124] init guide workflow working --- ui/app/components/ui-wizard.js | 7 ++- ui/app/controllers/vault/cluster/init.js | 3 +- ui/app/machines/tutorial-machine.js | 61 ++++++++++++------- ui/app/routes/vault/cluster/auth.js | 12 ++++ ui/app/routes/vault/cluster/init.js | 4 +- ui/app/routes/vault/cluster/unseal.js | 15 ++++- ui/app/services/wizard.js | 9 ++- ui/app/templates/components/ui-wizard.hbs | 8 ++- .../components/wizard/init-login.hbs | 8 +++ .../components/wizard/init-unseal.hbs | 14 +++-- .../components/wizard/tutorial-idle.hbs | 22 +++---- 11 files changed, 118 insertions(+), 45 deletions(-) create mode 100644 ui/app/templates/components/wizard/init-login.hbs diff --git a/ui/app/components/ui-wizard.js b/ui/app/components/ui-wizard.js index 50fdebc1eeb..667d8687f26 100644 --- a/ui/app/components/ui-wizard.js +++ b/ui/app/components/ui-wizard.js @@ -3,8 +3,13 @@ import Ember from 'ember'; const { inject, computed } = Ember; export default Ember.Component.extend({ + classNames: ['ui-wizard-container'], wizard: inject.service(), auth: inject.service(), + + shouldRender: computed('wizard.showWhenUnauthenticated', 'auth.currentToken', function() { + return this.get('auth.currentToken') || this.get('wizard.showWhenUnauthenticated'); + }), currentState: computed.alias('wizard.currentState'), featureState: computed.alias('wizard.featureState'), featureComponent: computed.alias('wizard.featureComponent'), @@ -17,7 +22,7 @@ export default Ember.Component.extend({ }, advanceWizard() { - let event = this.get('auth.currentToken') ? 'AUTH' : 'CONTINUE'; + let event = this.get('wizard.initEvent') || 'CONTINUE'; this.get('wizard').transitionTutorialMachine(this.get('currentState'), event); }, diff --git a/ui/app/controllers/vault/cluster/init.js b/ui/app/controllers/vault/cluster/init.js index d1841695dd9..4b163894566 100644 --- a/ui/app/controllers/vault/cluster/init.js +++ b/ui/app/controllers/vault/cluster/init.js @@ -19,7 +19,8 @@ export default Ember.Controller.extend(DEFAULTS, { initSuccess(resp) { this.set('loading', false); this.set('keyData', resp); - this.get('wizard').transitionTutorialMachine(this.get('wizard.currentState'), 'CONTINUE'); + this.get('wizard').set('initEvent', 'SAVE'); + this.get('wizard').transitionTutorialMachine(this.get('wizard.currentState'), 'TOSAVE'); }, initError(e) { diff --git a/ui/app/machines/tutorial-machine.js b/ui/app/machines/tutorial-machine.js index c15e8e6fbe9..e321a2f722d 100644 --- a/ui/app/machines/tutorial-machine.js +++ b/ui/app/machines/tutorial-machine.js @@ -2,38 +2,37 @@ export default { key: 'tutorial', initial: 'idle', states: { - active: { - key: 'feature', - initial: 'init', - on: { - DISMISS: 'dismissed', - }, - onEntry: { type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' }, + init: { + key: 'init', + initial: 'idle', + on: { DONE: 'active.select' }, + onEntry: [ + 'showTutorialAlways', + { type: 'render', level: 'tutorial', component: 'wizard/tutorial-idle' }, + ], + onExit: ['showTutorialWhenAuthenticated'], states: { - select: { + idle: { on: { - CONTINUE: 'feature', + START: 'active.setup', + SAVE: 'active.save', + UNSEAL: 'active.unseal', + LOGIN: 'active.login', }, - onEntry: { type: 'render', level: 'feature', component: 'wizard/features-selection' }, }, - feature: {}, - init: { - key: 'init', - initial: 'setup', - on: { DONE: 'select' }, - onExit: ['showTutorialWhenAuthenticated'], - onEntry: ['showTutorialAlways'], + active: { + onEntry: { type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' }, states: { setup: { - on: { CONTINUE: 'save' }, + on: { TOSAVE: 'save' }, onEntry: { type: 'render', level: 'feature', component: 'wizard/init-setup' }, }, save: { - on: { CONTINUE: 'unseal' }, + on: { TOUNSEAL: 'unseal' }, onEntry: { type: 'render', level: 'feature', component: 'wizard/init-save-keys' }, }, unseal: { - on: { CONTINUE: 'login' }, + on: { TOLOGIN: 'login' }, onEntry: { type: 'render', level: 'feature', component: 'wizard/init-unseal' }, }, login: { @@ -43,13 +42,29 @@ export default { }, }, }, + active: { + key: 'feature', + initial: 'select', + on: { + DISMISS: 'dismissed', + }, + onEntry: { type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' }, + states: { + select: { + on: { + CONTINUE: 'feature', + }, + onEntry: { type: 'render', level: 'feature', component: 'wizard/features-selection' }, + }, + feature: {}, + }, + }, idle: { on: { + INIT: 'init.idle', + AUTH: 'active.select', DISMISS: 'dismissed', CONTINUE: 'active', - AUTH: 'active.select', - UNSEAL: 'active.init.unseal', - LOGIN: 'active.init.login', }, onEntry: { type: 'render', level: 'tutorial', component: 'wizard/tutorial-idle' }, }, diff --git a/ui/app/routes/vault/cluster/auth.js b/ui/app/routes/vault/cluster/auth.js index e28d66e7c65..8aadd684ef6 100644 --- a/ui/app/routes/vault/cluster/auth.js +++ b/ui/app/routes/vault/cluster/auth.js @@ -7,6 +7,7 @@ const { inject } = Ember; export default ClusterRouteBase.extend({ flashMessages: inject.service(), version: inject.service(), + wizard: inject.service(), beforeModel() { return this._super().then(() => { return this.get('version').fetchFeatures(); @@ -25,4 +26,15 @@ export default ClusterRouteBase.extend({ this.get('flashMessages').stickyInfo(config.welcomeMessage); } }, + activate() { + this.get('wizard').set('initEvent', 'LOGIN'); + this.get('wizard').transitionTutorialMachine(this.get('wizard.currentState'), 'TOLOGIN'); + }, + actions: { + willTransition(transition) { + if (transition.targetName !== this.routeName) { + this.get('wizard').transitionTutorialMachine(this.get('wizard.currentState'), 'IDLE'); + } + }, + }, }); diff --git a/ui/app/routes/vault/cluster/init.js b/ui/app/routes/vault/cluster/init.js index ba2e6a5e53b..2972d08af8f 100644 --- a/ui/app/routes/vault/cluster/init.js +++ b/ui/app/routes/vault/cluster/init.js @@ -7,6 +7,8 @@ export default ClusterRoute.extend({ wizard: inject.service(), activate() { - this.get('wizard').set('currentState', 'idle'); + // always start from idle instead of using the current state + this.get('wizard').transitionTutorialMachine('idle', 'INIT'); + this.get('wizard').set('initEvent', 'START'); }, }); diff --git a/ui/app/routes/vault/cluster/unseal.js b/ui/app/routes/vault/cluster/unseal.js index 875583f717c..ab68729d8d1 100644 --- a/ui/app/routes/vault/cluster/unseal.js +++ b/ui/app/routes/vault/cluster/unseal.js @@ -1 +1,14 @@ -export { default } from './cluster-route-base'; +import Ember from 'ember'; +import ClusterRoute from './cluster-route-base'; + +const { inject } = Ember; + +export default ClusterRoute.extend({ + wizard: inject.service(), + + activate() { + let wizard = this.get('wizard'); + this.get('wizard').set('initEvent', 'UNSEAL'); + this.get('wizard').transitionTutorialMachine(this.get('wizard.currentState'), 'TOUNSEAL'); + }, +}); diff --git a/ui/app/services/wizard.js b/ui/app/services/wizard.js index dbffdad1c9c..0cbc55a7c05 100644 --- a/ui/app/services/wizard.js +++ b/ui/app/services/wizard.js @@ -26,6 +26,7 @@ export default Service.extend({ tutorialComponent: null, featureComponent: null, componentState: null, + showWhenUnauthenticated: false, init() { this._super(...arguments); @@ -96,6 +97,7 @@ export default Service.extend({ }, executeActions(actions, event) { + console.log(actions); for (let action of actions) { let type = action; if (action.type) { @@ -117,6 +119,12 @@ export default Service.extend({ case 'handleDismissed': this.handleDismissed(); break; + case 'showTutorialWhenAuthenticated': + this.set('showWhenUnauthenticated', false); + break; + case 'showTutorialAlways': + this.set('showWhenUnauthenticated', true); + break; default: break; } @@ -127,7 +135,6 @@ export default Service.extend({ this.storage().removeItem(FEATURE_STATE); this.storage().removeItem(FEATURE_LIST); this.storage().removeItem(MACHINES); - this.storage().removeItem(COMPLETED_FEATURES); }, saveFeatures(features) { diff --git a/ui/app/templates/components/ui-wizard.hbs b/ui/app/templates/components/ui-wizard.hbs index 71db3fa5af2..9417ac2ee13 100644 --- a/ui/app/templates/components/ui-wizard.hbs +++ b/ui/app/templates/components/ui-wizard.hbs @@ -1,3 +1,9 @@ -{{#component tutorialComponent onDismiss=(action "dismissWizard") onAdvance=(action "advanceWizard")}} +
+ {{yield}} +
+{{#component (if shouldRender tutorialComponent) + onDismiss=(action "dismissWizard") + onAdvance=(action "advanceWizard") +}} {{component featureComponent componentState=componentState}} {{/component}} diff --git a/ui/app/templates/components/wizard/init-login.hbs b/ui/app/templates/components/wizard/init-login.hbs new file mode 100644 index 00000000000..81be64c09cc --- /dev/null +++ b/ui/app/templates/components/wizard/init-login.hbs @@ -0,0 +1,8 @@ +
+

Log in to Vault

+

+

+ + Docs: Initialization + +
diff --git a/ui/app/templates/components/wizard/init-unseal.hbs b/ui/app/templates/components/wizard/init-unseal.hbs index 9610edeb2ba..806c0b39cee 100644 --- a/ui/app/templates/components/wizard/init-unseal.hbs +++ b/ui/app/templates/components/wizard/init-unseal.hbs @@ -1,15 +1,19 @@

Setting up your master keys

- Now we will provide the {{pluralize 'key' componentState.threshold}} that + Now we will provide the {{pluralize componentState.threshold 'key'}} that you copied or downloaded to unseal the vault so that we can get started - using it. You'll need {{pluralize 'key' componentState.threshold}} total, - and {{pluralize 'key has' componentState.progress}} already been provided. + using it. You'll need {{pluralize componentState.threshold 'key'}} total, + and {{#with (pluralize componentState.progress 'key' without-count=true) as |word|}} + {{if (eq word 'key') + (concat componentState.progress " " word " has ") + (concat componentState.progress " " word " have ") + }} + {{/with}} already been provided. Please provide - {{pluralize 'more key ' (dec componentState.progress componentState.threshold)}} to unseal. + {{pluralize (dec componentState.progress componentState.threshold) 'more key'}} to unseal.

Docs: Initialization
- diff --git a/ui/app/templates/components/wizard/tutorial-idle.hbs b/ui/app/templates/components/wizard/tutorial-idle.hbs index 70713caa833..9a560f61e26 100644 --- a/ui/app/templates/components/wizard/tutorial-idle.hbs +++ b/ui/app/templates/components/wizard/tutorial-idle.hbs @@ -1,12 +1,12 @@ -