Storybook scripts and references removed (#22232)

This commit is contained in:
Phil Renaud
2024-05-29 16:34:26 -04:00
committed by GitHub
parent 140747240f
commit 8a9d58ae8f
54 changed files with 146 additions and 11236 deletions

1
.gitignore vendored
View File

@@ -97,7 +97,6 @@ rkt-*
# misc
/ui/.sass-cache
/ui/.eslintcache
/ui/.storybook/preview-head.html
/ui/connect.lock
/ui/coverage/*
/ui/libpeerconnection.log

View File

@@ -69,17 +69,6 @@ module.exports = {
'node/no-unpublished-require': 'off',
},
},
{
files: ['stories/**/*.js'],
parserOptions: {
sourceType: 'module',
},
env: {
browser: false,
node: true,
},
plugins: ['node'],
},
{
// Test files:
files: ['tests/**/*-test.{js,ts}'],

View File

@@ -1,23 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
/* eslint-env node */
module.exports = {
framework: '@storybook/ember',
addons: [
'@storybook/addon-docs',
'@storybook/addon-storysource',
'@storybook/addon-knobs',
'@storybook/addon-viewport',
],
stories: [
'../stories/theme/*.stories.js',
'../stories/components/*.stories.js',
'../stories/charts/*.stories.js',
],
core: {
builder: '@storybook/builder-webpack4',
},
};

View File

@@ -1,46 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { addDecorator, addParameters } from '@storybook/ember';
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';
import theme from './theme.js';
addParameters({
viewport: { viewports: INITIAL_VIEWPORTS },
options: {
showPanel: true,
theme,
},
});
addDecorator((storyFn) => {
let { template, context } = storyFn();
let wrapperElementStyle = {
margin: '20px',
};
let applicationWrapperElement = document.createElement('div');
Object.assign(applicationWrapperElement.style, wrapperElementStyle);
let storybookElement = document.createElement('div');
storybookElement.setAttribute('id', 'storybook');
let wormhole = document.createElement('div');
wormhole.setAttribute('id', 'ember-basic-dropdown-wormhole');
storybookElement.appendChild(wormhole);
applicationWrapperElement.appendChild(storybookElement);
storybookElement.appendTo = function appendTo(el) {
el.appendChild(applicationWrapperElement);
};
return {
template,
context,
element: storybookElement,
};
});

View File

@@ -1,41 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { create } from '@storybook/theming';
// From Bulma
let blackBis = 'hsl(0, 0%, 7%)';
let greyLight = 'hsl(0, 0%, 71%)';
// From product-colors.scss
let vagrantBlue = '#1563ff';
export default create({
base: 'light',
colorPrimary: blackBis,
colorSecondary: vagrantBlue,
// UI
appBorderColor: greyLight,
// Typography
// From variables.scss
fontBase:
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif",
// From Bulma
fontCode: 'monospace',
// Text colors
textColor: blackBis,
// Toolbar default and active colors
barTextColor: greyLight,
barSelectedColor: 'white',
barBg: blackBis,
brandTitle: 'Nomad Storybook',
brandUrl: 'https://developer.hashicorp.com/nomad/',
});

View File

@@ -1,9 +1,6 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1
STORYBOOK_LINK=true ember build
ember build
mkdir -p ui-dist/ui
mv dist/* ui-dist/ui/
yarn build-storybook
mv storybook-static ui-dist/storybook/

View File

@@ -78,12 +78,6 @@ Nomad UI releases are in lockstep with Nomad releases and are integrated into th
- UI branches should be prefix with `f-ui-` for feature work and `b-ui-` for bug fixes. This instructs CI to skip running nomad backend tests.
### Storybook UI Library
The Storybook project provides a browser to see what components and patterns are present in the application and how to use them. You can run it locally with `yarn storybook` after you have `ember serve` running. The latest version from the `main` branch is at [`nomad-storybook-and-ui.vercel.app/storybook/`](https://nomad-storybook-and-ui.vercel.app/storybook/).
To generate a new story for a component, run `ember generate story component-name`. You can use the existing stories as a guide.
### Troubleshooting
#### The UI is running, but none of the API requests are working

View File

@@ -1,23 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { helper } from '@ember/component/helper';
// Generated at compile-time by ember-inline-svg
import SVGs from '../svgs';
/**
* Icons array
*
* Usage: {{#each (all-icons) as |icon|}}
*
* Returns the array of all icon strings available to {{x-icon}}. This is a bit of a hack
* since the above SVGs import isn't available in the Storybook context.
*/
export function allIcons() {
return Object.keys(SVGs);
}
export default helper(allIcons);

View File

@@ -10,6 +10,3 @@
@import './components';
@import '@hashicorp/design-system-components';
@import './charts';
// Only necessary in dev
@import './storybook.scss';

View File

@@ -1,199 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
#storybook {
.mock-content {
display: flex;
min-height: 250px;
height: 100%;
.mock-image,
.mock-copy {
min-height: 100%;
width: 100%;
margin: 1em;
}
.mock-image {
background: linear-gradient(
to top right,
transparent 0%,
transparent 49%,
$grey-blue 49%,
$grey-blue 51%,
transparent 51%,
transparent 100%
),
linear-gradient(
to bottom right,
transparent 0%,
transparent 49%,
$grey-blue 49%,
$grey-blue 51%,
transparent 51%,
transparent 100%
);
}
.mock-copy {
background: repeating-linear-gradient(
to bottom,
$grey-blue,
$grey-blue 5px,
transparent 5px,
transparent 14px
);
}
.mock-vague {
background: lighten($grey-blue, 15%);
width: 100%;
height: 100%;
}
}
.mock-spacing {
padding: 2em;
}
.annotation {
padding: 1rem 0;
font-size: 0.9rem;
}
.palette {
.title {
font-size: 1.4rem;
font-weight: $weight-bold;
padding-bottom: 2px;
padding-top: 10px;
}
.description {
font-size: 0.8rem;
padding-bottom: 5px;
}
.item {
border: solid 1px $grey-blue;
display: inline-block;
margin: 0 5px 5px 0;
.color {
height: 90px;
width: 160px;
}
.info {
background-color: white;
border-top: solid 1px $grey-blue;
padding: 5px;
}
.hex {
font-size: 12px;
font-weight: $weight-bold;
margin-bottom: 0;
}
.name {
color: $ui-gray-500;
font-size: 11px;
margin-top: 0;
}
}
}
.typeface {
.hero,
.sample {
font-family: inherit;
}
.hero {
font-size: 140px;
line-height: 1.05;
}
.sample {
font-size: 15px;
margin: 0;
}
}
.multiples {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
&.with-spacing {
> * {
margin-right: 1em;
margin-bottom: 1em;
}
}
&.is-left-aligned {
justify-content: flex-start;
}
}
.chart-container {
width: 200px;
padding: 15px;
border: 1px solid $ui-gray-200;
display: inline-block;
&.is-small {
width: 150px;
}
&.is-large {
width: 250px;
}
&.is-xlarge {
width: 300px;
}
}
.tile-list {
display: flex;
flex-wrap: wrap;
.icon-tile {
width: 240px;
padding: 10px;
margin: 10px;
border: 1px solid $grey-lighter;
border-radius: $radius;
svg {
margin: auto;
width: 30px;
height: 30;
fill: $grey;
color: $grey;
}
}
}
.mock-hover-region {
width: 200px;
height: 100px;
position: relative;
border-radius: $radius;
margin: 1em 0;
padding: 1em;
border: 1px solid $grey-blue;
background: $white-ter;
color: $grey;
font-weight: $weight-bold;
}
.title:not(:first-child) {
margin-top: 2em;
}
}

View File

@@ -30,16 +30,6 @@
{{/unless}}
{{/if}}
<div class="navbar-end">
{{#if this.config.APP.showStorybookLink}}
<a
href="/storybook/"
target="_blank"
rel="noopener noreferrer"
class="navbar-item"
>
Storybook
</a>
{{/if}}
{{#if this.system.agent.config.UI.Consul.BaseUIURL}}
<a
data-test-header-consul-link
@@ -82,4 +72,4 @@
{{yield}}
</ul>
</nav>
</div>
</div>

View File

@@ -1,20 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components|<%= classifiedModuleName %>',
};
export let <%= classifiedModuleName %> = () => {
return {
template: hbs`
<h5 class="title is-5"><%= header %></h5>
<<%= classifiedModuleName %>/>
`,
context: {},
}
};

View File

@@ -1,42 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
/* eslint-disable ember/no-string-prototype-extensions */
const getPathOption = require('ember-cli-get-component-path-option');
const stringUtil = require('ember-cli-string-utils');
const path = require('path');
module.exports = {
description: 'generates a story for storybook',
fileMapTokens: function () {
let { project } = this;
return {
__path__: function () {
return path.relative(project.root, project.root);
},
__markdownname__: function (options) {
return options.dasherizedModuleName;
},
__name__: function (options) {
return options.dasherizedModuleName;
},
};
},
locals: function (options) {
let contents = '';
return {
contents: contents,
path: getPathOption(options),
header: stringUtil
.dasherize(options.entity.name)
.split('-')
.map((word) => stringUtil.capitalize(word))
.join(' '),
};
},
};

View File

@@ -40,7 +40,6 @@ module.exports = function (environment) {
mirageWithNamespaces: true,
mirageWithTokens: true,
mirageWithRegions: true,
showStorybookLink: process.env.STORYBOOK_LINK === 'true',
},
percy: {
@@ -59,11 +58,6 @@ module.exports = function (environment) {
enabled: USE_MIRAGE,
excludeFilesFromBuild: !USE_MIRAGE,
};
if (process.env.STORYBOOK === 'true') {
ENV.APP.autoboot = false;
ENV.rootURL = '/';
}
}
if (environment === 'test') {

View File

@@ -17,8 +17,6 @@
"lint:js": "eslint . --cache",
"lint:js:fix": "eslint . --fix",
"start": "ember server",
"build-storybook": "STORYBOOK=true ember build && NODE_OPTIONS=--openssl-legacy-provider build-storybook -s dist",
"storybook": "NODE_OPTIONS=--openssl-legacy-provider STORYBOOK=true start-storybook -p 6006 -s dist",
"test": "npm-run-all lint test:*",
"exam": "percy exec -- ember exam --split=4 --parallel",
"exam:parallel": "percy exec --parallel -- ember exam",
@@ -34,7 +32,7 @@
}
},
"lint-staged": {
"{app,tests,config,lib,mirage,stories}/**/*.js": [
"{app,tests,config,lib,mirage}/**/*.js": [
"prettier --write"
],
"app/styles/**/*.*": [
@@ -50,7 +48,6 @@
"@glimmer/component": "^1.0.4",
"@glimmer/tracking": "^1.0.4",
"@hashicorp/structure-icons": "^1.3.0",
"@storybook/ember-cli-storybook": "^0.6.1",
"anser": "^2.1.1",
"babel-eslint": "^10.1.0",
"base64-js": "^1.3.1",
@@ -153,12 +150,6 @@
},
"optionalDependencies": {
"@babel/plugin-transform-member-expression-literals": "^7.16.7",
"@storybook/addon-docs": "^6.3.20",
"@storybook/addon-knobs": "^6.3.1",
"@storybook/addon-storysource": "^6.3.10",
"@storybook/addon-viewport": "^6.3.10",
"@storybook/addons": "^6.3.10",
"@storybook/ember": "6.5.14",
"babel-loader": "^8.0.6",
"ember-cli-get-component-path-option": "^1.0.0",
"ember-cli-string-utils": "^1.1.0"

View File

@@ -1,179 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
import EmberObject, { computed } from '@ember/object';
import { on } from '@ember/object/evented';
import DelayedTruth from '../utils/delayed-truth';
export default {
title: 'Charts/Distribution Bar',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Distribution Bar</h5>
<div class="block" style="height:50px; width:200px;">
{{#if delayedTruth.complete}}
<DistributionBar @data={{distributionBarData}} />
{{/if}}
</div>
<p class="annotation">The distribution bar chart proportionally show data in a single bar. It includes a tooltip out of the box, assumes the size of the container element, and is designed to be styled with CSS.</p>
`,
context: {
delayedTruth: DelayedTruth.create(),
distributionBarData: [
{ label: 'one', value: 10 },
{ label: 'two', value: 20 },
{ label: 'three', value: 30 },
],
},
};
};
export let WithClasses = () => {
return {
template: hbs`
<h5 class="title is-5">Distribution Bar with classes</h5>
<div class="block" style="height:50px; width:200px;">
{{#if delayedTruth.complete}}
<DistributionBar @data={{distributionBarDataWithClasses}} />
{{/if}}
</div>
<p class="annotation">If a datum provides a <code>className</code> property, it will be assigned to the corresponding <code>rect</code> element, allowing for custom colorization.</p>
`,
context: {
delayedTruth: DelayedTruth.create(),
distributionBarDataWithClasses: [
{ label: 'Queued', value: 10, className: 'queued' },
{ label: 'Complete', value: 20, className: 'complete' },
{ label: 'Failed', value: 30, className: 'failed' },
],
},
};
};
export let Flexibility = () => {
return {
template: hbs`
<h5 class="title is-5">Distribution Bar flexibility</h5>
<div class="block" style="height:10px; width:600px;">
{{#if delayedTruth.complete}}
<DistributionBar @data={{distributionBarData}} />
{{/if}}
</div>
<div class="block" style="height:200px; width:30px;">
{{#if delayedTruth.complete}}
<DistributionBar @data={{distributionBarData}} />
{{/if}}
</div>
<p class="annotation">Distribution bar assumes the dimensions of the container.</p>
`,
context: {
delayedTruth: DelayedTruth.create(),
distributionBarData: [
{ label: 'one', value: 10 },
{ label: 'two', value: 20 },
{ label: 'three', value: 30 },
],
},
};
};
export let LiveUpdating = () => {
return {
template: hbs`
<h5 class="title is-5">Live-updating Distribution Bar</h5>
<div class="block" style="height:50px; width:600px;">
<DistributionBar @data={{controller.distributionBarDataRotating}} />
</div>
<p class="annotation">Distribution bar animates with data changes.</p>
<div class="boxed-section">
<div class="boxed-section-body is-dark">
<JsonViewer @json={{controller.distributionBarDataRotating}} />
</div>
</div>
`,
context: {
controller: EmberObject.extend({
timerTicks: 0,
startTimer: on('init', function () {
this.set(
'timer',
setInterval(() => {
this.incrementProperty('timerTicks');
}, 500)
);
}),
willDestroy() {
clearInterval(this.timer);
},
distributionBarDataRotating: computed('timerTicks', function () {
return [
{ label: 'one', value: Math.round(Math.random() * 50) },
{ label: 'two', value: Math.round(Math.random() * 50) },
{ label: 'three', value: Math.round(Math.random() * 50) },
];
}),
}).create(),
},
};
};
export let SingleBar = () => {
return {
template: hbs`
<h5 class="title is-5">Distribution Bar with single bar</h5>
<div class="block" style="height:50px; width:600px;">
{{#if delayedTruth.complete}}
<DistributionBar @data={{distributionBarDatum}} />
{{/if}}
</div>
`,
context: {
delayedTruth: DelayedTruth.create(),
distributionBarDatum: [{ label: 'one', value: 10 }],
},
};
};
export let Jumbo = () => {
return {
template: hbs`
<h5 class="title is-5">Jumbo Distribution Bar</h5>
{{#if delayedTruth.complete}}
<DistributionBar @data={{distributionBarData}} @class="split-view" as |chart|>
<ol class="legend">
{{#each chart.data as |datum index|}}
<li class="{{datum.className}} {{if (eq datum.index chart.activeDatum.index) "is-active"}} {{if (eq datum.value 0) "is-empty"}}">
<span class="color-swatch {{if datum.className datum.className (concat "swatch-" index)}}" />
<span class="value" data-test-legend-value="{{datum.className}}">{{datum.value}}</span>
<span class="label">
{{datum.label}}
</span>
</li>
{{/each}}
</ol>
</DistributionBar>
{{/if}}
<p class="annotation">A variation of the Distribution Bar component for when the distribution bar is the central component of the page. It's a larger format that requires no interaction to see the data labels and values.</p>
`,
context: {
delayedTruth: DelayedTruth.create(),
distributionBarData: [
{ label: 'one', value: 10 },
{ label: 'two', value: 20 },
{ label: 'three', value: 0 },
{ label: 'four', value: 35 },
],
},
};
};

View File

@@ -1,120 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
import DelayedArray from '../utils/delayed-array';
import DelayedTruth from '../utils/delayed-truth';
export default {
title: 'Charts/Gauge Chart',
};
let totalVariations = [
{ value: 0, total: 10 },
{ value: 1, total: 10 },
{ value: 2, total: 10 },
{ value: 3, total: 10 },
{ value: 4, total: 10 },
{ value: 5, total: 10 },
{ value: 6, total: 10 },
{ value: 7, total: 10 },
{ value: 8, total: 10 },
{ value: 9, total: 10 },
{ value: 10, total: 10 },
];
let complementVariations = [
{ value: 0, complement: 10 },
{ value: 1, complement: 9 },
{ value: 2, complement: 8 },
{ value: 3, complement: 7 },
{ value: 4, complement: 6 },
{ value: 5, complement: 5 },
{ value: 6, complement: 4 },
{ value: 7, complement: 3 },
{ value: 8, complement: 2 },
{ value: 9, complement: 1 },
{ value: 10, complement: 0 },
];
let colorVariations = ['is-info', 'is-warning', 'is-success', 'is-danger'];
export let Total = () => {
return {
template: hbs`
<div class="multiples">
{{#each variations as |v|}}
<div class="chart-container">
<GaugeChart @value={{v.value}} @total={{v.total}} @label="Total" @chartClass="is-info" />
</div>
{{/each}}
</div>
`,
context: {
variations: DelayedArray.create(totalVariations),
},
};
};
export let Complement = () => {
return {
template: hbs`
<div class="multiples">
{{#each variations as |v|}}
<div class="chart-container">
<GaugeChart @value={{v.value}} @complement={{v.complement}} @label="Complement" @chartClass="is-info" />
</div>
{{/each}}
</div>
`,
context: {
variations: DelayedArray.create(complementVariations),
},
};
};
export let Colors = () => {
return {
template: hbs`
<div class="multiples">
{{#each variations as |color|}}
<div class="chart-container">
<GaugeChart @value={{7}} @total={{10}} @label={{color}} @chartClass={{color}} />
</div>
{{/each}}
</div>
`,
context: {
variations: DelayedArray.create(colorVariations),
},
};
};
export let Sizing = () => {
return {
template: hbs`
{{#if delayedTruth.complete}}
<div class="multiples">
<div class="chart-container is-small">
<GaugeChart @value={{7}} @total={{10}} @label="Small" />
</div>
<div class="chart-container">
<GaugeChart @value={{7}} @total={{10}} @label="Regular" />
</div>
<div class="chart-container is-large">
<GaugeChart @value={{7}} @total={{10}} @label="Large" />
</div>
<div class="chart-container is-xlarge">
<GaugeChart @value={{7}} @total={{10}} @label="X-Large" />
</div>
</div>
{{/if}}
<p class="annotation">GaugeCharts fill the width of their container and have a dynamic height according to the height of the arc. However, the text within a gauge chart is fixed. This can create unsightly overlap or whitespace, so be careful about responsiveness when using this chart type.</p>
`,
context: {
delayedTruth: DelayedTruth.create(),
},
};
};

View File

@@ -1,463 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
import EmberObject from '@ember/object';
import { on } from '@ember/object/evented';
import moment from 'moment';
import DelayedArray from '../utils/delayed-array';
export default {
title: 'Charts/Line Chart',
};
let data1 = [
{ year: 2010, value: 10 },
{ year: 2011, value: 10 },
{ year: 2012, value: 20 },
{ year: 2013, value: 30 },
{ year: 2014, value: 50 },
{ year: 2015, value: 80 },
{ year: 2016, value: 130 },
{ year: 2017, value: 210 },
{ year: 2018, value: 340 },
];
let data2 = [
{ year: 2010, value: 100 },
{ year: 2011, value: 90 },
{ year: 2012, value: 120 },
{ year: 2013, value: 130 },
{ year: 2014, value: 115 },
{ year: 2015, value: 105 },
{ year: 2016, value: 90 },
{ year: 2017, value: 85 },
{ year: 2018, value: 90 },
];
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Line Chart</h5>
<div class="block" style="height:100px; width: 400px;">
{{#if this.lineChartData}}
<LineChart @data={{this.lineChartData}} @xProp="year" @yProp="value">
<:svg as |c|>
<c.Area @data={{this.lineChartData}} />
</:svg>
<:after as |c|>
<c.Tooltip class="is-snappy" as |series datum|>
<li>
<span class="label"><span class="color-swatch is-primary" />{{datum.formattedX}}</span>
<span class="value">{{datum.formattedY}}</span>
</li>
</c.Tooltip>
</:after>
</LineChart>
{{/if}}
</div>
<div class="block" style="height:100px; width: 400px;">
{{#if this.lineChartMild}}
<LineChart @data={{this.lineChartMild}} @xProp="year" @yProp="value">
<:svg as |c|>
<c.Area @data={{this.lineChartMild}} @colorClass="is-info" />
</:svg>
<:after as |c|>
<c.Tooltip class="is-snappy" as |series datum|>
<li>
<span class="label"><span class="color-swatch is-info" />{{datum.formattedX}}</span>
<span class="value">{{datum.formattedY}}</span>
</li>
</c.Tooltip>
</:after>
</LineChart>
{{/if}}
</div>
`,
context: {
lineChartData: DelayedArray.create(data1),
lineChartMild: DelayedArray.create(data2),
},
};
};
export let FluidWidth = () => {
return {
template: hbs`
<h5 class="title is-5">Fluid-width Line Chart</h5>
<div class="block" style="height:250px;">
{{#if this.lineChartData}}
<LineChart @data={{this.lineChartData}} @xProp="year" @yProp="value">
<:svg as |c|>
<c.Area @data={{this.lineChartData}} @colorClass="is-danger" />
</:svg>
<:after as |c|>
<c.Tooltip class="is-snappy" as |series datum|>
<li>
<span class="label"><span class="color-swatch is-danger" />{{datum.formattedX}}</span>
<span class="value">{{datum.formattedY}}</span>
</li>
</c.Tooltip>
</:after>
</LineChart>
{{/if}}
</div>
<div class="block" style="height:250px;">
{{#if this.lineChartMild}}
<LineChart @data={{this.lineChartMild}} @xProp="year" @yProp="value">
<:svg as |c|>
<c.Area @data={{this.lineChartMild}} @colorClass="is-warning" />
</:svg>
<:after as |c|>
<c.Tooltip class="is-snappy" as |series datum|>
<li>
<span class="label"><span class="color-swatch is-warning" />{{datum.formattedX}}</span>
<span class="value">{{datum.formattedY}}</span>
</li>
</c.Tooltip>
</:after>
</LineChart>
{{/if}}
</div>
<p class="annotation">A line chart will assume the width of its container. This includes the dimensions of the axes, which are calculated based on real DOM measurements. This requires a two-pass render: first the axes are placed with their real domains (in order to capture width and height of tick labels), second the axes are adjusted to make sure both the x and y axes are within the height and width bounds of the container.</p>
`,
context: {
lineChartData: DelayedArray.create(data1),
lineChartMild: DelayedArray.create(data2),
},
};
};
export let LiveData = () => {
return {
template: hbs`
<h5 class="title is-5">Live data Line Chart</h5>
<div class="block" style="height:250px">
{{#if this.controller.lineChartLive}}
<LineChart
@data={{this.controller.lineChartLive}}
@xProp="ts"
@yProp="val"
@timeseries={{true}}
@xFormat={{this.controller.secondsFormat}}>
<:svg as |c|>
<c.Area @data={{this.controller.lineChartLive}} />
</:svg>
</LineChart>
{{/if}}
</div>
`,
context: {
controller: EmberObject.extend({
startTimer: on('init', function () {
this.lineChartLive = [];
this.set(
'timer',
setInterval(() => {
this.incrementProperty('timerTicks');
let ref = this.lineChartLive;
ref.addObject({ ts: Date.now(), val: Math.random() * 30 + 20 });
if (ref.length > 60) {
ref.splice(0, ref.length - 60);
}
}, 500)
);
}),
willDestroy() {
clearInterval(this.timer);
},
get secondsFormat() {
return (date) => moment(date).format('HH:mm:ss');
},
}).create(),
},
};
};
export let Gaps = () => {
return {
template: hbs`
<h5 class="title is-5">Line Chart data with gaps</h5>
<div class="block" style="height:250px">
{{#if this.lineChartGapData}}
<LineChart @data={{this.lineChartGapData}} @xProp="year" @yProp="value">
<:svg as |c|>
<c.Area @data={{this.lineChartGapData}} />
</:svg>
<:after as |c|>
<c.Tooltip class="is-snappy" as |series datum|>
<li>
<span class="label"><span class="color-swatch is-primary" />{{datum.formattedX}}</span>
<span class="value">{{datum.formattedY}}</span>
</li>
</c.Tooltip>
</:after>
</LineChart>
{{/if}}
</div>
`,
context: {
lineChartGapData: DelayedArray.create([
{ year: 2010, value: 10 },
{ year: 2011, value: 10 },
{ year: 2012, value: null },
{ year: 2013, value: 30 },
{ year: 2014, value: 50 },
{ year: 2015, value: 80 },
{ year: 2016, value: null },
{ year: 2017, value: 210 },
{ year: 2018, value: 340 },
]),
},
};
};
export let VerticalAnnotations = () => {
return {
template: hbs`
<h5 class="title is-5">Line Chart data with annotations</h5>
<div class="block" style="height:250px">
{{#if (and this.data this.annotations)}}
<LineChart
class="with-annotations"
@timeseries={{true}}
@xProp="x"
@yProp="y"
@data={{this.data}}>
<:svg as |c|>
<c.Area @data={{this.data}} @annotationClick={{action (mut this.activeAnnotation)}} />
</:svg>
<:after as |c|>
<c.VAnnotations
@annotations={{this.annotations}}
@annotationClick={{action (mut this.activeAnnotation)}}
@activeAnnotation={{this.activeAnnotation}} />
</:after>
</LineChart>
{{/if}}
</div>
<p style="margin:2em 0; padding: 1em; background:#FFEEAC">{{this.activeAnnotation.info}}</p>
<h5 class="title is-5">Line Chart data with staggered annotations</h5>
<div class="block" style="height:150px; width:450px">
{{#if (and this.data this.annotations)}}
<LineChart
class="with-annotations"
@timeseries={{true}}
@xProp="x"
@yProp="y"
@data={{this.data}}>
<:svg as |c|>
<c.Area @data={{this.data}} @annotationClick={{action (mut this.activeAnnotation)}} />
</:svg>
<:after as |c|>
<c.VAnnotations
@annotations={{this.annotations}}
@annotationClick={{action (mut this.activeAnnotation)}}
@activeAnnotation={{this.activeAnnotation}} />
<c.Tooltip class="is-snappy" as |series datum|>
<li>
<span class="label"><span class="color-swatch is-primary" />{{datum.formattedX}}</span>
<span class="value">{{datum.formattedY}}</span>
</li>
</c.Tooltip>
</:after>
</LineChart>
{{/if}}
</div>
`,
context: {
data: DelayedArray.create(
new Array(180).fill(null).map((_, idx) => ({
y: Math.sin((idx * 4 * Math.PI) / 180) * 100 + 200,
x: moment().add(idx, 'd').toDate(),
}))
),
annotations: [
{
x: moment().toDate(),
type: 'info',
info: 'Far left',
},
{
x: moment()
.add(90 / 4, 'd')
.toDate(),
type: 'error',
info: 'This is the max of the sine curve',
},
{
x: moment().add(89, 'd').toDate(),
type: 'info',
info: 'This is the end of the first period',
},
{
x: moment().add(96, 'd').toDate(),
type: 'info',
info: 'A close annotation for staggering purposes',
},
{
x: moment()
.add((90 / 4) * 3, 'd')
.toDate(),
type: 'error',
info: 'This is the min of the sine curve',
},
{
x: moment().add(179, 'd').toDate(),
type: 'info',
info: 'Far right',
},
],
},
};
};
export let HorizontalAnnotations = () => {
return {
template: hbs`
<div class="block" style="height:250px">
{{#if (and this.data this.annotations)}}
<LineChart
class="with-annotations"
@timeseries={{true}}
@xProp="x"
@yProp="y"
@data={{this.data}}>
<:svg as |c|>
<c.Area @data={{this.data}} @annotationClick={{action (mut this.activeAnnotation)}} />
</:svg>
<:after as |c|>
<c.HAnnotations @annotations={{this.annotations}} @labelProp="info" />
</:after>
</LineChart>
{{/if}}
</div>
`,
context: {
data: DelayedArray.create(
new Array(180).fill(null).map((_, idx) => ({
y: Math.sin((idx * 4 * Math.PI) / 180) * 100 + 200,
x: moment().add(idx, 'd').toDate(),
}))
),
annotations: [
{
y: 300,
info: 'High',
},
{
y: 100,
info: 'Low',
},
],
},
};
};
export let StepLine = () => {
return {
template: hbs`
<h5 class="title is-5">Line Chart with a Step Line</h5>
<div class="block" style="height:250px">
{{#if this.data}}
<LineChart
@xProp="x"
@yProp="y"
@data={{this.data}}>
<:svg as |c|>
<c.Area @data={{this.data}} @curve="stepAfter" />
</:svg>
<:after as |c|>
<c.Tooltip class="is-snappy" as |series datum|>
<li>
<span class="label"><span class="color-swatch is-primary" />{{datum.formattedX}}</span>
<span class="value">{{datum.formattedY}}</span>
</li>
</c.Tooltip>
</:after>
</LineChart>
<p>{{this.activeAnnotation.info}}</p>
{{/if}}
</div>
`,
context: {
data: DelayedArray.create([
{ x: 1, y: 5 },
{ x: 2, y: 1 },
{ x: 3, y: 2 },
{ x: 4, y: 2 },
{ x: 5, y: 9 },
{ x: 6, y: 3 },
{ x: 7, y: 4 },
{ x: 8, y: 1 },
{ x: 9, y: 5 },
]),
},
};
};
export let MultiLine = () => ({
template: hbs`
<h5 class="title is-5">Multiple Lines on One Chart</h5>
<div class="block" style="height:250px">
{{#if this.data}}
<LineChart
@xProp="x"
@yProp="y"
@dataProp="data"
@data={{this.data}}>
<:svg as |c|>
{{#each this.data as |series idx|}}
<c.Area @data={{series.data}} @colorScale="reds" @index={{idx}} />
{{/each}}
</:svg>
<:after as |c|>
<c.Tooltip class="is-snappy" as |series datum index|>
<li>
<span class="label"><span class="color-swatch swatch-reds swatch-reds-{{index}}" />{{series.name}}</span>
<span class="value">{{datum.formattedY}}</span>
</li>
</c.Tooltip>
</:after>
</LineChart>
<p>{{this.activeAnnotation.info}}</p>
{{/if}}
</div>
`,
context: {
data: DelayedArray.create([
{
name: 'Series 1',
data: [
{ x: 3, y: 7 },
{ x: 4, y: 5 },
{ x: 5, y: 8 },
{ x: 6, y: 9 },
{ x: 7, y: 10 },
{ x: 8, y: 8 },
{ x: 9, y: 6 },
],
},
{
name: 'Series 2',
data: [
{ x: 1, y: 5 },
{ x: 2, y: 1 },
{ x: 3, y: 2 },
{ x: 4, y: 2 },
{ x: 5, y: 9 },
{ x: 6, y: 3 },
{ x: 7, y: 4 },
],
},
]),
},
});

View File

@@ -1,73 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Charts/Primitives',
};
export let Tooltip = () => ({
template: hbs`
<h5 class="title is-5">Single Entry</h5>
<div class="mock-hover-region" style="width:300px;height:100px">
<ChartPrimitives::Tooltip @active={{true}} @style={{this.style}} @data={{this.dataSingle}} as |series|>
<li>
<span class="label"><span class="color-swatch swatch-reds" />{{series.name}}</span>
<span class="value">{{series.value}}</span>
</li>
</ChartPrimitives::Tooltip>
</div>
<h5 class="title is-5">Multiple Entries</h5>
<div class="mock-hover-region" style="width:300px;height:100px">
<ChartPrimitives::Tooltip @active={{true}} @style={{this.style}} @data={{take 4 this.dataMultiple}} as |series datum index|>
<li>
<span class="label"><span class="color-swatch swatch-reds swatch-reds-{{index}}" />{{series.name}}</span>
<span class="value">{{datum.value}}</span>
</li>
</ChartPrimitives::Tooltip>
</div>
<h5 class="title is-5">Active Entry</h5>
<div class="mock-hover-region" style="width:300px;height:100px">
<ChartPrimitives::Tooltip @active={{true}} @style={{this.style}} @data={{take 4 this.dataMultiple}} class="with-active-datum" as |series datum index|>
<li class="{{if (eq series.name "Three") "is-active"}}">
<span class="label"><span class="color-swatch swatch-reds swatch-reds-{{index}}" />{{series.name}}</span>
<span class="value">{{datum.value}}</span>
</li>
</ChartPrimitives::Tooltip>
</div>
<h5 class="title is-5">Color Scales</h5>
<div class="multiples is-left-aligned with-spacing">
{{#each this.scales as |scale|}}
<div class="mock-hover-region" style="width:300px;height:200px">
{{scale}}
<ChartPrimitives::Tooltip @active={{true}} @style="left:70%;top:75%" @data={{this.dataMultiple}} as |series datum index|>
<li>
<span class="label"><span class="color-swatch swatch-{{scale}} swatch-{{scale}}-{{index}}" />{{series.name}}</span>
<span class="value">{{datum.value}}</span>
</li>
</ChartPrimitives::Tooltip>
</div>
{{/each}}
</div>
`,
context: {
style: 'left:70%;top:50%;',
dataSingle: [{ series: { name: 'Example', value: 12 } }],
dataMultiple: [
{ series: { name: 'One' }, datum: { value: 12 }, index: 0 },
{ series: { name: 'Two' }, datum: { value: 24 }, index: 1 },
{ series: { name: 'Three' }, datum: { value: 36 }, index: 2 },
{ series: { name: 'Four' }, datum: { value: 48 }, index: 3 },
{ series: { name: 'Five' }, datum: { value: 60 }, index: 4 },
{ series: { name: 'Six' }, datum: { value: 72 }, index: 5 },
{ series: { name: 'Seven' }, datum: { value: 84 }, index: 6 },
],
scales: ['reds', 'blues', 'ordinal'],
},
});

View File

@@ -1,145 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
import EmberObject, { computed } from '@ember/object';
import { on } from '@ember/object/evented';
export default {
title: 'Charts/Progress Bar',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Progress Bar</h5>
<div class="inline-chart tooltip" role="tooltip" aria-label="5 / 15">
<progress
class="progress is-primary is-small"
value="0.33"
max="1">
0.33
</progress>
</div>
`,
};
};
export let Colors = () => {
return {
template: hbs`
<h5 class="title is-5">Progress Bar colors</h5>
<div class="columns">
<div class="column">
<div class="inline-chart tooltip" role="tooltip" aria-label="5 / 15">
<progress
class="progress is-info is-small"
value="0.33"
max="1">
0.33
</progress>
</div>
</div>
<div class="column">
<div class="inline-chart tooltip" role="tooltip" aria-label="5 / 15">
<progress
class="progress is-success is-small"
value="0.33"
max="1">
0.33
</progress>
</div>
</div>
<div class="column">
<div class="inline-chart tooltip" role="tooltip" aria-label="5 / 15">
<progress
class="progress is-warning is-small"
value="0.33"
max="1">
0.33
</progress>
</div>
</div>
<div class="column">
<div class="inline-chart tooltip" role="tooltip" aria-label="5 / 15">
<progress
class="progress is-danger is-small"
value="0.33"
max="1">
0.33
</progress>
</div>
</div>
</div>
`,
};
};
export let LiveUpdates = () => {
return {
template: hbs`
<h5 class="title is-5">Progress Bar with live updates</h5>
<div class="columns">
<div class="column is-one-third">
<div class="inline-chart tooltip" role="tooltip" aria-label="{{data.numerator}} / {{data.denominator}}">
<progress
class="progress is-primary is-small"
value="{{data.percentage}}"
max="1">
{{data.percentage}}
</progress>
</div>
</div>
</div>
<p class="annotation">
<div class="boxed-section">
<div class="boxed-section-body is-dark">
<JsonViewer @json={{data.liveDetails}} />
</div>
</div>
</p>
`,
context: {
data: EmberObject.extend({
timerTicks: 0,
startTimer: on('init', function () {
this.set(
'timer',
setInterval(() => {
this.incrementProperty('timerTicks');
}, 1000)
);
}),
willDestroy() {
clearInterval(this.timer);
},
denominator: computed('timerTicks', function () {
return Math.round(Math.random() * 1000);
}),
percentage: computed('timerTicks', function () {
return Math.round(Math.random() * 100) / 100;
}),
numerator: computed('denominator', 'percentage', function () {
return Math.round(this.denominator * this.percentage * 100) / 100;
}),
liveDetails: computed(
'denominator',
'numerator',
'percentage',
function () {
return this.getProperties('denominator', 'numerator', 'percentage');
}
),
}).create(),
},
};
};

View File

@@ -1,116 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
import DelayedTruth from '../utils/delayed-truth';
import {
withKnobs,
optionsKnob,
number,
boolean,
} from '@storybook/addon-knobs';
export default {
title: 'Charts/Recomendation Chart',
decorators: [withKnobs],
};
export let Configurable = () => {
return {
template: hbs`
<SvgPatterns />
{{#if delayedTruth.complete}}
<Das::RecommendationChart
@resource={{resource}}
@currentValue={{current}}
@recommendedValue={{recommendedValue}}
@stats={{stats}}
@disabled={{disabled}}
/>
{{/if}}
`,
context: contextFactory(),
};
};
export let Standard = () => {
return {
template: hbs`
<SvgPatterns />
<div style="max-width: 500px">
{{#if delayedTruth.complete}}
<Das::RecommendationChart
@resource="CPU"
@currentValue={{cpu.current}}
@recommendedValue={{cpu.recommendedValue}}
@stats={{cpu.stats}}
/>
<Das::RecommendationChart
@resource="MemoryMB"
@currentValue={{mem.current}}
@recommendedValue={{mem.recommendedValue}}
@stats={{mem.stats}}
/>
<hr/>
<Das::RecommendationChart
@resource="CPU"
@currentValue={{cpu.current}}
@recommendedValue={{cpu.recommendedValue}}
@stats={{cpu.stats}}
@disabled={{true}}
/>
<Das::RecommendationChart
@resource="MemoryMB"
@currentValue={{mem.current}}
@recommendedValue={{mem.recommendedValue}}
@stats={{mem.stats}}
/>
{{/if}}
</div>
`,
context: {
delayedTruth: DelayedTruth.create(),
cpu: {
current: 100,
recommendedValue: 600,
stats: {
mean: 300,
p99: 500,
max: 525,
},
},
mem: {
current: 2048,
recommendedValue: 256,
stats: {
mean: 140,
p99: 215,
max: 225,
},
},
},
};
};
function contextFactory() {
const numberConfig = { range: true, min: 0, max: 1000, step: 1 };
return {
delayedTruth: DelayedTruth.create(),
resource: optionsKnob(
'Resource',
{ Cpu: 'CPU', Memory: 'MemoryMB' },
'CPU',
{ display: 'inline-radio' }
),
current: number('Current', 100, numberConfig),
recommendedValue: number('Recommendation', 300, numberConfig),
stats: {
mean: number('Stat: mean', 150, numberConfig),
p99: number('Stat: p99', 600, numberConfig),
max: number('Stat: max', 650, numberConfig),
},
disabled: boolean('Disabled', false),
};
}

View File

@@ -1,125 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
import EmberObject, { computed } from '@ember/object';
import { on } from '@ember/object/evented';
import moment from 'moment';
import DelayedArray from '../utils/delayed-array';
export default {
title: 'Charts/Stats Time Series',
};
let ts = (offset) => moment().subtract(offset, 'm').toDate();
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Stats Time Series</h5>
<div class="block" style="height:100px; width: 400px;">
{{#if staticMetrics}}
<StatsTimeSeries @data={{staticMetrics}} @chartClass="is-primary" />
{{/if}}
</div>
`,
context: {
staticMetrics: DelayedArray.create([
{ timestamp: ts(20), percent: 0.5 },
{ timestamp: ts(18), percent: 0.5 },
{ timestamp: ts(16), percent: 0.4 },
{ timestamp: ts(14), percent: 0.3 },
{ timestamp: ts(12), percent: 0.9 },
{ timestamp: ts(10), percent: 0.3 },
{ timestamp: ts(8), percent: 0.3 },
{ timestamp: ts(6), percent: 0.4 },
{ timestamp: ts(4), percent: 0.5 },
{ timestamp: ts(2), percent: 0.6 },
{ timestamp: ts(0), percent: 0.6 },
]),
},
};
};
export let HighLowComparison = () => {
return {
template: hbs`
<h5 class="title is-5">Stats Time Series high/low comparison</h5>
<div class="columns">
<div class="block column" style="height:200px; width:400px">
{{#if data.metricsHigh}}
<StatsTimeSeries @data={{data.metricsHigh}} @chartClass="is-info" />
{{/if}}
</div>
<div class="block column" style="height:200px; width:400px">
{{#if data.metricsLow}}
<StatsTimeSeries @data={{data.metricsLow}} @chartClass="is-info" />
{{/if}}
</div>
</div>
<p class="annotation">Line charts, and therefore stats time series charts, use a letant linear gradient with a height equal to the canvas. This makes the color intensity of the gradient at values consistent across charts as long as those charts have the same y-axis domain.</p>
<p class="annotation">This is used to great effect with stats charts since they all have a y-axis domain of 0-100%.</p>
`,
context: {
data: EmberObject.extend({
timerTicks: 0,
startTimer: on('init', function () {
this.set(
'timer',
setInterval(() => {
let metricsHigh = this.metricsHigh;
let prev = metricsHigh.length
? metricsHigh[metricsHigh.length - 1].percent
: 0.9;
this.appendTSValue(
metricsHigh,
Math.min(Math.max(prev + Math.random() * 0.05 - 0.025, 0.5), 1)
);
let metricsLow = this.metricsLow;
let prev2 = metricsLow.length
? metricsLow[metricsLow.length - 1].percent
: 0.1;
this.appendTSValue(
metricsLow,
Math.min(Math.max(prev2 + Math.random() * 0.05 - 0.025, 0), 0.5)
);
}, 1000)
);
}),
appendTSValue(array, percent, maxLength = 300) {
array.addObject({
timestamp: Date.now(),
percent,
});
if (array.length > maxLength) {
array.splice(0, array.length - maxLength);
}
},
willDestroy() {
clearInterval(this.timer);
},
metricsHigh: computed(function () {
return [];
}),
metricsLow: computed(function () {
return [];
}),
secondsFormat() {
return (date) => moment(date).format('HH:mm:ss');
},
}).create(),
},
};
};

View File

@@ -1,195 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
import DelayedTruth from '../utils/delayed-truth';
import { withKnobs, boolean } from '@storybook/addon-knobs';
import { getOwner } from '@ember/application';
import { tracked } from '@glimmer/tracking';
import { scaleLinear } from 'd3-scale';
import faker from 'faker';
export default {
title: 'Charts/Topo Viz',
decorators: [withKnobs],
};
const nodeGen = (name, datacenter, memory, cpu, allocations = []) => ({
datacenter,
memory,
cpu,
node: { name, isEligible: true, isDraining: false },
allocations: allocations.map((alloc) => ({
memory: alloc.memory,
cpu: alloc.cpu,
memoryPercent: alloc.memory / memory,
cpuPercent: alloc.cpu / cpu,
allocation: {
id: faker.random.uuid(),
isScheduled: true,
clientStatus: alloc.clientStatus,
},
})),
});
const nodeModelGen = (datacenter, id, name, resources = '2000/1000') => {
const [cpu, memory] = resources.split('/');
return {
datacenter,
id,
name,
isEligible: true,
isDraining: false,
resources: { cpu, memory },
};
};
const allocModelGen = (
id,
taskGroupName,
clientStatus,
nodeId,
jobId,
resources = '100/100'
) => {
const [cpu, memory] = resources.split('/');
return {
id,
taskGroupName,
clientStatus,
isScheduled: true,
allocatedResources: { cpu, memory },
belongsTo(t) {
return {
id() {
return t === 'node' ? nodeId : jobId;
},
};
},
};
};
export let Node = () => ({
template: hbs`
<SvgPatterns />
{{#if delayedTruth.complete}}
<TopoViz::Node
@node={{node}}
@isDense={{isDense}}
@heightScale={{heightScale}}
/>
{{/if}}
`,
context: {
delayedTruth: DelayedTruth.create(),
isDense: boolean('isDense', false),
heightScale: scaleLinear().range([15, 40]).domain([100, 1000]),
node: nodeGen('Node One', 'dc1', 1000, 1000, [
{ memory: 100, cpu: 100, clientStatus: 'pending' },
{ memory: 250, cpu: 300, clientStatus: 'running' },
{ memory: 300, cpu: 200, clientStatus: 'running' },
]),
},
});
export let Datacenter = () => ({
template: hbs`
<SvgPatterns />
{{#if delayedTruth.complete}}
<TopoViz::Datacenter
@datacenter={{dc}}
@isSingleColumn={{isSingleColumn}}
@isDense={{isDense}}
@heightScale={{heightScale}}
/>
{{/if}}
`,
context: {
delayedTruth: DelayedTruth.create(),
isSingleColumn: boolean('isSingleColumn', false),
isDense: boolean('isDense', false),
heightScale: scaleLinear().range([15, 40]).domain([100, 1000]),
dc: {
name: 'dc1',
nodes: [
nodeGen('Node One', 'dc1', 1000, 1000, [
{ memory: 100, cpu: 100, clientStatus: 'pending' },
{ memory: 250, cpu: 300, clientStatus: 'running' },
{ memory: 300, cpu: 200, clientStatus: 'running' },
]),
nodeGen('And Two', 'dc1', 500, 1000, [
{ memory: 100, cpu: 100, clientStatus: 'pending' },
{ memory: 250, cpu: 300, clientStatus: 'running' },
{ memory: 100, cpu: 100, clientStatus: 'running' },
{ memory: 100, cpu: 100, clientStatus: 'running' },
{ memory: 100, cpu: 100, clientStatus: 'running' },
]),
nodeGen('Three', 'dc1', 500, 500, [
{ memory: 100, cpu: 300, clientStatus: 'running' },
{ memory: 300, cpu: 200, clientStatus: 'pending' },
]),
],
},
},
});
export let FullViz = () => ({
template: hbs`
{{#if delayedTruth.complete}}
<TopoViz
@nodes={{nodes}}
@allocations={{allocations}}
/>
{{/if}}
`,
context: {
delayedTruth: DelayedTruth.create(),
nodes: [
nodeModelGen('dc1', '1', 'pdx-1', '2000/1000'),
nodeModelGen('dc1', '2', 'pdx-2', '2000/1000'),
nodeModelGen('dc1', '3', 'pdx-3', '2000/3000'),
nodeModelGen('dc2', '4', 'yyz-1', '2000/1000'),
nodeModelGen('dc2', '5', 'yyz-2', '2000/2000'),
],
allocations: [
allocModelGen('1', 'name', 'running', '1', 'job-1', '200/500'),
allocModelGen('1', 'name', 'running', '5', 'job-1', '200/500'),
],
},
});
export let EmberData = () => ({
template: hbs`
<div class="notification is-info">
<h3 class='title is-4'>This visualization uses data from mirage.</h3>
<p>Change the mirage scenario to see different cluster states visualized.</p>
</div>
{{#if (and delayedTruth.complete nodes allocations)}}
<TopoViz
@nodes={{nodes}}
@allocations={{allocations}}
/>
{{/if}}
`,
context: {
delayedTruth: DelayedTruth.create(),
nodes: tracked([]),
allocations: tracked([]),
async init() {
this._super(...arguments);
const owner = getOwner(this);
const store = owner.lookup('service:store');
this.nodes = await store.query('node', { resources: true });
this.allocations = await store.query('allocation', {
resources: true,
task_states: false,
namespace: '*',
});
},
},
});

View File

@@ -1,89 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
import productMetadata from '../../app/utils/styleguide/product-metadata';
export default {
title: 'Components/Accordion',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Accordion</h5>
<ListAccordion @source={{products}} @key="name" as |ac|>
<ac.head @buttonLabel="details">
<div class="columns inline-definitions">
<div class="column is-1">{{ac.item.name}}</div>
<div class="column is-1">
<span class="bumper-left badge is-light">{{ac.item.lang}}</span>
</div>
</div>
</ac.head>
<ac.body>
<h1 class="title is-4">{{ac.item.name}}</h1>
<p>{{ac.item.desc}}</p>
<p><a href="{{ac.item.link}}" target="_parent">Learn more...</a></p>
</ac.body>
</ListAccordion>
`,
context: {
products: productMetadata,
},
};
};
export let OneItem = () => {
return {
template: hbs`
<h5 class="title is-5">Accordion, one item</h5>
<ListAccordion @source={{take 1 products}} @key="name" as |a|>
<a.head @buttonLabel="details">
<div class="columns inline-definitions">
<div class="column is-1">{{a.item.name}}</div>
<div class="column is-1">
<span class="bumper-left badge is-light">{{a.item.lang}}</span>
</div>
</div>
</a.head>
<a.body>
<h1 class="title is-4">{{a.item.name}}</h1>
<p>{{a.item.desc}}</p>
<p><a href="{{a.item.link}}">Learn more...</a></p>
</a.body>
</ListAccordion>
`,
context: {
products: productMetadata,
},
};
};
export let NotExpandable = () => {
return {
template: hbs`
<h5 class="title is-5">Accordion, not expandable</h5>
<ListAccordion @source={{products}} @key="name" as |a|>
<a.head @buttonLabel="details" @isExpandable={{eq a.item.lang "golang"}}>
<div class="columns inline-definitions">
<div class="column is-1">{{a.item.name}}</div>
<div class="column is-1">
<span class="bumper-left badge is-light">{{a.item.lang}}</span>
</div>
</div>
</a.head>
<a.body>
<h1 class="title is-4">{{a.item.name}}</h1>
<p>{{a.item.desc}}</p>
<p><a href="{{a.item.link}}">Learn more...</a></p>
</a.body>
</ListAccordion>
`,
context: {
products: productMetadata,
},
};
};

View File

@@ -1,107 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components/Alerts',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Alert</h5>
<div class="notification is-info">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<p class="annotation">Alerts use Bulma's notification component.</p>
`,
};
};
export let Colors = () => {
return {
template: hbs`
<h5 class="title is-5">Alert colors</h5>
<div class="notification is-info">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="notification is-success">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="notification is-warning">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="notification is-danger">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<p class="annotation">Alerts are always paired with an emotive color. If there is no emotive association with the content of the alert, then an alert is the wrong component to use.</p>
`,
};
};
export let Dismissal = () => {
return {
template: hbs`
<h5 class="title is-5">Alert dismissal</h5>
<div class="notification is-info">
<div class="columns">
<div class="column">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="column is-centered is-minimum">
<button class="button is-info">Okay</button>
</div>
</div>
</div>
<div class="notification is-success">
<div class="columns">
<div class="column">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="column is-centered is-minimum">
<button class="button is-success">Okay</button>
</div>
</div>
</div>
<div class="notification is-warning">
<div class="columns">
<div class="column">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="column is-centered is-minimum">
<button class="button is-warning">Okay</button>
</div>
</div>
</div>
<div class="notification is-danger">
<div class="columns">
<div class="column">
<h3 class="title is-4">This is an alert</h3>
<p>Alerts are used for both situational and reactionary information.</p>
</div>
<div class="column is-centered is-minimum">
<button class="button is-danger">Okay</button>
</div>
</div>
</div>
`,
};
};

View File

@@ -1,169 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
import { withKnobs, optionsKnob } from '@storybook/addon-knobs';
export default {
title: 'Components/Boxed Section',
decorators: [withKnobs],
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Boxed section</h5>
<div class="boxed-section {{variant}}">
<div class="boxed-section-head">
Boxed Section
</div>
<div class="boxed-section-body">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
</div>
`,
context: contextFactory(),
};
};
export let RightHandDetails = () => {
return {
template: hbs`
<h5 class="title is-5">Boxed section with right hand details</h5>
<div class="boxed-section {{variant}}">
<div class="boxed-section-head">
Boxed Section With Right Hand Details
<span class="pull-right">{{now interval=1000}}</span>
</div>
<div class="boxed-section-body">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
</div>
`,
context: contextFactory(),
};
};
export let TitleDecoration = () => {
return {
template: hbs`
<h5 class="title is-5">Boxed section with title decoration</h5>
<div class="boxed-section {{variant}}">
<div class="boxed-section-head">
Boxed Section With Title Decoration
<span class="badge is-white">7</span>
</div>
<div class="boxed-section-body">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
</div>
`,
context: contextFactory(),
};
};
export let Foot = () => {
return {
template: hbs`
<h5 class="title is-5">Boxed section with foot</h5>
<div class="boxed-section {{variant}}">
<div class="boxed-section-head">
Boxed Section With Large Header
</div>
<div class="boxed-section-body with-foot">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
<div class="boxed-section-foot">
<span>Left-aligned message</span>
<a href="javascript:;" class="pull-right">Toggle or other action</a>
</div>
</div>
`,
context: contextFactory(),
};
};
export let LargeHeader = () => {
return {
template: hbs`
<h5 class="title is-5">Boxed section with large header</h5>
<div class="boxed-section {{variant}}">
<div class="boxed-section-head">
<div class="boxed-section-row">
Boxed Section With Large Header
<span class="badge is-white is-subtle bumper-left">Status</span>
</div>
<div class="boxed-section-row">
<span class="tag is-outlined">A tag that goes on a second line because it's rather long</span>
</div>
</div>
<div class="boxed-section-body">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
</div>
`,
context: contextFactory(),
};
};
export let DarkBody = () => {
return {
template: hbs`
<h5 class="title is-5">Boxed section with dark body</h5>
<div class="boxed-section {{variant}}">
<div class="boxed-section-head">
Boxed Section With Dark Body
</div>
<div class="boxed-section-body is-dark">
<div class="mock-content">
<div class="mock-image"></div>
<div class="mock-copy"></div>
<div class="mock-copy"></div>
</div>
</div>
</div>
`,
context: contextFactory(),
};
};
function contextFactory() {
return {
variant: optionsKnob(
'Variant',
{
Normal: '',
Info: 'is-info',
Warning: 'is-warning',
Danger: 'is-danger',
},
'',
{
display: 'inline-radio',
},
'variant-id'
),
};
}

View File

@@ -1,50 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components/Breadcrumbs',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Breadcrumbs</h5>
<div class="navbar is-secondary">
<div class="navbar-item"></div>
<nav class="breadcrumb is-large">
<li>
<a href="javascript:;">Topic</a>
</li>
<li>
<a href="javascript:;">Sub-topic</a>
</li>
<li class="is-active">
<a href="javascript:;">Active Topic</a>
</li>
</nav>
</div>
<p class="annotation">Breadcrumbs are only ever used in the secondary nav of the primary header.</p>
`,
};
};
export let Single = () => {
return {
template: hbs`
<h5 class="title is-5">Single breadcrumb</h5>
<div class="navbar is-secondary">
<div class="navbar-item"></div>
<nav class="breadcrumb is-large">
<li>
<a href="javascript:;">Topic</a>
</li>
</nav>
</div>
<p class="annotation">Breadcrumbs are given a lot of emphasis and often double as a page title. Since they are also global state, they are important for helping a user keep their bearings.</p>
`,
};
};

View File

@@ -1,135 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components/Buttons',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Buttons</h5>
<div class="block">
<a class="button">Button</a>
<a class="button is-white">White</a>
<a class="button is-light">Light</a>
<a class="button is-dark">Dark</a>
<a class="button is-black">Black</a>
<a class="button is-link">Link</a>
</div>
<div class="block">
<a class="button is-primary">Primary</a>
<a class="button is-info">Info</a>
<a class="button is-success">Success</a>
<a class="button is-warning">Warning</a>
<a class="button is-danger">Danger</a>
</div>
`,
};
};
export let Outline = () => {
return {
template: hbs`
<h5 class="title is-5">Outline buttons</h5>
<div class="block">
<a class="button is-outlined">Outlined</a>
<a class="button is-primary is-outlined">Primary</a>
<a class="button is-info is-outlined">Info</a>
<a class="button is-success is-outlined">Success</a>
<a class="button is-warning is-outlined">Warning</a>
<a class="button is-danger is-outlined is-important">Danger</a>
</div>
`,
};
};
export let Hollow = () => {
return {
template: hbs`
<h5 class="title is-5">Hollow buttons</h5>
<div class="block" style="background:#25BA81; padding:30px">
<a class="button is-primary is-inverted is-outlined">Primary</a>
<a class="button is-info is-inverted is-outlined">Info</a>
<a class="button is-success is-inverted is-outlined">Success</a>
<a class="button is-warning is-inverted is-outlined">Warning</a>
<a class="button is-danger is-inverted is-outlined">Danger</a>
</div>
`,
};
};
export let Sizes = () => {
return {
template: hbs`
<h5 class="title is-5">Button sizes</h5>
<div class="block">
<a class="button is-small">Small</a>
<a class="button">Normal</a>
<a class="button is-medium">Medium</a>
<a class="button is-large">Large</a>
</div>
`,
};
};
export let Disabled = () => {
return {
template: hbs`
<h5 class="title is-5">Anchor elements as buttons</h5>
<div class="block">
<a class="button is-disabled">Button</a>
<a class="button is-white is-disabled">White</a>
<a class="button is-light is-disabled">Light</a>
<a class="button is-dark is-disabled">Dark</a>
<a class="button is-black is-disabled">Black</a>
<a class="button is-link is-disabled">Link</a>
</div>
<div class="block">
<a class="button is-primary is-disabled">Primary</a>
<a class="button is-info is-disabled">Info</a>
<a class="button is-success is-disabled">Success</a>
<a class="button is-warning is-disabled">Warning</a>
<a class="button is-danger is-disabled">Danger</a>
</div>
<h5 class="title is-5">Button elements with <code>disabled</code> attribute</h5>
<div class="block">
<button class="button is-disabled" disabled>Button</button>
<button class="button is-white is-disabled" disabled>White</button>
<button class="button is-light is-disabled" disabled>Light</button>
<button class="button is-dark is-disabled" disabled>Dark</button>
<button class="button is-black is-disabled" disabled>Black</button>
<button class="button is-link is-disabled" disabled>Link</button>
</div>
<div class="block">
<button class="button is-primary is-disabled" disabled>Primary</button>
<button class="button is-info is-disabled" disabled>Info</button>
<button class="button is-success is-disabled" disabled>Success</button>
<button class="button is-warning is-disabled" disabled>Warning</button>
<button class="button is-danger is-disabled" disabled>Danger</button>
</div>
<h5 class="title is-5">Button elements with <code>aria-disabled="true"</code></h5>
<div class="block">
<button class="button is-disabled" aria-disabled="true">Button</button>
<button class="button is-white is-disabled" aria-disabled="true">White</button>
<button class="button is-light is-disabled" aria-disabled="true">Light</button>
<button class="button is-dark is-disabled" aria-disabled="true">Dark</button>
<button class="button is-black is-disabled" aria-disabled="true">Black</button>
<button class="button is-link is-disabled" aria-disabled="true">Link</button>
</div>
<div class="block">
<button class="button is-primary is-disabled" aria-disabled="true">Primary</button>
<button class="button is-info is-disabled" aria-disabled="true">Info</button>
<button class="button is-success is-disabled" aria-disabled="true">Success</button>
<button class="button is-warning is-disabled" aria-disabled="true">Warning</button>
<button class="button is-danger is-disabled" aria-disabled="true">Danger</button>
</div>
`,
};
};

View File

@@ -1,34 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
import { withKnobs, text } from '@storybook/addon-knobs';
export default {
title: 'Components/Copy Button',
decorators: [withKnobs],
};
export let CopyButton = () => {
return {
template: hbs`
<h5 class="title is-5">Copy Button</h5>
<span class="tag is-hollow is-small no-text-transform">
{{clipboardText}}
<CopyButton @clipboardText={{clipboardText}} />
</span>
<h5 class="title is-5">Copy Button with content</h5>
<span class="tag is-hollow is-small no-text-transform">
<CopyButton @clipboardText={{clipboardText}}>{{clipboardText}}</CopyButton>
</span>
`,
context: {
clipboardText: text(
'Clipboard Text',
'e8c898a0-794b-9063-7a7f-bf0c4a405f83'
),
},
};
};

View File

@@ -1,736 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components/Diff Viewer',
};
export let DiffViewerWithInsertions = () => {
return {
template: hbs`
<h5 class="title is-5">Diff Viewer with insertions</h5>
<div class="boxed-section">
<div class="boxed-section-body is-dark">
<JobDiff @diff={{insertionsOnly}} />
</div>
</div>
`,
context: {
insertionsOnly: generateDiff([
{
Annotations: null,
Name: 'Attempts',
New: '15',
Old: '15',
Type: 'None',
},
{
Annotations: null,
Name: 'Delay',
New: '25000000000',
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'Interval',
New: '900000000000',
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'Mode',
New: 'delay',
Old: 'delay',
Type: 'None',
},
]),
},
};
};
export let DiffViewerWithDeletions = () => {
return {
template: hbs`
<h5 class="title is-5">Diff Viewer with deletions</h5>
<div class="boxed-section">
<div class="boxed-section-body is-dark">
<JobDiff @diff={{deletionsOnly}} />
</div>
</div>
`,
context: {
deletionsOnly: generateDiff([
{
Annotations: null,
Name: 'Attempts',
New: '15',
Old: '15',
Type: 'None',
},
{
Annotations: null,
Name: 'Delay',
New: '25000000000',
Old: '25000000000',
Type: 'None',
},
{
Annotations: null,
Name: 'Interval',
New: '900000000000',
Old: '900000000000',
Type: 'None',
},
{
Annotations: null,
Name: 'Mode',
New: '',
Old: 'delay',
Type: 'Deleted',
},
]),
},
};
};
export let DiffViewerWithEdits = () => {
return {
template: hbs`
<h5 class="title is-5">Diff Viewer with edits</h5>
<div class="boxed-section">
<div class="boxed-section-body is-dark">
<JobDiff @diff={{editsOnly}} />
</div>
<p class="annotation">Often times a diff will only have a couple lines. Minor tweaks to a job spec result in small diffs.</p>
</div>
`,
context: {
editsOnly: generateDiff([
{
Annotations: null,
Name: 'Attempts',
New: '15',
Old: '15',
Type: 'None',
},
{
Annotations: null,
Name: 'Delay',
New: '25000000000',
Old: '25000000000',
Type: 'None',
},
{
Annotations: null,
Name: 'Interval',
New: '900000000000',
Old: '250000000000',
Type: 'Edited',
},
{
Annotations: null,
Name: 'Mode',
New: 'delay',
Old: 'delay',
Type: 'None',
},
]),
},
};
};
export let DiffViewerWithManyChanges = () => {
return {
template: hbs`
<h5 class="title is-5">Diff Viewer with many changes</h5>
<div class="boxed-section">
<div class="boxed-section-body is-dark">
<JobDiff @diff={{largeDiff}} />
</div>
</div>
`,
context: {
largeDiff: {
Fields: null,
ID: 'example',
Objects: null,
TaskGroups: [
{
Fields: null,
Name: 'cache',
Objects: null,
Tasks: [
{
Annotations: null,
Fields: [
{
Annotations: null,
Name: 'Meta[one]',
New: "flew over the cuckoo's nest",
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'Meta[two]',
New: 'birds on a wire',
Old: '',
Type: 'Added',
},
],
Name: 'redis',
Objects: [
{
Fields: [
{
Annotations: null,
Name: 'image',
New: 'redis:3.4',
Old: 'redis:7',
Type: 'Edited',
},
{
Annotations: null,
Name: 'port_map[0][db]',
New: '6380',
Old: '6379',
Type: 'Edited',
},
],
Name: 'Config',
Objects: null,
Type: 'Edited',
},
{
Fields: [
{
Annotations: null,
Name: 'CPU',
New: '1000',
Old: '500',
Type: 'Edited',
},
{
Annotations: null,
Name: 'DiskMB',
New: '0',
Old: '0',
Type: 'None',
},
{
Annotations: null,
Name: 'IOPS',
New: '0',
Old: '0',
Type: 'None',
},
{
Annotations: null,
Name: 'MemoryMB',
New: '512',
Old: '256',
Type: 'Edited',
},
],
Name: 'Resources',
Objects: [
{
Fields: [
{
Annotations: null,
Name: 'MBits',
New: '100',
Old: '',
Type: 'Added',
},
],
Name: 'Network',
Objects: [
{
Fields: [
{
Annotations: null,
Name: 'Label',
New: 'db',
Old: '',
Type: 'Added',
},
],
Name: 'Dynamic Port',
Objects: null,
Type: 'Added',
},
],
Type: 'Added',
},
{
Fields: [
{
Annotations: null,
Name: 'MBits',
New: '',
Old: '10',
Type: 'Deleted',
},
],
Name: 'Network',
Objects: [
{
Fields: [
{
Annotations: null,
Name: 'Label',
New: '',
Old: 'db',
Type: 'Deleted',
},
],
Name: 'Dynamic Port',
Objects: null,
Type: 'Deleted',
},
],
Type: 'Deleted',
},
],
Type: 'Edited',
},
{
Fields: [
{
Annotations: null,
Name: 'AddressMode',
New: 'auto',
Old: 'auto',
Type: 'None',
},
{
Annotations: null,
Name: 'Name',
New: 'redis-cache',
Old: 'redis-cache',
Type: 'None',
},
{
Annotations: null,
Name: 'PortLabel',
New: 'db',
Old: 'db',
Type: 'None',
},
],
Name: 'Service',
Objects: [
{
Fields: [
{
Annotations: null,
Name: 'Tags',
New: 'redis',
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'Tags',
New: 'cache',
Old: 'cache',
Type: 'None',
},
{
Annotations: null,
Name: 'Tags',
New: 'global',
Old: 'global',
Type: 'None',
},
],
Name: 'Tags',
Objects: null,
Type: 'Added',
},
{
Fields: [
{
Annotations: null,
Name: 'AddressMode',
New: '',
Old: '',
Type: 'None',
},
{
Annotations: null,
Name: 'Command',
New: '',
Old: '',
Type: 'None',
},
{
Annotations: null,
Name: 'GRPCService',
New: '',
Old: '',
Type: 'None',
},
{
Annotations: null,
Name: 'GRPCUseTLS',
New: 'false',
Old: 'false',
Type: 'None',
},
{
Annotations: null,
Name: 'InitialStatus',
New: '',
Old: '',
Type: 'None',
},
{
Annotations: null,
Name: 'Interval',
New: '15000000000',
Old: '10000000000',
Type: 'Edited',
},
{
Annotations: null,
Name: 'Method',
New: '',
Old: '',
Type: 'None',
},
{
Annotations: null,
Name: 'Name',
New: 'alive',
Old: 'alive',
Type: 'None',
},
{
Annotations: null,
Name: 'Path',
New: '',
Old: '',
Type: 'None',
},
{
Annotations: null,
Name: 'PortLabel',
New: '',
Old: '',
Type: 'None',
},
{
Annotations: null,
Name: 'Protocol',
New: '',
Old: '',
Type: 'None',
},
{
Annotations: null,
Name: 'TLSServerName',
New: '',
Old: '',
Type: 'None',
},
{
Annotations: null,
Name: 'TLSSkipVerify',
New: 'false',
Old: 'false',
Type: 'None',
},
{
Annotations: null,
Name: 'Timeout',
New: '7000000000',
Old: '2000000000',
Type: 'Edited',
},
{
Annotations: null,
Name: 'Type',
New: 'tcp',
Old: 'tcp',
Type: 'None',
},
],
Name: 'Check',
Objects: null,
Type: 'Edited',
},
],
Type: 'Edited',
},
],
Type: 'Edited',
},
],
Type: 'Edited',
Updates: null,
},
{
Fields: [
{
Annotations: null,
Name: 'Count',
New: '1',
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'Meta[key]',
New: 'value',
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'Meta[red]',
New: 'fish',
Old: '',
Type: 'Added',
},
],
Name: 'cache2',
Objects: [
{
Fields: [
{
Annotations: null,
Name: 'Attempts',
New: '2',
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'Delay',
New: '15000000000',
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'Interval',
New: '1800000000000',
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'Mode',
New: 'fail',
Old: '',
Type: 'Added',
},
],
Name: 'RestartPolicy',
Objects: null,
Type: 'Added',
},
{
Fields: [
{
Annotations: null,
Name: 'Migrate',
New: 'false',
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'SizeMB',
New: '300',
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'Sticky',
New: 'false',
Old: '',
Type: 'Added',
},
],
Name: 'EphemeralDisk',
Objects: null,
Type: 'Added',
},
],
Tasks: [
{
Annotations: null,
Fields: [
{
Annotations: null,
Name: 'Driver',
New: 'docker',
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'KillTimeout',
New: '5000000000',
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'Leader',
New: 'false',
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'ShutdownDelay',
New: '0',
Old: '',
Type: 'Added',
},
],
Name: 'redis',
Objects: [
{
Fields: [
{
Annotations: null,
Name: 'image',
New: 'redis:7',
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'port_map[0][db]',
New: '6379',
Old: '',
Type: 'Added',
},
],
Name: 'Config',
Objects: null,
Type: 'Added',
},
{
Fields: [
{
Annotations: null,
Name: 'CPU',
New: '500',
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'DiskMB',
New: '0',
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'IOPS',
New: '0',
Old: '',
Type: 'Added',
},
{
Annotations: null,
Name: 'MemoryMB',
New: '256',
Old: '',
Type: 'Added',
},
],
Name: 'Resources',
Objects: [
{
Fields: [
{
Annotations: null,
Name: 'MBits',
New: '10',
Old: '',
Type: 'Added',
},
],
Name: 'Network',
Objects: [
{
Fields: [
{
Annotations: null,
Name: 'Label',
New: 'db',
Old: '',
Type: 'Added',
},
],
Name: 'Dynamic Port',
Objects: null,
Type: 'Added',
},
],
Type: 'Added',
},
],
Type: 'Added',
},
],
Type: 'Added',
},
],
Type: 'Added',
Updates: null,
},
],
Type: 'Edited',
},
},
};
};
function generateDiff(changeset) {
return {
Fields: null,
ID: 'insertions-only',
Objects: null,
TaskGroups: [
{
Fields: [
{
Annotations: null,
Name: 'Count',
New: '2',
Old: '2',
Type: 'None',
},
],
Name: 'cache',
Objects: [
{
Fields: changeset,
Name: 'RestartPolicy',
Objects: null,
Type: 'Edited',
},
],
Type: 'Edited',
Updates: null,
},
],
Type: 'Edited',
};
}

View File

@@ -1,88 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components/Dropdown',
};
let options = [
{ name: 'Consul' },
{ name: 'Nomad' },
{ name: 'Packer' },
{ name: 'Terraform' },
{ name: 'Vagrant' },
{ name: 'Vault' },
];
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Dropdown</h5>
<PowerSelect @options={{options}} @selected={{selectedOption}} @searchField="name" @searchEnabled={{gt options.length 10}} @onChange={{action (mut selectedOption)}} as |option|>
{{option.name}}
</PowerSelect>
<p class="annotation">Power Select currently fulfills all of Nomad's dropdown needs out of the box.</p>
`,
context: {
options,
},
};
};
export let Resized = () => {
return {
template: hbs`
<h5 class="title is-5">Dropdown resized</h5>
<div class="columns">
<div class="column is-3">
<PowerSelect @options={{options}} @selected={{selectedOption2}} @searchField="name" @searchEnabled={{gt options.length 10}} @onChange={{action (mut selectedOption2)}} as |option|>
{{option.name}}
</PowerSelect>
</div>
</div>
<p class="annotation">Dropdowns are always 100% wide. To control the width of a dropdown, adjust the dimensions of its container. One way to achieve this is using columns.</p>
`,
context: {
options,
},
};
};
export let Search = () => {
return {
template: hbs`
<h5 class="title is-5">Dropdown with search</h5>
<div class="columns">
<div class="column is-3">
<PowerSelect @options={{manyOptions}} @selected={{selectedOption3}} @searchField="name" @searchEnabled={{gt manyOptions.length 10}} @onChange={{action (mut selectedOption3)}} as |option|>
{{option.name}}
</PowerSelect>
</div>
</div>
<p class="annotation">Whether or not the dropdown has a search box is configurable. Typically the default is to show a search once a dropdown has more than 10 options.</p>
`,
context: {
manyOptions: [
'One',
'Two',
'Three',
'Four',
'Five',
'Six',
'Seven',
'Eight',
'Nine',
'Ten',
'Eleven',
'Twelve',
'Thirteen',
'Fourteen',
'Fifteen',
].map((name) => ({ name })),
},
};
};

View File

@@ -1,212 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components/Filter Facets',
};
let options1 = [
{ key: 'option-1', label: 'Option One' },
{ key: 'option-2', label: 'Option Two' },
{ key: 'option-3', label: 'Option Three' },
{ key: 'option-4', label: 'Option Four' },
{ key: 'option-5', label: 'Option Five' },
];
let selection1 = ['option-2', 'option-4', 'option-5'];
export let MultiSelect = () => {
return {
template: hbs`
<h5 class="title is-5">Multi-Select Dropdown</h5>
<MultiSelectDropdown
@label="Example Dropdown"
@options={{this.options1}}
@selection={{this.selection1}}
@onSelect={{action (mut selection1)}} />
<p class="annotation">A wrapper around basic-dropdown for creating a list of checkboxes and tracking the state thereof.</p>
`,
context: {
options1,
selection1,
},
};
};
export let SingleSelect = () => ({
template: hbs`
<h5 class="title is-5">Single-Select Dropdown</h5>
<SingleSelectDropdown
@label="Single"
@options={{this.options1}}
@selection={{this.selection}}
@onSelect={{action (mut this.selection)}} />
`,
context: {
options1,
selection: 'option-2',
},
});
export let RightAligned = () => {
return {
template: hbs`
<h5 class="title is-5">Multi-Select Dropdown right-aligned</h5>
<div style="display:flex; justify-content:flex-end">
<MultiSelectDropdown
@label="Example right-aligned Dropdown"
@options={{this.options1}}
@selection={{this.selection1}}
@onSelect={{action (mut selection1)}} />
</div>
`,
context: {
options1,
selection1,
},
};
};
export let ManyOptionsMulti = () => {
return {
template: hbs`
<h5 class="title is-5">Multi-Select Dropdown with many options</h5>
<MultiSelectDropdown
@label="Lots of options in here"
@options={{this.optionsMany}}
@selection={{this.selectionMany}}
@onSelect={{action (mut this.selectionMany)}} />
<p class="annotation">
A strength of the multi-select-dropdown is its simple presentation. It is quick to select options and it is quick to remove options.
However, this strength becomes a weakness when there are too many options. Since the selection isn't pinned in any way, removing a selection
can become an adventure of scrolling up and down. Also since the selection isn't pinned, this component can't support search, since search would
entirely mask the selection.
</p>
`,
context: {
optionsMany: Array(100)
.fill(null)
.map((_, i) => ({ label: `Option ${i}`, key: `option-${i}` })),
selectionMany: [],
},
};
};
export let ManyOptionsSingle = () => {
return {
template: hbs`
<h5 class="title is-5">Single-Select Dropdown with many options</h5>
<SingleSelectDropdown
@label="Lots of options in here"
@options={{this.optionsMany}}
@selection={{this.selection}}
@onSelect={{action (mut this.selection)}} />
<p class="annotation">
Single select supports search at a certain option threshold via Ember Power Select.
</p>
`,
context: {
optionsMany: Array(100)
.fill(null)
.map((_, i) => ({ label: `Option ${i}`, key: `option-${i}` })),
selection: 'option-1',
},
};
};
export let Bar = () => {
return {
template: hbs`
<h5 class="title is-5">Multi-Select Dropdown bar</h5>
<div class="button-bar">
<MultiSelectDropdown
@label="Datacenter"
@options={{this.optionsDatacenter}}
@selection={{this.selectionDatacenter}}
@onSelect={{action (mut this.selectionDatacenter)}} />
<MultiSelectDropdown
@label="Type"
@options={{this.optionsType}}
@selection={{this.selectionType}}
@onSelect={{action (mut this.selectionType)}} />
<MultiSelectDropdown
@label="Status"
@options={{this.optionsStatus}}
@selection={{this.selectionStatus}}
@onSelect={{action (mut this.selectionStatus)}} />
</div>
<h5 class="title is-5">Single-Select Dropdown bar</h5>
<div class="button-bar">
<SingleSelectDropdown
@label="Datacenter"
@options={{this.optionsDatacenter}}
@selection={{this.selectionDatacenterSingle}}
@onSelect={{action (mut this.selectionDatacenterSingle)}} />
<SingleSelectDropdown
@label="Type"
@options={{this.optionsType}}
@selection={{this.selectionTypeSingle}}
@onSelect={{action (mut this.selectionTypeSingle)}} />
<SingleSelectDropdown
@label="Status"
@options={{this.optionsStatus}}
@selection={{this.selectionStatusSingle}}
@onSelect={{action (mut this.selectionStatusSingle)}} />
</div>
<h5 class="title is-5">Mixed Dropdown bar</h5>
<div class="button-bar">
<SingleSelectDropdown
@label="Datacenter"
@options={{this.optionsDatacenter}}
@selection={{this.selectionDatacenterSingle}}
@onSelect={{action (mut this.selectionDatacenterSingle)}} />
<MultiSelectDropdown
@label="Type"
@options={{this.optionsType}}
@selection={{this.selectionType}}
@onSelect={{action (mut this.selectionType)}} />
<MultiSelectDropdown
@label="Status"
@options={{this.optionsStatus}}
@selection={{this.selectionStatus}}
@onSelect={{action (mut this.selectionStatus)}} />
</div>
<p class="annotation">
Since this is a core component for faceted search, it makes sense to letruct an arrangement of multi-select dropdowns.
Do this by wrapping all the options in a <code>.button-bar</code> container.
</p>
`,
context: {
optionsDatacenter: [
{ key: 'pdx-1', label: 'pdx-1' },
{ key: 'jfk-1', label: 'jfk-1' },
{ key: 'jfk-2', label: 'jfk-2' },
{ key: 'muc-1', label: 'muc-1' },
],
selectionDatacenter: ['jfk-1'],
selectionDatacenterSingle: 'jfk-1',
optionsType: [
{ key: 'batch', label: 'Batch' },
{ key: 'service', label: 'Service' },
{ key: 'system', label: 'System' },
{ key: 'periodic', label: 'Periodic' },
{ key: 'parameterized', label: 'Parameterized' },
],
selectionType: ['system', 'service'],
selectionTypeSingle: 'system',
optionsStatus: [
{ key: 'pending', label: 'Pending' },
{ key: 'running', label: 'Running' },
{ key: 'dead', label: 'Dead' },
],
selectionStatus: [],
selectionStatusSingle: 'dead',
},
};
};

View File

@@ -1,212 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components/Gutter Menu',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Gutter menu</h5>
<div class="columns">
<div class="column is-4">
<div class="gutter">
<aside class="menu">
<p class="menu-label">Places</p>
<ul class="menu-list">
<li><a href="javascript:;" class="is-active">Place One</a></li>
<li><a href="javascript:;">Place Two</a></li>
</ul>
<p class="menu-label">Features</p>
<ul class="menu-list">
<li><a href="javascript:;">Feature One</a></li>
<li><a href="javascript:;">Feature Two</a></li>
</ul>
</aside>
</div>
</div>
<div class="column">
<div class="mock-content">
<div class="mock-vague"></div>
</div>
</div>
</div>
`,
};
};
export let RichComponents = () => {
return {
template: hbs`
<h5 class="title is-5">Gutter navigation with rich components</h5>
<div class="columns">
<div class="column is-4">
<div class="gutter">
<aside class="menu">
<p class="menu-label">Places</p>
<ul class="menu-list">
<li>
<div class="menu-item">
<PowerSelect @selected={{or selection "One"}} @options={{array "One" "Two" "Three"}} @onChange={{action (mut selection)}} as |option|>
{{option}}
</PowerSelect>
</div>
</li>
<li><a href="javascript:;" class="is-active">Place One</a></li>
<li><a href="javascript:;">Place Two</a></li>
</ul>
<p class="menu-label">Features</p>
<ul class="menu-list">
<li><a href="javascript:;">Feature One</a></li>
<li><a href="javascript:;">Feature Two</a></li>
</ul>
</aside>
</div>
</div>
<div class="column">
<div class="mock-content">
<div class="mock-vague"></div>
</div>
</div>
</div>
<p class="annotation">In order to keep the gutter navigation streamlined and easy to navigation, rich components should be avoided when possible. When not possible, they should be kept near the top.</p>
`,
};
};
export let ManyItems = () => {
return {
template: hbs`
<h5 class="title is-5">Hypothetical gutter navigation with many items</h5>
<div class="columns">
<div class="column is-4">
<div class="gutter">
<aside class="menu">
<p class="menu-label">Places</p>
<ul class="menu-list">
{{#each (array "One Two" "Three" "Four" "Five" "Six" "Seven") as |item|}}
<li><a href="javascript:;">Place {{item}}</a></li>
{{/each}}
</ul>
<p class="menu-label">Features</p>
<ul class="menu-list">
{{#each (array "One Two" "Three" "Four" "Five" "Six" "Seven") as |item|}}
<li><a href="javascript:;">Feature {{item}}</a></li>
{{/each}}
</ul>
<p class="menu-label">Other</p>
<ul class="menu-list">
<li><a href="javascript:;" class="is-active">The one that didn't fit in</a></li>
</ul>
<p class="menu-label">Things</p>
<ul class="menu-list">
{{#each (array "One Two" "Three" "Four" "Five" "Six" "Seven") as |item|}}
<li><a href="javascript:;">Thing {{item}}</a></li>
{{/each}}
</ul>
</aside>
</div>
</div>
<div class="column">
<div class="mock-content">
<div class="mock-vague"></div>
</div>
</div>
</div>
<p class="annotation">There will only ever be one gutter menu in the Nomad UI, but it helps to imagine a situation where there are many navigation items in the gutter.</p>
`,
};
};
export let IconItems = () => {
return {
template: hbs`
<h5 class="title is-5">Hypothetical gutter navigation with icon items</h5>
<div class="columns">
<div class="column is-4">
<div class="gutter">
<aside class="menu">
<p class="menu-label">Places</p>
<ul class="menu-list">
<li><a href="javascript:;">{{x-icon "clock"}} Place One</a></li>
<li><a href="javascript:;" class="is-active">{{x-icon "history"}} Place Two</a></li>
</ul>
<p class="menu-label">Features</p>
<ul class="menu-list">
<li><a href="javascript:;">{{x-icon "alert-triangle"}} Feature One</a></li>
<li><a href="javascript:;">{{x-icon "media-pause"}} Feature Two</a></li>
</ul>
</aside>
</div>
</div>
<div class="column">
<div class="mock-content">
<div class="mock-vague"></div>
</div>
</div>
</div>
<p class="annotation">In the future, the gutter menu may have icons.</p>
`,
};
};
export let TaggedItems = () => {
return {
template: hbs`
<h5 class="title is-5">Hypothetical gutter navigation with icon items</h5>
<div class="columns">
<div class="column is-4">
<div class="gutter">
<aside class="menu">
<p class="menu-label">Places</p>
<ul class="menu-list">
<li><a href="javascript:;">Place One <span class="tag is-small">Beta</span></a></li>
<li><a href="javascript:;" class="is-active">{{x-icon "history"}} Place Two</a></li>
</ul>
<p class="menu-label">Features</p>
<ul class="menu-list">
<li><a href="javascript:;">{{x-icon "alert-triangle"}} Feature One</a></li>
<li><a href="javascript:;">{{x-icon "media-pause"}} Feature Two <span class="tag is-small is-warning">3</span></a></li>
</ul>
</aside>
</div>
</div>
<div class="column">
<div class="mock-content">
<div class="mock-vague"></div>
</div>
</div>
</div>
<p class="annotation">Tags can be used to denote beta features or low-priority notifications.</p>
`,
};
};
export let Global = () => {
return {
template: hbs`
<h5 class="title is-5">Global gutter navigation</h5>
<div class="columns">
<div class="column is-4">
<GutterMenu>
{{!-- Page content here --}}
</GutterMenu>
</div>
</div>
<p class="annotation">Since there will only ever be one gutter menu in the UI, it makes sense to express the menu as a singleton component. This is what that singleton component looks like.</p>
<p class="annotation"><strong>Note:</strong> Normally the gutter menu is rendered within a page layout and is fixed position. The columns shown in this example are only to imitate the actual width without applying fixed positioning.</p>
`,
};
};

View File

@@ -1,33 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components/Header',
};
export let Header = () => {
return {
template: hbs`
<h5 class="title is-5">Global header</h5>
<nav class="navbar is-primary">
<div class="navbar-brand">
<span class="gutter-toggle" aria-label="menu">
<HamburgerMenu />
</span>
<span class="navbar-item is-logo">
<NomadLogo />
</span>
</div>
<div class="navbar-end">
<a class="navbar-item">Secondary</a>
<a class="navbar-item">Links</a>
<a class="navbar-item">Here</a>
</div>
</nav>
`,
};
};

View File

@@ -1,97 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components/Inline Definitions',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Inline definitions</h5>
<div class="boxed-section is-small">
<div class="boxed-section-body inline-definitions">
<span class="label">Some Label</span>
<span class="pair">
<span class="term">Term Name</span>
<span>Term Value</span>
</span>
<span class="pair">
<span class="term">Running?</span>
<span>Yes</span>
</span>
<span class="pair">
<span class="term">Last Updated</span>
<span>{{format-ts (now)}}</span>
</span>
</div>
</div>
<p class="annotation">A way to tightly display key/value information. Typically seen at the top of pages.</p>
`,
};
};
export let Variants = () => {
return {
template: hbs`
<h5 class="title is-5">Inline definitions variants</h5>
<div class="boxed-section is-small is-success">
<div class="boxed-section-body inline-definitions">
<span class="label">Success Label</span>
<span class="pair">
<span class="term">Term Name</span>
<span>Term Value</span>
</span>
<span class="pair">
<span class="term">Last Updated</span>
<span>{{format-ts (now)}}</span>
</span>
</div>
</div>
<div class="boxed-section is-small is-warning">
<div class="boxed-section-body inline-definitions">
<span class="label">Warning Label</span>
<span class="pair">
<span class="term">Term Name</span>
<span>Term Value</span>
</span>
<span class="pair">
<span class="term">Last Updated</span>
<span>{{format-ts (now)}}</span>
</span>
</div>
</div>
<div class="boxed-section is-small is-danger">
<div class="boxed-section-body inline-definitions">
<span class="label">Danger Label</span>
<span class="pair">
<span class="term">Term Name</span>
<span>Term Value</span>
</span>
<span class="pair">
<span class="term">Last Updated</span>
<span>{{format-ts (now)}}</span>
</span>
</div>
</div>
<div class="boxed-section is-small is-info">
<div class="boxed-section-body inline-definitions">
<span class="label">Info Label</span>
<span class="pair">
<span class="term">Term Name</span>
<span>Term Value</span>
</span>
<span class="pair">
<span class="term">Last Updated</span>
<span>{{format-ts (now)}}</span>
</span>
</div>
</div>
<p class="annotation">Inline definitions are meant to pair well with emotive color variations.</p>
`,
};
};

View File

@@ -1,195 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
import DelayedTruth from '../utils/delayed-truth';
export default {
title: 'Components/JSON Viewer',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">JSON Viewer</h5>
{{#if delayedTruth.complete}}
<JsonViewer @json={{jsonSmall}} />
{{/if}}
`,
context: {
delayedTruth: DelayedTruth.create(),
jsonSmall: {
delayedData: {},
data: {
foo: 'bar',
number: 123456789,
products: [
'Consul',
'Nomad',
'Packer',
'Terraform',
'Vagrant',
'Vault',
],
currentTime: '2019-10-16T14:24:12.378Z',
nested: {
obj: 'ject',
},
nonexistent: null,
isTrue: false,
},
},
},
};
};
export let FullDocument = () => {
return {
template: hbs`
<h5 class="title is-5">JSON Viewer for full document</h5>
{{#if delayedTruth.complete}}
<JsonViewer @json={{jsonLarge}} />
{{/if}}
`,
context: {
delayedTruth: DelayedTruth.create(),
jsonLarge: {
delayedData: {},
data: {
Stop: false,
Region: 'global',
Namespace: 'default',
ID: 'syslog',
ParentID: '',
Name: 'syslog',
Type: 'system',
Priority: 50,
AllAtOnce: false,
Datacenters: ['dc1', 'dc2'],
letraints: null,
TaskGroups: [
{
Name: 'syslog',
Count: 1,
Update: {
Stagger: 10000000000,
MaxParallel: 1,
HealthCheck: 'checks',
MinHealthyTime: 10000000000,
HealthyDeadline: 300000000000,
ProgressDeadline: 600000000000,
AutoRevert: false,
Canary: 0,
},
Migrate: null,
letraints: [
{
LTarget: '',
RTarget: '',
Operand: 'distinct_hosts',
},
],
RestartPolicy: {
Attempts: 10,
Interval: 300000000000,
Delay: 25000000000,
Mode: 'delay',
},
Tasks: [
{
Name: 'syslog',
Driver: 'docker',
User: '',
Config: {
port_map: [
{
tcp: 601,
udp: 514,
},
],
image: 'balabit/syslog-ng:latest',
},
Env: null,
Services: null,
Vault: null,
Templates: null,
letraints: null,
Resources: {
CPU: 500,
MemoryMB: 256,
DiskMB: 0,
IOPS: 0,
Networks: [
{
Device: '',
CIDR: '',
IP: '',
MBits: 10,
ReservedPorts: [
{
Label: 'udp',
Value: 514,
},
{
Label: 'tcp',
Value: 601,
},
],
DynamicPorts: null,
},
],
},
DispatchPayload: null,
Meta: null,
KillTimeout: 5000000000,
LogConfig: {
MaxFiles: 10,
MaxFileSizeMB: 10,
},
Artifacts: null,
Leader: false,
ShutdownDelay: 0,
KillSignal: '',
},
],
EphemeralDisk: {
Sticky: false,
SizeMB: 300,
Migrate: false,
},
Meta: null,
ReschedulePolicy: null,
},
],
Update: {
Stagger: 10000000000,
MaxParallel: 1,
HealthCheck: '',
MinHealthyTime: 0,
HealthyDeadline: 0,
ProgressDeadline: 0,
AutoRevert: false,
Canary: 0,
},
Periodic: null,
ParameterizedJob: null,
Dispatched: false,
Payload: null,
Meta: null,
VaultToken: '',
Status: 'running',
StatusDescription: '',
Stable: false,
Version: 0,
SubmitTime: 1530052201331477800,
CreateIndex: 27,
ModifyIndex: 27,
JobModifyIndex: 27,
},
},
},
};
};

View File

@@ -1,71 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components/Log Stream',
};
export let LogStream = () => {
return {
template: hbs`
<h5 class="title is-5">Log stream</h5>
<div class="boxed-section">
<div class="boxed-section-head">
<span>
<button
class="button {{if (eq mode1 "stdout") "is-info"}}"
onclick={{action (mut mode1) "stdout"}}>stdout</button>
<button
class="button {{if (eq mode1 "stderr") "is-danger"}}"
onclick={{action (mut mode1) "stderr"}}>stderr</button>
</span>
<span class="pull-right">
<button class="button is-white">Head</button>
<button class="button is-white">Tail</button>
<button class="button is-white" onclick={{toggle "isPlaying1" this}}>
{{x-icon (if isPlaying1 "media-play" "media-pause") class="is-text"}}
</button>
</span>
</div>
<div class="boxed-section-body is-dark is-full-bleed">
<pre class="cli-window"><code>{{if (eq mode1 "stdout") sampleOutput sampleError}}</code></pre>
</div>
</div>
`,
context: {
mode1: 'stdout',
isPlaying1: true,
sampleOutput: `Sample output
> 1
> 2
> 3
[00:12:58] Log output here
[00:15:29] [ERR] Uh oh
Loading.
Loading..
Loading...
>> Done! <<
`,
sampleError: `Sample error
[====|--------------------] 20%
!!! Unrecoverable error:
Cannot continue beyond this point. Exception should be caught.
This is not a mistake. You did something wrong. Check the code.
No, you will not receive any more details or guidance from this
error message.
`,
},
};
};

View File

@@ -1,148 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components/Metrics',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Metrics</h5>
<div class="metric-group">
<div class="metric">
<h3 class="label">Label</h3>
<p class="value">12</p>
</div>
</div>
<p class="annotation">Metrics are a way to show simple values (generally numbers). Labels are smaller than numbers to put emphasis on the data.</p>
`,
};
};
export let Groups = () => {
return {
template: hbs`
<h5 class="title is-5">Metric groups</h5>
<div class="metric-group">
<div class="metric">
<h3 class="label">Label</h3>
<p class="value">1 / 2</p>
</div>
<div class="metric">
<h3 class="label">Number</h3>
<p class="value">1,300</p>
</div>
<div class="metric">
<h3 class="label">Datacenter</h3>
<p class="value">dc1</p>
</div>
</div>
<div class="metric-group">
<div class="metric">
<h3 class="label">Today</h3>
<p class="value">81º</p>
</div>
<div class="metric">
<h3 class="label">Tomorrow</h3>
<p class="value">73º</p>
</div>
</div>
<p class="annotation">Related metrics should be lumped together in metric groups. All metrics have to be in a metric group. By putting multiple metrics in a single group, they will be visually lumped together.</p>
`,
};
};
export let Colors = () => {
return {
template: hbs`
<h5 class="title is-5">Metric colors</h5>
<div class="metric-group">
<div class="metric is-info">
<h3 class="label">Info</h3>
<p class="value">1</p>
</div>
<div class="metric is-success">
<h3 class="label">Success</h3>
<p class="value">2</p>
</div>
<div class="metric is-warning">
<h3 class="label">Warning</h3>
<p class="value">3</p>
</div>
<div class="metric is-danger">
<h3 class="label">Danger</h3>
<p class="value">4</p>
</div>
</div>
<div class="metric-group">
<div class="metric is-white">
<h3 class="label">White</h3>
<p class="value">5</p>
</div>
<div class="metric is-light">
<h3 class="label">Light</h3>
<p class="value">6</p>
</div>
<div class="metric is-primary">
<h3 class="label">Primary</h3>
<p class="value">7</p>
</div>
<div class="metric is-dark">
<h3 class="label">Dark</h3>
<p class="value">8</p>
</div>
<div class="metric is-black">
<h3 class="label">Black</h3>
<p class="value">9</p>
</div>
</div>
<p class="annotation">All color-modifiers work for metrics, but some work better than others.</p>
<p class="annotation">Emotive colors work well and are put to use when applicable. Other colors have worse support and less utility.</p>
`,
};
};
export let States = () => {
return {
template: hbs`
<h5 class="title is-5">Metric states</h5>
<div class="metric-group">
<div class="metric is-primary is-faded">
<h3 class="label">One</h3>
<p class="value">A</p>
</div>
<div class="metric is-primary">
<h3 class="label">Two</h3>
<p class="value">B</p>
</div>
<div class="metric is-primary is-faded">
<h3 class="label">Three</h3>
<p class="value">C</p>
</div>
</div>
<div class="metric-group">
<div class="metric is-danger is-faded">
<h3 class="label">One</h3>
<p class="value">A</p>
</div>
<div class="metric is-danger is-faded">
<h3 class="label">Two</h3>
<p class="value">B</p>
</div>
<div class="metric is-danger">
<h3 class="label">Three</h3>
<p class="value">C</p>
</div>
</div>
<p class="annotation">Metrics have a disabled state. This is used when a metric is non-existent or irrelevant. It's just as important to show the lack of value as it is to show a value, so simply not rendering non-existent or irrelevant metrics would be worse.</p>
`,
};
};

View File

@@ -1,39 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components/Page Tabs',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Page tabs</h5>
<div class="tabs">
<ul>
<li><a href="javascript:;">Overview</a></li>
<li><a href="javascript:;" class="is-active">Definition</a></li>
<li><a href="javascript:;">Versions</a></li>
<li><a href="javascript:;">Deployments</a></li>
</ul>
</div>
`,
};
};
export let Single = () => {
return {
template: hbs`
<h5 class="title is-5">Single page tab</h5>
<div class="tabs">
<ul>
<li><a href="javascript:;" class="is-active">Overview</a></li>
</ul>
</div>
`,
};
};

View File

@@ -1,74 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components/Page Title',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Page title</h5>
<div class="mock-spacing">
<h1 class="title">This is the Page Title</h1>
</div>
<p class="annotation">In its simplest form, a page title is just an H1.</p>
`,
};
};
export let AfterElements = () => {
return {
template: hbs`
<h5 class="title is-5">Page title with after elements</h5>
<div class="mock-spacing">
<h1 class="title">
This is the Page Title
<span class="bumper-left tag is-running">Running</span>
<span class="tag is-hollow is-small no-text-transform">237aedcb8982fe09bcee0877acedd</span>
</h1>
</div>
<p class="annotation">It is common to put high-impact tags and badges to the right of titles. These tags should only ever appear on the right-hand side of the title, and they should be listed in descending weights. Tags with a background are heavier than tags that are hollow. Longer values are heavier than shorter values.</p>
`,
};
};
export let StatusLight = () => {
return {
template: hbs`
<h5 class="title is-5">Page title with status light</h5>
<div class="mock-spacing">
<h1 class="title">
<span class="node-status-light initializing"></span>
This is the Page Title
<span class="bumper-left tag is-running">Running</span>
<span class="tag is-hollow is-small no-text-transform">237aedcb8982fe09bcee0877acedd</span>
</h1>
</div>
<p class="annotation">A simple color or pattern is faster to scan than a title and can often say more than words can. For pages that have an important status component to them (e.g., client detail page), a status light can be shown to the left of the title where typically eyes will begin to scan a page.</p>
`,
};
};
export let Actions = () => {
return {
template: hbs`
<h5 class="title is-5">Page title with actions</h5>
<div class="mock-spacing">
<h1 class="title">
<span class="node-status-light initializing"></span>
This is the Page Title
<span class="bumper-left tag is-running">Running</span>
<span class="tag is-hollow is-small no-text-transform">237aedcb8982fe09bcee0877acedd</span>
<button class="button is-warning is-small is-inline">If you wish</button>
<button class="button is-danger is-outlined is-important is-small is-inline">No Regrets</button>
</h1>
</div>
<p class="annotation">When actions apply to the entire context of a page, (e.g., job actions on the job detail page), buttons for these actions go in the page title. Buttons are always placed on the far right end of a page title. No elements can go to the right of these buttons.</p>
`,
};
};

View File

@@ -1,19 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components/Proxy Tag',
};
export let ProxyTag = () => {
return {
template: hbs`
<h5 class="title is-5">Proxy Tag</h5>
<h6 class="title is-6">Some kind of title <ProxyTag/></h6>
`,
};
};

View File

@@ -1,36 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components/Search Box',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Search Box</h5>
<SearchBox
@searchTerm={{mut searchTerm1}}
@placeholder="Search things..." />
<p class="annotation">The search box component is a thin wrapper around a simple input. Although the searchTerm passed to it is a mutable reference, internally search term is debounced. This is to prevent potentially expensive code that depends on searchTerm from recomputing many times as a user types.</p>
<p class="annotation">There is no form of the search box component that defers updating the searchTerm reference until the user manually clicks a "Search" button. This can be achieved by placing a button next to the search bar component and using it to perform search, but search should be automatic whenever possible.</p>
`,
};
};
export let Compact = () => {
return {
template: hbs`
<h5 class="title is-5">Compact Search Box</h5>
<SearchBox
@searchTerm={{mut searchTerm2}}
@placeholder="Search things..."
@inputClass="is-compact" />
<p class="annotation">Search box provides an inputClass property to control the inner input. This is nice for fitting the search box into smaller spaces, such as boxed-section heads.</p>
`,
};
};

View File

@@ -1,53 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
import { withKnobs, optionsKnob } from '@storybook/addon-knobs';
export default {
title: 'Components/Stepper Input',
decorators: [withKnobs],
};
const variantKnob = () =>
optionsKnob(
'Variant',
{
Primary: 'is-primary',
Info: 'is-info',
Warning: 'is-warning',
Danger: 'is-danger',
},
'is-primary',
{
display: 'inline-radio',
},
'variant-id'
);
export let Standard = () => {
return {
template: hbs`
<p class="mock-spacing">
<StepperInput
@value={{value}}
@min={{min}}
@max={{max}}
@class={{variant}}
@onChange={{action (mut value)}}>
Stepper
</StepperInput>
<button class="button is-info">Button for Context</button>
</p>
<p class="mock-spacing"><strong>External Value:</strong> {{value}}</p>
`,
context: {
min: 0,
max: 10,
value: 5,
variant: variantKnob(),
},
};
};

View File

@@ -1,45 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components/Table, Configuration',
};
export let TableConfiguration = () => {
return {
template: hbs`
<h5 class="title is-5">Table, configuration</h5>
<AttributesTable @attributePairs={{attributes}} @class="attributes-table" />
`,
context: {
attributes: {
key: 'val',
deep: {
key: 'val',
more: 'stuff',
},
array: ['one', 'two', 'three', 'four'],
very: {
deep: {
key: {
incoming: {
one: 1,
two: 2,
three: 3,
four: 'surprisingly long value that is unlike the other properties in this object',
},
},
},
},
},
},
};
};
TableConfiguration.story = {
title: 'Table, Configuration',
};

View File

@@ -1,763 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
import productMetadata from '../../app/utils/styleguide/product-metadata';
import EmberObject, { computed } from '@ember/object';
import { getOwner } from '@ember/application';
import { on } from '@ember/object/evented';
import Controller from '@ember/controller';
export default {
title: 'Components/Table',
};
/**
* The Ember integration for Storybook renders a container component with no routing,
* which means things that need query parameters, like sorting and pagination, wont work.
* This initialiser turns on routing and accepts a controller definition that gets wired up
* to a generated `storybook` route. The controller is attached to the Storybook component
* as the `controller` property so its query parameters are accessible from the template.
*/
function injectRoutedController(controllerClass) {
return on('init', function () {
let container = getOwner(this);
container.register('controller:storybook', controllerClass);
let routerFactory = container.factoryFor('router:main');
routerFactory.class.map(function () {
this.route('storybook');
});
/* eslint-disable-next-line ember/no-private-routing-service */
let router = container.lookup('router:main');
router.initialURL = 'storybook';
router.startRouting(true);
this.set('controller', container.lookup('controller:storybook'));
});
}
let longList = [
{
city: 'New York',
growth: 0.048,
population: '8405837',
rank: '1',
state: 'New York',
},
{
city: 'Los Angeles',
growth: 0.048,
population: '3884307',
rank: '2',
state: 'California',
},
{
city: 'Chicago',
growth: -0.061,
population: '2718782',
rank: '3',
state: 'Illinois',
},
{
city: 'Houston',
growth: 0.11,
population: '2195914',
rank: '4',
state: 'Texas',
},
{
city: 'Philadelphia',
growth: 0.026,
population: '1553165',
rank: '5',
state: 'Pennsylvania',
},
{
city: 'Phoenix',
growth: 0.14,
population: '1513367',
rank: '6',
state: 'Arizona',
},
{
city: 'San Antonio',
growth: 0.21,
population: '1409019',
rank: '7',
state: 'Texas',
},
{
city: 'San Diego',
growth: 0.105,
population: '1355896',
rank: '8',
state: 'California',
},
{
city: 'Dallas',
growth: 0.056,
population: '1257676',
rank: '9',
state: 'Texas',
},
{
city: 'San Jose',
growth: 0.105,
population: '998537',
rank: '10',
state: 'California',
},
{
city: 'Austin',
growth: 0.317,
population: '885400',
rank: '11',
state: 'Texas',
},
{
city: 'Indianapolis',
growth: 0.078,
population: '843393',
rank: '12',
state: 'Indiana',
},
{
city: 'Jacksonville',
growth: 0.143,
population: '842583',
rank: '13',
state: 'Florida',
},
{
city: 'San Francisco',
growth: 0.077,
population: '837442',
rank: '14',
state: 'California',
},
{
city: 'Columbus',
growth: 0.148,
population: '822553',
rank: '15',
state: 'Ohio',
},
{
city: 'Charlotte',
growth: 0.391,
population: '792862',
rank: '16',
state: 'North Carolina',
},
{
city: 'Fort Worth',
growth: 0.451,
population: '792727',
rank: '17',
state: 'Texas',
},
{
city: 'Detroit',
growth: -0.271,
population: '688701',
rank: '18',
state: 'Michigan',
},
{
city: 'El Paso',
growth: 0.194,
population: '674433',
rank: '19',
state: 'Texas',
},
{
city: 'Memphis',
growth: -0.053,
population: '653450',
rank: '20',
state: 'Tennessee',
},
{
city: 'Seattle',
growth: 0.156,
population: '652405',
rank: '21',
state: 'Washington',
},
{
city: 'Denver',
growth: 0.167,
population: '649495',
rank: '22',
state: 'Colorado',
},
{
city: 'Washington',
growth: 0.13,
population: '646449',
rank: '23',
state: 'District of Columbia',
},
{
city: 'Boston',
growth: 0.094,
population: '645966',
rank: '24',
state: 'Massachusetts',
},
{
city: 'Nashville-Davidson',
growth: 0.162,
population: '634464',
rank: '25',
state: 'Tennessee',
},
{
city: 'Baltimore',
growth: -0.04,
population: '622104',
rank: '26',
state: 'Maryland',
},
{
city: 'Oklahoma City',
growth: 0.202,
population: '610613',
rank: '27',
state: 'Oklahoma',
},
{
city: 'Louisville/Jefferson County',
growth: 0.1,
population: '609893',
rank: '28',
state: 'Kentucky',
},
{
city: 'Portland',
growth: 0.15,
population: '609456',
rank: '29',
state: 'Oregon',
},
{
city: 'Las Vegas',
growth: 0.245,
population: '603488',
rank: '30',
state: 'Nevada',
},
{
city: 'Milwaukee',
growth: 0.003,
population: '599164',
rank: '31',
state: 'Wisconsin',
},
{
city: 'Albuquerque',
growth: 0.235,
population: '556495',
rank: '32',
state: 'New Mexico',
},
{
city: 'Tucson',
growth: 0.075,
population: '526116',
rank: '33',
state: 'Arizona',
},
{
city: 'Fresno',
growth: 0.183,
population: '509924',
rank: '34',
state: 'California',
},
{
city: 'Sacramento',
growth: 0.172,
population: '479686',
rank: '35',
state: 'California',
},
{
city: 'Long Beach',
growth: 0.015,
population: '469428',
rank: '36',
state: 'California',
},
{
city: 'Kansas City',
growth: 0.055,
population: '467007',
rank: '37',
state: 'Missouri',
},
{
city: 'Mesa',
growth: 0.135,
population: '457587',
rank: '38',
state: 'Arizona',
},
{
city: 'Virginia Beach',
growth: 0.051,
population: '448479',
rank: '39',
state: 'Virginia',
},
{
city: 'Atlanta',
growth: 0.062,
population: '447841',
rank: '40',
state: 'Georgia',
},
{
city: 'Colorado Springs',
growth: 0.214,
population: '439886',
rank: '41',
state: 'Colorado',
},
{
city: 'Omaha',
growth: 0.059,
population: '434353',
rank: '42',
state: 'Nebraska',
},
{
city: 'Raleigh',
growth: 0.487,
population: '431746',
rank: '43',
state: 'North Carolina',
},
{
city: 'Miami',
growth: 0.149,
population: '417650',
rank: '44',
state: 'Florida',
},
{
city: 'Oakland',
growth: 0.013,
population: '406253',
rank: '45',
state: 'California',
},
{
city: 'Minneapolis',
growth: 0.045,
population: '400070',
rank: '46',
state: 'Minnesota',
},
{
city: 'Tulsa',
growth: 0.013,
population: '398121',
rank: '47',
state: 'Oklahoma',
},
{
city: 'Cleveland',
growth: -0.181,
population: '390113',
rank: '48',
state: 'Ohio',
},
{
city: 'Wichita',
growth: 0.097,
population: '386552',
rank: '49',
state: 'Kansas',
},
{
city: 'Arlington',
growth: 0.133,
population: '379577',
rank: '50',
state: 'Texas',
},
];
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Table</h5>
<ListTable @source={{shortList}} as |t|>
<t.head>
<th>Name</th>
<th>Language</th>
<th>Description</th>
</t.head>
<t.body @key="model.name" as |row|>
<tr>
<td>{{row.model.name}}</td>
<td>{{row.model.lang}}</td>
<td>{{row.model.desc}}</td>
</tr>
</t.body>
</ListTable>
<p class="annotation">Tables have airy designs with a minimal amount of borders. This maximizes their utility.</p>
`,
context: {
shortList: productMetadata,
},
};
};
export let Search = () => {
return {
template: hbs`
<h5 class="title is-5">Table search</h5>
<div class="boxed-section">
<div class="boxed-section-head">
Table Name
<SearchBox
@searchTerm={{mut controller.searchTerm}}
@placeholder="Search..."
@class="is-inline pull-right"
@inputClass="is-compact" />
</div>
<div class="boxed-section-body {{if controller.filteredShortList.length "is-full-bleed"}}">
{{#if controller.filteredShortList.length}}
<ListTable @source={{controller.filteredShortList}} as |t|>
<t.head>
<th>Name</th>
<th>Language</th>
<th>Description</th>
</t.head>
<t.body @key="model.name" as |row|>
<tr>
<td>{{row.model.name}}</td>
<td>{{row.model.lang}}</td>
<td>{{row.model.desc}}</td>
</tr>
</t.body>
</ListTable>
{{else}}
<div class="empty-message">
<h3 class="empty-message-headline">No Matches</h3>
<p class="empty-message-body">No products match your query.</p>
</div>
{{/if}}
</div>
</div>
<p class="annotation">Tables compose with boxed-section and boxed-section composes with search box.</p>
`,
context: {
controller: EmberObject.extend({
searchTerm: '',
filteredShortList: computed('searchTerm', function () {
let term = this.searchTerm.toLowerCase();
return productMetadata.filter((product) =>
product.name.toLowerCase().includes(term)
);
}),
}).create(),
},
};
};
export let SortableColumns = () => {
return {
template: hbs`
<h5 class="title is-5">Table with sortable columns</h5>
<ListTable @source={{sortedShortList}} @sortProperty={{controller.sortProperty}} @sortDescending={{controller.sortDescending}} as |t|>
<t.head>
<t.sort-by @prop="name">Name</t.sort-by>
<t.sort-by @prop="lang" @class="is-2">Language</t.sort-by>
<th>Description</th>
</t.head>
<t.body @key="model.name" as |row|>
<tr>
<td>{{row.model.name}}</td>
<td>{{row.model.lang}}</td>
<td>{{row.model.desc}}</td>
</tr>
</t.body>
</ListTable>
<p class="annotation">The list-table component provides a <code>sort-by</code> contextual component for building <code>link-to</code> components with the appropriate query params.</p>
<p class="annotation">This leaves the component stateless, relying on data to be passed down and sending actions back up via the router (via link-to).</p>
`,
context: {
injectRoutedController: injectRoutedController(
Controller.extend({
queryParams: ['sortProperty', 'sortDescending'],
sortProperty: 'name',
sortDescending: false,
})
),
sortedShortList: computed(
'controller.{sortProperty,sortDescending}',
function () {
let sorted = productMetadata.sortBy(
this.get('controller.sortProperty') || 'name'
);
return this.get('controller.sortDescending')
? sorted.reverse()
: sorted;
}
),
},
};
};
export let MultiRow = () => {
return {
template: hbs`
<h5 class="title is-5">Multi-row Table</h5>
<ListTable @source={{sortedShortList}} @sortProperty={{controller.sortProperty}} @sortDescending={{controller.sortDescending}} @class="is-striped" as |t|>
<t.head>
<t.sort-by @prop="name">Name</t.sort-by>
<t.sort-by @prop="lang">Language</t.sort-by>
</t.head>
<t.body @key="model.name" as |row|>
<tr>
<td>{{row.model.name}}</td>
<td>{{row.model.lang}}</td>
</tr>
<tr>
<td colspan="2">{{row.model.desc}}</td>
</tr>
</t.body>
</ListTable>
<p class="annotation">The list-table component attempts to be as flexible as possible. For this reason, <code>t.body</code> does not provide the typical <code>tr</code> element. It's sometimes desired to have multiple elements per record.</p>
`,
context: {
injectRoutedController: injectRoutedController(
Controller.extend({
queryParams: ['sortProperty', 'sortDescending'],
sortProperty: 'name',
sortDescending: false,
})
),
sortedShortList: computed(
'controller.{sortProperty,sortDescending}',
function () {
let sorted = productMetadata.sortBy(
this.get('controller.sortProperty') || 'name'
);
return this.get('controller.sortDescending')
? sorted.reverse()
: sorted;
}
),
},
};
};
export let Pagination = () => {
return {
template: hbs`
<h5 class="title is-5">Table pagination</h5>
<ListPagination @source={{longList}} @size={{5}} @page={{controller.currentPage}} as |p|>
<ListTable @source={{p.list}} @class="with-foot" as |t|>
<t.head>
<th class="is-1">Rank</th>
<th>City</th>
<th>State</th>
<th>Population</th>
<th>Growth</th>
</t.head>
<t.body @key="model.rank" as |row|>
<tr>
<td>{{row.model.rank}}</td>
<td>{{row.model.city}}</td>
<td>{{row.model.state}}</td>
<td>{{row.model.population}}</td>
<td>{{format-percentage row.model.growth total=1}}</td>
</tr>
</t.body>
</ListTable>
<div class="table-foot">
<nav class="pagination">
<span class="bumper-left">U.S. City population and growth from 2000-2013</span>
<div class="pagination-numbers">
{{p.startsAt}}&ndash;{{p.endsAt}} of {{longList.length}}
</div>
<p.prev @class="pagination-previous"> &lt; </p.prev>
<p.next @class="pagination-next"> &gt; </p.next>
<ul class="pagination-list"></ul>
</nav>
</div>
</ListPagination>
<p class="annotation">Pagination works like sorting: using <code>link-to</code>s to set a query param.</p>
<p class="annotation">Pagination, like Table, is a minimal design. Only a next and previous button are available. The current place in the set of pages is tracked by showing which slice of items is currently shown.</p>
<p class="annotation">The pagination component exposes first and last components (for jumping to the beginning and end of a list) as well as pageLinks for generating links around the current page.</p>
`,
context: {
injectRoutedController: injectRoutedController(
Controller.extend({
queryParams: ['currentPage'],
currentPage: 1,
})
),
longList,
},
};
};
export let RowLinks = () => {
return {
template: hbs`
<h5 class="title is-5">Table row links</h5>
<ListTable @source={{shortList}} as |t|>
<t.head>
<th>Name</th>
<th>Language</th>
<th>Description</th>
</t.head>
<t.body @key="model.name" as |row|>
<tr class="is-interactive">
<td><a href="javascript:;" class="is-primary">{{row.model.name}}</a></td>
<td>{{row.model.lang}}</td>
<td>{{row.model.desc}}</td>
</tr>
</t.body>
</ListTable>
<p class="annotation">It is common for tables to act as lists of links, (e.g., clients list all allocations, each row links to the allocation detail). The helper class <code>is-interactive</code> on the <code>tr</code> makes table rows have a pointer cursor. The helper class <code>is-primary</code> on the <code>a</code> element in a table row makes the link bold and black instead of blue. This makes the link stand out less, since the entire row is a link.</p>
<p class="annotation">
A few rules for using table row links:
<ol>
<li>The <code>is-primary</code> cell should always be the first cell</li>
<li>The <code>is-primary</code> cell should always contain a link to the destination in the form of an <code>a</code> element. This is to support opening a link in a new tab.</li>
<li>The full row should transition to the destination on click. This is to improve the usability of a table by creating a larger click area.</li>
</ol>
</p>
`,
context: {
shortList: productMetadata,
},
};
};
export let CellLinks = () => {
return {
template: hbs`
<h5 class="title is-5">Table cell links</h5>
<ListTable @source={{shortList}} as |t|>
<t.head>
<th>Name</th>
<th>Language</th>
<th>Description</th>
</t.head>
<t.body @key="model.name" as |row|>
<tr>
<td><a href={{row.model.link}} target="_parent">{{row.model.name}}</a></td>
<td>{{row.model.lang}}</td>
<td>{{row.model.desc}}</td>
</tr>
</t.body>
</ListTable>
<p class="annotation">Links in table cells are just links.</p>
`,
context: {
shortList: productMetadata,
},
};
};
export let CellDecorations = () => {
return {
template: hbs`
<h5 class="title is-5">Table cell decorations</h5>
<ListTable @source={{shortList}} as |t|>
<t.head>
<th>Name</th>
<th>Language</th>
<th>Description</th>
</t.head>
<t.body @key="model.name" as |row|>
<tr>
<td><a href={{row.model.link}}>{{row.model.name}}</a></td>
<td class="nowrap">
<span class="color-swatch
{{if (eq row.model.lang "ruby") "swatch-6"}}
{{if (eq row.model.lang "golang") "swatch-5"}}" />
{{row.model.lang}}
</td>
<td>{{row.model.desc}}</td>
</tr>
</t.body>
</ListTable>
<p class="annotation">Small icons and accents of color make tables easier to scan.</p>
`,
context: {
shortList: productMetadata,
},
};
};
export let CellIcons = () => {
return {
template: hbs`
<h5 class="title is-5">Table cell icons</h5>
<ListPagination @source={{longList}} @size={{5}} @page={{controller.currentPage}} as |p|>
<ListTable @source={{p.list}} @class="with-foot" as |t|>
<t.head>
<th class="is-narrow"></th>
<th class="is-1">Rank</th>
<th>City</th>
<th>State</th>
<th>Population</th>
<th>Growth</th>
</t.head>
<t.body @key="model.rank" as |row|>
<tr>
<td class="is-narrow">
{{#if (lt row.model.growth 0)}}
{{x-icon "alert-triangle" class="is-warning"}}
{{/if}}
</td>
<td>{{row.model.rank}}</td>
<td>{{row.model.city}}</td>
<td>{{row.model.state}}</td>
<td>{{row.model.population}}</td>
<td>{{format-percentage row.model.growth total=1}}</td>
</tr>
</t.body>
</ListTable>
<div class="table-foot">
<nav class="pagination">
<span class="bumper-left">U.S. City population and growth from 2000-2013. Cities with negative growth denoted.</span>
<div class="pagination-numbers">
{{p.startsAt}}&ndash;{{p.endsAt}} of {{longList.length}}
</div>
<p.prev @class="pagination-previous"> &lt; </p.prev>
<p.next @class="pagination-next"> &gt; </p.next>
<ul class="pagination-list"></ul>
</nav>
</div>
</ListPagination>
`,
context: {
injectRoutedController: injectRoutedController(
Controller.extend({
queryParams: ['currentPage'],
currentPage: 1,
})
),
longList,
},
};
};

View File

@@ -1,248 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
import moment from 'moment';
export default {
title: 'Components/Timeline',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Timeline</h5>
<ol class="timeline">
<li class="timeline-note">
{{format-date yesterday}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
Object number one
</div>
</div>
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
Object number two
</div>
</div>
</li>
<li class="timeline-note">
{{format-date today}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
Object number three
</div>
</div>
</li>
</ol>
<p class="annotation">Timelines are a combination of objects and notes. Objects compose with boxed sections to create structure.</p>
<p class="annotation">Timeline notes should be used sparingly when possible. In this example there is a note per day rather than a note per object.</p>
`,
context: {
yesterday: moment().subtract(1, 'd'),
today: moment(),
},
};
};
export let Detailed = () => {
return {
template: hbs`
<h5 class="title is-5">Detailed timeline</h5>
<ol class="timeline">
<li class="timeline-note">
{{format-date today}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="tag is-running">Running</span>
<span class="bumper-left pair is-faded">
<span class="term">Stable</span>
<span class="badge is-light is-faded"><code>a387e243</code></span>
</span>
<span class="bumper-left pair is-faded">
<span class="term">Submitted</span>
<span class="tooltip" aria-label="{{format-month-ts (now)}}">{{moment-from-now (now)}}</span>
</span>
</div>
</div>
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="tag is-complete">Complete</span>
<span class="bumper-left pair is-faded">
<span class="term">Expired</span>
<span class="badge is-light is-faded"><code>b3220efb</code></span>
</span>
<span class="bumper-left pair is-faded">
<span class="term">Submitted</span>
<span>{{format-month-ts yesterday}}</span>
</span>
</div>
</div>
</li>
<li class="timeline-note">
{{format-date yesterday}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="tag is-error">Failed</span>
<span class="bumper-left pair is-faded">
<span class="term">Reverted</span>
<span class="badge is-light is-faded"><code>fec9218e</code></span>
</span>
<span class="bumper-left pair is-faded">
<span class="term">Submitted</span>
<span>{{format-month-ts yesterday}}</span>
</span>
</div>
</div>
</li>
</ol>
`,
context: {
yesterday: moment().subtract(1, 'd'),
today: moment(),
},
};
};
export let Toggling = () => {
return {
template: hbs`
<h5 class="title is-5">Toggling timeline objects</h5>
<ol class="timeline">
<li class="timeline-note">
{{format-date today}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="tag is-running">Running</span>
<span class="bumper-left pair is-faded">
<span class="term">Stable</span>
<span class="badge is-light is-faded"><code>a387e243</code></span>
</span>
<button
class="button is-light is-compact pull-right"
onclick={{action (mut toggle1) (not toggle1)}}>
{{if toggle1 "Close" "Open"}}
</button>
</div>
{{#if toggle1}}
<div class="boxed-section-body">
<p>Some details for the timeline object.</p>
</div>
{{/if}}
</div>
</li>
<li class="timeline-note">
{{format-date yesterday}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="tag is-complete">Complete</span>
<span class="bumper-left pair is-faded">
<span class="term">Expired</span>
<span class="badge is-light is-faded"><code>b3220efb</code></span>
</span>
<button
class="button is-light is-compact pull-right"
onclick={{action (mut toggle2) (not toggle2)}}>
{{if toggle2 "Close" "Open"}}
</button>
</div>
{{#if toggle2}}
<div class="boxed-section-body">
<p>Some details for the timeline object.</p>
</div>
{{/if}}
</div>
</li>
</ol>
<p class="annotation"></p>
`,
context: {
yesterday: moment().subtract(1, 'd'),
today: moment(),
},
};
};
export let Emphasizing = () => {
return {
template: hbs`
<h5 class="title is-5">Emphasizing timeline objects</h5>
<ol class="timeline">
<li class="timeline-note">
{{format-date today}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="pair is-faded">
<span class="term">Stable</span>
<span class="badge is-light is-faded"><code>a387e243</code></span>
</span>
<span class="bumper-left pair is-faded">
<span class="term">Submitted</span>
<span class="tooltip" aria-label="{{format-ts (now)}}">{{moment-from-now (now)}}</span>
</span>
</div>
</div>
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head">
Pay attention here
</div>
<div class="boxed-section-body">
<span class="pair is-faded">
<span class="term">Expired</span>
<span class="badge is-light is-faded"><code>b3220efb</code></span>
</span>
<span class="bumper-left pair is-faded">
<span class="term">Submitted</span>
<span>{{format-ts yesterday}}</span>
</span>
</div>
</div>
</li>
<li class="timeline-note">
{{format-date yesterday}}
</li>
<li class="timeline-object">
<div class="boxed-section">
<div class="boxed-section-head is-light">
<span class="pair is-faded">
<span class="term">Reverted</span>
<span class="badge is-light is-faded"><code>fec9218e</code></span>
</span>
<span class="bumper-left pair is-faded">
<span class="term">Submitted</span>
<span>{{format-ts yesterday}}</span>
</span>
</div>
</div>
</li>
</ol>
<p class="annotation">By using a full boxed-section for an emphasized timeline object, the object takes up more space and gets more visual weight. It also adheres to existing patterns.</p>
`,
context: {
yesterday: moment().subtract(1, 'd'),
today: moment(),
},
};
};

View File

@@ -1,111 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Components/Two-Step Button',
};
export let Standard = () => {
return {
template: hbs`
<h5 class="title is-5">Two-Step Button</h5>
<br><br>
<TwoStepButton
@idleText="Scary Action"
@cancelText="Nvm"
@confirmText="Yep"
@confirmationMessage="Wait, really? Like...seriously?"
/>
`,
};
};
export let Styled = () => {
return {
template: hbs`
<h5 class="title is-5">Two-Step Button with class overrides</h5>
<br><br>
<TwoStepButton
@idleText="Scary Action"
@cancelText="Nvm"
@confirmText="Yep"
@confirmationMessage="Wait, really? Like...seriously?"
@classes={{hash
idleButton="is-danger is-large"
confirmationMessage="badge is-warning"
confirmButton="is-large"
cancelButton="is-hollow"
}}
/>
`,
};
};
export let InTitle = () => {
return {
template: hbs`
<h5 class="title is-5">Two-Step Button in title</h5>
<br><br>
<h1 class="title">
This is a page title
<TwoStepButton
@idleText="Scary Action"
@cancelText="Nvm"
@confirmText="Yep"
@confirmationMessage="Wait, really? Like...seriously?"
/>
</h1>
`,
};
};
export let InlineText = () => {
return {
template: hbs`
<h5 class="title is-5">Two-Step Button with inline confirmation message</h5>
<br><br>
<TwoStepButton
@idleText="Scary Action"
@cancelText="Nvm"
@confirmText="Yep"
@confirmationMessage="Really?"
@inlineText={{true}}
/>
<br><br>
<span style="padding-left: 4rem"></span>
<TwoStepButton
@idleText="Scary Action"
@cancelText="Nvm"
@confirmText="Yep"
@confirmationMessage="Really?"
@alignRight={{true}}
@inlineText={{true}}
/>
`,
};
};
export let LoadingState = () => {
return {
template: hbs`
<h5 class="title is-5">Two-Step Button loading state</h5>
<br><br>
<h1 class="title">
This is a page title
<TwoStepButton
@idleText="Scary Action"
@cancelText="Nvm"
@confirmText="Yep"
@confirmationMessage="Wait, really? Like...seriously?"
@awaitingConfirmation={{true}}
@state="prompt"
/>
</h1>
<p class="annotation"> <strong>Note:</strong> the <code>state</code> property is internal state and only used here to bypass the idle state for demonstration purposes.</p>
`,
};
};

View File

@@ -1,145 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
import { htmlSafe } from '@ember/string';
export default {
title: 'Theme/Colors',
};
export let Colors = () => {
return {
template: hbs`
{{#each palettes as |palette|}}
<div class='palette'>
<div class='title'>{{palette.title}}</div>
<div class='description'>{{palette.description}}</div>
{{#each palette.colors as |color|}}
<div class='item'>
<div class='color' style={{color.style}}></div>
<div class='info'>
<p class='hex'>{{color.base}}</p>
<p class='name'>{{color.name}}</p>
</div>
</div>
{{/each}}
</div>
{{/each}}
`,
context: {
palettes: [
{
title: 'Nomad Theme',
description: 'Accent and neutrals.',
colors: [
{
name: 'Primary',
base: '#25ba81',
},
{
name: 'Primary Dark',
base: '#1d9467',
},
{
name: 'Text',
base: '#0a0a0a',
},
{
name: 'Link',
base: '#1563ff',
},
{
name: 'Gray',
base: '#bbc4d1',
},
{
name: 'Off-white',
base: '#f5f5f5',
},
],
},
{
title: 'Product Colors',
description:
'Colors from other HashiCorp products. Often borrowed for alternative accents and color schemes.',
colors: [
{
name: 'Consul Pink',
base: '#ff0087',
},
{
name: 'Consul Pink Dark',
base: '#c62a71',
},
{
name: 'Packer Blue',
base: '#1daeff',
},
{
name: 'Packer Blue Dark',
base: '#1d94dd',
},
{
name: 'Terraform Purple',
base: '#5c4ee5',
},
{
name: 'Terraform Purple Dark',
base: '#4040b2',
},
{
name: 'Vagrant Blue',
base: '#1563ff',
},
{
name: 'Vagrant Blue Dark',
base: '#104eb2',
},
{
name: 'Nomad Green',
base: '#25ba81',
},
{
name: 'Nomad Green Dark',
base: '#1d9467',
},
{
name: 'Nomad Green Darker',
base: '#16704d',
},
],
},
{
title: 'Emotive Colors',
description: 'Colors used in conjunction with an emotional response.',
colors: [
{
name: 'Success',
base: '#23d160',
},
{
name: 'Warning',
base: '#fa8e23',
},
{
name: 'Danger',
base: '#c84034',
},
{
name: 'Info',
base: '#1563ff',
},
],
},
].map((palette) => {
palette.colors.forEach((color) => {
color.style = htmlSafe(`background-color: ${color.base}`);
});
return palette;
}),
},
};
};

View File

@@ -1,49 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
import { htmlSafe } from '@ember/string';
export default {
title: 'Theme/Font Stacks',
};
export let FontStacks = () => {
return {
template: hbs`
<h5 class="title is-5">Font Stacks</h5>
{{#each fontFamilies as |fontFamily|}}
<h6 class="title is-6 with-headroom">{{fontFamily.name}}</h6>
<div class="typeface" style={{fontFamily.style}}>
<div class="hero">Aa</div>
<p class="sample">A B C D E F G H I J K L M N O P Q R S T U V W X Y Z</p>
<p class="sample">a b c d e f g h i j k l m n o p q r s t u v w x y z</p>
<p class="sample">0 1 2 3 4 5 6 7 8 9</p>
</div>
<br>
{{/each}}
`,
context: {
fontFamilies: [
'-apple-system',
'BlinkMacSystemFont',
'Segoe UI',
'Roboto',
'Oxygen-Sans',
'Ubuntu',
'Cantarell',
'Helvetica Neue',
'sans-serif',
'monospace',
].map((family) => {
return {
name: family,
style: htmlSafe(`font-family: ${family}`),
};
}),
},
};
};

View File

@@ -1,23 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Theme/Icons',
};
export let Icons = () => ({
template: hbs`
<ul class="tile-list">
{{#each (all-icons) as |icon|}}
<li class="icon-tile">
{{x-icon icon}}
<code>{{icon}}</code>
</li>
{{/each}}
</ul>
`,
});

View File

@@ -1,30 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import hbs from 'htmlbars-inline-precompile';
export default {
title: 'Theme/Text Sizing',
};
export let TextSizing = () => {
return {
template: hbs`
<h5 class="title is-5">Text sizing</h5>
<div class="block">
<h1 class="title">Large Title</h1>
<p>Some prose to follow the large title. Not necessarily meant for reading.</p>
</div>
<div class="block">
<h2 class="title is-4">Medium Title</h2>
<p>Some prose to follow the large title. Not necessarily meant for reading.</p>
</div>
<div class="block">
<h3 class="title is-5">Small Title</h3>
<p>Some prose to follow the large title. Not necessarily meant for reading.</p>
</div>
`,
};
};

View File

@@ -1,27 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { A } from '@ember/array';
import ArrayProxy from '@ember/array/proxy';
import { next } from '@ember/runloop';
/**
* This is an array whose content is empty until the next
* tick, which fixes Storybook race condition rendering
* problems.
*/
export default ArrayProxy.extend({
init(array) {
this.set('content', A([]));
this._super(...arguments);
this[Symbol.iterator] = this.content[Symbol.iterator];
next(this, () => {
this.set('content', A(array));
this[Symbol.iterator] = this.content[Symbol.iterator];
});
},
});

View File

@@ -1,24 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import EmberObject from '@ember/object';
import { next } from '@ember/runloop';
/**
* This has a `complete` property that turns from false
* to true in the next tick, which helps with some
* Storybook race condition rendering problems.
*/
export default EmberObject.extend({
init() {
this._super(...arguments);
this.set('complete', false);
next(this, () => {
this.set('complete', true);
});
},
});

File diff suppressed because it is too large Load Diff