Add storybook (#6496)

* add storybook

* add storybook files

* add ToggleButton and AlertBanner stories

* add knobs addon

* add notes addon

* add ToggleButton and AlertsBanner notes

* move panel to right

* add ICon

* create story blueprint

* add header to blueprint

* upgrade to storybook 5.0.1

* add confirm-action stories

* move addon panel to bottom

* update ConfirmAction

* add jsdoc comments to alert banner component

* add AlertInline

* set showPanel to true in blueprint

* include newly generated markdown for stories

* adjust code example for toggle button

* add json-to-markdown to package.json

* update AuthForm

* add Storybook readme

* add AlertPopup

* add story markdown custom template

* make storybook dependencies optional

* center all stories

* use message-types helper to dynamically render alerts

* hide panel

* nest alert stories

* move icons into table

* separate homelink into multiple stories

* add homelink with nav example

* remove see links from alert-banner

* add script to autogenerate markdown from component and add it to stories

* add viewport addon and remove centered addon

* update README to include markdown generation

* remove @see links from jsdoc comments

* update README to include jsdoc example

* update alert banner md

* get rid of trailing ######

* update jsdoc and regenerate notes files

* update i-con md

* Update ui/scripts/gen-story-md.js

Co-Authored-By: noelledaley <noelledaley@users.noreply.github.com>

* Update ui/scripts/gen-story-md.js

Co-Authored-By: noelledaley <noelledaley@users.noreply.github.com>

* add storybook docs to vault ui readme

* add jsdoc comments to component blueprint, automatically import md file in story blueprint

* add template template to component blueprint override

* apply basic theme to storybook

* remove comment

* make sure all stories are using auto generated md

* storybook: show optional props in brackets

* storybook: 🔪 HomeLink

* storybook: show AuthConfigForm stories with knobs
This commit is contained in:
Noelle Daley 2019-04-03 14:06:20 -07:00 committed by GitHub
parent bec4846953
commit eed91ba84d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 5663 additions and 93 deletions

1
ui/.env Normal file
View File

@ -0,0 +1 @@
STORYBOOK_NAME=vault

5
ui/.storybook/addons.js Normal file
View File

@ -0,0 +1,5 @@
import '@storybook/addon-actions/register';
import '@storybook/addon-links/register';
import '@storybook/addon-knobs/register';
import '@storybook/addon-notes/register';
import '@storybook/addon-viewport/register';

44
ui/.storybook/config.js Normal file
View File

@ -0,0 +1,44 @@
import { configure, addParameters, addDecorator } from '@storybook/ember';
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';
import theme from './theme.js';
function loadStories() {
// automatically import all files ending in *.stories.js
const req = require.context('../stories/', true, /.stories.js$/);
req.keys().forEach(filename => req(filename));
}
addParameters({
viewport: { viewports: INITIAL_VIEWPORTS },
options: { theme },
});
addDecorator(storyFn => {
const { template, context } = storyFn();
// This adds styling to the Canvas tab.
const styles = {
style: {
margin: '20px',
},
};
// Create a div to wrap the Canvas tab with the applied styles.
const element = document.createElement('div');
Object.assign(element.style, styles.style);
const innerElement = document.createElement('div');
element.appendChild(innerElement);
innerElement.appendTo = function appendTo(el) {
el.appendChild(element);
};
return {
template,
context,
element: innerElement,
};
});
configure(loadStories, module);

View File

@ -0,0 +1,22 @@
<meta name="vault/config/environment" content="%7B%22modulePrefix%22%3A%22vault%22%2C%22environment%22%3A%22development%22%2C%22rootURL%22%3A%22/ui/%22%2C%22locationType%22%3A%22auto%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%7D%7D%2C%22APP%22%3A%7B%22POLLING_URLS%22%3A%5B%22sys/health%22%2C%22sys/replication/status%22%2C%22sys/seal-status%22%5D%2C%22NAMESPACE_ROOT_URLS%22%3A%5B%22sys/health%22%2C%22sys/seal-status%22%2C%22sys/license/features%22%5D%2C%22DEFAULT_PAGE_SIZE%22%3A15%2C%22LOG_TRANSITIONS%22%3Atrue%7D%2C%22flashMessageDefaults%22%3A%7B%22timeout%22%3A7000%2C%22sticky%22%3Afalse%2C%22preventDuplicates%22%3Atrue%7D%2C%22contentSecurityPolicyHeader%22%3A%22Content-Security-Policy%22%2C%22contentSecurityPolicyMeta%22%3Atrue%2C%22contentSecurityPolicy%22%3A%7B%22connect-src%22%3A%5B%22%27self%27%22%5D%2C%22img-src%22%3A%5B%22%27self%27%22%2C%22data%3A%22%5D%2C%22form-action%22%3A%5B%22%27none%27%22%5D%2C%22script-src%22%3A%5B%22%27self%27%22%5D%2C%22style-src%22%3A%5B%22%27unsafe-inline%27%22%2C%22%27self%27%22%5D%2C%22default-src%22%3A%5B%22%27none%27%22%5D%2C%22font-src%22%3A%5B%22%27self%27%22%5D%2C%22media-src%22%3A%5B%22%27self%27%22%5D%7D%2C%22emberData%22%3A%7B%22enableRecordDataRFCBuild%22%3Afalse%7D%2C%22exportApplicationGlobal%22%3Atrue%7D" />
<link rel="stylesheet" href="/assets/vendor.css" />
<link rel="stylesheet" href="/assets/vault.css" />
<link rel="icon" href="/favicon.png" />
<script>
(function() {
var srcUrl = null;
var host = location.hostname || 'localhost';
var defaultPort = location.protocol === 'https:' ? 443 : 80;
var port = 4200;
var path = '';
var prefixURL = '';
var src = srcUrl || prefixURL + '/_lr/livereload.js?port=' + port + '&host=' + host + path;
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = location.protocol + '//' + host + ':4200' + src;
document.getElementsByTagName('head')[0].appendChild(script);
}());
</script>
<script>runningTests = true;</script>
<script src="/assets/vendor.js"></script>
<script src="/assets/vault.js"></script>

32
ui/.storybook/theme.js Normal file
View File

@ -0,0 +1,32 @@
import { create } from '@storybook/theming';
// Fonts and colors are pulled from _colors.scss and _bulma_variables.scss.
const uiGray300 = '#BAC1CC';
const uiGray900 = '#1f2124';
const blue500 = '#1563ff';
export default create({
base: 'light',
colorPrimary: uiGray900,
colorSecondary: blue500,
// UI
appBorderColor: uiGray300,
// Typography
fontBase: 'system-ui, -apple-system, BlinkMacSystemFont, sans-serif',
fontCode: '"SFMono-Regular", Consolas, monospace',
// Text colors
textColor: uiGray900,
// Toolbar default and active colors
barTextColor: uiGray300,
barSelectedColor: 'white',
barBg: uiGray900,
brandTitle: 'Vault UI Storybook',
brandUrl: 'https://www.vaultproject.io/',
});

View File

@ -1,7 +1,24 @@
# vault <!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**
- [Vault UI](#vault-ui)
- [Prerequisites](#prerequisites)
- [Running / Development](#running--development)
- [Code Generators](#code-generators)
- [Running Tests](#running-tests)
- [Linting](#linting)
- [Building Vault UI into a Vault Binary](#building-vault-ui-into-a-vault-binary)
- [Vault Storybook](#vault-storybook)
- [Storybook Commands at a Glance](#storybook-commands-at-a-glance)
- [Writing Stories](#writing-stories)
- [Adding a new story](#adding-a-new-story)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Vault UI
This README outlines the details of collaborating on this Ember application. This README outlines the details of collaborating on this Ember application.
A short introduction of this app could easily go here.
## Prerequisites ## Prerequisites
@ -24,7 +41,7 @@ running `yarn --force`.
To get all of the JavaScript dependencies installed, run this in the `ui` directory: To get all of the JavaScript dependencies installed, run this in the `ui` directory:
`yarn` - `yarn`
If you want to run Vault UI and proxy back to a Vault server running If you want to run Vault UI and proxy back to a Vault server running
on the default port, 8200, run the following in the `ui` directory: on the default port, 8200, run the following in the `ui` directory:
@ -74,6 +91,62 @@ This will result in a Vault binary that has the UI built-in - though in
a non-dev setup it will still need to be enabled via the `ui` config or a non-dev setup it will still need to be enabled via the `ui` config or
setting `VAULT_UI` environment variable. setting `VAULT_UI` environment variable.
## Vault Storybook
The Vault UI uses Storybook to catalog all of its components. Below are details for running and contributing to Storybook.
### Storybook Commands at a Glance
| Command | Description |
| ------------------------------------------ | ------------------------- |
| `yarn storybook` | run storybook |
| `ember generate story [name-of-component]` | generate a new story |
| `yarn gen-story-md [name-of-component]` | update a story notes file |
### Writing Stories
Each component in `vault/ui/app/components` should have a corresponding `[component-name].stories.js` and `[component-name].md` files within `vault/ui/stories`.
#### Adding a new story
1. Make sure the component is well-documented using [jsdoc](http://usejsdoc.org/tags-exports.html). This documentation should at minimum include the module name, an example of usage, and the params passed into the handlebars template. For example, here is how we document the ToggleButton Component:
````js
/**
* @module ToggleButton
* `ToggleButton` components are used to expand and collapse content with a toggle.
*
* @example
* ```js
* <ToggleButton @openLabel="Encrypt Output with PGP" @closedLabel="Encrypt Output with PGP" @toggleTarget={{this}} @toggleAttr="showOptions"/>
* {{#if showOptions}}
* <div>
* <p>
* I will be toggled!
* </p>
* </div>
* {{/if}}
* ```
*
* @param toggleAttr=null {String} - The attribute upon which to toggle.
* @param attrTarget=null {Object} - The target upon which the event handler should be added.
* @param [openLabel=Hide options] {String} - The message to display when the toggle is open. //optional params are denoted by square brackets
* @param [closedLabel=More options] {String} - The message to display when the toggle is closed.
*/
````
Note that placing a param inside brackets (e.g. `[closedLabel=More options]` indicates it is optional and has a default value of `'More options'`.)
2. Generate a new story with `ember generate story [name-of-component]`
3. Inside the newly generated `stories` file, add at least one example of the component. If the component should be interactive, enable the [Storybook Knobs addon](https://github.com/storybooks/storybook/tree/master/addons/knobs).
4. Generate the `notes` file for the component with `yarn gen-story-md [name-of-component]` (e.g. `yarn gen-md alert-banner`). This will generate markdown documentation of the component and place it at `vault/ui/stories/[name-of-component].md`. If your component is a template-only component, you will need to manually create the markdown file.
See the [Storybook Docs](https://storybook.js.org/docs/basics/introduction/) for more information on writing stories.
### Code Generators
It is important to add all new components into Storybook and to keep the story and notes files up to date. To ease the process of creating and updating stories please use the code generators using the [commands listed above](#storybook-commands-at-a-glance).
## Further Reading / Useful Links ## Further Reading / Useful Links
- [ember.js](http://emberjs.com/) - [ember.js](http://emberjs.com/)
@ -81,3 +154,6 @@ setting `VAULT_UI` environment variable.
- Development Browser Extensions - Development Browser Extensions
- [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) - [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi)
- [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) - [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/)
- [Storybook for Ember Live Example](https://storybooks-ember.netlify.com/?path=/story/addon-centered--button)
- [Storybook Addons](https://github.com/storybooks/storybook/tree/master/addons/)
- [Storybook Docs](https://storybook.js.org/docs/basics/introduction/)

View File

@ -1,13 +1,25 @@
import Component from '@ember/component'; import Component from '@ember/component';
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import { messageTypes } from 'vault/helpers/message-types'; import { messageTypes } from 'vault/helpers/message-types';
/**
* @module AlertBanner
* `AlertBanner` components are used to inform users of important messages.
*
* @example
* ```js
* <AlertBanner @type="danger" @message="{{model.keyId}} is not a valid lease ID"/>
* ```
*
* @param type=null {String} - The banner type. This comes from the message-types helper.
* @param [message=null {String}] - The message to display within the banner.
*
*/
export default Component.extend({ export default Component.extend({
type: null, type: null,
message: null,
yieldWithoutColumn: false, yieldWithoutColumn: false,
classNameBindings: ['containerClass'], classNameBindings: ['containerClass'],
containerClass: computed('type', function() { containerClass: computed('type', function() {

View File

@ -1,8 +1,21 @@
import Component from '@ember/component'; import Component from '@ember/component';
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import { messageTypes } from 'vault/helpers/message-types'; import { messageTypes } from 'vault/helpers/message-types';
/**
* @module AlertInline
* `AlertInline` components are used to inform users of important messages.
*
* @example
* ```js
* <AlertInline @type="danger" @message="{{model.keyId}} is not a valid lease ID"/>
* ```
*
* @param type=null{String} - The alert type. This comes from the message-types helper.
* @param [message=null]{String} - The message to display within the alert.
*
*/
export default Component.extend({ export default Component.extend({
type: null, type: null,
message: null, message: null,

View File

@ -1,5 +1,19 @@
import OuterHTML from './outer-html'; import OuterHTML from './outer-html';
/**
* @module AlertPopup
* The `AlertPopup` is an implementation of the [ember-cli-flash](https://github.com/poteto/ember-cli-flash) `flashMessage`.
*
* @example ```js
* // All properties are passed in from the flashMessage service.
* <AlertPopup @type={{message-types flash.type}} @message={{flash.message}} @close={{close}}/>```
*
* @param type=null {String} - The alert type. This comes from the message-types helper.
* @param [message=null] {String} - The alert message.
* @param close=null {Func} - The close action which will close the alert.
*
*/
export default OuterHTML.extend({ export default OuterHTML.extend({
type: null, type: null,
message: null, message: null,

View File

@ -3,6 +3,19 @@ import Component from '@ember/component';
import { task } from 'ember-concurrency'; import { task } from 'ember-concurrency';
import DS from 'ember-data'; import DS from 'ember-data';
/**
* @module AuthConfigForm/Config
* The `AuthConfigForm/Config` is the base form to configure auth methods.
*
* @example
* ```js
* {{auth-config-form/config model.model}}
* ```
*
* @property model=null {String} - The corresponding auth model that is being configured.
*
*/
const AuthConfigBase = Component.extend({ const AuthConfigBase = Component.extend({
tagName: '', tagName: '',
model: null, model: null,

View File

@ -3,6 +3,19 @@ import { inject as service } from '@ember/service';
import { task } from 'ember-concurrency'; import { task } from 'ember-concurrency';
import DS from 'ember-data'; import DS from 'ember-data';
/**
* @module AuthConfigForm/Options
* The `AuthConfigForm/Options` is options portion of the auth config form.
*
* @example
* ```js
* {{auth-config-form/options model.model}}
* ```
*
* @property model=null {String} - The corresponding auth model that is being configured.
*
*/
export default AuthConfigComponent.extend({ export default AuthConfigComponent.extend({
router: service(), router: service(),
wizard: service(), wizard: service(),

View File

@ -9,6 +9,21 @@ import { supportedAuthBackends } from 'vault/helpers/supported-auth-backends';
import { task } from 'ember-concurrency'; import { task } from 'ember-concurrency';
const BACKENDS = supportedAuthBackends(); const BACKENDS = supportedAuthBackends();
/**
* @module AuthForm
* The `AuthForm` is used to sign users into Vault.
*
* @example ```js
* // All properties are passed in via query params.
* <AuthForm @wrappedToken={{wrappedToken}} @cluster={{model}} @namespace={{namespaceQueryParam}} @redirectTo={{redirectTo}} @selectedAuth={{authMethod}}/>```
*
* @param wrappedToken=null {String} - The auth method that is currently selected in the dropdown.
* @param cluster=null {Object} - The auth method that is currently selected in the dropdown. This corresponds to an Ember Model.
* @param namespace=null {String} - The currently active namespace.
* @param redirectTo=null {String} - The name of the route to redirect to.
* @param selectedAuth=null {String} - The auth method that is currently selected in the dropdown.
*/
const DEFAULTS = { const DEFAULTS = {
token: null, token: null,
username: null, username: null,

View File

@ -1,6 +1,27 @@
import Component from '@ember/component'; import Component from '@ember/component';
import hbs from 'htmlbars-inline-precompile'; import hbs from 'htmlbars-inline-precompile';
/**
* @module ConfirmAction
* `ConfirmAction` is a button followed by a confirmation message and button used to prevent users from performing actions they do not intend to.
*
* @example
* ```js
* <ConfirmAction
* @onConfirmAction={{ () => { console.log('Action!') } }}
* @confirmMessage="Are you sure you want to delete this config?">
* Delete
* </ConfirmAction>
* ```
*
* @property {Func} onConfirmAction=null - The action to take upon confirming.
* @property {String} [confirmMessage=Are you sure you want to do this?] - The message to display upon confirming.
* @property {String} [confirmButtonText=Delete] - The confirm button text.
* @property {String} [cancelButtonText=Cancel] - The cancel button text.
* @property {String} [disabledMessage=Complete the form to complete this action] - The message to display when the button is disabled.
*
*/
export default Component.extend({ export default Component.extend({
tagName: 'span', tagName: 'span',
classNames: ['confirm-action'], classNames: ['confirm-action'],

View File

@ -1,6 +1,21 @@
import Component from '@ember/component'; import Component from '@ember/component';
import { computed } from '@ember/object'; import { computed } from '@ember/object';
/**
* @module HomeLink
* `HomeLink` is a span that contains either the text `home` or the `LogoEdition` component.
*
* @example
* ```js
* <HomeLink @class="navbar-item splash-page-logo">
* <LogoEdition />
* </HomeLink>
* ```
*
* @see {@link https://github.com/hashicorp/vault/search?l=Handlebars&q=HomeLink|Uses of HomeLink}
* @see {@link https://github.com/hashicorp/vault/blob/master/ui/app/components/home-link.js|HomeLink Source Code}
*/
export default Component.extend({ export default Component.extend({
tagName: '', tagName: '',

View File

@ -3,7 +3,19 @@ import Component from '@ember/component';
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import hbs from 'htmlbars-inline-precompile'; import hbs from 'htmlbars-inline-precompile';
const GLYPHS_WITH_SVG_TAG = [ /**
* @module ICon
* `ICon` components are glyphs used to indicate important information.
*
* @example
* ```js
* <ICon @glyph="cancel-square-outline" />
* ```
* @param glyph=null {String} - The glyph type.
*
*/
export const GLYPHS_WITH_SVG_TAG = [
'cancel-square-outline', 'cancel-square-outline',
'cancel-square-fill', 'cancel-square-fill',
'check-circle-fill', 'check-circle-fill',

View File

@ -1,6 +1,26 @@
import Component from '@ember/component'; import Component from '@ember/component';
import { set, get, defineProperty, computed } from '@ember/object'; import { set, get, defineProperty, computed } from '@ember/object';
/**
* @module ToggleButton
* `ToggleButton` components are used to expand and collapse content with a toggle.
*
* @example
* ```js
* <ToggleButton @openLabel="Encrypt Output with PGP" @closedLabel="Encrypt Output with PGP" @toggleTarget={{this}} @toggleAttr="showOptions"/>
* {{#if showOptions}}
* <div>
* <p>
* I will be toggled!
* </p>
* </div>
* {{/if}}
* ```
*
* @param toggleAttr=null {String} - The attribute upon which to toggle.
* @param openLabel=Hide options {String} - The message to display when the toggle is open.
* @param closedLabel=More options {String} - The message to display when the toggle is closed.
*/
export default Component.extend({ export default Component.extend({
tagName: 'button', tagName: 'button',
type: 'button', type: 'button',

View File

@ -1,6 +1,6 @@
import { helper as buildHelper } from '@ember/component/helper'; import { helper as buildHelper } from '@ember/component/helper';
const MESSAGE_TYPES = { export const MESSAGE_TYPES = {
info: { info: {
class: 'is-info', class: 'is-info',
glyphClass: 'has-text-info', glyphClass: 'has-text-info',

View File

@ -0,0 +1,16 @@
/**
* @module <%= classifiedModuleName %>
* <%= classifiedModuleName %> components are used to...
*
* @example
* ```js
* <<%= classifiedModuleName %> @param1={param1} @param2={param2} />
* ```
*
* @param param1 {String} - param1 is...
* @param [param2=value] {String} - param2 is... //brackets mean it is optional and = sets the default value
*/
import Component from '@ember/component';
<%= importTemplate %>
export default Component.extend({<%= contents %>
});

View File

@ -0,0 +1,83 @@
'use strict';
const path = require('path');
const stringUtil = require('ember-cli-string-utils');
const pathUtil = require('ember-cli-path-utils');
const validComponentName = require('ember-cli-valid-component-name');
const getPathOption = require('ember-cli-get-component-path-option');
const normalizeEntityName = require('ember-cli-normalize-entity-name');
module.exports = {
description: 'Generates a component. Name must contain a hyphen.',
availableOptions: [
{
name: 'path',
type: String,
default: 'components',
aliases: [{ 'no-path': '' }],
},
],
filesPath: function() {
let filesDirectory = 'files';
return path.join(this.path, filesDirectory);
},
fileMapTokens: function() {
return {
__path__: function(options) {
if (options.pod) {
return path.join(options.podPath, options.locals.path, options.dasherizedModuleName);
} else {
return 'components';
}
},
__templatepath__: function(options) {
if (options.pod) {
return path.join(options.podPath, options.locals.path, options.dasherizedModuleName);
}
return 'templates/components';
},
__templatename__: function(options) {
if (options.pod) {
return 'template';
}
return options.dasherizedModuleName;
},
};
},
normalizeEntityName: function(entityName) {
entityName = normalizeEntityName(entityName);
return validComponentName(entityName);
},
locals: function(options) {
let templatePath = '';
let importTemplate = '';
let contents = '';
// if we're in an addon, build import statement
if (options.project.isEmberCLIAddon() || (options.inRepoAddon && !options.inDummy)) {
if (options.pod) {
templatePath = './template';
} else {
templatePath =
pathUtil.getRelativeParentPath(options.entity.name) +
'templates/components/' +
stringUtil.dasherize(options.entity.name);
}
importTemplate = "import layout from '" + templatePath + "';\n";
contents = '\n layout';
}
return {
importTemplate: importTemplate,
contents: contents,
path: getPathOption(options),
};
},
};

View File

@ -0,0 +1,16 @@
/* eslint-disable import/extensions */
import hbs from 'htmlbars-inline-precompile';
import { storiesOf } from '@storybook/ember';
<%= importMD %>
storiesOf('<%= classifiedModuleName %>/', module)
.addParameters({ options: { showPanel: true } })
.add(`<%= classifiedModuleName %>`, () => ({
template: hbs`
<h5 class="title is-5"><%= header %></h5>
<<%= classifiedModuleName %>/>
`,
context: {},
}),
{notes}
);

View File

@ -0,0 +1,34 @@
'use strict';
const getPathOption = require('ember-cli-get-component-path-option');
const stringUtil = require('ember-cli-string-utils');
module.exports = {
description: 'generates a story for storybook',
fileMapTokens: function() {
return {
__markdownname__: function(options) {
return options.dasherizedModuleName;
},
__name__: function(options) {
return options.dasherizedModuleName;
},
};
},
locals: function(options) {
let contents = '';
let importMD = "import notes from './" + stringUtil.dasherize(options.entity.name) + "';\n";
return {
importMD: importMD,
contents: contents,
path: getPathOption(options),
header: stringUtil
.dasherize(options.entity.name)
.split('-')
.map(word => stringUtil.capitalize(word))
.join(' '),
};
},
};

28
ui/lib/story-md.hbs Normal file
View File

@ -0,0 +1,28 @@
{{#if (showMainIndex)~}}
{{>module-index~}}
{{>global-index~}}
{{/if~}}
{{#orphans ~}}
{{>heading-indent}}{{>sig-name}}
{{>description~}}
{{>params~}}
{{>properties~}}
{{#examples}}
**Example**
{{#if caption}} *({{caption}})* {{else}} {{/if}}
{{{inlineLinks example}}}
{{/examples}}
{{>member-index~}}
{{>separator~}}
{{>members~}}
{{/orphans~}}
---

View File

@ -19,7 +19,10 @@
"test-oss": "yarn run test -f='!enterprise'", "test-oss": "yarn run test -f='!enterprise'",
"fmt-js": "prettier-eslint --single-quote --no-use-tabs --trailing-comma es5 --print-width=110 --write '{app,tests,config,lib}/**/*.js'", "fmt-js": "prettier-eslint --single-quote --no-use-tabs --trailing-comma es5 --print-width=110 --write '{app,tests,config,lib}/**/*.js'",
"fmt-styles": "prettier --write app/styles/**/*.*", "fmt-styles": "prettier --write app/styles/**/*.*",
"fmt": "yarn run fmt-js && yarn run fmt-styles" "fmt": "yarn run fmt-js && yarn run fmt-styles",
"build-storybook": "build-storybook -s ../pkg/web_ui",
"storybook": "start-storybook -p 6006 -s ../pkg/web_ui",
"gen-story-md": "node scripts/gen-story-md.js"
}, },
"lint-staged": { "lint-staged": {
"linters": { "linters": {
@ -49,6 +52,7 @@
"cool-checkboxes-for-bulma.io": "^1.1.0", "cool-checkboxes-for-bulma.io": "^1.1.0",
"date-fns": "^1.29.0", "date-fns": "^1.29.0",
"deepmerge": "^2.1.1", "deepmerge": "^2.1.1",
"doctoc": "^1.4.0",
"ember-ajax": "^3.1.0", "ember-ajax": "^3.1.0",
"ember-api-actions": "^0.1.8", "ember-api-actions": "^0.1.8",
"ember-auto-import": "^1.2.3", "ember-auto-import": "^1.2.3",
@ -117,7 +121,18 @@
"yargs-parser": "^10.0.0" "yargs-parser": "^10.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@babel/core": "^7.3.4",
"@storybook/addon-actions": "^5.0.5",
"@storybook/addon-knobs": "^5.0.5",
"@storybook/addon-links": "^5.0.5",
"@storybook/addon-notes": "^5.0.5",
"@storybook/addon-viewport": "^5.0.5",
"@storybook/addons": "^5.0.5",
"@storybook/ember": "^5.0.5",
"@storybook/ember-cli-storybook": "meirish/ember-cli-storybook",
"babel-loader": "^8.0.5",
"husky": "^1.1.3", "husky": "^1.1.3",
"jsdoc-to-markdown": "^4.0.1",
"lint-staged": "^8.0.4" "lint-staged": "^8.0.4"
}, },
"engines": { "engines": {
@ -133,5 +148,6 @@
"hooks": { "hooks": {
"pre-commit": "lint-staged" "pre-commit": "lint-staged"
} }
} },
"dependencies": {}
} }

View File

@ -0,0 +1,32 @@
#!/usr/bin/env node
/* eslint-disable */
const fs = require('fs');
const jsdoc2md = require('jsdoc-to-markdown');
var args = process.argv.slice(2);
const name = args[0];
const inputFile = `app/components/${name}.js`;
const outputFile = `stories/${name}.md`;
const component = name
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join('');
const options = {
files: inputFile,
template: fs.readFileSync('./lib/story-md.hbs', 'utf8'),
};
let md = jsdoc2md.renderSync(options);
const pageBreakIndex = md.lastIndexOf('---'); //this is our last page break
const seeLinks = `**See**
- [Uses of ${component}](https://github.com/hashicorp/vault/search?l=Handlebars&q=${component})
- [${component} Source Code](https://github.com/hashicorp/vault/blob/master/ui/app/components/${name}.js)
`;
const generatedWarning = `<!--THIS FILE IS AUTO GENERATED. This file is generated from JSDoc comments in ${inputFile}. To make changes, first edit that file and run "yarn gen-story-md ${name}" to re-generate the content.-->
`;
md = generatedWarning + md.slice(0, pageBreakIndex) + seeLinks + md.slice(pageBreakIndex);
fs.writeFileSync(outputFile, md);

View File

@ -0,0 +1,23 @@
<!--THIS FILE IS AUTO GENERATED. This file is generated from JSDoc comments in app/components/alert-banner.js. To make changes, first edit that file and run "yarn gen-story-md alert-banner" to re-generate the content.-->
## AlertBanner
`AlertBanner` components are used to inform users of important messages.
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| type | <code>String</code> | <code></code> | The banner type. This comes from the message-types helper. |
| [message] | <code>String</code> | <code></code> | The message to display within the banner. |
**Example**
```js
<AlertBanner @type="danger" @message="{{model.keyId}} is not a valid lease ID"/>
```
**See**
- [Uses of AlertBanner](https://github.com/hashicorp/vault/search?l=Handlebars&q=AlertBanner)
- [AlertBanner Source Code](https://github.com/hashicorp/vault/blob/master/ui/app/components/alert-banner.js)
---

View File

@ -0,0 +1,24 @@
/* eslint-disable import/extensions */
import hbs from 'htmlbars-inline-precompile';
import { storiesOf } from '@storybook/ember';
import notes from './alert-banner.md';
import { MESSAGE_TYPES } from '../app/helpers/message-types.js';
storiesOf('Alerts/AlertBanner/', module)
.addParameters({ options: { showPanel: false } })
.add(
'AlertBanner',
() => ({
template: hbs`
{{#each types as |type|}}
<h5 class="title is-5">{{humanize type}}</h5>
<AlertBanner @type={{type}} @message={{message}}/>
{{/each}}
`,
context: {
types: Object.keys(MESSAGE_TYPES),
message: 'Here is a message.',
},
}),
{ notes }
);

View File

@ -0,0 +1,25 @@
<!--THIS FILE IS AUTO GENERATED. This file is generated from JSDoc comments in app/components/alert-inline.js. To make changes, first edit that file and run "yarn gen-story-md alert-inline" to re-generate the content.-->
## AlertInline
`AlertInline` components are used to inform users of important messages.
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| type | <code>String</code> | <code></code> | The alert type. This comes from the message-types helper. |
| [message] | <code>String</code> | <code></code> | The message to display within the alert. |
**Example**
```js
<AlertInline
@type="danger"
@message="{{model.keyId}} is not a valid lease ID"/>
```
**See**
- [Uses of AlertInline](https://github.com/hashicorp/vault/search?l=Handlebars&q=AlertInline)
- [AlertInline Source Code](https://github.com/hashicorp/vault/blob/master/ui/app/components/alert-inline.js)
---

View File

@ -0,0 +1,24 @@
/* eslint-disable import/extensions */
import hbs from 'htmlbars-inline-precompile';
import { storiesOf } from '@storybook/ember';
import notes from './alert-inline.md';
import { MESSAGE_TYPES } from '../app/helpers/message-types.js';
storiesOf('Alerts/AlertInline/', module)
.addParameters({ options: { showPanel: false } })
.add(
'AlertInline',
() => ({
template: hbs`
{{#each types as |type|}}
<h5 class="title is-5">{{humanize type}}</h5>
<AlertInline @type={{type}} @message={{message}}/>
{{/each}}
`,
context: {
types: Object.keys(MESSAGE_TYPES),
message: 'Here is a message.',
},
}),
{ notes }
);

28
ui/stories/alert-popup.md Normal file
View File

@ -0,0 +1,28 @@
<!--THIS FILE IS AUTO GENERATED. This file is generated from JSDoc comments in app/components/alert-popup.js. To make changes, first edit that file and run "yarn gen-story-md alert-popup" to re-generate the content.-->
## AlertPopup
The `AlertPopup` is an implementation of the [ember-cli-flash](https://github.com/poteto/ember-cli-flash) `flashMessage`.
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| type | <code>String</code> | <code></code> | The alert type. This comes from the message-types helper. |
| [message] | <code>String</code> | <code></code> | The alert message. |
| close | <code>Func</code> | <code></code> | The close action which will close the alert. |
**Example**
```js
// All properties are passed in from the flashMessage service.
<AlertPopup
@type={{message-types flash.type}}
@message={{flash.message}}
@close={{close}}/>
```
**See**
- [Uses of AlertPopup](https://github.com/hashicorp/vault/search?l=Handlebars&q=AlertPopup)
- [AlertPopup Source Code](https://github.com/hashicorp/vault/blob/master/ui/app/components/alert-popup.js)
---

View File

@ -0,0 +1,30 @@
/* eslint-disable import/extensions */
import hbs from 'htmlbars-inline-precompile';
import { storiesOf } from '@storybook/ember';
import notes from './alert-popup.md';
import { MESSAGE_TYPES } from '../app/helpers/message-types.js';
storiesOf('Alerts/AlertPopup/', module)
.addParameters({ options: { showPanel: false } })
.add(
`AlertPopup`,
() => ({
template: hbs`
{{#each types as |type|}}
<h5 class="title is-5">{{humanize type}}</h5>
<AlertPopup
@type={{message-types type}}
@message={{message}}
@close={{close}}/>
{{/each}}
`,
context: {
close: () => {
console.log('closing!');
},
types: Object.keys(MESSAGE_TYPES),
message: 'Hello!',
},
}),
{ notes }
);

View File

@ -0,0 +1,23 @@
<!--THIS FILE IS AUTO GENERATED. This file is generated from JSDoc comments in app/components/auth-config-form/config.js. To make changes, first edit that file and run "yarn gen-story-md auth-config-form/config" to re-generate the content.-->
## AuthConfigForm/Config
The `AuthConfigForm/Config` is the base form to configure auth methods.
**Properties**
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| model | <code>String</code> | <code></code> | The corresponding auth model that is being configured. |
**Example**
```js
{{auth-config-form/config model.model}}
```
**See**
- [Uses of AuthConfigForm/config](https://github.com/hashicorp/vault/search?l=Handlebars&q=AuthConfigForm/config)
- [AuthConfigForm/config Source Code](https://github.com/hashicorp/vault/blob/master/ui/app/components/auth-config-form/config.js)
---

View File

@ -0,0 +1,48 @@
/* eslint-disable import/extensions */
import hbs from 'htmlbars-inline-precompile';
import { storiesOf } from '@storybook/ember';
import { withKnobs, select } from '@storybook/addon-knobs';
import notes from './config.md';
// This will need to be replaced with a fake model, since the form fields associated with
// each model come from OpenApi and Storybook doesn't have a Vault server to call OpenApi from.
const MODELS = {
Approle: 'approle',
AWS: 'aws/client',
Azure: 'azure',
Cert: 'cert',
GCP: 'gcp',
Github: 'github',
JWT: 'jwt',
Kubernetes: 'kubernetes',
LDAP: 'ldap',
OKTA: 'okta',
Radius: 'radius',
Userpass: 'userpass',
};
const DEFAULT_VALUE = 'aws/client';
storiesOf('AuthConfigForm/Config/', module)
.addParameters({ options: { showPanel: true } })
.addDecorator(withKnobs())
.add(
`Config`,
() => ({
template: hbs`
<h5 class="title is-5">Config</h5>
{{auth-config-form/config (compute (action 'getModel' model))}}
`,
context: {
actions: {
getModel(modelType) {
return Ember.getOwner(this)
.lookup('service:store')
.createRecord(`auth-config/${modelType}`);
},
},
model: select('model', MODELS, DEFAULT_VALUE),
},
}),
{ notes }
);

View File

@ -0,0 +1,23 @@
<!--THIS FILE IS AUTO GENERATED. This file is generated from JSDoc comments in app/components/auth-config-form/options.js. To make changes, first edit that file and run "yarn gen-story-md auth-config-form/options" to re-generate the content.-->
## AuthConfigForm/Options
The `AuthConfigForm/Options` is options portion of the auth config form.
**Properties**
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| model | <code>String</code> | <code></code> | The corresponding auth model that is being configured. |
**Example**
```js
{{auth-config-form/options model.model}}
```
**See**
- [Uses of AuthConfigForm/options](https://github.com/hashicorp/vault/search?l=Handlebars&q=AuthConfigForm/options)
- [AuthConfigForm/options Source Code](https://github.com/hashicorp/vault/blob/master/ui/app/components/auth-config-form/options.js)
---

View File

@ -0,0 +1,48 @@
/* eslint-disable import/extensions */
import hbs from 'htmlbars-inline-precompile';
import { storiesOf } from '@storybook/ember';
import { withKnobs, select } from '@storybook/addon-knobs';
import notes from './options.md';
// This will need to be replaced with a fake model, since the form fields associated with
// each model come from OpenApi and Storybook doesn't have a Vault server to call OpenApi from.
const MODELS = {
Approle: 'approle',
AWS: 'aws/client',
Azure: 'azure',
Cert: 'cert',
GCP: 'gcp',
Github: 'github',
JWT: 'jwt',
Kubernetes: 'kubernetes',
LDAP: 'ldap',
OKTA: 'okta',
Radius: 'radius',
Userpass: 'userpass',
};
const DEFAULT_VALUE = 'aws/client';
storiesOf('AuthConfigForm/Options/', module)
.addParameters({ options: { showPanel: true } })
.addDecorator(withKnobs())
.add(
`Options`,
() => ({
template: hbs`
<h5 class="title is-5">Options</h5>
{{auth-config-form/options (compute (action 'getModel' model))}}
`,
context: {
actions: {
getModel(modelType) {
return Ember.getOwner(this)
.lookup('service:store')
.createRecord(`auth-config/${modelType}`);
},
},
model: select('model', MODELS, DEFAULT_VALUE),
},
}),
{ notes }
);

31
ui/stories/auth-form.md Normal file
View File

@ -0,0 +1,31 @@
<!--THIS FILE IS AUTO GENERATED. This file is generated from JSDoc comments in app/components/auth-form.js. To make changes, first edit that file and run "yarn gen-story-md auth-form" to re-generate the content.-->
## AuthForm
The `AuthForm` is used to sign users into Vault.
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| wrappedToken | <code>String</code> | <code></code> | The auth method that is currently selected in the dropdown. |
| cluster | <code>Object</code> | <code></code> | The auth method that is currently selected in the dropdown. This corresponds to an Ember Model. |
| namespace | <code>String</code> | <code></code> | The currently active namespace. |
| redirectTo | <code>String</code> | <code></code> | The name of the route to redirect to. |
| selectedAuth | <code>String</code> | <code></code> | The auth method that is currently selected in the dropdown. |
**Example**
```js
// All properties are passed in via query params.
<AuthForm
@wrappedToken={{wrappedToken}}
@cluster={{model}}
@namespace={{namespaceQueryParam}}
@redirectTo={{redirectTo}}
@selectedAuth={{authMethod}}/>```
**See**
- [Uses of AuthForm](https://github.com/hashicorp/vault/search?l=Handlebars&q=AuthForm)
- [AuthForm Source Code](https://github.com/hashicorp/vault/blob/master/ui/app/components/auth-form.js)
---

View File

@ -0,0 +1,18 @@
/* eslint-disable import/extensions */
import hbs from 'htmlbars-inline-precompile';
import { storiesOf } from '@storybook/ember';
import notes from './auth-form.md';
storiesOf('AuthForm/', module)
.addParameters({ options: { showPanel: false } })
.add(
`AuthForm`,
() => ({
template: hbs`
<h5 class="title is-5">Auth Form</h5>
<AuthForm />
`,
context: {},
}),
{ notes }
);

View File

@ -0,0 +1,32 @@
<!--THIS FILE IS AUTO GENERATED. This file is generated from JSDoc comments in app/components/confirm-action.js. To make changes, first edit that file and run "yarn gen-story-md confirm-action" to re-generate the content.-->
## ConfirmAction
`ConfirmAction` is a button followed by a confirmation message and button used to prevent users from performing actions they do not intend to.
**Properties**
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| onConfirmAction | <code>Func</code> | <code></code> | The action to take upon confirming. |
| [confirmMessage] | <code>String</code> | <code>Are you sure you want to do this?</code> | The message to display upon confirming. |
| [confirmButtonText] | <code>String</code> | <code>Delete</code> | The confirm button text. |
| [cancelButtonText] | <code>String</code> | <code>Cancel</code> | The cancel button text. |
| [disabledMessage] | <code>String</code> | <code>Complete the form to complete this action</code> | The message to display when the button is disabled. |
**Example**
```js
<ConfirmAction
@onConfirmAction={{ () => { console.log('Action!') } }}
@confirmMessage="Are you sure you want to delete this config?">
Delete
</ConfirmAction>
```
**See**
- [Uses of ConfirmAction](https://github.com/hashicorp/vault/search?l=Handlebars&q=ConfirmAction)
- [ConfirmAction Source Code](https://github.com/hashicorp/vault/blob/master/ui/app/components/confirm-action.js)
---

View File

@ -0,0 +1,39 @@
/* eslint-disable import/extensions */
import hbs from 'htmlbars-inline-precompile';
import { storiesOf } from '@storybook/ember';
import { withKnobs, text, boolean } from '@storybook/addon-knobs';
import notes from './confirm-action.md';
storiesOf('ConfirmAction/', module)
.addDecorator(
withKnobs({
escapeHTML: false,
})
)
.add(
`ConfirmAction`,
() => ({
template: hbs`
<h5 class="title is-5">Confirm Action</h5>
<ConfirmAction
@onConfirmAction={{onComfirmAction}}
@confirmButtonText={{confirmButtonText}}
@confirmMessage={{confirmMessage}}
@cancelButtonText={{cancelButtonText}}
@disabled={{disabled}}
>
Delete
</ConfirmAction>
`,
context: {
onComfirmAction: () => {
console.log('Action!');
},
confirmButtonText: text('confirmButtonText', 'Yes'),
confirmMessage: text('confirmMessage', 'Are you sure you want to do this?'),
cancelButtonText: text('cancelButtonText', 'Cancel'),
disabled: boolean('disabled', false),
},
}),
{ notes }
);

22
ui/stories/i-con.md Normal file
View File

@ -0,0 +1,22 @@
<!--THIS FILE IS AUTO GENERATED. This file is generated from JSDoc comments in app/components/i-con.js. To make changes, first edit that file and run "yarn gen-story-md i-con" to re-generate the content.-->
## ICon
`ICon` components are glyphs used to indicate important information.
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| glyph | <code>String</code> | <code></code> | The glyph type. |
**Example**
```js
<ICon @glyph="cancel-square-outline" />
```
**See**
- [Uses of ICon](https://github.com/hashicorp/vault/search?l=Handlebars&q=ICon)
- [ICon Source Code](https://github.com/hashicorp/vault/blob/master/ui/app/components/i-con.js)
---

View File

@ -0,0 +1,39 @@
/* eslint-disable import/extensions */
import hbs from 'htmlbars-inline-precompile';
import { storiesOf } from '@storybook/ember';
import notes from './i-con.md';
import { GLYPHS_WITH_SVG_TAG } from '../app/components/i-con.js';
storiesOf('ICon/', module)
.addParameters({ options: { showPanel: false } })
.add(
'ICon',
() => ({
template: hbs`
<table class="table">
<thead>
<tr>
<th>Glyph title</th>
<th>Glyph</th>
</tr>
</thead>
<tbody>
{{#each types as |type|}}
<tr>
<td>
<h5>{{humanize type}}</h5>
</td>
<td>
<ICon @glyph={{type}} />
</td>
</tr>
{{/each}}
</tbody>
</table>
`,
context: {
types: GLYPHS_WITH_SVG_TAG,
},
}),
{ notes }
);

View File

@ -0,0 +1,36 @@
<!--THIS FILE IS AUTO GENERATED. This file is generated from JSDoc comments in app/components/toggle-button.js. To make changes, first edit that file and run "yarn gen-story-md toggle-button" to re-generate the content.-->
## ToggleButton
`ToggleButton` components are used to expand and collapse content with a toggle.
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| toggleAttr | <code>String</code> | <code></code> | The attribute upon which to toggle. |
| attrTarget | <code>Object</code> | <code></code> | The target upon which the event handler should be added. |
| [openLabel] | <code>String</code> | <code>Hide</code> | options - The message to display when the toggle is open. |
| [closedLabel] | <code>String</code> | <code>More</code> | options - The message to display when the toggle is closed. |
**Example**
```js
<ToggleButton
@openLabel="Encrypt Output with PGP"
@closedLabel="Encrypt Output with PGP"
@toggleTarget={{this}}
@toggleAttr="showOptions"/>
{{#if showOptions}}
<div>
<p>
I will be toggled!
</p>
</div>
{{/if}}
```
**See**
- [Uses of ToggleButton](https://github.com/hashicorp/vault/search?l=Handlebars&q=ToggleButton)
- [ToggleButton Source Code](https://github.com/hashicorp/vault/blob/master/ui/app/components/toggle-button.js)
---

View File

@ -0,0 +1,41 @@
/* eslint-disable import/extensions */
import hbs from 'htmlbars-inline-precompile';
import { storiesOf } from '@storybook/ember';
import notes from './toggle-button.md';
storiesOf('ToggleButton', module)
.addParameters({ options: { showPanel: false } })
.add(
'ToggleButton',
() => ({
template: hbs`
<ToggleButton
@toggleAttr="showOptions"
@toggleTarget={{this}}
/>
`,
}),
{ notes }
)
.add(
'ToggleButton with content',
() => ({
template: hbs`
<ToggleButton
@openLabel="Hide me!"
@closedLabel="Show me!"
@toggleTarget={{this}}
@toggleAttr="showOptions"
/>
{{#if showOptions}}
<div>
<p>
I will be toggled!
</p>
</div>
{{/if}}
`,
}),
{ notes }
);

File diff suppressed because it is too large Load Diff