UI Onboarding Wizards (#5196)
This commit is contained in:
parent
9f99ac44f4
commit
bd34be9144
|
@ -1,9 +1,10 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
auth: Ember.inject.service(),
|
||||
|
||||
routing: Ember.inject.service('-routing'),
|
||||
const { Component, inject, computed, run } = Ember;
|
||||
export default Component.extend({
|
||||
auth: inject.service(),
|
||||
wizard: inject.service(),
|
||||
routing: inject.service('-routing'),
|
||||
|
||||
transitionToRoute: function() {
|
||||
var router = this.get('routing.router');
|
||||
|
@ -12,12 +13,15 @@ export default Ember.Component.extend({
|
|||
|
||||
classNames: 'user-menu auth-info',
|
||||
|
||||
isRenewing: Ember.computed.or('fakeRenew', 'auth.isRenewing'),
|
||||
isRenewing: computed.or('fakeRenew', 'auth.isRenewing'),
|
||||
|
||||
actions: {
|
||||
restartGuide() {
|
||||
this.get('wizard').restartGuide();
|
||||
},
|
||||
renewToken() {
|
||||
this.set('fakeRenew', true);
|
||||
Ember.run.later(() => {
|
||||
run.later(() => {
|
||||
this.set('fakeRenew', false);
|
||||
this.get('auth').renew();
|
||||
}, 200);
|
||||
|
|
|
@ -5,6 +5,7 @@ const { Component, computed } = Ember;
|
|||
|
||||
export default Component.extend({
|
||||
tagName: 'a',
|
||||
classNames: ['doc-link'],
|
||||
attributeBindings: ['target', 'rel', 'href'],
|
||||
|
||||
layout: hbs`{{yield}}`,
|
||||
|
@ -14,6 +15,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')}`;
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -26,6 +26,7 @@ const MODEL_TYPES = {
|
|||
};
|
||||
|
||||
export default Component.extend({
|
||||
wizard: inject.service(),
|
||||
store: inject.service(),
|
||||
routing: inject.service('-routing'),
|
||||
// set on the component
|
||||
|
@ -57,6 +58,16 @@ export default Component.extend({
|
|||
this.createOrReplaceModel();
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
if (this.get('wizard.featureState') === 'displayRole') {
|
||||
this.get('wizard').transitionFeatureMachine(
|
||||
this.get('wizard.featureState'),
|
||||
'CONTINUE',
|
||||
this.get('backend.type')
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
willDestroy() {
|
||||
this.get('model').unloadRecord();
|
||||
this._super(...arguments);
|
||||
|
@ -84,10 +95,21 @@ export default Component.extend({
|
|||
create() {
|
||||
let model = this.get('model');
|
||||
this.set('loading', true);
|
||||
model.save().finally(() => {
|
||||
model.set('hasGenerated', true);
|
||||
this.set('loading', false);
|
||||
});
|
||||
this.model
|
||||
.save()
|
||||
.catch(() => {
|
||||
if (this.get('wizard.featureState') === 'credentials') {
|
||||
this.get('wizard').transitionFeatureMachine(
|
||||
this.get('wizard.featureState'),
|
||||
'ERROR',
|
||||
this.get('backend.type')
|
||||
);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
model.set('hasGenerated', true);
|
||||
this.set('loading', false);
|
||||
});
|
||||
},
|
||||
|
||||
codemirrorUpdated(attr, val, codemirror) {
|
||||
|
|
|
@ -3,6 +3,10 @@ import hbs from 'htmlbars-inline-precompile';
|
|||
|
||||
const { computed } = Ember;
|
||||
const GLYPHS_WITH_SVG_TAG = [
|
||||
'learn',
|
||||
'video',
|
||||
'tour',
|
||||
'stopwatch',
|
||||
'download',
|
||||
'folder',
|
||||
'file',
|
||||
|
@ -16,7 +20,7 @@ const GLYPHS_WITH_SVG_TAG = [
|
|||
'upload',
|
||||
'control-lock',
|
||||
'edition-enterprise',
|
||||
'edition-oss'
|
||||
'edition-oss',
|
||||
];
|
||||
|
||||
export default Ember.Component.extend({
|
||||
|
@ -40,11 +44,12 @@ export default Ember.Component.extend({
|
|||
glyph: null,
|
||||
|
||||
excludeSVG: computed('glyph', function() {
|
||||
return GLYPHS_WITH_SVG_TAG.includes(this.get('glyph'));
|
||||
let glyph = this.get('glyph');
|
||||
return glyph.startsWith('enable/') || GLYPHS_WITH_SVG_TAG.includes(glyph);
|
||||
}),
|
||||
|
||||
size: computed(function() {
|
||||
return 12;
|
||||
size: computed('glyph', function() {
|
||||
return this.get('glyph').startsWith('enable/') ? 48 : 12;
|
||||
}),
|
||||
|
||||
partialName: computed('glyph', function() {
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import Ember from 'ember';
|
||||
import { task } from 'ember-concurrency';
|
||||
import { methods } from 'vault/helpers/mountable-auth-methods';
|
||||
import { engines } from 'vault/helpers/mountable-secret-engines';
|
||||
|
||||
const { inject } = Ember;
|
||||
const { inject, computed, Component } = Ember;
|
||||
const METHODS = methods();
|
||||
const ENGINES = engines();
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend({
|
||||
store: inject.service(),
|
||||
wizard: inject.service(),
|
||||
flashMessages: inject.service(),
|
||||
routing: inject.service('-routing'),
|
||||
|
||||
|
@ -38,23 +41,29 @@ export default Ember.Component.extend({
|
|||
*/
|
||||
mountModel: null,
|
||||
|
||||
showConfig: false,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
const type = this.get('mountType');
|
||||
const modelType = type === 'secret' ? 'secret-engine' : 'auth-method';
|
||||
const model = this.get('store').createRecord(modelType);
|
||||
this.set('mountModel', model);
|
||||
this.changeConfigModel(model.get('type'));
|
||||
},
|
||||
|
||||
mountTypes: computed('mountType', function() {
|
||||
return this.get('mountType') === 'secret' ? ENGINES : METHODS;
|
||||
}),
|
||||
|
||||
willDestroy() {
|
||||
// if unsaved, we want to unload so it doesn't show up in the auth mount list
|
||||
this.get('mountModel').rollbackAttributes();
|
||||
},
|
||||
|
||||
getConfigModelType(methodType) {
|
||||
let mountType = this.get('mountType');
|
||||
let noConfig = ['approle'];
|
||||
if (noConfig.includes(methodType)) {
|
||||
if (mountType === 'secret' || noConfig.includes(methodType)) {
|
||||
return;
|
||||
}
|
||||
if (methodType === 'aws') {
|
||||
|
@ -64,27 +73,32 @@ export default Ember.Component.extend({
|
|||
},
|
||||
|
||||
changeConfigModel(methodType) {
|
||||
const mount = this.get('mountModel');
|
||||
const configRef = mount.hasMany('authConfigs').value();
|
||||
const currentConfig = configRef.get('firstObject');
|
||||
let mount = this.get('mountModel');
|
||||
if (this.get('mountType') === 'secret') {
|
||||
return;
|
||||
}
|
||||
let configRef = mount.hasMany('authConfigs').value();
|
||||
let currentConfig = configRef.get('firstObject');
|
||||
if (currentConfig) {
|
||||
// rollbackAttributes here will remove the the config model from the store
|
||||
// because `isNew` will be true
|
||||
currentConfig.rollbackAttributes();
|
||||
currentConfig.unloadRecord();
|
||||
}
|
||||
const configType = this.getConfigModelType(methodType);
|
||||
let configType = this.getConfigModelType(methodType);
|
||||
if (!configType) return;
|
||||
const config = this.get('store').createRecord(configType);
|
||||
let config = this.get('store').createRecord(configType);
|
||||
config.set('backend', mount);
|
||||
},
|
||||
|
||||
checkPathChange(type) {
|
||||
const mount = this.get('mountModel');
|
||||
const currentPath = mount.get('path');
|
||||
let mount = this.get('mountModel');
|
||||
let currentPath = mount.get('path');
|
||||
let list = this.get('mountTypes');
|
||||
// if the current path matches a type (meaning the user hasn't altered it),
|
||||
// change it here to match the new type
|
||||
const isUnchanged = METHODS.findBy('type', currentPath);
|
||||
if (isUnchanged) {
|
||||
let isUnchanged = list.findBy('type', currentPath);
|
||||
if (!currentPath || isUnchanged) {
|
||||
mount.set('path', type);
|
||||
}
|
||||
},
|
||||
|
@ -101,6 +115,10 @@ export default Ember.Component.extend({
|
|||
this.get('flashMessages').success(
|
||||
`Successfully mounted ${type} ${this.get('mountType')} method at ${path}.`
|
||||
);
|
||||
if (this.get('mountType') === 'secret') {
|
||||
yield this.get('onMountSuccess')(type, path);
|
||||
return;
|
||||
}
|
||||
yield this.get('saveConfig').perform(mountModel);
|
||||
}).drop(),
|
||||
|
||||
|
@ -111,11 +129,16 @@ export default Ember.Component.extend({
|
|||
try {
|
||||
if (config && Object.keys(config.changedAttributes()).length) {
|
||||
yield config.save();
|
||||
this.get('wizard').transitionFeatureMachine(
|
||||
this.get('wizard.featureState'),
|
||||
'CONTINUE',
|
||||
this.get('mountModel').get('type')
|
||||
);
|
||||
this.get('flashMessages').success(
|
||||
`The config for ${type} ${this.get('mountType')} method at ${path} was saved successfully.`
|
||||
);
|
||||
}
|
||||
yield this.get('onMountSuccess')();
|
||||
yield this.get('onMountSuccess')(type, path);
|
||||
} catch (err) {
|
||||
this.get('flashMessages').danger(
|
||||
`There was an error saving the configuration for ${type} ${this.get(
|
||||
|
@ -129,9 +152,27 @@ export default Ember.Component.extend({
|
|||
actions: {
|
||||
onTypeChange(path, value) {
|
||||
if (path === 'type') {
|
||||
this.get('wizard').set('componentState', value);
|
||||
this.changeConfigModel(value);
|
||||
this.checkPathChange(value);
|
||||
}
|
||||
},
|
||||
|
||||
toggleShowConfig(value) {
|
||||
this.set('showConfig', value);
|
||||
if (value === true && this.get('wizard.featureState') === 'idle') {
|
||||
this.get('wizard').transitionFeatureMachine(
|
||||
this.get('wizard.featureState'),
|
||||
'CONTINUE',
|
||||
this.get('mountModel').get('type')
|
||||
);
|
||||
} else {
|
||||
this.get('wizard').transitionFeatureMachine(
|
||||
this.get('wizard.featureState'),
|
||||
'RESET',
|
||||
this.get('mountModel').get('type')
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
// THIS COMPONENT IS ONLY FOR EXTENDING
|
||||
// You should use this component if you want to use outerHTML symantics
|
||||
// in your components - this is the default for upcoming Glimmer components
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
tagName: '',
|
||||
});
|
||||
|
||||
// yep! that's it, it's more of a way to keep track of what components
|
||||
// use tagless semantics to make the upgrade to glimmer components easier
|
|
@ -1,5 +1,5 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
tagName: '',
|
||||
tagName: 'span',
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@ import Ember from 'ember';
|
|||
import decodeConfigFromJWT from 'vault/utils/decode-config-from-jwt';
|
||||
import ReplicationActions from 'vault/mixins/replication-actions';
|
||||
|
||||
const { computed, get } = Ember;
|
||||
const { computed, get, Component, inject } = Ember;
|
||||
|
||||
const DEFAULTS = {
|
||||
mode: 'primary',
|
||||
|
@ -17,7 +17,9 @@ const DEFAULTS = {
|
|||
replicationMode: 'dr',
|
||||
};
|
||||
|
||||
export default Ember.Component.extend(ReplicationActions, DEFAULTS, {
|
||||
export default Component.extend(ReplicationActions, DEFAULTS, {
|
||||
wizard: inject.service(),
|
||||
version: inject.service(),
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
const initialReplicationMode = this.get('initialReplicationMode');
|
||||
|
@ -28,7 +30,6 @@ export default Ember.Component.extend(ReplicationActions, DEFAULTS, {
|
|||
showModeSummary: false,
|
||||
initialReplicationMode: null,
|
||||
cluster: null,
|
||||
version: Ember.inject.service(),
|
||||
|
||||
replicationAttrs: computed.alias('cluster.replicationAttrs'),
|
||||
|
||||
|
@ -55,7 +56,6 @@ export default Ember.Component.extend(ReplicationActions, DEFAULTS, {
|
|||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
),
|
||||
|
@ -66,7 +66,12 @@ export default Ember.Component.extend(ReplicationActions, DEFAULTS, {
|
|||
|
||||
actions: {
|
||||
onSubmit(/*action, mode, data, event*/) {
|
||||
return this.submitHandler(...arguments);
|
||||
let promise = this.submitHandler(...arguments);
|
||||
let wizard = this.get('wizard');
|
||||
promise.then(() => {
|
||||
wizard.transitionFeatureMachine(wizard.get('featureState'), 'ENABLEREPLICATION');
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
clear() {
|
||||
|
|
|
@ -5,10 +5,6 @@ const { get, set } = Ember;
|
|||
const SHOW_ROUTE = 'vault.cluster.secrets.backend.show';
|
||||
|
||||
export default RoleEdit.extend({
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
actions: {
|
||||
createOrUpdate(type, event) {
|
||||
event.preventDefault();
|
||||
|
@ -21,13 +17,13 @@ export default RoleEdit.extend({
|
|||
}
|
||||
|
||||
var credential_type = get(this, 'model.credential_type');
|
||||
if (credential_type == "iam_user") {
|
||||
if (credential_type == 'iam_user') {
|
||||
set(this, 'model.role_arns', []);
|
||||
}
|
||||
if (credential_type == "assumed_role") {
|
||||
if (credential_type == 'assumed_role') {
|
||||
set(this, 'model.policy_arns', []);
|
||||
}
|
||||
if (credential_type == "federation_token") {
|
||||
if (credential_type == 'federation_token') {
|
||||
set(this, 'model.role_arns', []);
|
||||
set(this, 'model.policy_arns', []);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import Ember from 'ember';
|
|||
import FocusOnInsertMixin from 'vault/mixins/focus-on-insert';
|
||||
import keys from 'vault/lib/keycodes';
|
||||
|
||||
const { get, set, computed } = Ember;
|
||||
const { get, set, computed, inject } = Ember;
|
||||
const LIST_ROOT_ROUTE = 'vault.cluster.secrets.backend.list-root';
|
||||
const SHOW_ROUTE = 'vault.cluster.secrets.backend.show';
|
||||
|
||||
|
@ -12,8 +12,31 @@ export default Ember.Component.extend(FocusOnInsertMixin, {
|
|||
onDataChange: () => {},
|
||||
refresh: 'refresh',
|
||||
model: null,
|
||||
routing: Ember.inject.service('-routing'),
|
||||
routing: inject.service('-routing'),
|
||||
wizard: inject.service(),
|
||||
requestInFlight: computed.or('model.isLoading', 'model.isReloading', 'model.isSaving'),
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
if (
|
||||
(this.get('wizard.featureState') === 'details' && this.get('mode') === 'create') ||
|
||||
(this.get('wizard.featureState') === 'role' && this.get('mode') === 'show')
|
||||
) {
|
||||
this.get('wizard').transitionFeatureMachine(
|
||||
this.get('wizard.featureState'),
|
||||
'CONTINUE',
|
||||
this.get('backendType')
|
||||
);
|
||||
}
|
||||
if (this.get('wizard.featureState') === 'displayRole') {
|
||||
this.get('wizard').transitionFeatureMachine(
|
||||
this.get('wizard.featureState'),
|
||||
'NOOP',
|
||||
this.get('backendType')
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
const model = this.get('model');
|
||||
if (get(model, 'isError')) {
|
||||
|
@ -49,6 +72,9 @@ export default Ember.Component.extend(FocusOnInsertMixin, {
|
|||
const model = get(this, 'model');
|
||||
return model[method]().then(() => {
|
||||
if (!Ember.get(model, 'isError')) {
|
||||
if (this.get('wizard.featureState') === 'role') {
|
||||
this.get('wizard').transitionFeatureMachine('role', 'CONTINUE', this.get('backendType'));
|
||||
}
|
||||
successCallback(model);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
import RoleEdit from './role-edit';
|
||||
|
||||
export default RoleEdit.extend({});
|
||||
export default RoleEdit.extend({
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.set('backendType', 'pki');
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
import RoleEdit from './role-edit';
|
||||
|
||||
export default RoleEdit.extend({});
|
||||
export default RoleEdit.extend({
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.set('backendType', 'ssh');
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@ import KVObject from 'vault/lib/kv-object';
|
|||
const LIST_ROUTE = 'vault.cluster.secrets.backend.list';
|
||||
const LIST_ROOT_ROUTE = 'vault.cluster.secrets.backend.list-root';
|
||||
const SHOW_ROUTE = 'vault.cluster.secrets.backend.show';
|
||||
const { get, computed } = Ember;
|
||||
const { get, computed, inject } = Ember;
|
||||
|
||||
export default Ember.Component.extend(FocusOnInsertMixin, {
|
||||
// a key model
|
||||
|
@ -35,6 +35,8 @@ export default Ember.Component.extend(FocusOnInsertMixin, {
|
|||
|
||||
hasLintError: false,
|
||||
|
||||
wizard: inject.service(),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
const secrets = this.get('key.secretData');
|
||||
|
@ -45,6 +47,11 @@ export default Ember.Component.extend(FocusOnInsertMixin, {
|
|||
this.set('preferAdvancedEdit', true);
|
||||
}
|
||||
this.checkRows();
|
||||
if (this.get('wizard.featureState') === 'details' && this.get('mode') === 'create') {
|
||||
let engine = this.get('key').backend.includes('kv') ? 'kv' : this.get('key').backend;
|
||||
this.get('wizard').transitionFeatureMachine('details', 'CONTINUE', engine);
|
||||
}
|
||||
|
||||
if (this.get('mode') === 'edit') {
|
||||
this.send('addRow');
|
||||
}
|
||||
|
@ -144,6 +151,9 @@ export default Ember.Component.extend(FocusOnInsertMixin, {
|
|||
|
||||
return model[method]().then(() => {
|
||||
if (!Ember.get(model, 'isError')) {
|
||||
if (this.get('wizard.featureState') === 'secret') {
|
||||
this.get('wizard').transitionFeatureMachine('secret', 'CONTINUE');
|
||||
}
|
||||
successCallback(key);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -26,15 +26,20 @@ export default Component.extend(DEFAULTS, {
|
|||
buttonText: 'Submit',
|
||||
thresholdPath: 'required',
|
||||
generateAction: false,
|
||||
encoded_token: null,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
if (this.get('fetchOnInit')) {
|
||||
this.attemptProgress();
|
||||
}
|
||||
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) {
|
||||
|
@ -56,17 +61,23 @@ export default Component.extend(DEFAULTS, {
|
|||
hasProgress: computed.gt('progress', 0),
|
||||
|
||||
actionSuccess(resp) {
|
||||
const { isComplete, onShamirSuccess, thresholdPath } = this.getProperties(
|
||||
let { onUpdate, isComplete, onShamirSuccess, thresholdPath } = this.getProperties(
|
||||
'onUpdate',
|
||||
'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);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ const WRAPPING_ENDPOINTS = ['lookup', 'wrap', 'unwrap', 'rewrap'];
|
|||
|
||||
export default Ember.Component.extend(DEFAULTS, {
|
||||
store: Ember.inject.service(),
|
||||
wizard: Ember.inject.service(),
|
||||
// putting these attrs here so they don't get reset when you click back
|
||||
//random
|
||||
bytes: 32,
|
||||
|
@ -76,11 +77,13 @@ export default Ember.Component.extend(DEFAULTS, {
|
|||
props = Ember.assign({}, props, { unwrap_data: secret });
|
||||
}
|
||||
props = Ember.assign({}, props, secret);
|
||||
|
||||
if (resp && resp.wrap_info) {
|
||||
const keyName = action === 'rewrap' ? 'rewrap_token' : 'token';
|
||||
props = Ember.assign({}, props, { [keyName]: resp.wrap_info.token });
|
||||
}
|
||||
if (props.token || props.rewrap_token || props.unwrap_data || action === 'lookup') {
|
||||
this.get('wizard').transitionFeatureMachine(this.get('wizard.featureState'), 'CONTINUE');
|
||||
}
|
||||
setProperties(this, props);
|
||||
},
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import Ember from 'ember';
|
|||
import FocusOnInsertMixin from 'vault/mixins/focus-on-insert';
|
||||
import keys from 'vault/lib/keycodes';
|
||||
|
||||
const { get, set, computed } = Ember;
|
||||
const { get, set, computed, inject } = Ember;
|
||||
const LIST_ROOT_ROUTE = 'vault.cluster.secrets.backend.list-root';
|
||||
const SHOW_ROUTE = 'vault.cluster.secrets.backend.show';
|
||||
|
||||
|
@ -11,8 +11,14 @@ export default Ember.Component.extend(FocusOnInsertMixin, {
|
|||
onDataChange: null,
|
||||
refresh: 'refresh',
|
||||
key: null,
|
||||
routing: Ember.inject.service('-routing'),
|
||||
routing: inject.service('-routing'),
|
||||
requestInFlight: computed.or('key.isLoading', 'key.isReloading', 'key.isSaving'),
|
||||
wizard: inject.service(),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
const key = this.get('key');
|
||||
if (get(key, 'isError')) {
|
||||
|
@ -48,6 +54,13 @@ export default Ember.Component.extend(FocusOnInsertMixin, {
|
|||
const key = get(this, 'key');
|
||||
return key[method]().then(() => {
|
||||
if (!Ember.get(key, 'isError')) {
|
||||
if (this.get('wizard.featureState') === 'secret') {
|
||||
this.get('wizard').transitionFeatureMachine('secret', 'CONTINUE');
|
||||
} else {
|
||||
if (this.get('wizard.featureState') === 'encryption') {
|
||||
this.get('wizard').transitionFeatureMachine('encryption', 'CONTINUE', 'transit');
|
||||
}
|
||||
}
|
||||
successCallback(key);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
import Ember from 'ember';
|
||||
import { matchesState } from 'xstate';
|
||||
|
||||
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'),
|
||||
tutorialComponent: computed.alias('wizard.tutorialComponent'),
|
||||
componentState: computed.alias('wizard.componentState'),
|
||||
nextFeature: computed.alias('wizard.nextFeature'),
|
||||
nextStep: computed.alias('wizard.nextStep'),
|
||||
|
||||
actions: {
|
||||
dismissWizard() {
|
||||
this.get('wizard').transitionTutorialMachine(this.get('currentState'), 'DISMISS');
|
||||
},
|
||||
|
||||
advanceWizard() {
|
||||
let inInit = matchesState('init', this.get('wizard.currentState'));
|
||||
let event = inInit ? this.get('wizard.initEvent') || 'CONTINUE' : 'CONTINUE';
|
||||
this.get('wizard').transitionTutorialMachine(this.get('currentState'), event);
|
||||
},
|
||||
|
||||
advanceFeature() {
|
||||
this.get('wizard').transitionFeatureMachine(this.get('featureState'), 'CONTINUE');
|
||||
},
|
||||
|
||||
finishFeature() {
|
||||
this.get('wizard').transitionFeatureMachine(this.get('featureState'), 'DONE');
|
||||
},
|
||||
|
||||
repeatStep() {
|
||||
this.get('wizard').transitionFeatureMachine(
|
||||
this.get('featureState'),
|
||||
'REPEAT',
|
||||
this.get('componentState')
|
||||
);
|
||||
},
|
||||
|
||||
resetFeature() {
|
||||
this.get('wizard').transitionFeatureMachine(
|
||||
this.get('featureState'),
|
||||
'RESET',
|
||||
this.get('componentState')
|
||||
);
|
||||
},
|
||||
|
||||
pauseWizard() {
|
||||
this.get('wizard').transitionTutorialMachine(this.get('currentState'), 'PAUSE');
|
||||
},
|
||||
},
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
const { Component, inject } = Ember;
|
||||
export default Component.extend({
|
||||
wizard: inject.service(),
|
||||
classNames: ['ui-wizard'],
|
||||
glyph: null,
|
||||
headerText: null,
|
||||
actions: {
|
||||
dismissWizard() {
|
||||
this.get('wizard').transitionTutorialMachine(this.get('wizard.currentState'), 'DISMISS');
|
||||
},
|
||||
},
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
import outerHTMLComponent from './outer-html';
|
||||
|
||||
export default outerHTMLComponent.extend({
|
||||
headerText: null,
|
||||
headerIcon: null,
|
||||
docText: null,
|
||||
docPath: null,
|
||||
});
|
|
@ -0,0 +1,75 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
const { inject, computed } = Ember;
|
||||
|
||||
export default Ember.Component.extend({
|
||||
wizard: inject.service(),
|
||||
version: inject.service(),
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.maybeHideFeatures();
|
||||
},
|
||||
|
||||
maybeHideFeatures() {
|
||||
if (this.get('showReplication') === false) {
|
||||
let feature = this.get('allFeatures').findBy('key', 'replication');
|
||||
feature.show = false;
|
||||
}
|
||||
},
|
||||
|
||||
allFeatures: computed(function() {
|
||||
return [
|
||||
{
|
||||
key: 'secrets',
|
||||
name: 'Secrets',
|
||||
steps: ['Enabling a secrets engine', 'Adding a secret'],
|
||||
selected: false,
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
key: 'authentication',
|
||||
name: 'Authentication',
|
||||
steps: ['Enabling an auth method', 'Managing your auth method'],
|
||||
selected: false,
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
key: 'policies',
|
||||
name: 'Policies',
|
||||
steps: ['Choosing a policy type', 'Creating a policy', 'Deleting your policy', 'Other types of policies'],
|
||||
selected: false,
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
key: 'replication',
|
||||
name: 'Replication',
|
||||
steps: ['Setting up replication', 'Your cluster information'],
|
||||
selected: false,
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
key: 'tools',
|
||||
name: 'Tools',
|
||||
steps: ['Wrapping data', 'Lookup wrapped data', 'Rewrapping your data', 'Unwrapping your data'],
|
||||
selected: false,
|
||||
show: true,
|
||||
},
|
||||
];
|
||||
}),
|
||||
|
||||
showReplication: computed('version.hasPerfReplication', 'version.hasDRReplication', function() {
|
||||
return this.get('version.hasPerfReplication') || this.get('version.hasDRReplication');
|
||||
}),
|
||||
|
||||
selectedFeatures: computed('allFeatures.@each.selected', function() {
|
||||
return this.get('allFeatures').filterBy('selected').mapBy('key');
|
||||
}),
|
||||
|
||||
actions: {
|
||||
saveFeatures() {
|
||||
let wizard = this.get('wizard');
|
||||
wizard.saveFeatures(this.get('selectedFeatures'));
|
||||
wizard.transitionTutorialMachine('active.select', 'CONTINUE');
|
||||
},
|
||||
},
|
||||
});
|
|
@ -0,0 +1,71 @@
|
|||
import Ember from 'ember';
|
||||
import { engines } from 'vault/helpers/mountable-secret-engines';
|
||||
import { methods } from 'vault/helpers/mountable-auth-methods';
|
||||
import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends';
|
||||
const supportedSecrets = supportedSecretBackends();
|
||||
import { supportedAuthBackends } from 'vault/helpers/supported-auth-backends';
|
||||
const supportedAuth = supportedAuthBackends();
|
||||
const { inject, computed } = Ember;
|
||||
|
||||
export default Ember.Component.extend({
|
||||
wizard: inject.service(),
|
||||
featureState: computed.alias('wizard.featureState'),
|
||||
currentState: computed.alias('wizard.currentState'),
|
||||
currentMachine: computed.alias('wizard.currentMachine'),
|
||||
mountSubtype: computed.alias('wizard.componentState'),
|
||||
fullNextStep: computed.alias('wizard.nextStep'),
|
||||
nextFeature: computed.alias('wizard.nextFeature'),
|
||||
nextStep: computed('fullNextStep', function() {
|
||||
return this.get('fullNextStep').split('.').lastObject;
|
||||
}),
|
||||
needsEncryption: computed('mountSubtype', function() {
|
||||
return this.get('mountSubtype') === 'transit';
|
||||
}),
|
||||
stepComponent: computed.alias('wizard.stepComponent'),
|
||||
detailsComponent: computed('mountSubtype', function() {
|
||||
let suffix = this.get('currentMachine') === 'secrets' ? 'engine' : 'method';
|
||||
return this.get('mountSubtype') ? `wizard/${this.get('mountSubtype')}-${suffix}` : null;
|
||||
}),
|
||||
isSupported: computed('mountSubtype', function() {
|
||||
if (this.get('currentMachine') === 'secrets') {
|
||||
return supportedSecrets.includes(this.get('mountSubtype'));
|
||||
} else {
|
||||
return supportedAuth.includes(this.get('mountSubtype'));
|
||||
}
|
||||
}),
|
||||
mountName: computed('mountSubtype', function() {
|
||||
if (this.get('currentMachine') === 'secrets') {
|
||||
var secret = engines().find(engine => {
|
||||
return engine.type === this.get('mountSubtype');
|
||||
});
|
||||
if (secret) {
|
||||
return secret.displayName;
|
||||
}
|
||||
} else {
|
||||
var auth = methods().find(method => {
|
||||
return method.type === this.get('mountSubtype');
|
||||
});
|
||||
if (auth) {
|
||||
return auth.displayName;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
actionText: computed('mountSubtype', function() {
|
||||
switch (this.get('mountSubtype')) {
|
||||
case 'aws':
|
||||
return 'Generate Credential';
|
||||
case 'ssh':
|
||||
return 'Sign Keys';
|
||||
case 'pki':
|
||||
return 'Generate Certificate';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
|
||||
onAdvance() {},
|
||||
onRepeat() {},
|
||||
onReset() {},
|
||||
onDone() {},
|
||||
});
|
|
@ -10,6 +10,8 @@ const DEFAULTS = {
|
|||
};
|
||||
|
||||
export default Ember.Controller.extend(DEFAULTS, {
|
||||
wizard: Ember.inject.service(),
|
||||
|
||||
reset() {
|
||||
this.setProperties(DEFAULTS);
|
||||
},
|
||||
|
@ -17,6 +19,8 @@ export default Ember.Controller.extend(DEFAULTS, {
|
|||
initSuccess(resp) {
|
||||
this.set('loading', false);
|
||||
this.set('keyData', resp);
|
||||
this.get('wizard').set('initEvent', 'SAVE');
|
||||
this.get('wizard').transitionTutorialMachine(this.get('wizard.currentState'), 'TOSAVE');
|
||||
},
|
||||
|
||||
initError(e) {
|
||||
|
|
|
@ -5,7 +5,6 @@ import PolicyEditController from 'vault/mixins/policy-edit-controller';
|
|||
export default Ember.Controller.extend(PolicyEditController, {
|
||||
showFileUpload: false,
|
||||
file: null,
|
||||
|
||||
actions: {
|
||||
setPolicyFromFile(index, fileInfo) {
|
||||
let { value, fileName } = fileInfo;
|
||||
|
|
|
@ -3,6 +3,7 @@ let { inject } = Ember;
|
|||
|
||||
export default Ember.Controller.extend({
|
||||
flashMessages: inject.service(),
|
||||
wizard: inject.service(),
|
||||
|
||||
queryParams: {
|
||||
page: 'page',
|
||||
|
@ -54,6 +55,9 @@ export default Ember.Controller.extend({
|
|||
.destroyRecord()
|
||||
.then(() => {
|
||||
flash.success(`${policyType.toUpperCase()} policy "${name}" was successfully deleted.`);
|
||||
if (this.get('wizard.featureState') === 'delete') {
|
||||
this.get('wizard').transitionFeatureMachine('delete', 'CONTINUE', policyType);
|
||||
}
|
||||
// this will clear the dataset cache on the store
|
||||
this.send('willTransition');
|
||||
})
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
wizard: Ember.inject.service(),
|
||||
actions: {
|
||||
onMountSuccess: function() {
|
||||
return this.transitionToRoute('vault.cluster.access.methods');
|
||||
onMountSuccess: function(type) {
|
||||
let transition = this.transitionToRoute('vault.cluster.access.methods');
|
||||
return transition.followRedirects().then(() => {
|
||||
this.get('wizard').transitionFeatureMachine(this.get('wizard.featureState'), 'CONTINUE', type);
|
||||
});
|
||||
},
|
||||
onConfigError: function(modelId) {
|
||||
return this.transitionToRoute('vault.cluster.settings.auth.configure', modelId);
|
||||
|
|
|
@ -3,138 +3,24 @@ import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends
|
|||
|
||||
const SUPPORTED_BACKENDS = supportedSecretBackends();
|
||||
|
||||
const { computed } = Ember;
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
mountTypes: [
|
||||
{ label: 'Active Directory', value: 'ad' },
|
||||
{ label: 'AWS', value: 'aws' },
|
||||
{ label: 'Consul', value: 'consul' },
|
||||
{ label: 'Databases', value: 'database' },
|
||||
{ label: 'Google Cloud', value: 'gcp' },
|
||||
{ label: 'KV', value: 'kv' },
|
||||
{ label: 'Nomad', value: 'nomad' },
|
||||
{ label: 'PKI', value: 'pki' },
|
||||
{ label: 'RabbitMQ', value: 'rabbitmq' },
|
||||
{ label: 'SSH', value: 'ssh' },
|
||||
{ label: 'Transit', value: 'transit' },
|
||||
{ label: 'TOTP', value: 'totp' },
|
||||
{ label: 'Cassandra', value: 'cassandra', deprecated: true },
|
||||
{ label: 'MongoDB', value: 'mongodb', deprecated: true },
|
||||
{ label: 'MSSQL', value: 'mssql', deprecated: true },
|
||||
{ label: 'MySQL', value: 'mysql', deprecated: true },
|
||||
{ label: 'PostgreSQL', value: 'postgresql', deprecated: true },
|
||||
],
|
||||
|
||||
selectedType: null,
|
||||
selectedPath: null,
|
||||
description: null,
|
||||
default_lease_ttl: null,
|
||||
max_lease_ttl: null,
|
||||
showConfig: false,
|
||||
local: false,
|
||||
sealWrap: false,
|
||||
version: 2,
|
||||
|
||||
selection: computed('selectedType', function() {
|
||||
return this.get('mountTypes').findBy('value', this.get('selectedType'));
|
||||
}),
|
||||
|
||||
flashMessages: Ember.inject.service(),
|
||||
|
||||
reset() {
|
||||
const defaultBackend = this.get('mountTypes.firstObject.value');
|
||||
this.setProperties({
|
||||
selectedPath: defaultBackend,
|
||||
selectedType: defaultBackend,
|
||||
description: null,
|
||||
default_lease_ttl: null,
|
||||
max_lease_ttl: null,
|
||||
local: false,
|
||||
showConfig: false,
|
||||
sealWrap: false,
|
||||
version: 2,
|
||||
});
|
||||
},
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.reset();
|
||||
},
|
||||
const { inject, Controller } = Ember;
|
||||
|
||||
export default Controller.extend({
|
||||
wizard: inject.service(),
|
||||
actions: {
|
||||
onTypeChange(val) {
|
||||
const { selectedPath, selectedType } = this.getProperties('selectedPath', 'selectedType');
|
||||
this.set('selectedType', val);
|
||||
if (selectedPath === selectedType) {
|
||||
this.set('selectedPath', val);
|
||||
onMountSuccess: function(type, path) {
|
||||
let transition;
|
||||
if (SUPPORTED_BACKENDS.includes(type)) {
|
||||
transition = this.transitionToRoute('vault.cluster.secrets.backend.index', path);
|
||||
} else {
|
||||
transition = this.transitionToRoute('vault.cluster.secrets.backends');
|
||||
}
|
||||
},
|
||||
|
||||
toggleShowConfig() {
|
||||
this.toggleProperty('showConfig');
|
||||
},
|
||||
|
||||
mountBackend() {
|
||||
const {
|
||||
selectedPath: path,
|
||||
selectedType: type,
|
||||
description,
|
||||
default_lease_ttl,
|
||||
local,
|
||||
max_lease_ttl,
|
||||
sealWrap,
|
||||
version,
|
||||
} = this.getProperties(
|
||||
'selectedPath',
|
||||
'selectedType',
|
||||
'description',
|
||||
'default_lease_ttl',
|
||||
'local',
|
||||
'max_lease_ttl',
|
||||
'sealWrap',
|
||||
'version'
|
||||
);
|
||||
const currentModel = this.get('model');
|
||||
if (currentModel && currentModel.rollbackAttributes) {
|
||||
currentModel.rollbackAttributes();
|
||||
}
|
||||
let attrs = {
|
||||
path,
|
||||
type,
|
||||
description,
|
||||
local,
|
||||
sealWrap,
|
||||
};
|
||||
|
||||
if (this.get('showConfig')) {
|
||||
attrs.config = {
|
||||
defaultLeaseTtl: default_lease_ttl,
|
||||
maxLeaseTtl: max_lease_ttl,
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'kv') {
|
||||
attrs.options = {
|
||||
version,
|
||||
};
|
||||
}
|
||||
|
||||
const model = this.store.createRecord('secret-engine', attrs);
|
||||
|
||||
this.set('model', model);
|
||||
model.save().then(() => {
|
||||
this.reset();
|
||||
let transition;
|
||||
if (SUPPORTED_BACKENDS.includes(type)) {
|
||||
transition = this.transitionToRoute('vault.cluster.secrets.backend.index', path);
|
||||
} else {
|
||||
transition = this.transitionToRoute('vault.cluster.secrets.backends');
|
||||
}
|
||||
transition.followRedirects().then(() => {
|
||||
this.get('flashMessages').success(`Successfully mounted '${type}' at '${path}'!`);
|
||||
});
|
||||
return transition.followRedirects().then(() => {
|
||||
this.get('wizard').transitionFeatureMachine(this.get('wizard.featureState'), 'CONTINUE', type);
|
||||
});
|
||||
},
|
||||
onConfigError: function(modelId) {
|
||||
return this.transitionToRoute('vault.cluster.settings.configure-secret-backend', modelId);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
wizard: Ember.inject.service(),
|
||||
|
||||
actions: {
|
||||
transitionToCluster() {
|
||||
transitionToCluster(resp) {
|
||||
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;
|
||||
},
|
||||
|
|
|
@ -5,61 +5,76 @@ const MOUNTABLE_AUTH_METHODS = [
|
|||
displayName: 'AppRole',
|
||||
value: 'approle',
|
||||
type: 'approle',
|
||||
category: 'generic',
|
||||
},
|
||||
{
|
||||
displayName: 'AWS',
|
||||
value: 'aws',
|
||||
type: 'aws',
|
||||
category: 'cloud',
|
||||
},
|
||||
{
|
||||
displayName: 'Azure',
|
||||
value: 'azure',
|
||||
type: 'azure',
|
||||
category: 'cloud',
|
||||
},
|
||||
{
|
||||
displayName: 'Google Cloud',
|
||||
value: 'gcp',
|
||||
type: 'gcp',
|
||||
category: 'cloud',
|
||||
},
|
||||
{
|
||||
displayName: 'GitHub',
|
||||
value: 'github',
|
||||
type: 'github',
|
||||
category: 'cloud',
|
||||
},
|
||||
{
|
||||
displayName: 'JWT/OIDC',
|
||||
value: 'jwt',
|
||||
type: 'jwt',
|
||||
glyph: 'auth',
|
||||
category: 'generic',
|
||||
},
|
||||
{
|
||||
displayName: 'Kubernetes',
|
||||
value: 'kubernetes',
|
||||
type: 'kubernetes',
|
||||
category: 'infra',
|
||||
},
|
||||
{
|
||||
displayName: 'LDAP',
|
||||
value: 'ldap',
|
||||
type: 'ldap',
|
||||
glyph: 'auth',
|
||||
category: 'infra',
|
||||
},
|
||||
{
|
||||
displayName: 'Okta',
|
||||
value: 'okta',
|
||||
type: 'okta',
|
||||
category: 'infra',
|
||||
},
|
||||
{
|
||||
displayName: 'RADIUS',
|
||||
value: 'radius',
|
||||
type: 'radius',
|
||||
glyph: 'auth',
|
||||
category: 'infra',
|
||||
},
|
||||
{
|
||||
displayName: 'TLS Certificates',
|
||||
value: 'cert',
|
||||
type: 'cert',
|
||||
category: 'generic',
|
||||
},
|
||||
{
|
||||
displayName: 'Username & Password',
|
||||
value: 'userpass',
|
||||
type: 'userpass',
|
||||
category: 'generic',
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
const MOUNTABLE_SECRET_ENGINES = [
|
||||
{
|
||||
displayName: 'Active Directory',
|
||||
value: 'ad',
|
||||
type: 'ad',
|
||||
glyph: 'azure',
|
||||
category: 'cloud',
|
||||
},
|
||||
{
|
||||
displayName: 'AWS',
|
||||
value: 'aws',
|
||||
type: 'aws',
|
||||
category: 'cloud',
|
||||
},
|
||||
{
|
||||
displayName: 'Consul',
|
||||
value: 'consul',
|
||||
type: 'consul',
|
||||
category: 'infra',
|
||||
},
|
||||
{
|
||||
displayName: 'Databases',
|
||||
value: 'database',
|
||||
type: 'database',
|
||||
category: 'infra',
|
||||
},
|
||||
{
|
||||
displayName: 'Google Cloud',
|
||||
value: 'gcp',
|
||||
type: 'gcp',
|
||||
category: 'cloud',
|
||||
},
|
||||
{
|
||||
displayName: 'KV',
|
||||
value: 'kv',
|
||||
type: 'kv',
|
||||
category: 'generic',
|
||||
},
|
||||
{
|
||||
displayName: 'Nomad',
|
||||
value: 'nomad',
|
||||
type: 'nomad',
|
||||
category: 'infra',
|
||||
},
|
||||
{
|
||||
displayName: 'PKI Certificates',
|
||||
value: 'pki',
|
||||
type: 'pki',
|
||||
category: 'generic',
|
||||
},
|
||||
{
|
||||
displayName: 'RabbitMQ',
|
||||
value: 'rabbitmq',
|
||||
type: 'rabbitmq',
|
||||
category: 'infra',
|
||||
},
|
||||
{
|
||||
displayName: 'SSH',
|
||||
value: 'ssh',
|
||||
type: 'ssh',
|
||||
category: 'generic',
|
||||
},
|
||||
{
|
||||
displayName: 'Transit',
|
||||
value: 'transit',
|
||||
type: 'transit',
|
||||
category: 'generic',
|
||||
},
|
||||
{
|
||||
displayName: 'TOTP',
|
||||
value: 'totp',
|
||||
type: 'totp',
|
||||
category: 'generic',
|
||||
},
|
||||
];
|
||||
|
||||
export function engines() {
|
||||
return MOUNTABLE_SECRET_ENGINES;
|
||||
}
|
||||
|
||||
export default Ember.Helper.helper(engines);
|
|
@ -0,0 +1,50 @@
|
|||
export default {
|
||||
key: 'auth',
|
||||
initial: 'idle',
|
||||
on: {
|
||||
RESET: 'idle',
|
||||
DONE: 'complete',
|
||||
},
|
||||
states: {
|
||||
idle: {
|
||||
onEntry: [
|
||||
{ type: 'routeTransition', params: ['vault.cluster.settings.auth.enable'] },
|
||||
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
|
||||
{ type: 'render', level: 'step', component: 'wizard/auth-idle' },
|
||||
],
|
||||
on: {
|
||||
CONTINUE: 'enable',
|
||||
},
|
||||
},
|
||||
enable: {
|
||||
onEntry: [
|
||||
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
|
||||
{ type: 'render', level: 'step', component: 'wizard/auth-enable' },
|
||||
],
|
||||
on: {
|
||||
CONTINUE: 'list',
|
||||
},
|
||||
},
|
||||
list: {
|
||||
onEntry: [
|
||||
{ type: 'render', level: 'step', component: 'wizard/auth-list' },
|
||||
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
|
||||
],
|
||||
on: {
|
||||
DETAILS: 'details',
|
||||
},
|
||||
},
|
||||
details: {
|
||||
onEntry: [
|
||||
{ type: 'render', level: 'step', component: 'wizard/auth-details' },
|
||||
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
|
||||
],
|
||||
on: {
|
||||
CONTINUE: 'complete',
|
||||
},
|
||||
},
|
||||
complete: {
|
||||
onEntry: ['completeFeature'],
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
export default {
|
||||
key: 'policies',
|
||||
initial: 'idle',
|
||||
states: {
|
||||
idle: {
|
||||
onEntry: [
|
||||
{ type: 'routeTransition', params: ['vault.cluster.policies.index', 'acl'] },
|
||||
{ type: 'render', level: 'feature', component: 'wizard/policies-intro' },
|
||||
],
|
||||
on: {
|
||||
CONTINUE: 'create',
|
||||
},
|
||||
},
|
||||
create: {
|
||||
on: {
|
||||
CONTINUE: 'details',
|
||||
},
|
||||
onEntry: [{ type: 'render', level: 'feature', component: 'wizard/policies-create' }],
|
||||
},
|
||||
details: {
|
||||
on: {
|
||||
CONTINUE: 'delete',
|
||||
},
|
||||
onEntry: [{ type: 'render', level: 'feature', component: 'wizard/policies-details' }],
|
||||
},
|
||||
delete: {
|
||||
on: {
|
||||
CONTINUE: 'others',
|
||||
},
|
||||
onEntry: [{ type: 'render', level: 'feature', component: 'wizard/policies-delete' }],
|
||||
},
|
||||
others: {
|
||||
on: {
|
||||
CONTINUE: 'complete',
|
||||
},
|
||||
onEntry: [{ type: 'render', level: 'feature', component: 'wizard/policies-others' }],
|
||||
},
|
||||
complete: {
|
||||
onEntry: ['completeFeature'],
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,25 @@
|
|||
export default {
|
||||
key: 'replication',
|
||||
initial: 'setup',
|
||||
states: {
|
||||
setup: {
|
||||
on: {
|
||||
ENABLEREPLICATION: 'details',
|
||||
},
|
||||
onEntry: [
|
||||
{ type: 'routeTransition', params: ['vault.cluster.replication'] },
|
||||
{ type: 'render', level: 'feature', component: 'wizard/replication-setup' },
|
||||
],
|
||||
},
|
||||
details: {
|
||||
on: {
|
||||
CONTINUE: 'complete',
|
||||
},
|
||||
onEntry: [{ type: 'render', level: 'feature', component: 'wizard/replication-details' }],
|
||||
},
|
||||
complete: {
|
||||
onEntry: ['completeFeature'],
|
||||
on: { RESET: 'idle' },
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,143 @@
|
|||
import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends';
|
||||
const supportedBackends = supportedSecretBackends();
|
||||
|
||||
export default {
|
||||
key: 'secrets',
|
||||
initial: 'idle',
|
||||
on: {
|
||||
RESET: 'idle',
|
||||
DONE: 'complete',
|
||||
ERROR: 'error',
|
||||
},
|
||||
states: {
|
||||
idle: {
|
||||
onEntry: [
|
||||
{ type: 'routeTransition', params: ['vault.cluster.settings.mount-secret-backend'] },
|
||||
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
|
||||
{ type: 'render', level: 'step', component: 'wizard/secrets-idle' },
|
||||
],
|
||||
on: {
|
||||
CONTINUE: 'enable',
|
||||
},
|
||||
},
|
||||
enable: {
|
||||
onEntry: [
|
||||
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
|
||||
{ type: 'render', level: 'step', component: 'wizard/secrets-enable' },
|
||||
],
|
||||
on: {
|
||||
CONTINUE: {
|
||||
details: { cond: type => supportedBackends.includes(type) },
|
||||
list: { cond: type => !supportedBackends.includes(type) },
|
||||
},
|
||||
},
|
||||
},
|
||||
details: {
|
||||
onEntry: [
|
||||
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
|
||||
{ type: 'render', level: 'step', component: 'wizard/secrets-details' },
|
||||
],
|
||||
on: {
|
||||
CONTINUE: {
|
||||
role: {
|
||||
cond: type => ['pki', 'aws', 'ssh'].includes(type),
|
||||
},
|
||||
secret: {
|
||||
cond: type => ['cubbyhole', 'database', 'gcp', 'kv', 'nomad', 'rabbitmq', 'totp'].includes(type),
|
||||
},
|
||||
encryption: {
|
||||
cond: type => type === 'transit',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
encryption: {
|
||||
onEntry: [
|
||||
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
|
||||
{ type: 'render', level: 'step', component: 'wizard/secrets-encryption' },
|
||||
],
|
||||
on: {
|
||||
CONTINUE: 'display',
|
||||
},
|
||||
},
|
||||
credentials: {
|
||||
onEntry: [
|
||||
{ type: 'render', level: 'step', component: 'wizard/secrets-credentials' },
|
||||
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
|
||||
],
|
||||
on: {
|
||||
CONTINUE: 'display',
|
||||
},
|
||||
},
|
||||
role: {
|
||||
onEntry: [
|
||||
{ type: 'render', level: 'step', component: 'wizard/secrets-role' },
|
||||
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
|
||||
],
|
||||
on: {
|
||||
CONTINUE: 'displayRole',
|
||||
},
|
||||
},
|
||||
displayRole: {
|
||||
onEntry: [
|
||||
{ type: 'render', level: 'step', component: 'wizard/secrets-display-role' },
|
||||
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
|
||||
],
|
||||
on: {
|
||||
CONTINUE: 'credentials',
|
||||
},
|
||||
},
|
||||
secret: {
|
||||
onEntry: [
|
||||
{ type: 'render', level: 'step', component: 'wizard/secrets-secret' },
|
||||
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
|
||||
],
|
||||
on: {
|
||||
CONTINUE: 'display',
|
||||
},
|
||||
},
|
||||
display: {
|
||||
onEntry: [
|
||||
{ type: 'render', level: 'step', component: 'wizard/secrets-display' },
|
||||
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
|
||||
],
|
||||
on: {
|
||||
REPEAT: {
|
||||
role: {
|
||||
cond: type => ['pki', 'aws', 'ssh'].includes(type),
|
||||
actions: [{ type: 'routeTransition', params: ['vault.cluster.secrets.backend.create-root'] }],
|
||||
},
|
||||
secret: {
|
||||
cond: type => ['cubbyhole', 'database', 'gcp', 'kv', 'nomad', 'rabbitmq', 'totp'].includes(type),
|
||||
actions: [{ type: 'routeTransition', params: ['vault.cluster.secrets.backend.create-root'] }],
|
||||
},
|
||||
encryption: {
|
||||
cond: type => type === 'transit',
|
||||
actions: [{ type: 'routeTransition', params: ['vault.cluster.secrets.backend.create-root'] }],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
list: {
|
||||
onEntry: [
|
||||
{ type: 'render', level: 'step', component: 'wizard/secrets-list' },
|
||||
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
|
||||
],
|
||||
on: {
|
||||
CONTINUE: 'display',
|
||||
},
|
||||
},
|
||||
error: {
|
||||
onEntry: [
|
||||
{ type: 'render', level: 'step', component: 'wizard/tutorial-error' },
|
||||
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
|
||||
],
|
||||
on: {
|
||||
CONTINUE: 'complete',
|
||||
},
|
||||
},
|
||||
complete: {
|
||||
onEntry: ['completeFeature'],
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,61 @@
|
|||
export default {
|
||||
key: 'tools',
|
||||
initial: 'wrap',
|
||||
states: {
|
||||
wrap: {
|
||||
onEntry: [
|
||||
{ type: 'routeTransition', params: ['vault.cluster.tools'] },
|
||||
{ type: 'render', level: 'feature', component: 'wizard/tools-wrap' },
|
||||
],
|
||||
on: {
|
||||
CONTINUE: 'wrapped',
|
||||
},
|
||||
},
|
||||
wrapped: {
|
||||
onEntry: [{ type: 'render', level: 'feature', component: 'wizard/tools-wrapped' }],
|
||||
on: {
|
||||
LOOKUP: 'lookup',
|
||||
},
|
||||
},
|
||||
lookup: {
|
||||
onEntry: [{ type: 'render', level: 'feature', component: 'wizard/tools-lookup' }],
|
||||
on: {
|
||||
CONTINUE: 'info',
|
||||
},
|
||||
},
|
||||
info: {
|
||||
onEntry: [{ type: 'render', level: 'feature', component: 'wizard/tools-info' }],
|
||||
on: {
|
||||
REWRAP: 'rewrap',
|
||||
},
|
||||
},
|
||||
rewrap: {
|
||||
onEntry: [{ type: 'render', level: 'feature', component: 'wizard/tools-rewrap' }],
|
||||
on: {
|
||||
CONTINUE: 'rewrapped',
|
||||
},
|
||||
},
|
||||
rewrapped: {
|
||||
onEntry: [{ type: 'render', level: 'feature', component: 'wizard/tools-rewrapped' }],
|
||||
on: {
|
||||
UNWRAP: 'unwrap',
|
||||
},
|
||||
},
|
||||
unwrap: {
|
||||
onEntry: [{ type: 'render', level: 'feature', component: 'wizard/tools-unwrap' }],
|
||||
on: {
|
||||
CONTINUE: 'unwrapped',
|
||||
},
|
||||
},
|
||||
unwrapped: {
|
||||
onEntry: [{ type: 'render', level: 'feature', component: 'wizard/tools-unwrapped' }],
|
||||
on: {
|
||||
CONTINUE: 'complete',
|
||||
},
|
||||
},
|
||||
complete: {
|
||||
onEntry: ['completeFeature'],
|
||||
on: { RESET: 'idle' },
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,109 @@
|
|||
export default {
|
||||
key: 'tutorial',
|
||||
initial: 'idle',
|
||||
on: {
|
||||
DISMISS: 'dismissed',
|
||||
DONE: 'complete',
|
||||
PAUSE: 'paused',
|
||||
},
|
||||
states: {
|
||||
init: {
|
||||
key: 'init',
|
||||
initial: 'idle',
|
||||
on: { INITDONE: 'active.select' },
|
||||
onEntry: [
|
||||
'showTutorialAlways',
|
||||
{ type: 'render', level: 'tutorial', component: 'wizard/tutorial-idle' },
|
||||
{ type: 'render', level: 'feature', component: null },
|
||||
],
|
||||
onExit: ['showTutorialWhenAuthenticated'],
|
||||
states: {
|
||||
idle: {
|
||||
on: {
|
||||
START: 'active.setup',
|
||||
SAVE: 'active.save',
|
||||
UNSEAL: 'active.unseal',
|
||||
LOGIN: 'active.login',
|
||||
},
|
||||
},
|
||||
active: {
|
||||
onEntry: { type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' },
|
||||
states: {
|
||||
setup: {
|
||||
on: { TOSAVE: 'save' },
|
||||
onEntry: { type: 'render', level: 'feature', component: 'wizard/init-setup' },
|
||||
},
|
||||
save: {
|
||||
on: { TOUNSEAL: 'unseal' },
|
||||
onEntry: { type: 'render', level: 'feature', component: 'wizard/init-save-keys' },
|
||||
},
|
||||
unseal: {
|
||||
on: { TOLOGIN: 'login' },
|
||||
onEntry: { type: 'render', level: 'feature', component: 'wizard/init-unseal' },
|
||||
},
|
||||
login: {
|
||||
onEntry: { type: 'render', level: 'feature', component: 'wizard/init-login' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
active: {
|
||||
key: 'feature',
|
||||
initial: 'select',
|
||||
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',
|
||||
CONTINUE: 'active',
|
||||
},
|
||||
onEntry: [
|
||||
{ type: 'render', level: 'feature', component: null },
|
||||
{ type: 'render', level: 'step', component: null },
|
||||
{ type: 'render', level: 'detail', component: null },
|
||||
{ type: 'render', level: 'tutorial', component: 'wizard/tutorial-idle' },
|
||||
],
|
||||
},
|
||||
dismissed: {
|
||||
onEntry: [
|
||||
{ type: 'render', level: 'tutorial', component: null },
|
||||
{ type: 'render', level: 'feature', component: null },
|
||||
{ type: 'render', level: 'step', component: null },
|
||||
{ type: 'render', level: 'detail', component: null },
|
||||
'handleDismissed',
|
||||
],
|
||||
},
|
||||
paused: {
|
||||
on: {
|
||||
CONTINUE: 'active.feature',
|
||||
},
|
||||
onEntry: [
|
||||
{ type: 'render', level: 'feature', component: null },
|
||||
{ type: 'render', level: 'step', component: null },
|
||||
{ type: 'render', level: 'detail', component: null },
|
||||
{ type: 'render', level: 'tutorial', component: 'wizard/tutorial-paused' },
|
||||
'handlePaused',
|
||||
],
|
||||
onExit: ['handleResume'],
|
||||
},
|
||||
complete: {
|
||||
onEntry: [
|
||||
{ type: 'render', level: 'feature', component: null },
|
||||
{ type: 'render', level: 'step', component: null },
|
||||
{ type: 'render', level: 'detail', component: null },
|
||||
{ type: 'render', level: 'tutorial', component: 'wizard/tutorial-complete' },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
|
@ -4,6 +4,7 @@ let { inject } = Ember;
|
|||
|
||||
export default Ember.Mixin.create({
|
||||
flashMessages: inject.service(),
|
||||
wizard: inject.service(),
|
||||
actions: {
|
||||
deletePolicy(model) {
|
||||
let policyType = model.get('policyType');
|
||||
|
@ -29,6 +30,9 @@ export default Ember.Mixin.create({
|
|||
let name = model.get('name');
|
||||
model.save().then(m => {
|
||||
flash.success(`${policyType.toUpperCase()} policy "${name}" was successfully saved.`);
|
||||
if (this.get('wizard.featureState') === 'create') {
|
||||
this.get('wizard').transitionFeatureMachine('create', 'CONTINUE', policyType);
|
||||
}
|
||||
return this.transitionToRoute('vault.cluster.policy.show', m.get('policyType'), m.get('name'));
|
||||
});
|
||||
},
|
||||
|
|
|
@ -2,7 +2,6 @@ import Ember from 'ember';
|
|||
import DS from 'ember-data';
|
||||
import { fragment } from 'ember-data-model-fragments/attributes';
|
||||
import { queryRecord } from 'ember-computed-query';
|
||||
import { methods } from 'vault/helpers/mountable-auth-methods';
|
||||
import fieldToAttrs, { expandAttributeMeta } from 'vault/utils/field-to-attrs';
|
||||
import { memberAction } from 'ember-api-actions';
|
||||
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
|
||||
|
@ -10,8 +9,6 @@ import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
|
|||
const { attr, hasMany } = DS;
|
||||
const { computed } = Ember;
|
||||
|
||||
const METHODS = methods();
|
||||
|
||||
const configPath = function configPath(strings, key) {
|
||||
return function(...values) {
|
||||
return `${strings[0]}${values[key]}${strings[1]}`;
|
||||
|
@ -19,15 +16,10 @@ const configPath = function configPath(strings, key) {
|
|||
};
|
||||
export default DS.Model.extend({
|
||||
authConfigs: hasMany('auth-config', { polymorphic: true, inverse: 'backend', async: false }),
|
||||
path: attr('string', {
|
||||
defaultValue: METHODS[0].value,
|
||||
}),
|
||||
path: attr('string'),
|
||||
accessor: attr('string'),
|
||||
name: attr('string'),
|
||||
type: attr('string', {
|
||||
defaultValue: METHODS[0].value,
|
||||
possibleValues: METHODS,
|
||||
}),
|
||||
type: attr('string'),
|
||||
// namespaces introduced types with a `ns_` prefix for built-in engines
|
||||
// so we need to strip that to normalize the type
|
||||
methodType: computed('type', function() {
|
||||
|
@ -37,8 +29,14 @@ export default DS.Model.extend({
|
|||
editType: 'textarea',
|
||||
}),
|
||||
config: fragment('mount-config', { defaultValue: {} }),
|
||||
local: attr('boolean'),
|
||||
sealWrap: attr('boolean'),
|
||||
local: attr('boolean', {
|
||||
helpText:
|
||||
'When replication is enabled, a local mount will not be replicated across clusters. This can only be specified at mount time.',
|
||||
}),
|
||||
sealWrap: attr('boolean', {
|
||||
helpText:
|
||||
'When enabled - if a seal supporting seal wrapping is specified in the configuration, all critical security parameters (CSPs) in this backend will be seal wrapped. (For K/V mounts, all values will be seal wrapped.) This can only be specified at mount time.',
|
||||
}),
|
||||
|
||||
// used when the `auth` prefix is important,
|
||||
// currently only when setting perf mount filtering
|
||||
|
@ -74,7 +72,7 @@ export default DS.Model.extend({
|
|||
],
|
||||
|
||||
formFieldGroups: [
|
||||
{ default: ['type', 'path'] },
|
||||
{ default: ['path'] },
|
||||
{
|
||||
'Method Options': [
|
||||
'description',
|
||||
|
|
|
@ -4,5 +4,9 @@ import Fragment from 'ember-data-model-fragments/fragment';
|
|||
export default Fragment.extend({
|
||||
version: attr('number', {
|
||||
label: 'Version',
|
||||
helpText:
|
||||
'The KV Secrets engine can operate in different modes. Version 1 is the original generic secrets engine the allows for storing of static key/value pairs. Version 2 added more features including data versioning, TTLs, and check and set.',
|
||||
possibleValues: [2, 1],
|
||||
defaultFormValue: 2,
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -3,7 +3,7 @@ import DS from 'ember-data';
|
|||
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
|
||||
import { fragment } from 'ember-data-model-fragments/attributes';
|
||||
|
||||
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
|
||||
import fieldToAttrs, { expandAttributeMeta } from 'vault/utils/field-to-attrs';
|
||||
|
||||
const { attr } = DS;
|
||||
const { computed } = Ember;
|
||||
|
@ -16,12 +16,22 @@ export default DS.Model.extend({
|
|||
path: attr('string'),
|
||||
accessor: attr('string'),
|
||||
name: attr('string'),
|
||||
type: attr('string'),
|
||||
description: attr('string'),
|
||||
type: attr('string', {
|
||||
label: 'Secret engine type',
|
||||
}),
|
||||
description: attr('string', {
|
||||
editType: 'textarea',
|
||||
}),
|
||||
config: fragment('mount-config', { defaultValue: {} }),
|
||||
options: fragment('mount-options', { defaultValue: {} }),
|
||||
local: attr('boolean'),
|
||||
sealWrap: attr('boolean'),
|
||||
local: attr('boolean', {
|
||||
helpText:
|
||||
'When replication is enabled, a local mount will not be replicated across clusters. This can only be specified at mount time.',
|
||||
}),
|
||||
sealWrap: attr('boolean', {
|
||||
helpText:
|
||||
'When enabled - if a seal supporting seal wrapping is specified in the configuration, all critical security parameters (CSPs) in this backend will be seal wrapped. (For K/V mounts, all values will be seal wrapped.) This can only be specified at mount time.',
|
||||
}),
|
||||
|
||||
modelTypeForKV: computed('engineType', 'options.version', function() {
|
||||
let type = this.get('engineType');
|
||||
|
@ -33,21 +43,51 @@ export default DS.Model.extend({
|
|||
return modelType;
|
||||
}),
|
||||
|
||||
formFields: [
|
||||
'type',
|
||||
'path',
|
||||
'description',
|
||||
'accessor',
|
||||
'local',
|
||||
'sealWrap',
|
||||
'config.{defaultLeaseTtl,maxLeaseTtl}',
|
||||
'options.{version}',
|
||||
],
|
||||
formFields: computed('engineType', function() {
|
||||
let type = this.get('engineType');
|
||||
let fields = [
|
||||
'type',
|
||||
'path',
|
||||
'description',
|
||||
'accessor',
|
||||
'local',
|
||||
'sealWrap',
|
||||
'config.{defaultLeaseTtl,maxLeaseTtl,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders}',
|
||||
];
|
||||
if (type === 'kv' || type === 'generic') {
|
||||
fields.push('options.{version}');
|
||||
}
|
||||
return fields;
|
||||
}),
|
||||
|
||||
formFieldGroups: computed('engineType', function() {
|
||||
let type = this.get('engineType');
|
||||
let defaultGroup = { default: ['path'] };
|
||||
if (type === 'kv' || type === 'generic') {
|
||||
defaultGroup.default.push('options.{version}');
|
||||
}
|
||||
return [
|
||||
defaultGroup,
|
||||
{
|
||||
'Method Options': [
|
||||
'description',
|
||||
'config.listingVisibility',
|
||||
'local',
|
||||
'sealWrap',
|
||||
'config.{defaultLeaseTtl,maxLeaseTtl,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders}',
|
||||
],
|
||||
},
|
||||
];
|
||||
}),
|
||||
|
||||
attrs: computed('formFields', function() {
|
||||
return expandAttributeMeta(this, this.get('formFields'));
|
||||
}),
|
||||
|
||||
fieldGroups: computed('formFieldGroups', function() {
|
||||
return fieldToAttrs(this, this.get('formFieldGroups'));
|
||||
}),
|
||||
|
||||
// namespaces introduced types with a `ns_` prefix for built-in engines
|
||||
// so we need to strip that to normalize the type
|
||||
engineType: computed('type', function() {
|
||||
|
|
|
@ -5,6 +5,7 @@ const { inject } = Ember;
|
|||
export default Ember.Route.extend({
|
||||
controlGroup: inject.service(),
|
||||
routing: inject.service('router'),
|
||||
wizard: inject.service(),
|
||||
namespaceService: inject.service('namespace'),
|
||||
|
||||
actions: {
|
||||
|
@ -55,5 +56,27 @@ export default Ember.Route.extend({
|
|||
|
||||
return true;
|
||||
},
|
||||
didTransition() {
|
||||
let wizard = this.get('wizard');
|
||||
|
||||
if (wizard.get('currentState') !== 'active.feature') {
|
||||
return true;
|
||||
}
|
||||
Ember.run.next(() => {
|
||||
let applicationURL = this.get('routing.currentURL');
|
||||
let activeRoute = this.get('routing.currentRouteName');
|
||||
|
||||
if (this.get('wizard.setURLAfterTransition')) {
|
||||
this.set('wizard.setURLAfterTransition', false);
|
||||
this.set('wizard.expectedURL', applicationURL);
|
||||
this.set('wizard.expectedRouteName', activeRoute);
|
||||
}
|
||||
let expectedRouteName = this.get('wizard.expectedRouteName');
|
||||
if (this.get('routing').isActive(expectedRouteName) === false) {
|
||||
wizard.transitionTutorialMachine(wizard.get('currentState'), 'PAUSE');
|
||||
}
|
||||
});
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -12,7 +12,9 @@ export default Ember.Route.extend(UnloadModel, {
|
|||
},
|
||||
|
||||
model(params) {
|
||||
return this.get('version.isOSS') ? null : this.store.findRecord('control-group', params.accessor);
|
||||
return this.get('version').hasFeature('Control Groups')
|
||||
? this.store.findRecord('control-group', params.accessor)
|
||||
: null;
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
|
|
@ -12,6 +12,6 @@ export default Ember.Route.extend(UnloadModel, {
|
|||
},
|
||||
|
||||
model() {
|
||||
return this.get('version.isOSS') ? null : this.store.createRecord('control-group');
|
||||
return this.get('version').hasFeature('Control Groups') ? this.store.createRecord('control-group') : null;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -2,6 +2,7 @@ import Ember from 'ember';
|
|||
import DS from 'ember-data';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
wizard: Ember.inject.service(),
|
||||
model(params) {
|
||||
const { section_name: section } = params;
|
||||
if (section !== 'configuration') {
|
||||
|
@ -9,7 +10,13 @@ export default Ember.Route.extend({
|
|||
Ember.set(error, 'httpStatus', 404);
|
||||
throw error;
|
||||
}
|
||||
return this.modelFor('vault.cluster.access.method');
|
||||
let backend = this.modelFor('vault.cluster.access.method');
|
||||
this.get('wizard').transitionFeatureMachine(
|
||||
this.get('wizard.featureState'),
|
||||
'DETAILS',
|
||||
backend.get('type')
|
||||
);
|
||||
return backend;
|
||||
},
|
||||
|
||||
setupController(controller) {
|
||||
|
|
|
@ -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'), 'INITDONE');
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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() {
|
||||
// always start from idle instead of using the current state
|
||||
this.get('wizard').transitionTutorialMachine('idle', 'INIT');
|
||||
this.get('wizard').set('initEvent', 'START');
|
||||
},
|
||||
});
|
||||
|
|
|
@ -5,9 +5,16 @@ import UnsavedModelRoute from 'vault/mixins/unsaved-model-route';
|
|||
const { inject } = Ember;
|
||||
export default Ember.Route.extend(UnloadModelRoute, UnsavedModelRoute, {
|
||||
version: inject.service(),
|
||||
wizard: inject.service(),
|
||||
model() {
|
||||
let policyType = this.policyType();
|
||||
|
||||
if (
|
||||
policyType === 'acl' &&
|
||||
this.get('wizard.currentMachine') === 'policies' &&
|
||||
this.get('wizard.featureState') === 'idle'
|
||||
) {
|
||||
this.get('wizard').transitionFeatureMachine(this.get('wizard.featureState'), 'CONTINUE');
|
||||
}
|
||||
if (!this.get('version.hasSentinel') && policyType !== 'acl') {
|
||||
return this.transitionTo('vault.cluster.policies', policyType);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ const { inject } = Ember;
|
|||
|
||||
export default Ember.Route.extend(ClusterRoute, {
|
||||
version: inject.service(),
|
||||
wizard: inject.service(),
|
||||
queryParams: {
|
||||
page: {
|
||||
refreshModel: true,
|
||||
|
@ -14,6 +15,12 @@ export default Ember.Route.extend(ClusterRoute, {
|
|||
},
|
||||
},
|
||||
|
||||
activate() {
|
||||
if (this.get('wizard.featureState') === 'details') {
|
||||
this.get('wizard').transitionFeatureMachine('details', 'CONTINUE', this.policyType());
|
||||
}
|
||||
},
|
||||
|
||||
shouldReturnEmptyModel(policyType, version) {
|
||||
return policyType !== 'acl' && (version.get('isOSS') || !version.get('hasSentinel'));
|
||||
},
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
wizard: Ember.inject.service(),
|
||||
model() {
|
||||
return this.modelFor('vault.cluster.secrets.backend');
|
||||
let backend = this.modelFor('vault.cluster.secrets.backend');
|
||||
if (this.get('wizard.featureState') === 'list') {
|
||||
this.get('wizard').transitionFeatureMachine(
|
||||
this.get('wizard.featureState'),
|
||||
'CONTINUE',
|
||||
backend.get('type')
|
||||
);
|
||||
}
|
||||
return backend;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -20,6 +20,7 @@ var SecretProxy = Ember.Object.extend(KeyMixin, {
|
|||
});
|
||||
|
||||
export default EditBase.extend({
|
||||
wizard: Ember.inject.service(),
|
||||
createModel(transition, parentKey) {
|
||||
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
|
||||
const modelType = this.modelType(backend);
|
||||
|
@ -27,6 +28,9 @@ export default EditBase.extend({
|
|||
return this.store.createRecord(modelType, { keyType: 'ca' });
|
||||
}
|
||||
if (modelType !== 'secret' && modelType !== 'secret-v2') {
|
||||
if (this.get('wizard.featureState') === 'details' && this.get('wizard.componentState') === 'transit') {
|
||||
this.get('wizard').transitionFeatureMachine('details', 'CONTINUE', 'transit');
|
||||
}
|
||||
return this.store.createRecord(modelType);
|
||||
}
|
||||
const key = transition.queryParams.initialKey || '';
|
||||
|
|
|
@ -2,9 +2,10 @@ import Ember from 'ember';
|
|||
import DS from 'ember-data';
|
||||
import UnloadModelRoute from 'vault/mixins/unload-model-route';
|
||||
|
||||
const { RSVP } = Ember;
|
||||
const { RSVP, inject } = Ember;
|
||||
export default Ember.Route.extend(UnloadModelRoute, {
|
||||
modelPath: 'model.model',
|
||||
wizard: inject.service(),
|
||||
modelType(backendType, section) {
|
||||
const MODELS = {
|
||||
'aws-client': 'auth-config/aws/client',
|
||||
|
@ -26,6 +27,11 @@ export default Ember.Route.extend(UnloadModelRoute, {
|
|||
const backend = this.modelFor('vault.cluster.settings.auth.configure');
|
||||
const { section_name: section } = params;
|
||||
if (section === 'options') {
|
||||
this.get('wizard').transitionFeatureMachine(
|
||||
this.get('wizard.featureState'),
|
||||
'EDIT',
|
||||
backend.get('type')
|
||||
);
|
||||
return RSVP.hash({
|
||||
model: backend,
|
||||
section,
|
||||
|
@ -39,6 +45,11 @@ export default Ember.Route.extend(UnloadModelRoute, {
|
|||
}
|
||||
const model = this.store.peekRecord(modelType, backend.id);
|
||||
if (model) {
|
||||
this.get('wizard').transitionFeatureMachine(
|
||||
this.get('wizard.featureState'),
|
||||
'EDIT',
|
||||
backend.get('type')
|
||||
);
|
||||
return RSVP.hash({
|
||||
model,
|
||||
section,
|
||||
|
@ -47,6 +58,11 @@ export default Ember.Route.extend(UnloadModelRoute, {
|
|||
return this.store
|
||||
.findRecord(modelType, backend.id)
|
||||
.then(config => {
|
||||
this.get('wizard').transitionFeatureMachine(
|
||||
this.get('wizard.featureState'),
|
||||
'EDIT',
|
||||
backend.get('type')
|
||||
);
|
||||
config.set('backend', backend);
|
||||
return RSVP.hash({
|
||||
model: config,
|
||||
|
|
|
@ -13,9 +13,8 @@ export default Ember.Route.extend(UnloadModel, {
|
|||
|
||||
model() {
|
||||
let type = 'control-group-config';
|
||||
return this.get('version.isOSS')
|
||||
? null
|
||||
: this.store.findRecord(type, 'config').catch(e => {
|
||||
return this.get('version').hasFeature('Control Groups')
|
||||
? this.store.findRecord(type, 'config').catch(e => {
|
||||
// if you haven't saved a config, the API 404s, so create one here to edit and return it
|
||||
if (e.httpStatus === 404) {
|
||||
return this.store.createRecord(type, {
|
||||
|
@ -23,7 +22,8 @@ export default Ember.Route.extend(UnloadModel, {
|
|||
});
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
})
|
||||
: null;
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
|
|
@ -2,6 +2,8 @@ import Ember from 'ember';
|
|||
import { toolsActions } from 'vault/helpers/tools-actions';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
wizard: Ember.inject.service(),
|
||||
|
||||
beforeModel(transition) {
|
||||
const supportedActions = toolsActions();
|
||||
const { selectedAction } = this.paramsFor(this.routeName);
|
||||
|
@ -14,7 +16,14 @@ export default Ember.Route.extend({
|
|||
actions: {
|
||||
didTransition() {
|
||||
const params = this.paramsFor(this.routeName);
|
||||
if (this.get('wizard.currentMachine') === 'tools') {
|
||||
this.get('wizard').transitionFeatureMachine(
|
||||
this.get('wizard.featureState'),
|
||||
params.selectedAction.toUpperCase()
|
||||
);
|
||||
}
|
||||
this.controller.setProperties(params);
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1 +1,13 @@
|
|||
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('initEvent', 'UNSEAL');
|
||||
this.get('wizard').transitionTutorialMachine(this.get('wizard.currentState'), 'TOUNSEAL');
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
import DS from 'ember-data';
|
||||
import Ember from 'ember';
|
||||
const { decamelize } = Ember.String;
|
||||
import ApplicationSerializer from './application';
|
||||
|
||||
export default DS.RESTSerializer.extend({
|
||||
keyForAttribute: function(attr) {
|
||||
return decamelize(attr);
|
||||
},
|
||||
export default ApplicationSerializer.extend({
|
||||
normalizeBackend(path, backend) {
|
||||
let struct = {};
|
||||
for (let attribute in backend) {
|
||||
|
@ -51,11 +47,20 @@ export default DS.RESTSerializer.extend({
|
|||
}
|
||||
}
|
||||
|
||||
const transformedPayload = { [primaryModelClass.modelName]: backends };
|
||||
return this._super(store, primaryModelClass, transformedPayload, id, requestType);
|
||||
return this._super(store, primaryModelClass, backends, id, requestType);
|
||||
},
|
||||
|
||||
serialize() {
|
||||
return this._super(...arguments);
|
||||
serialize(snapshot) {
|
||||
let type = snapshot.record.get('engineType');
|
||||
let data = this._super(...arguments);
|
||||
// only KV uses options
|
||||
if (type !== 'kv' && type !== 'generic') {
|
||||
delete data.options;
|
||||
} else if (!data.options.version) {
|
||||
// if options.version isn't set for some reason
|
||||
// default to 2
|
||||
data.options.version = 2;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -3,13 +3,16 @@ import { task } from 'ember-concurrency';
|
|||
|
||||
const { Service, inject, computed } = Ember;
|
||||
|
||||
const hasFeatureMethod = (context, featureKey) => {
|
||||
const features = context.get('features');
|
||||
if (!features) {
|
||||
return false;
|
||||
}
|
||||
return features.includes(featureKey);
|
||||
};
|
||||
const hasFeature = featureKey => {
|
||||
return computed('features', 'features.[]', function() {
|
||||
const features = this.get('features');
|
||||
if (!features) {
|
||||
return false;
|
||||
}
|
||||
return features.includes(featureKey);
|
||||
return hasFeatureMethod(this, featureKey);
|
||||
});
|
||||
};
|
||||
export default Service.extend({
|
||||
|
@ -33,6 +36,10 @@ export default Service.extend({
|
|||
this.set('version', resp.version);
|
||||
},
|
||||
|
||||
hasFeature(feature) {
|
||||
return hasFeatureMethod(this, feature);
|
||||
},
|
||||
|
||||
setFeatures(resp) {
|
||||
if (!resp.features) {
|
||||
return;
|
||||
|
|
|
@ -0,0 +1,317 @@
|
|||
import Ember from 'ember';
|
||||
import { Machine } from 'xstate';
|
||||
|
||||
const { Service, inject } = Ember;
|
||||
|
||||
import getStorage from 'vault/lib/token-storage';
|
||||
|
||||
import TutorialMachineConfig from 'vault/machines/tutorial-machine';
|
||||
import SecretsMachineConfig from 'vault/machines/secrets-machine';
|
||||
import PoliciesMachineConfig from 'vault/machines/policies-machine';
|
||||
import ReplicationMachineConfig from 'vault/machines/replication-machine';
|
||||
import ToolsMachineConfig from 'vault/machines/tools-machine';
|
||||
import AuthMachineConfig from 'vault/machines/auth-machine';
|
||||
|
||||
const TutorialMachine = Machine(TutorialMachineConfig);
|
||||
let FeatureMachine = null;
|
||||
const TUTORIAL_STATE = 'vault:ui-tutorial-state';
|
||||
const FEATURE_LIST = 'vault:ui-feature-list';
|
||||
const FEATURE_STATE = 'vault:ui-feature-state';
|
||||
const COMPLETED_FEATURES = 'vault:ui-completed-list';
|
||||
const COMPONENT_STATE = 'vault:ui-component-state';
|
||||
const RESUME_URL = 'vault:ui-tutorial-resume-url';
|
||||
const RESUME_ROUTE = 'vault:ui-tutorial-resume-route';
|
||||
const MACHINES = {
|
||||
secrets: SecretsMachineConfig,
|
||||
policies: PoliciesMachineConfig,
|
||||
replication: ReplicationMachineConfig,
|
||||
tools: ToolsMachineConfig,
|
||||
authentication: AuthMachineConfig,
|
||||
};
|
||||
|
||||
const DEFAULTS = {
|
||||
currentState: null,
|
||||
featureList: null,
|
||||
featureState: null,
|
||||
currentMachine: null,
|
||||
tutorialComponent: null,
|
||||
featureComponent: null,
|
||||
stepComponent: null,
|
||||
detailsComponent: null,
|
||||
componentState: null,
|
||||
nextFeature: null,
|
||||
nextStep: null,
|
||||
};
|
||||
|
||||
export default Service.extend(DEFAULTS, {
|
||||
router: inject.service(),
|
||||
showWhenUnauthenticated: false,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.initializeMachines();
|
||||
},
|
||||
|
||||
initializeMachines() {
|
||||
if (!this.storageHasKey(TUTORIAL_STATE)) {
|
||||
let state = TutorialMachine.initialState;
|
||||
this.saveState('currentState', state.value);
|
||||
this.saveExtState(TUTORIAL_STATE, state.value);
|
||||
}
|
||||
this.saveState('currentState', this.getExtState(TUTORIAL_STATE));
|
||||
if (this.storageHasKey(COMPONENT_STATE)) {
|
||||
this.set('componentState', this.getExtState(COMPONENT_STATE));
|
||||
}
|
||||
let stateNodes = TutorialMachine.getStateNodes(this.get('currentState'));
|
||||
this.executeActions(stateNodes.reduce((acc, node) => acc.concat(node.onEntry), []), null, 'tutorial');
|
||||
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();
|
||||
}
|
||||
},
|
||||
|
||||
restartGuide() {
|
||||
let storage = this.storage();
|
||||
// empty storage
|
||||
[
|
||||
TUTORIAL_STATE,
|
||||
FEATURE_LIST,
|
||||
FEATURE_STATE,
|
||||
COMPLETED_FEATURES,
|
||||
COMPONENT_STATE,
|
||||
RESUME_URL,
|
||||
RESUME_ROUTE,
|
||||
].forEach(key => storage.removeItem(key));
|
||||
// reset wizard state
|
||||
this.setProperties(DEFAULTS);
|
||||
// restart machines from blank state
|
||||
this.initializeMachines();
|
||||
// progress machine to 'active.select'
|
||||
this.transitionTutorialMachine('idle', 'AUTH');
|
||||
},
|
||||
|
||||
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, extendedState) {
|
||||
if (extendedState) {
|
||||
this.set('componentState', extendedState);
|
||||
this.saveExtState(COMPONENT_STATE, extendedState);
|
||||
}
|
||||
let { actions, value } = TutorialMachine.transition(currentState, event);
|
||||
this.saveState('currentState', value);
|
||||
this.saveExtState(TUTORIAL_STATE, this.get('currentState'));
|
||||
this.executeActions(actions, event, 'tutorial');
|
||||
},
|
||||
|
||||
transitionFeatureMachine(currentState, event, extendedState) {
|
||||
if (!FeatureMachine || !this.get('currentState').includes('active')) {
|
||||
return;
|
||||
}
|
||||
if (extendedState) {
|
||||
this.set('componentState', extendedState);
|
||||
this.saveExtState(COMPONENT_STATE, extendedState);
|
||||
}
|
||||
|
||||
let { actions, value } = FeatureMachine.transition(currentState, event, this.get('componentState'));
|
||||
this.saveState('featureState', value);
|
||||
this.saveExtState(FEATURE_STATE, value);
|
||||
this.executeActions(actions, event, 'feature');
|
||||
// if all features were completed, the FeatureMachine gets nulled
|
||||
// out and won't exist here as there is no next step
|
||||
if (FeatureMachine) {
|
||||
let next;
|
||||
if (this.get('currentMachine') === 'secrets' && value === 'display') {
|
||||
next = FeatureMachine.transition(value, 'REPEAT', this.get('componentState'));
|
||||
} else {
|
||||
next = FeatureMachine.transition(value, 'CONTINUE', this.get('componentState'));
|
||||
}
|
||||
this.saveState('nextStep', next.value);
|
||||
}
|
||||
},
|
||||
|
||||
saveExtState(key, value) {
|
||||
this.storage().setItem(key, value);
|
||||
},
|
||||
|
||||
getExtState(key) {
|
||||
return this.storage().getItem(key);
|
||||
},
|
||||
|
||||
storageHasKey(key) {
|
||||
return Boolean(this.getExtState(key));
|
||||
},
|
||||
|
||||
executeActions(actions, event, machineType) {
|
||||
let transitionURL;
|
||||
let expectedRouteName;
|
||||
let router = this.get('router');
|
||||
|
||||
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':
|
||||
expectedRouteName = action.params[0];
|
||||
transitionURL = router.urlFor(...action.params).replace(/^\/ui/, '');
|
||||
Ember.run.next(() => {
|
||||
router.transitionTo(...action.params);
|
||||
});
|
||||
break;
|
||||
case 'saveFeatures':
|
||||
this.saveFeatures(event.features);
|
||||
break;
|
||||
case 'completeFeature':
|
||||
this.completeFeature();
|
||||
break;
|
||||
case 'handleDismissed':
|
||||
this.handleDismissed();
|
||||
break;
|
||||
case 'handlePaused':
|
||||
this.handlePaused();
|
||||
return;
|
||||
case 'handleResume':
|
||||
this.handleResume();
|
||||
break;
|
||||
case 'showTutorialWhenAuthenticated':
|
||||
this.set('showWhenUnauthenticated', false);
|
||||
break;
|
||||
case 'showTutorialAlways':
|
||||
this.set('showWhenUnauthenticated', true);
|
||||
break;
|
||||
case 'continueFeature':
|
||||
this.transitionFeatureMachine(this.get('featureState'), 'CONTINUE', this.get('componentState'));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (machineType === 'tutorial') {
|
||||
return;
|
||||
}
|
||||
// if we're transitioning in the actions, we want that url,
|
||||
// else we want the URL we land on in didTransition in the
|
||||
// application route - we'll notify the application route to
|
||||
// update the route
|
||||
if (transitionURL) {
|
||||
this.set('expectedURL', transitionURL);
|
||||
this.set('expectedRouteName', expectedRouteName);
|
||||
this.set('setURLAfterTransition', false);
|
||||
} else {
|
||||
this.set('setURLAfterTransition', true);
|
||||
}
|
||||
},
|
||||
|
||||
handlePaused() {
|
||||
let expected = this.get('expectedURL');
|
||||
if (expected) {
|
||||
this.saveExtState(RESUME_URL, this.get('expectedURL'));
|
||||
this.saveExtState(RESUME_ROUTE, this.get('expectedRouteName'));
|
||||
}
|
||||
},
|
||||
|
||||
handleResume() {
|
||||
let resumeURL = this.storage().getItem(RESUME_URL);
|
||||
if (!resumeURL) {
|
||||
return;
|
||||
}
|
||||
this.get('router').transitionTo(resumeURL).followRedirects().then(() => {
|
||||
this.set('expectedRouteName', this.storage().getItem(RESUME_ROUTE));
|
||||
this.set('expectedURL', resumeURL);
|
||||
this.initializeMachines();
|
||||
this.storage().removeItem(RESUME_URL);
|
||||
});
|
||||
},
|
||||
|
||||
handleDismissed() {
|
||||
this.storage().removeItem(FEATURE_STATE);
|
||||
this.storage().removeItem(FEATURE_LIST);
|
||||
this.storage().removeItem(COMPONENT_STATE);
|
||||
},
|
||||
|
||||
saveFeatures(features) {
|
||||
this.set('featureList', features);
|
||||
this.saveExtState(FEATURE_LIST, this.get('featureList'));
|
||||
this.buildFeatureMachine();
|
||||
},
|
||||
|
||||
buildFeatureMachine() {
|
||||
if (this.get('featureList') === null) {
|
||||
return;
|
||||
}
|
||||
this.startFeature();
|
||||
if (this.storageHasKey(FEATURE_STATE)) {
|
||||
this.saveState('featureState', this.getExtState(FEATURE_STATE));
|
||||
}
|
||||
this.saveExtState(FEATURE_STATE, this.get('featureState'));
|
||||
let nextFeature =
|
||||
this.get('featureList').length > 1 ? this.get('featureList').objectAt(1).capitalize() : 'Finish';
|
||||
this.set('nextFeature', nextFeature);
|
||||
let next;
|
||||
if (this.get('currentMachine') === 'secrets' && this.get('featureState') === 'display') {
|
||||
next = FeatureMachine.transition(this.get('featureState'), 'REPEAT', this.get('componentState'));
|
||||
} else {
|
||||
next = FeatureMachine.transition(this.get('featureState'), 'CONTINUE', this.get('componentState'));
|
||||
}
|
||||
this.saveState('nextStep', next.value);
|
||||
let stateNodes = FeatureMachine.getStateNodes(this.get('featureState'));
|
||||
this.executeActions(stateNodes.reduce((acc, node) => acc.concat(node.onEntry), []), null, 'feature');
|
||||
},
|
||||
|
||||
startFeature() {
|
||||
const FeatureMachineConfig = MACHINES[this.get('featureList').objectAt(0)];
|
||||
FeatureMachine = Machine(FeatureMachineConfig);
|
||||
this.set('currentMachine', this.get('featureList').objectAt(0));
|
||||
this.saveState('featureState', FeatureMachine.initialState);
|
||||
},
|
||||
|
||||
completeFeature() {
|
||||
let features = this.get('featureList');
|
||||
let done = features.shift();
|
||||
if (!this.getExtState(COMPLETED_FEATURES)) {
|
||||
let completed = [];
|
||||
completed.push(done);
|
||||
this.saveExtState(COMPLETED_FEATURES, completed);
|
||||
} else {
|
||||
this.saveExtState(COMPLETED_FEATURES, this.getExtState(COMPLETED_FEATURES).toArray().addObject(done));
|
||||
}
|
||||
|
||||
this.saveExtState(FEATURE_LIST, features.length ? features : null);
|
||||
this.storage().removeItem(FEATURE_STATE);
|
||||
if (features.length > 0) {
|
||||
this.buildFeatureMachine();
|
||||
} else {
|
||||
this.storage().removeItem(FEATURE_LIST);
|
||||
FeatureMachine = null;
|
||||
this.transitionTutorialMachine(this.get('currentState'), 'DONE');
|
||||
}
|
||||
},
|
||||
|
||||
storage() {
|
||||
return getStorage();
|
||||
},
|
||||
});
|
|
@ -0,0 +1,61 @@
|
|||
.box-radio-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.title.box-radio-header {
|
||||
font-size: $size-6;
|
||||
color: $grey;
|
||||
margin: $size-7 0 0 0;
|
||||
}
|
||||
.box-radio {
|
||||
box-sizing: border-box;
|
||||
flex-basis: 7rem;
|
||||
width: 7rem;
|
||||
height: 7.5rem;
|
||||
padding: $size-10 $size-6 $size-10;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
border-radius: $radius;
|
||||
box-shadow: $box-shadow;
|
||||
text-align: center;
|
||||
color: $grey;
|
||||
font-weight: $font-weight-semibold;
|
||||
line-height: 1;
|
||||
margin: $size-6 $size-3 $size-6 0;
|
||||
font-size: 12px;
|
||||
transition: box-shadow ease-in-out $speed;
|
||||
will-change: box-shadow;
|
||||
|
||||
&.is-selected {
|
||||
box-shadow: 0 0 0 1px $grey-light, $box-shadow-middle;
|
||||
}
|
||||
|
||||
input[type=radio].radio {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
input[type=radio].radio + label {
|
||||
border: 1px solid $grey-light;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
margin: 1rem auto 0;
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
input[type=radio].radio:checked + label {
|
||||
background: $blue;
|
||||
border: 1px solid $blue;
|
||||
box-shadow: inset 0 0 0 0.15rem $white;
|
||||
}
|
||||
input[type=radio].radio:focus + label {
|
||||
box-shadow: 0 0 10px 1px rgba($blue, 0.4), inset 0 0 0 0.15rem $white;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
.doc-link {
|
||||
color: $link;
|
||||
text-decoration: none;
|
||||
font-weight: $font-weight-semibold;
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
.feature-header {
|
||||
font-size: $size-6;
|
||||
font-weight: $font-weight-semibold;
|
||||
color: $grey;
|
||||
}
|
||||
|
||||
.feature-box {
|
||||
box-shadow: $box-shadow;
|
||||
border-radius: $radius;
|
||||
padding: $size-8;
|
||||
margin: $size-8 0;
|
||||
|
||||
&.is-active {
|
||||
box-shadow: 0 0 0 1px $grey-light;
|
||||
}
|
||||
}
|
||||
|
||||
.feature-box label {
|
||||
font-weight: $font-weight-semibold;
|
||||
padding-left: $size-10;
|
||||
|
||||
&::before {
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.feature-steps {
|
||||
font-size: $size-8;
|
||||
color: $grey;
|
||||
line-height: 1.5;
|
||||
margin-left: $size-3;
|
||||
margin-top: $size-10;
|
||||
|
||||
li::before {
|
||||
// bullet
|
||||
content: '\2022';
|
||||
position: relative;
|
||||
right: $size-11;
|
||||
}
|
||||
}
|
|
@ -21,4 +21,8 @@
|
|||
.breadcrumb + .level .title {
|
||||
margin-top: $size-4;
|
||||
}
|
||||
.title .icon {
|
||||
height: auto;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
.ui-wizard-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.ui-wizard-container .app-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.ui-wizard-container .app-content.wizard-open {
|
||||
padding-right: 324px;
|
||||
|
||||
@include until($tablet) {
|
||||
padding-right: 0;
|
||||
padding-bottom: 50vh;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-wizard {
|
||||
z-index: 300;
|
||||
padding: $size-5;
|
||||
width: 300px;
|
||||
background: $white;
|
||||
box-shadow: $box-shadow, $box-shadow-highest;
|
||||
position: fixed;
|
||||
right: $size-8;
|
||||
bottom: $size-8;
|
||||
top: calc(3.5rem + #{$size-8});
|
||||
overflow: auto;
|
||||
|
||||
p {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.dismiss-collapsed {
|
||||
position: absolute;
|
||||
top: $size-8;
|
||||
right: $size-8;
|
||||
color: $grey;
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
@include until($tablet) {
|
||||
box-shadow: $box-shadow, 0 0 20px rgba($black, 0.24);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.doc-link {
|
||||
margin-top: $size-5;
|
||||
display: block;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background: $ui-gray-050;
|
||||
margin: $size-8 0;
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-header {
|
||||
margin-bottom: $size-5;
|
||||
position: relative;
|
||||
|
||||
.icon {
|
||||
margin-right: $size-11;
|
||||
vertical-align: -0.33rem;
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-dismiss-menu {
|
||||
position: absolute;
|
||||
right: $size-6;
|
||||
top: $size-6;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.ui-wizard.collapsed {
|
||||
color: $white;
|
||||
background: $black;
|
||||
bottom: auto;
|
||||
box-shadow: $box-shadow-middle;
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
padding-bottom: $size-11;
|
||||
position: fixed;
|
||||
right: $size-8;
|
||||
top: calc(3.5rem + #{$size-8});
|
||||
|
||||
@include until($tablet) {
|
||||
box-shadow: $box-shadow, 0 0 20px rgba($black, 0.24);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: auto;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.wizard-header {
|
||||
margin-bottom: $size-10;
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-divider-box {
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
margin: $size-8 0 0;
|
||||
padding: 0 $size-8;
|
||||
border-top: solid 1px $white;
|
||||
border-image: $dark-vault-gradient 1;
|
||||
button {
|
||||
font-size: $size-7;
|
||||
font-weight: $font-weight-semibold;
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-section .title .icon {
|
||||
height: auto;
|
||||
margin-right: $size-11;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.wizard-section:last-of-type {
|
||||
margin-bottom: $size-5;
|
||||
}
|
||||
|
||||
.wizard-section button:not(:last-of-type) {
|
||||
margin-bottom: $size-10;
|
||||
}
|
||||
|
||||
.wizard-details {
|
||||
padding-top: $size-4;
|
||||
margin-top: $size-4;
|
||||
border-top: 1px solid $grey-light;
|
||||
}
|
|
@ -44,11 +44,14 @@
|
|||
@import "./components/auth-form";
|
||||
@import "./components/b64-toggle";
|
||||
@import "./components/box-label";
|
||||
@import "./components/box-radio";
|
||||
@import "./components/codemirror";
|
||||
@import "./components/confirm";
|
||||
@import "./components/console-ui-panel";
|
||||
@import "./components/control-group";
|
||||
@import "./components/doc-link";
|
||||
@import "./components/env-banner";
|
||||
@import "./components/features-selection";
|
||||
@import "./components/form-section";
|
||||
@import "./components/global-flash";
|
||||
@import "./components/hover-copy-button";
|
||||
|
@ -77,4 +80,5 @@
|
|||
@import "./components/tool-tip";
|
||||
@import "./components/unseal-warning";
|
||||
@import "./components/upgrade-overlay";
|
||||
@import "./components/ui-wizard";
|
||||
@import "./components/vault-loading";
|
||||
|
|
|
@ -184,6 +184,7 @@ $button-box-shadow-standard: 0 3px 1px 0 rgba($black, 0.12);
|
|||
&,
|
||||
&:first-child:last-child {
|
||||
margin-left: -$size-10;
|
||||
margin-right: $size-11;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -214,3 +215,16 @@ $button-box-shadow-standard: 0 3px 1px 0 rgba($black, 0.12);
|
|||
width: auto;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.button.next-feature-step {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
background: $white;
|
||||
color: $blue;
|
||||
box-shadow: none;
|
||||
display: block;
|
||||
border: 1px solid $grey-light;
|
||||
border-radius: $radius;
|
||||
height: auto;
|
||||
padding: $size-8;
|
||||
}
|
||||
|
|
|
@ -184,7 +184,8 @@ label {
|
|||
}
|
||||
}
|
||||
|
||||
.select:not(.is-multiple)::after {
|
||||
.select:not(.is-multiple)::after,
|
||||
.select:not(.is-multiple)::before {
|
||||
border-color: $black;
|
||||
border-width: 2px;
|
||||
margin-top: 0;
|
||||
|
@ -192,7 +193,6 @@ label {
|
|||
}
|
||||
|
||||
.select:not(.is-multiple)::before {
|
||||
@extend .select:not(.is-multiple)::after;
|
||||
transform: translateY(-75%) rotate(135deg);
|
||||
z-index: 5;
|
||||
}
|
||||
|
|
|
@ -46,3 +46,7 @@ input::-webkit-inner-spin-button {
|
|||
-ms-user-select: text; /* IE 10+ */
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.link-plain {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
$dark-vault-gradient: linear-gradient(to right, $vault-gray-dark, $vault-gray);
|
||||
.has-dark-vault-gradient {
|
||||
background: linear-gradient(to right, $vault-gray-dark, $vault-gray);
|
||||
background: $dark-vault-gradient;
|
||||
}
|
||||
|
|
|
@ -46,6 +46,11 @@
|
|||
</li>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
<li class="action">
|
||||
<button type="button" class="button link" onclick={{action "restartGuide"}}>
|
||||
Restart guide
|
||||
</button>
|
||||
</li>
|
||||
<li class="action">
|
||||
{{#link-to "vault.cluster.logout" activeClusterName id="logout"}}
|
||||
Sign out
|
||||
|
|
|
@ -73,6 +73,7 @@
|
|||
}}
|
||||
{{else if (eq attr.options.editType 'ttl')}}
|
||||
{{ttl-picker
|
||||
data-test-input=attr.name
|
||||
initialValue=(or (get model valuePath) attr.options.defaultValue)
|
||||
labelText=labelString
|
||||
warning=attr.options.warning
|
||||
|
|
|
@ -1,10 +1,21 @@
|
|||
<PageHeader as |p|>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3" data-test-mount-form-header=true>
|
||||
{{#if (eq mountType "auth")}}
|
||||
Enable an authentication method
|
||||
{{#if showConfig}}
|
||||
{{#with (find-by 'type' mountModel.type mountTypes) as |typeInfo|}}
|
||||
<ICon @size=28 @glyph={{concat "enable/" (or typeInfo.glyph typeInfo.type)}} />
|
||||
{{#if (eq mountType "auth")}}
|
||||
Enable {{typeInfo.displayName}} authentication method
|
||||
{{else}}
|
||||
Enable {{typeInfo.displayName}} secrets engine
|
||||
{{/if}}
|
||||
{{/with}}
|
||||
{{else}}
|
||||
Enable a secrets engine
|
||||
{{#if (eq mountType "auth")}}
|
||||
Enable an authentication method
|
||||
{{else}}
|
||||
Enable a secrets engine
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
|
@ -13,19 +24,73 @@
|
|||
<div class="box is-sideless is-fullwidth is-marginless">
|
||||
<NamespaceReminder @mode="enable" @noun={{if (eq mountType "auth") "auth method" "secret engine"}} />
|
||||
{{message-error model=mountModel}}
|
||||
{{form-field-groups model=mountModel onChange=(action "onTypeChange") renderGroup="default"}}
|
||||
{{#if mountModel.authConfigs.firstObject}}
|
||||
{{form-field-groups model=mountModel.authConfigs.firstObject}}
|
||||
{{#if showConfig}}
|
||||
{{form-field-groups model=mountModel onChange=(action "onTypeChange") renderGroup="default"}}
|
||||
{{#if mountModel.authConfigs.firstObject}}
|
||||
{{form-field-groups model=mountModel.authConfigs.firstObject}}
|
||||
{{/if}}
|
||||
{{form-field-groups model=mountModel onChange=(action "onTypeChange") renderGroup="Method Options"}}
|
||||
{{else}}
|
||||
{{#each (array "generic" "cloud" "infra") as |category|}}
|
||||
<h3 class="title box-radio-header">
|
||||
{{capitalize category}}
|
||||
</h3>
|
||||
<div class="box-radio-container">
|
||||
{{#each (filter-by "category" category mountTypes) as |type|}}
|
||||
<label
|
||||
for={{type.type}}
|
||||
class="box-radio {{if (eq mountModel.type type.type) "is-selected" }}"
|
||||
data-test-mount-type-radio
|
||||
data-test-mount-type={{type.type}}
|
||||
>
|
||||
<ICon @size=36 @excludeIconClass={{true}} @glyph={{concat "enable/" (or type.glyph type.type)}} />
|
||||
{{type.displayName}}
|
||||
<RadioButton
|
||||
@value={{type.type}}
|
||||
@radioClass="radio"
|
||||
@groupValue={{mountModel.type}}
|
||||
@changed={{queue (action (mut mountModel.type)) (action "onTypeChange" "type")}}
|
||||
@name="mount-type"
|
||||
@radioId={{type.type}}
|
||||
/>
|
||||
<label for={{type.type}}></label>
|
||||
</label>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
{{form-field-groups model=mountModel onChange=(action "onTypeChange") renderGroup="Method Options"}}
|
||||
</div>
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
<button type="submit" data-test-mount-submit=true class="button is-primary {{if mountBackend.isRunning 'loading'}}" disabled={{mountBackend.isRunning}}>
|
||||
{{#if (eq mountType "auth")}}
|
||||
Enable Method
|
||||
{{else}}
|
||||
Enable Engine
|
||||
{{/if}}
|
||||
</button>
|
||||
{{#if showConfig}}
|
||||
<div class="control">
|
||||
<button type="submit" data-test-mount-submit=true class="button is-primary {{if mountBackend.isRunning 'loading'}}" disabled={{mountBackend.isRunning}}>
|
||||
{{#if (eq mountType "auth")}}
|
||||
Enable Method
|
||||
{{else}}
|
||||
Enable Engine
|
||||
{{/if}}
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button
|
||||
data-test-mount-back
|
||||
type="button"
|
||||
class="button"
|
||||
onclick={{action "toggleShowConfig" false}}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
{{else}}
|
||||
<button
|
||||
data-test-mount-next
|
||||
type="button"
|
||||
class="button is-primary"
|
||||
onclick={{action "toggleShowConfig" true}}
|
||||
disabled={{not mountModel.type}}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -12,19 +12,21 @@
|
|||
{{/if}}
|
||||
</Nav.items>
|
||||
</NavHeader>
|
||||
<div class="splash-page-container section is-flex-v-centered-tablet is-flex-1 is-fullwidth">
|
||||
<div class="columns is-centered is-gapless is-fullwidth">
|
||||
<div class="column is-4-desktop is-6-tablet">
|
||||
<div class="splash-page-header">
|
||||
{{yield (hash header=(component 'splash-page/splash-header'))}}
|
||||
<UiWizard>
|
||||
<div class="splash-page-container section is-flex-v-centered-tablet is-flex-1 is-fullwidth">
|
||||
<div class="columns is-centered is-gapless is-fullwidth">
|
||||
<div class="column is-4-desktop is-6-tablet">
|
||||
<div class="splash-page-header">
|
||||
{{yield (hash header=(component 'splash-page/splash-header'))}}
|
||||
</div>
|
||||
<div class="splash-page-sub-header">
|
||||
{{yield (hash sub-header=(component 'splash-page/splash-header'))}}
|
||||
</div>
|
||||
<div class="login-form box is-paddingless is-relative">
|
||||
{{yield (hash content=(component 'splash-page/splash-content'))}}
|
||||
</div>
|
||||
{{yield (hash footer=(component 'splash-page/splash-content')) }}
|
||||
</div>
|
||||
<div class="splash-page-sub-header">
|
||||
{{yield (hash sub-header=(component 'splash-page/splash-header'))}}
|
||||
</div>
|
||||
<div class="login-form box is-paddingless is-relative">
|
||||
{{yield (hash content=(component 'splash-page/splash-content'))}}
|
||||
</div>
|
||||
{{yield (hash footer=(component 'splash-page/splash-content')) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UiWizard>
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<div class="app-content {{if (and shouldRender featureComponent) "wizard-open"}}">
|
||||
{{yield}}
|
||||
</div>
|
||||
{{#component
|
||||
(if shouldRender tutorialComponent)
|
||||
onAdvance=(action "advanceWizard")
|
||||
onDismiss=(action "dismissWizard")
|
||||
}}
|
||||
{{component
|
||||
featureComponent
|
||||
componentState=componentState
|
||||
nextFeature=nextFeature
|
||||
nextStep=nextStep
|
||||
onDone=(action "finishFeature")
|
||||
onRepeat=(action "repeatStep")
|
||||
onReset=(action "resetFeature")
|
||||
onAdvance=(action "advanceFeature")
|
||||
}}
|
||||
{{/component}}
|
|
@ -0,0 +1,17 @@
|
|||
<PopupMenu @class="wizard-dismiss-menu">
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
<li class="action">
|
||||
<button type="button" class="button link" onclick={{action "dismissWizard"}}>
|
||||
Dismiss
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<div class="wizard-header">
|
||||
<h1 class="title is-5">
|
||||
<ICon @glyph={{glyph}} @size="21" /> {{headerText}}
|
||||
</h1>
|
||||
</div>
|
||||
{{yield}}
|
|
@ -0,0 +1,14 @@
|
|||
<div class="wizard-section {{class}}">
|
||||
<h2 class="title is-6">
|
||||
{{#if headerIcon}}
|
||||
<ICon @glyph={{headerIcon}} @size=24 />
|
||||
{{/if}}
|
||||
{{headerText}}
|
||||
</h2>
|
||||
{{yield}}
|
||||
{{#if docText}}
|
||||
<DocLink @path={{docPath}}>
|
||||
<ICon @glyph='learn' @size=16 /> {{docText}}
|
||||
</DocLink>
|
||||
{{/if}}
|
||||
</div>
|
|
@ -0,0 +1,11 @@
|
|||
<WizardSection
|
||||
@headerText="Active Directory"
|
||||
@headerIcon="enable/azure"
|
||||
@docText="Docs: Active Directory Secrets"
|
||||
@docPath="/docs/secrets/ad/index.html"
|
||||
>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</WizardSection>
|
|
@ -0,0 +1,10 @@
|
|||
<WizardSection
|
||||
@headerText="AppRole"
|
||||
@headerIcon="enable/approle"
|
||||
@docText="Docs: AppRole Authentication"
|
||||
@docPath="/docs/auth/approle.html"
|
||||
>
|
||||
<p>
|
||||
The approle auth method allows machines or apps to authenticate with Vault-defined roles. The open design of AppRole enables a varied set of workflows and configurations to handle large numbers of apps. This auth method is oriented to automated workflows (machines and services), and is less useful for human operators.
|
||||
</p>
|
||||
</WizardSection>
|
|
@ -0,0 +1,20 @@
|
|||
<WizardSection
|
||||
@headerText="Auth Method Details"
|
||||
@docText="Docs: Authentication Methods"
|
||||
@docPath="/docs/auth/index.html"
|
||||
>
|
||||
<p>
|
||||
Fantastic! Now you're ready to use your new {{mountName}} auth method!
|
||||
</p>
|
||||
</WizardSection>
|
||||
<WizardSection
|
||||
@headerText="Want to start again or move on?"
|
||||
@class="wizard-details"
|
||||
>
|
||||
<button type="button" class="button next-feature-step" {{action onReset}}>
|
||||
Enable another auth method <ICon @glyph="loop" @size=13 @class="is-pulled-right" />
|
||||
</button>
|
||||
<button type="button" class="button next-feature-step" {{action onAdvance}}>
|
||||
{{nextFeature}} <ICon @glyph="chevron-right" @size=10 @class="is-pulled-right" />
|
||||
</button>
|
||||
</WizardSection>
|
|
@ -0,0 +1,9 @@
|
|||
<WizardSection
|
||||
@headerText="Editing Your Auth Method"
|
||||
@docText="Docs: Authentication Methods"
|
||||
@docPath="/docs/auth/index.html"
|
||||
>
|
||||
<p>
|
||||
You can update your new auth method configuration here. Click the "View method" link to see its details.
|
||||
</p>
|
||||
</WizardSection>
|
|
@ -0,0 +1,9 @@
|
|||
<WizardSection
|
||||
@headerText="Entering Auth Method details"
|
||||
@docText="Docs: Authentication Methods"
|
||||
@docPath="/docs/auth/index.html"
|
||||
>
|
||||
<p>
|
||||
Great! Now you can customize this method with a name and description that makes sense for your team, and fill out any options that are specific to this method.
|
||||
</p>
|
||||
</WizardSection>
|
|
@ -0,0 +1,9 @@
|
|||
<WizardSection
|
||||
@headerText="Enabling an Auth Method"
|
||||
@docText="Docs: Authentication Methods"
|
||||
@docPath="/docs/auth/index.html"
|
||||
>
|
||||
<p>
|
||||
Controlling who can see your secrets is important. Let's set up a an authentication method for you and your team to use. Don't worry, you can add more methods later. Choose an authentication method to get started.
|
||||
</p>
|
||||
</WizardSection>
|
|
@ -0,0 +1,9 @@
|
|||
<WizardSection
|
||||
@headerText="Auth Method List"
|
||||
@docText="Docs: Authentication Methods"
|
||||
@docPath="/docs/auth/index.html"
|
||||
>
|
||||
<p>
|
||||
Awesome! Now you can see your new auth method in the list. Click the ellipsis menu for your method and then click "View Configuration" to see its details.
|
||||
</p>
|
||||
</WizardSection>
|
|
@ -0,0 +1,10 @@
|
|||
<WizardSection
|
||||
@headerText="AWS"
|
||||
@headerIcon="enable/aws"
|
||||
@docText="Docs: AWS Secrets"
|
||||
@docPath="/docs/secrets/aws/index.html"
|
||||
>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</WizardSection>
|
|
@ -0,0 +1,10 @@
|
|||
<WizardSection
|
||||
@headerText="AWS"
|
||||
@headerIcon="enable/aws"
|
||||
@docText="Docs: AWS Authentication"
|
||||
@docPath="/docs/auth/aws.html"
|
||||
>
|
||||
<p>
|
||||
The AWS auth method provides an automated mechanism to retrieve a Vault token for AWS EC2 instances and IAM principals. Unlike most Vault auth methods, this method does not require manual first-deploying, or provisioning security-sensitive credentials (tokens, username/password, client certificates, etc), by operators under many circumstances.
|
||||
</p>
|
||||
</WizardSection>
|
|
@ -0,0 +1,10 @@
|
|||
<WizardSection
|
||||
@headerText="Azure"
|
||||
@headerIcon="enable/azure"
|
||||
@docText="Docs: Azure Authentication"
|
||||
@docPath="/docs/auth/azure.html"
|
||||
>
|
||||
<p>
|
||||
The Azure auth method allows authentication against Vault using Azure Active Directory credentials.
|
||||
</p>
|
||||
</WizardSection>
|
|
@ -0,0 +1,10 @@
|
|||
<WizardSection
|
||||
@headerText="TLS Certificates"
|
||||
@headerIcon="enable/cert"
|
||||
@docText="Docs: TLS Certificates Authentication"
|
||||
@docPath="/docs/auth/cert.html"
|
||||
>
|
||||
<p>
|
||||
The TLS Certificates auth method allows authentication using SSL/TLS client certificates which are either signed by a CA or self-signed. CA certificates are associated with a role.
|
||||
</p>
|
||||
</WizardSection>
|
|
@ -0,0 +1,9 @@
|
|||
<WizardSection
|
||||
@headerText="Cubbyhole"
|
||||
@docText="Docs: Cubbyhole Secrets"
|
||||
@docPath="/docs/secrets/cubbyhole/index.html"
|
||||
>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</WizardSection>
|
|
@ -0,0 +1,10 @@
|
|||
<WizardSection
|
||||
@headerText="Consul"
|
||||
@headerIcon="enable/consul"
|
||||
@docText="Docs: Consul Secrets"
|
||||
@docPath="/docs/secrets/consul/index.html"
|
||||
>
|
||||
<p>
|
||||
The Consul secrets engine generates Consul API tokens dynamically based on Consul ACL policies.
|
||||
</p>
|
||||
</WizardSection>
|
|
@ -0,0 +1,10 @@
|
|||
<WizardSection
|
||||
@headerText="Databases"
|
||||
@headerIcon="enable/database"
|
||||
@docText="Docs: Database Secrets"
|
||||
@docPath="/docs/secrets/databases/index.html"
|
||||
>
|
||||
<p>
|
||||
The database secrets engine generates database credentials dynamically based on configured roles.
|
||||
</p>
|
||||
</WizardSection>
|
|
@ -0,0 +1,41 @@
|
|||
<WizardContent @headerText="Vault Web UI" @glyph="tour">
|
||||
<h2 class="title is-6">
|
||||
Choosing where to go
|
||||
</h2>
|
||||
<p>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</p>
|
||||
{{#if (or (has-feature "Performance Replication") (has-feature "DR Replication")) }}
|
||||
{{/if}}
|
||||
<h3 class="feature-header">Walk me through setting up:</h3>
|
||||
<form id="features-form" class="feature-selection" {{action "saveFeatures" on="submit"}}>
|
||||
{{#each allFeatures as |feature|}}
|
||||
{{#if feature.show}}
|
||||
<div class="feature-box {{if feature.selected 'is-active'}}">
|
||||
<div class="b-checkbox">
|
||||
<input
|
||||
id="feature-{{feature.key}}"
|
||||
type="checkbox"
|
||||
class="styled"
|
||||
checked={{feature.selected}}
|
||||
onchange={{action (mut feature.selected) value="target.checked"}}
|
||||
/>
|
||||
<label for="feature-{{feature.key}}">{{feature.name}}</label>
|
||||
<button type="button" class="button is-ghost icon is-pulled-right" onclick={{action (toggle (concat feature.key "-isOpen") this)}}>
|
||||
<ICon
|
||||
@glyph={{if (get this (concat feature.key "-isOpen")) "chevron-up" "chevron-down"}}
|
||||
@class="has-text-grey auto-width is-paddingless is-flex-column"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{{#if (get this (concat feature.key "-isOpen"))}}
|
||||
<ul class="feature-steps">
|
||||
{{#each feature.steps as |step|}}
|
||||
<li>{{step}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
<button type="submit" class="button is-primary">Start</button>
|
||||
</form>
|
||||
</WizardContent>
|
|
@ -0,0 +1,10 @@
|
|||
<WizardSection
|
||||
@headerText="Google Cloud"
|
||||
@headerIcon="enable/gcp"
|
||||
@docText="Docs: Google Cloud Secrets"
|
||||
@docPath="/docs/secrets/gcp/index.html"
|
||||
>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</WizardSection>
|
|
@ -0,0 +1,10 @@
|
|||
<WizardSection
|
||||
@headerText="Google Cloud"
|
||||
@headerIcon="enable/gcp"
|
||||
@docText="Docs: Google Cloud Authentication"
|
||||
@docPath="/docs/auth/gcp.html"
|
||||
>
|
||||
<p>
|
||||
The GCP auth method allows authentication against Vault using Google credentials.
|
||||
</p>
|
||||
</WizardSection>
|
|
@ -0,0 +1,10 @@
|
|||
<WizardSection
|
||||
@headerText="GitHub"
|
||||
@headerIcon="enable/github"
|
||||
@docText="Docs: GitHub Authentication"
|
||||
@docPath="/docs/auth/github.html"
|
||||
>
|
||||
<p>
|
||||
The Github auth method can be used to authenticate with Vault using a GitHub personal access token.
|
||||
</p>
|
||||
</WizardSection>
|
|
@ -0,0 +1,15 @@
|
|||
<WizardContent @headerText="Authentication" @glyph="tour">
|
||||
<WizardSection
|
||||
@headerText="Authenticate to Vault"
|
||||
@docText="Learn: Initialization"
|
||||
@docPath="/docs/concepts/tokens.html"
|
||||
>
|
||||
<p>
|
||||
Vault is unsealed, but we still need to authenticate using the Initial
|
||||
Root Token that was generated. We recommend setting up an Authentication
|
||||
Method such as Username & Password for regular use, and only using a root
|
||||
token for initial setup or for emergencies.
|
||||
</p>
|
||||
</WizardSection>
|
||||
</WizardContent>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<WizardContent @headerText="Initialization" @glyph="tour">
|
||||
<WizardSection
|
||||
@headerText="Saving your keys"
|
||||
@docText="Learn: Initialization"
|
||||
@docPath="/intro/getting-started/deploy.html#initializing-the-vault">
|
||||
<p>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!</p>
|
||||
</WizardSection>
|
||||
</WizardContent>
|
|
@ -0,0 +1,18 @@
|
|||
<WizardContent @headerText="Initialization" @glyph="tour">
|
||||
<WizardSection
|
||||
@headerText="Setting up your master keys"
|
||||
@docText="Learn: Initialization"
|
||||
@docPath="/intro/getting-started/deploy.html#initializing-the-vault"
|
||||
>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</WizardSection>
|
||||
</WizardContent>
|
|
@ -0,0 +1,21 @@
|
|||
<WizardContent @headerText="Initialization" @glyph="tour">
|
||||
<WizardSection
|
||||
@headerText="Unsealing your vault"
|
||||
@docText="Learn: Initialization"
|
||||
@docPath="/intro/getting-started/deploy.html#initializing-the-vault"
|
||||
>
|
||||
<p>
|
||||
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 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 (dec componentState.progress componentState.threshold) 'more key'}} to unseal.
|
||||
</p>
|
||||
</WizardSection>
|
||||
</WizardContent>
|
|
@ -0,0 +1,10 @@
|
|||
<WizardSection
|
||||
@headerText="Kubernetes"
|
||||
@headerIcon="enable/kubernetes"
|
||||
@docText="Docs: Kubernetes Authentication"
|
||||
@docPath="/docs/auth/kubernetes.html"
|
||||
>
|
||||
<p>
|
||||
The Kubernetes auth method can be used to authenticate with Vault using a Kubernetes Service Account Token. This method of authentication makes it easy to introduce a Vault token into a Kubernetes Pod.
|
||||
</p>
|
||||
</WizardSection>
|
|
@ -0,0 +1,10 @@
|
|||
<WizardSection
|
||||
@headerText="Key/Value"
|
||||
@headerIcon="enable/kv"
|
||||
@docText="Docs: Key/Value Secrets"
|
||||
@docPath="/docs/secrets/kv/index.html"
|
||||
>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</WizardSection>
|
|
@ -0,0 +1,10 @@
|
|||
<WizardSection
|
||||
@headerText="LDAP"
|
||||
@headerIcon="enable/ldap"
|
||||
@docText="Docs: LDAP Authentication"
|
||||
@docPath="/docs/auth/ldap.html"
|
||||
>
|
||||
<p>
|
||||
The LDAP auth method allows authentication using an existing LDAP server and user/password credentials. This allows Vault to be integrated into environments using LDAP without duplicating the user/pass configuration in multiple places.
|
||||
</p>
|
||||
</WizardSection>
|
|
@ -0,0 +1,23 @@
|
|||
<WizardContent @headerText={{capitalize currentMachine}} @glyph="tour">
|
||||
{{component
|
||||
stepComponent
|
||||
mountSubtype=mountSubtype
|
||||
mountName=mountName
|
||||
actionText=actionText
|
||||
nextFeature=nextFeature
|
||||
nextStep=nextStep
|
||||
needsEncryption=needsEncryption
|
||||
isSupported=isSupported
|
||||
onDone=onDone
|
||||
onAdvance=onAdvance
|
||||
onRepeat=onRepeat
|
||||
onReset=onReset
|
||||
class="wizard-step"
|
||||
}}
|
||||
{{component
|
||||
detailsComponent
|
||||
onAdvance=onAdvance
|
||||
onRepeat=onRepeat
|
||||
class="wizard-details"
|
||||
}}
|
||||
</WizardContent>
|
|
@ -0,0 +1,10 @@
|
|||
<WizardSection
|
||||
@headerText="Nomad"
|
||||
@headerIcon="enable/nomad"
|
||||
@docText="Docs: Nomad Secrets"
|
||||
@docPath="/docs/secrets/nomad/index.html"
|
||||
>
|
||||
<p>
|
||||
The Nomad secret backend for Vault generates Nomad API tokens dynamically based on pre-existing Nomad ACL policies.
|
||||
</p>
|
||||
</WizardSection>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue