Unverified Commit 9f86f5a1 authored by Buck Doyle's avatar Buck Doyle Committed by GitHub
Browse files

UI: Migrate to Storybook (#6507)

I originally planned to add component documentation, but as this dragged on and I found that JSDoc-to-Markdown sometimes needed hand-tuning, I decided to skip it and focus on replicating what was already present in Freestyle. Adding documentation is a finite task that can be revisited in the future.

My goal was to migrate everything from Freestyle with as few changes as possible. Some adaptations that I found necessary:
• the DelayedArray and DelayedTruth utilities that delay component rendering until slightly after initial render because without them:
  ◦ charts were rendering with zero width
  ◦ the JSON viewer was rendering with empty content
• Storybook in Ember renders components in a routerless/controllerless context by default, so some component stories needed changes:
  ◦ table pagination/sorting stories access to query params, which necessitates some reaching into Ember internals to start routing and dynamically generate a Storybook route/controller to render components into
  ◦ some stories have a faux controller as part of their Storybook context that hosts setInterval-linked dynamic computed properties
• some jiggery-pokery with anchor tags
  ◦ inert href='#' had to become href='javascript:;
  ◦ links that are actually meant to navigate need target='_parent' so they don’t navigate inside the Storybook iframe

Maybe some of these could be addressed by fixes in ember-cli-storybook but I’m wary of digging around in there any more than I already have, as I’ve lost a lot of time to Storybook confusion and frustrations already :disappointed:

The STORYBOOK=true environment variable tweaks some environment settings to get things working as expected in the Storybook context.

I chose to:
• use angle bracket invocation within stories rather than have to migrate them soon after having moved to Storybook
• keep Freestyle around for now for its palette and typeface components
parent 0aa58b90
Showing with 187 additions and 879 deletions
+187 -879
ui/.env 0 → 100644
STORYBOOK_NAME=nomad-ui
\ No newline at end of file
...@@ -53,5 +53,18 @@ module.exports = { ...@@ -53,5 +53,18 @@ module.exports = {
'node/no-unpublished-require': 'off' 'node/no-unpublished-require': 'off'
}), }),
}, },
{
files: [
'stories/**/*.js'
],
parserOptions: {
sourceType: 'module',
},
env: {
browser: false,
node: true,
},
plugins: ['node'],
},
], ],
}; };
import '@storybook/addon-storysource/register';
import '@storybook/addon-knobs/register';
import '@storybook/addon-viewport/register';
/* eslint-env node */
module.exports = {
presets: [
[
'@babel/preset-env',
{
shippedProposals: true,
useBuiltIns: 'usage',
corejs: '3',
targets: ['last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions'],
},
],
],
plugins: [
[
'@babel/plugin-proposal-decorators',
{
legacy: true,
},
],
['@babel/plugin-proposal-class-properties', { loose: true }],
'@babel/plugin-syntax-dynamic-import',
['@babel/plugin-proposal-object-rest-spread', { loose: true, useBuiltIns: true }],
'babel-plugin-macros',
['emotion', { sourceMap: true, autoLabel: true }],
],
};
/* eslint-env node */
import { addDecorator, addParameters, configure } 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);
};
/**
* Stories that require routing (table sorting/pagination) fail
* with the default iframe setup with this error:
* Path /iframe.html does not start with the provided rootURL /ui/
*
* Changing ENV.rootURL fixes that but then HistoryLocation.getURL
* fails because baseURL is undefined, which is usually set up by
* Ember CLI configuring the base element. This adds the href for
* Ember CLI to use.
*
* The default target="_parent" breaks table sorting and pagination
* by trying to navigate when clicking the query-params-changing
* elements. Removing the base target for the iframe means that
* navigation-requiring links within stories need to have the
* target themselves.
*/
let baseElement = document.querySelector('base');
baseElement.setAttribute('href', '/');
baseElement.removeAttribute('target');
return {
template,
context,
element: storybookElement,
};
});
// The order of import controls the sorting in the sidebar
configure([
require.context('../stories/theme', true, /\.stories\.js$/),
require.context('../stories/components', true, /\.stories\.js$/),
require.context('../stories/charts', true, /\.stories\.js$/),
], module);
<!-- This file is auto-generated by ember-cli-storybook -->
<meta name="nomad-ui/config/environment" content="%7B%22modulePrefix%22%3A%22nomad-ui%22%2C%22environment%22%3A%22development%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22auto%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%7D%2C%22_JQUERY_INTEGRATION%22%3Atrue%7D%2C%22APP%22%3A%7B%22blockingQueries%22%3Atrue%2C%22mirageScenario%22%3A%22smallCluster%22%2C%22mirageWithNamespaces%22%3Atrue%2C%22mirageWithTokens%22%3Atrue%2C%22mirageWithRegions%22%3Atrue%2C%22autoboot%22%3Afalse%7D%2C%22ember-cli-mirage%22%3A%7B%22enabled%22%3Atrue%2C%22excludeFilesFromBuild%22%3Afalse%2C%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Atrue%7D" />
<link rel="stylesheet" href="./assets/vendor.css" />
<link rel="stylesheet" href="./assets/nomad-ui.css" />
<link rel="icon" href=".//favicon.png" />
<script>
(function() {
var srcUrl = null;
var host = location.hostname || 'localhost';
var defaultPort = location.protocol === 'https:' ? 443 : 80;
var port = 4200;
var path = '';
var prefixURL = '';
var src = srcUrl || prefixURL + '/_lr/livereload.js?port=' + port + '&host=' + host + path;
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = location.protocol + '//' + host + ':4200' + src;
document.getElementsByTagName('head')[0].appendChild(script);
}());
</script>
<script src="./assets/vendor.js"></script>
<script>runningTests = true; Ember.testing=true;</script>
<script src="./assets/nomad-ui.js"></script>
\ No newline at end of file
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://www.nomadproject.io/',
});
/* eslint-env node */
module.exports = function({ config }) {
config.module.rules.push({
test: /\.stories\.jsx?$/,
loaders: [require.resolve('@storybook/source-loader')],
enforce: 'pre',
});
return config;
};
...@@ -79,6 +79,12 @@ Nomad UI releases are in lockstep with Nomad releases and are integrated into th ...@@ -79,6 +79,12 @@ 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. * 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`. The latest version from the `master` branch is at [`nomad-storybook.netlify.com`](https://nomad-storybook.netlify.com/).
To generate a new story for a component, run `ember generate story component-name`. You can use the existing stories as a guide.
### Troubleshooting ### Troubleshooting
#### The UI is running, but none of the API requests are working #### The UI is running, but none of the API requests are working
......
import Component from '@ember/component';
import productMetadata from 'nomad-ui/utils/styleguide/product-metadata';
export default Component.extend({
products: productMetadata,
});
import Component from '@ember/component';
import { computed } from '@ember/object';
export default Component.extend({
variants: computed(() => [
{
key: 'Normal',
title: 'Normal',
slug: '',
},
{
key: 'Info',
title: 'Info',
slug: 'is-info',
},
{
key: 'Warning',
title: 'Warning',
slug: 'is-warning',
},
{
key: 'Danger',
title: 'Danger',
slug: 'is-danger',
},
]),
});
import Component from '@ember/component';
import { computed } from '@ember/object';
export default Component.extend({
nomadTheme: computed(() => [
{
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',
},
]),
productColors: computed(() => [
{
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',
},
]),
emotiveColors: computed(() => [
{
name: 'Success',
base: '#23d160',
},
{
name: 'Warning',
base: '#fa8e23',
},
{
name: 'Danger',
base: '#c84034',
},
{
name: 'Info',
base: '#1563ff',
},
]),
});
import Component from '@ember/component';
const generateDiff = changeset => ({
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',
});
export default Component.extend({
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' },
]),
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' },
]),
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' },
]),
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:3.2',
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: '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:3.2', 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',
},
});
import Component from '@ember/component';
import { computed } from '@ember/object';
export default Component.extend({
distributionBarData: computed(() => {
return [
{ label: 'one', value: 10 },
{ label: 'two', value: 20 },
{ label: 'three', value: 0 },
{ label: 'four', value: 35 },
];
}),
});
import Component from '@ember/component';
import { computed } from '@ember/object';
import { on } from '@ember/object/evented';
export default Component.extend({
timerTicks: 0,
startTimer: on('init', function() {
this.set(
'timer',
setInterval(() => {
this.incrementProperty('timerTicks');
}, 500)
);
}),
willDestroy() {
clearInterval(this.timer);
},
distributionBarDatum: computed(() => {
return [{ label: 'one', value: 10 }];
}),
distributionBarData: computed(() => {
return [
{ label: 'one', value: 10 },
{ label: 'two', value: 20 },
{ label: 'three', value: 30 },
];
}),
distributionBarDataWithClasses: computed(() => {
return [
{ label: 'Queued', value: 10, className: 'queued' },
{ label: 'Complete', value: 20, className: 'complete' },
{ label: 'Failed', value: 30, className: 'failed' },
];
}),
distributionBarDataRotating: computed('timerTicks', () => {
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) },
];
}),
});
import Component from '@ember/component';
export default Component.extend({
options: [
{ name: 'Consul' },
{ name: 'Nomad' },
{ name: 'Packer' },
{ name: 'Terraform' },
{ name: 'Vagrant' },
{ name: 'Vault' },
],
manyOptions: [
'One',
'Two',
'Three',
'Four',
'Five',
'Six',
'Seven',
'Eight',
'Nine',
'Ten',
'Eleven',
'Twelve',
'Thirteen',
'Fourteen',
'Fifteen',
].map(name => ({ name })),
});
import Component from '@ember/component';
export default Component.extend({
jsonSmall: {
foo: 'bar',
number: 123456789,
products: ['Consul', 'Nomad', 'Packer', 'Terraform', 'Vagrant', 'Vault'],
currentTime: new Date().toISOString(),
nested: {
obj: 'ject',
},
nonexistent: null,
huh: undefined,
isTrue: false,
},
jsonLarge: {
Stop: false,
Region: 'global',
Namespace: 'default',
ID: 'syslog',
ParentID: '',
Name: 'syslog',
Type: 'system',
Priority: 50,
AllAtOnce: false,
Datacenters: ['dc1', 'dc2'],
Constraints: 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,
Constraints: [
{
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.0,
udp: 514.0,
},
],
image: 'balabit/syslog-ng:latest',
},
Env: null,
Services: null,
Vault: null,
Templates: null,
Constraints: 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: 1530052201331477665,
CreateIndex: 27,
ModifyIndex: 27,
JobModifyIndex: 27,
},
});
import Component from '@ember/component';
import { computed } from '@ember/object';
import { on } from '@ember/object/evented';
import d3TimeFormat from 'd3-time-format';
export default Component.extend({
timerTicks: 0,
startTimer: on('init', function() {
this.set(
'timer',
setInterval(() => {
this.incrementProperty('timerTicks');
const 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);
},
lineChartData: computed(() => {
return [
{ 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 },
];
}),
lineChartMild: computed(() => {
return [
{ 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 },
];
}),
lineChartGapData: computed(() => {
return [
{ 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 },
];
}),
lineChartLive: computed(() => {
return [];
}),
secondsFormat() {
return d3TimeFormat.timeFormat('%H:%M:%S');
},
});
import Component from '@ember/component';
export default Component.extend({
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.
`,
});
import Component from '@ember/component';
import { computed } from '@ember/object';
export default Component.extend({
options1: computed(() => [
{ 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' },
]),
selection1: computed(() => ['option-2', 'option-4', 'option-5']),
optionsMany: computed(() =>
Array(100)
.fill(null)
.map((_, i) => ({ label: `Option ${i}`, key: `option-${i}` }))
),
selectionMany: computed(() => []),
optionsDatacenter: computed(() => [
{ 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: computed(() => ['jfk-1', 'jfk-2']),
optionsType: computed(() => [
{ key: 'batch', label: 'Batch' },
{ key: 'service', label: 'Service' },
{ key: 'system', label: 'System' },
{ key: 'periodic', label: 'Periodic' },
{ key: 'parameterized', label: 'Parameterized' },
]),
selectionType: computed(() => ['system', 'service']),
optionsStatus: computed(() => [
{ key: 'pending', label: 'Pending' },
{ key: 'running', label: 'Running' },
{ key: 'dead', label: 'Dead' },
]),
selectionStatus: computed(() => []),
});
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment