diff --git a/ui/app/components/freestyle/sg-accordion.js b/ui/app/components/freestyle/sg-accordion.js new file mode 100644 index 000000000000..84f7a7360a8b --- /dev/null +++ b/ui/app/components/freestyle/sg-accordion.js @@ -0,0 +1,6 @@ +import Component from '@ember/component'; +import productMetadata from 'nomad-ui/utils/styleguide/product-metadata'; + +export default Component.extend({ + products: productMetadata, +}); diff --git a/ui/app/components/freestyle/sg-diff-viewer.js b/ui/app/components/freestyle/sg-diff-viewer.js new file mode 100644 index 000000000000..e81e64cf5f68 --- /dev/null +++ b/ui/app/components/freestyle/sg-diff-viewer.js @@ -0,0 +1,355 @@ +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', + }, +}); diff --git a/ui/app/components/freestyle/sg-dropdown.js b/ui/app/components/freestyle/sg-dropdown.js new file mode 100644 index 000000000000..bc9980efc472 --- /dev/null +++ b/ui/app/components/freestyle/sg-dropdown.js @@ -0,0 +1,30 @@ +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 })), +}); diff --git a/ui/app/components/freestyle/sg-json-viewer.js b/ui/app/components/freestyle/sg-json-viewer.js new file mode 100644 index 000000000000..5eea5837cf15 --- /dev/null +++ b/ui/app/components/freestyle/sg-json-viewer.js @@ -0,0 +1,148 @@ +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, + }, +}); diff --git a/ui/app/components/freestyle/sg-log-stream.js b/ui/app/components/freestyle/sg-log-stream.js new file mode 100644 index 000000000000..a2d736975dab --- /dev/null +++ b/ui/app/components/freestyle/sg-log-stream.js @@ -0,0 +1,33 @@ +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. + +`, +}); diff --git a/ui/app/components/freestyle/sg-progress-bar.js b/ui/app/components/freestyle/sg-progress-bar.js new file mode 100644 index 000000000000..613bc08e9bd5 --- /dev/null +++ b/ui/app/components/freestyle/sg-progress-bar.js @@ -0,0 +1,35 @@ +import Component from '@ember/component'; +import { computed } from '@ember/object'; + +export default Component.extend({ + timerTicks: 0, + + startTimer: function() { + this.set( + 'timer', + setInterval(() => { + this.incrementProperty('timerTicks'); + }, 1000) + ); + }.on('init'), + + willDestroy() { + clearInterval(this.get('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.get('denominator') * this.get('percentage') * 100) / 100; + }), + + liveDetails: computed('denominator', 'numerator', 'percentage', function() { + return this.getProperties('denominator', 'numerator', 'percentage'); + }), +}); diff --git a/ui/app/components/freestyle/sg-table-configuration.js b/ui/app/components/freestyle/sg-table-configuration.js new file mode 100644 index 000000000000..2e5a8e3ca1d6 --- /dev/null +++ b/ui/app/components/freestyle/sg-table-configuration.js @@ -0,0 +1,24 @@ +import Component from '@ember/component'; + +export default Component.extend({ + 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', + }, + }, + }, + }, + }, +}); diff --git a/ui/app/components/freestyle/sg-table.js b/ui/app/components/freestyle/sg-table.js new file mode 100644 index 000000000000..da8112e19ac4 --- /dev/null +++ b/ui/app/components/freestyle/sg-table.js @@ -0,0 +1,118 @@ +import Component from '@ember/component'; +import { computed } from '@ember/object'; +import productMetadata from 'nomad-ui/utils/styleguide/product-metadata'; + +export default Component.extend({ + searchTerm: '', + + currentPage: 1, + sortProperty: 'name', + sortDescending: false, + + shortList: productMetadata, + + 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' }, + ], + + filteredShortList: computed('searchTerm', 'shortList.[]', function() { + const term = this.get('searchTerm').toLowerCase(); + return this.get('shortList').filter(product => product.name.toLowerCase().includes(term)); + }), + + sortedShortList: computed('shortList.[]', 'sortProperty', 'sortDescending', function() { + const sorted = this.get('shortList').sortBy(this.get('sortProperty')); + return this.get('sortDescending') ? sorted.reverse() : sorted; + }), +}); diff --git a/ui/app/components/freestyle/sg-timeline.js b/ui/app/components/freestyle/sg-timeline.js new file mode 100644 index 000000000000..c84ac3ab3c3e --- /dev/null +++ b/ui/app/components/freestyle/sg-timeline.js @@ -0,0 +1,8 @@ +import Component from '@ember/component'; +import { computed } from '@ember/object'; +import moment from 'moment'; + +export default Component.extend({ + yesterday: computed(() => moment().subtract(1, 'd')), + today: computed(() => moment()), +}); diff --git a/ui/app/controllers/freestyle.js b/ui/app/controllers/freestyle.js index a5809f613a8a..f585f0460ee7 100644 --- a/ui/app/controllers/freestyle.js +++ b/ui/app/controllers/freestyle.js @@ -2,5 +2,15 @@ import { inject as service } from '@ember/service'; import FreestyleController from 'ember-freestyle/controllers/freestyle'; export default FreestyleController.extend({ + queryParams: { + currentPage: 'page', + sortProperty: 'sort', + sortDescending: 'desc', + }, + + currentPage: 1, + sortProperty: 'name', + sortDescending: true, + emberFreestyle: service(), }); diff --git a/ui/app/styles/components/two-step-button.scss b/ui/app/styles/components/two-step-button.scss index 18ed9fb01f3d..b46b1a4395f5 100644 --- a/ui/app/styles/components/two-step-button.scss +++ b/ui/app/styles/components/two-step-button.scss @@ -1,12 +1,16 @@ .two-step-button { - display: inline; + display: inline-block; + vertical-align: middle; position: relative; + font-size: $body-size; + line-height: 1; .confirmation-text { position: absolute; left: 0; - top: -1.2em; + top: -1.5em; font-size: $body-size; + line-height: 1; font-weight: $weight-normal; color: darken($grey-blue, 20%); white-space: nowrap; diff --git a/ui/app/styles/core/menu.scss b/ui/app/styles/core/menu.scss index 06fd74a56398..0811337afd90 100644 --- a/ui/app/styles/core/menu.scss +++ b/ui/app/styles/core/menu.scss @@ -13,6 +13,15 @@ background: transparent; box-shadow: inset -3px 0 0 $blue; color: $blue; + + .icon { + fill: $blue; + } + } + + .icon { + margin-right: 0.5em; + fill: lighten($text, 30%); } } diff --git a/ui/app/styles/styleguide.scss b/ui/app/styles/styleguide.scss index f77df0655c43..36093f53cb61 100644 --- a/ui/app/styles/styleguide.scss +++ b/ui/app/styles/styleguide.scss @@ -1,11 +1,12 @@ #styleguide { .mock-content { display: flex; - height: 250px; + min-height: 250px; + height: 100%; .mock-image, .mock-copy { - height: 100%; + min-height: 100%; width: 100%; margin: 1em; } @@ -40,5 +41,15 @@ transparent 14px ); } + + .mock-vague { + background: lighten($grey-blue, 15%); + width: 100%; + height: 100%; + } + } + + .mock-spacing { + padding: 2em; } } diff --git a/ui/app/templates/components/freestyle/sg-accordion.hbs b/ui/app/templates/components/freestyle/sg-accordion.hbs new file mode 100644 index 000000000000..13975dec9414 --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-accordion.hbs @@ -0,0 +1,53 @@ +{{#freestyle-usage "accordion" title="Accordion"}} + {{#list-accordion source=products key="name" as |a|}} + {{#a.head buttonLabel="details"}} +
+
{{a.item.name}}
+
+ {{a.item.lang}} +
+
+ {{/a.head}} + {{#a.body}} +

{{a.item.name}}

+

{{a.item.desc}}

+

Learn more...

+ {{/a.body}} + {{/list-accordion}} +{{/freestyle-usage}} + +{{#freestyle-usage "accordion-single" title="Accordion, One Item"}} + {{#list-accordion source=(take 1 products) key="name" as |a|}} + {{#a.head buttonLabel="details"}} +
+
{{a.item.name}}
+
+ {{a.item.lang}} +
+
+ {{/a.head}} + {{#a.body}} +

{{a.item.name}}

+

{{a.item.desc}}

+

Learn more...

+ {{/a.body}} + {{/list-accordion}} +{{/freestyle-usage}} + +{{#freestyle-usage "accordion-not-expandable" title="Accordion, Not Expandable"}} + {{#list-accordion source=products key="name" as |a|}} + {{#a.head buttonLabel="details" isExpandable=(eq a.item.lang "golang")}} +
+
{{a.item.name}}
+
+ {{a.item.lang}} +
+
+ {{/a.head}} + {{#a.body}} +

{{a.item.name}}

+

{{a.item.desc}}

+

Learn more...

+ {{/a.body}} + {{/list-accordion}} +{{/freestyle-usage}} diff --git a/ui/app/templates/components/freestyle/sg-alerts.hbs b/ui/app/templates/components/freestyle/sg-alerts.hbs new file mode 100644 index 000000000000..f1d7c8c30c09 --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-alerts.hbs @@ -0,0 +1,84 @@ +{{#freestyle-usage "alert-standard" title="Alert"}} +
+

This is an alert

+

Alerts are used for both situational and reactionary information.

+
+{{/freestyle-usage}} +{{#freestyle-annotation}} +

Alerts use Bulma's notification component.

+{{/freestyle-annotation}} + +{{#freestyle-usage "alert-colors" title="Alert colors"}} +
+

This is an alert

+

Alerts are used for both situational and reactionary information.

+
+ +
+

This is an alert

+

Alerts are used for both situational and reactionary information.

+
+ +
+

This is an alert

+

Alerts are used for both situational and reactionary information.

+
+ +
+

This is an alert

+

Alerts are used for both situational and reactionary information.

+
+{{/freestyle-usage}} +{{#freestyle-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.

+{{/freestyle-annotation}} + +{{#freestyle-usage "alert-dismissal" title="Alert dismissal"}} +
+
+
+

This is an alert

+

Alerts are used for both situational and reactionary information.

+
+
+ +
+
+
+ +
+
+
+

This is an alert

+

Alerts are used for both situational and reactionary information.

+
+
+ +
+
+
+ +
+
+
+

This is an alert

+

Alerts are used for both situational and reactionary information.

+
+
+ +
+
+
+ +
+
+
+

This is an alert

+

Alerts are used for both situational and reactionary information.

+
+
+ +
+
+
+{{/freestyle-usage}} diff --git a/ui/app/templates/components/freestyle/sg-diff-viewer.hbs b/ui/app/templates/components/freestyle/sg-diff-viewer.hbs new file mode 100644 index 000000000000..a7f217bece6b --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-diff-viewer.hbs @@ -0,0 +1,33 @@ +{{#freestyle-usage "diff-viewer-insertions" title="Diff Viewer with Insertions"}} +
+
+ {{job-diff diff=insertionsOnly}} +
+
+{{/freestyle-usage}} +{{#freestyle-usage "diff-viewer-deletion" title="Diff Viewer with Deletions"}} +
+
+ {{job-diff diff=deletionsOnly}} +
+
+{{/freestyle-usage}} +{{#freestyle-usage "diff-viewer-insertions-verbose" title="Diff Viewer with Edits"}} +
+
+ {{job-diff diff=editsOnly}} +
+
+{{/freestyle-usage}} + +{{#freestyle-annotation}} + Often times a diff will only have a couple lines. Minor tweaks to a job spec result in small diffs. +{{/freestyle-annotation}} + +{{#freestyle-usage "diff-viewer-large" title="Diff Viewer with Many Changes"}} +
+
+ {{job-diff diff=largeDiff}} +
+
+{{/freestyle-usage}} diff --git a/ui/app/templates/components/freestyle/sg-dropdown.hbs b/ui/app/templates/components/freestyle/sg-dropdown.hbs new file mode 100644 index 000000000000..c15239eb75d3 --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-dropdown.hbs @@ -0,0 +1,55 @@ +{{#freestyle-usage "dropdown" title="Simple Dropdown"}} + {{#power-select + options=options + selected=selectedOption + searchField="name" + searchEnabled=(gt options.length 10) + onchange=(action (mut selectedOption)) + as |option|}} + {{option.name}} + {{/power-select}} +{{/freestyle-usage}} + +{{#freestyle-annotation}} + Power Select currently fulfills all of Nomad's dropdown needs out of the box. +{{/freestyle-annotation}} + +{{#freestyle-usage "dropdown-sized" title="Dropdown Resized"}} +
+
+ {{#power-select + options=options + selected=selectedOption2 + searchField="name" + searchEnabled=(gt options.length 10) + onchange=(action (mut selectedOption2)) + as |option|}} + {{option.name}} + {{/power-select}} +
+
+{{/freestyle-usage}} + +{{#freestyle-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. +{{/freestyle-annotation}} + +{{#freestyle-usage "dropdown-search" title="Dropdown with Search"}} +
+
+ {{#power-select + options=manyOptions + selected=selectedOption3 + searchField="name" + searchEnabled=(gt manyOptions.length 10) + onchange=(action (mut selectedOption3)) + as |option|}} + {{option.name}} + {{/power-select}} +
+
+{{/freestyle-usage}} + +{{#freestyle-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. +{{/freestyle-annotation}} diff --git a/ui/app/templates/components/freestyle/sg-gutter-menu.hbs b/ui/app/templates/components/freestyle/sg-gutter-menu.hbs new file mode 100644 index 000000000000..8ccc3e7593af --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-gutter-menu.hbs @@ -0,0 +1,156 @@ +{{#freestyle-usage "gutter-nav" title="Gutter Menu"}} +
+
+
+ +
+
+
+
+
+
+
+
+{{/freestyle-usage}} + +{{#freestyle-usage "gutter-nav-rich-components" title="Gutter Navigation with Rich Components"}} +
+
+
+ +
+
+
+
+
+
+
+
+{{/freestyle-usage}} +{{#freestyle-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. +{{/freestyle-annotation}} + +{{#freestyle-usage "gutter-nav-many-items" title="Hypothetical Gutter Navigation with Many Items"}} +
+
+
+ +
+
+
+
+
+
+
+
+{{/freestyle-usage}} +{{#freestyle-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. +{{/freestyle-annotation}} + +{{#freestyle-usage "gutter-nav-icon-items" title="Hypothetical Gutter Navigation with Icon Items"}} +
+
+ +
+
+
+
+
+
+
+{{/freestyle-usage}} +{{#freestyle-annotation}} + In the future, the gutter menu may have icons. +{{/freestyle-annotation}} + +{{#freestyle-usage "gutter-nav-global" title="Global Gutter Navigation"}} +
+
+ {{#gutter-menu}} + {{!-- Page content here --}} + {{/gutter-menu}} +
+
+{{/freestyle-usage}} +{{#freestyle-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.

+ +

Note: 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.

+{{/freestyle-annotation}} diff --git a/ui/app/templates/components/freestyle/sg-header.hbs b/ui/app/templates/components/freestyle/sg-header.hbs new file mode 100644 index 000000000000..1a6a11addcec --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-header.hbs @@ -0,0 +1,14 @@ +{{#freestyle-usage "header" title="Global Header"}} + +{{/freestyle-usage}} diff --git a/ui/app/templates/components/freestyle/sg-inline-definitions.hbs b/ui/app/templates/components/freestyle/sg-inline-definitions.hbs new file mode 100644 index 000000000000..efca1ec815ed --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-inline-definitions.hbs @@ -0,0 +1,80 @@ +{{#freestyle-usage "inline-definitions" title="Inline Definitions"}} +
+
+ Some Label + + Term Name + Term Value + + + Running? + Yes + + + Last Updated + {{moment-format (now)}} + +
+
+{{/freestyle-usage}} +{{#freestyle-annotation}} + A way to tightly display key/value information. Typically seen at the top of pages. +{{/freestyle-annotation}} + +{{#freestyle-usage "inline-definitions-variants" title="Inline Definitions Variants"}} +
+
+ Success Label + + Term Name + Term Value + + + Last Updated + {{moment-format (now)}} + +
+
+
+
+ Warning Label + + Term Name + Term Value + + + Last Updated + {{moment-format (now)}} + +
+
+
+
+ Danger Label + + Term Name + Term Value + + + Last Updated + {{moment-format (now)}} + +
+
+
+
+ Info Label + + Term Name + Term Value + + + Last Updated + {{moment-format (now)}} + +
+
+{{/freestyle-usage}} +{{#freestyle-annotation}} + Inline definitions are meant to pair well with emotive color variations. +{{/freestyle-annotation}} diff --git a/ui/app/templates/components/freestyle/sg-json-viewer.hbs b/ui/app/templates/components/freestyle/sg-json-viewer.hbs new file mode 100644 index 000000000000..88f3b975757c --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-json-viewer.hbs @@ -0,0 +1,31 @@ +{{#freestyle-usage "json-viewer" title="JSON Viewer"}} +
+
+ {{json-viewer json=jsonSmall}} +
+
+{{/freestyle-usage}} + +{{#freestyle-usage "json-viewer-full" title="JSON Viewer for Full Document"}} +
+
+ {{json-viewer json=jsonLarge}} +
+
+{{/freestyle-usage}} + +{{#freestyle-collection defaultKey=0 as |collection|}} + {{#each (array 0 1 2 3 4 5) as |depth|}} + {{#collection.variant key=depth}} + {{#freestyle-usage + (concat "json-viewer-truncated-" depth) + title=(concat "JSON Viewer Expand Depth " depth)}} +
+
+ {{json-viewer json=jsonLarge expandDepth=depth}} +
+
+ {{/freestyle-usage}} + {{/collection.variant}} + {{/each}} +{{/freestyle-collection}} diff --git a/ui/app/templates/components/freestyle/sg-log-stream.hbs b/ui/app/templates/components/freestyle/sg-log-stream.hbs new file mode 100644 index 000000000000..c3d656dcbd65 --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-log-stream.hbs @@ -0,0 +1,24 @@ +{{#freestyle-usage "log-stream" title="Log Stream"}} +
+
+ + + + + + + + + +
+
+
{{if (eq mode1 "stdout") sampleOutput sampleError}}
+
+
+{{/freestyle-usage}} diff --git a/ui/app/templates/components/freestyle/sg-metrics.hbs b/ui/app/templates/components/freestyle/sg-metrics.hbs new file mode 100644 index 000000000000..cbd2172fa00f --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-metrics.hbs @@ -0,0 +1,125 @@ +{{#freestyle-usage "metrics" title="Metrics"}} +
+
+

Label

+

12

+
+
+{{/freestyle-usage}} +{{#freestyle-annotation}} +

Metrics are a way to show simple values (generally numbers). Labels are smaller than numbers to put emphasis on the data.

+{{/freestyle-annotation}} + +{{#freestyle-usage "metric-groups" title="Metric Groups"}} +
+
+

Label

+

1 / 2

+
+
+

Number

+

1,300

+
+
+

Datacenter

+

dc1

+
+
+ +
+
+

Today

+

81º

+
+
+

Tomorrow

+

73º

+
+
+{{/freestyle-usage}} +{{#freestyle-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.

+{{/freestyle-annotation}} + +{{#freestyle-usage "metric-colors" title="Metric Colors"}} +
+
+

Info

+

1

+
+
+

Success

+

2

+
+
+

Warning

+

3

+
+
+

Danger

+

4

+
+
+ +
+
+

White

+

5

+
+
+

Light

+

6

+
+
+

Primary

+

7

+
+
+

Dark

+

8

+
+
+

Black

+

9

+
+
+{{/freestyle-usage}} +{{#freestyle-annotation}} +

All color-modifiers work for metrics, but some work better than others.

+

Emotive colors work well and are put to use when applicable. Other colors have worse support and less utility.

+{{/freestyle-annotation}} + +{{#freestyle-usage "metric-states" title="Metric States"}} +
+
+

One

+

A

+
+
+

Two

+

B

+
+
+

Three

+

C

+
+
+ +
+
+

One

+

A

+
+
+

Two

+

B

+
+
+

Three

+

C

+
+
+{{/freestyle-usage}} +{{#freestyle-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.

+{{/freestyle-annotation}} diff --git a/ui/app/templates/components/freestyle/sg-page-tabs.hbs b/ui/app/templates/components/freestyle/sg-page-tabs.hbs new file mode 100644 index 000000000000..56fd755f40fc --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-page-tabs.hbs @@ -0,0 +1,18 @@ +{{#freestyle-usage "page-tabs" title="Page Tabs"}} +
+ +
+{{/freestyle-usage}} + +{{#freestyle-usage "page-tabs-single" title="Single Page Tab"}} +
+ +
+{{/freestyle-usage}} diff --git a/ui/app/templates/components/freestyle/sg-page-title.hbs b/ui/app/templates/components/freestyle/sg-page-title.hbs new file mode 100644 index 000000000000..abb419d899ec --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-page-title.hbs @@ -0,0 +1,51 @@ +{{#freestyle-usage "page-title" title="Page Title"}} +
+

This is the Page Title

+
+{{/freestyle-usage}} +{{#freestyle-annotation}} +

In its simplest form, a page title is just an H1.

+{{/freestyle-annotation}} + +{{#freestyle-usage "page-title-after-elements" title="Page Title with After Elements"}} +
+

+ This is the Page Title + Running + 237aedcb8982fe09bcee0877acedd +

+
+{{/freestyle-usage}} +{{#freestyle-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.

+{{/freestyle-annotation}} + +{{#freestyle-usage "page-title-with-status-light" title="Page Title with Status Light"}} +
+

+ + This is the Page Title + Running + 237aedcb8982fe09bcee0877acedd +

+
+{{/freestyle-usage}} +{{#freestyle-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.

+{{/freestyle-annotation}} + +{{#freestyle-usage "page-title-with-actions" title="Page Title with Actions"}} +
+

+ + This is the Page Title + Running + 237aedcb8982fe09bcee0877acedd + + +

+
+{{/freestyle-usage}} +{{#freestyle-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.

+{{/freestyle-annotation}} diff --git a/ui/app/templates/components/freestyle/sg-progress-bar.hbs b/ui/app/templates/components/freestyle/sg-progress-bar.hbs new file mode 100644 index 000000000000..24e34f00e6d9 --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-progress-bar.hbs @@ -0,0 +1,77 @@ +{{#freestyle-usage "progress-bar" title="Progress Bar"}} +
+ + 0.33 + +
+{{/freestyle-usage}} + +{{#freestyle-usage "progress-bar-colors" title="Progress Bar Colors"}} +
+
+
+ + 0.33 + +
+
+
+
+ + 0.33 + +
+
+
+
+ + 0.33 + +
+
+
+
+ + 0.33 + +
+
+
+{{/freestyle-usage}} + +{{#freestyle-usage "progress-bar-live" title="Progress Bar Live Updates"}} +
+
+
+ + {{percentage}} + +
+
+
+{{/freestyle-usage}} +{{#freestyle-annotation}} +
+
+ {{json-viewer json=liveDetails}} +
+
+{{/freestyle-annotation}} diff --git a/ui/app/templates/components/freestyle/sg-search-box.hbs b/ui/app/templates/components/freestyle/sg-search-box.hbs new file mode 100644 index 000000000000..4a0323b81cda --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-search-box.hbs @@ -0,0 +1,19 @@ +{{#freestyle-usage "search-box" title="Search Box"}} + {{search-box + searchTerm=(mut searchTerm1) + placeholder="Search things..."}} +{{/freestyle-usage}} +{{#freestyle-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.

+

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.

+{{/freestyle-annotation}} + +{{#freestyle-usage "search-box-compact" title="Search Box Compact"}} + {{search-box + searchTerm=(mut searchTerm2) + placeholder="Search things..." + inputClass="is-compact"}} +{{/freestyle-usage}} +{{#freestyle-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.

+{{/freestyle-annotation}} diff --git a/ui/app/templates/components/freestyle/sg-table-configuration.hbs b/ui/app/templates/components/freestyle/sg-table-configuration.hbs new file mode 100644 index 000000000000..5c7ff65b5e9f --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-table-configuration.hbs @@ -0,0 +1,3 @@ +{{#freestyle-usage "table-configuration" title="Table, Configuration"}} + {{attributes-table attributes=attributes class="attributes-table"}} +{{/freestyle-usage}} diff --git a/ui/app/templates/components/freestyle/sg-table.hbs b/ui/app/templates/components/freestyle/sg-table.hbs new file mode 100644 index 000000000000..fffa79ef033c --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-table.hbs @@ -0,0 +1,257 @@ +{{#freestyle-usage "table-simple" title="Table"}} + {{#list-table source=shortList as |t|}} + {{#t.head}} + Name + Language + Description + {{/t.head}} + {{#t.body key="model.name" as |row|}} + + {{row.model.name}} + {{row.model.lang}} + {{row.model.desc}} + + {{/t.body}} + {{/list-table}} +{{/freestyle-usage}} +{{#freestyle-annotation}} +

Tables have airy designs with a minimal amount of borders. This maximizes their utility.

+{{/freestyle-annotation}} + +{{#freestyle-usage "table-search" title="Table Search"}} +
+
+ Table Name + {{search-box + searchTerm=(mut searchTerm) + placeholder="Search..." + class="is-inline pull-right" + inputClass="is-compact"}} +
+
+ {{#if filteredShortList.length}} + {{#list-table source=filteredShortList as |t|}} + {{#t.head}} + Name + Language + Description + {{/t.head}} + {{#t.body key="model.name" as |row|}} + + {{row.model.name}} + {{row.model.lang}} + {{row.model.desc}} + + {{/t.body}} + {{/list-table}} + {{else}} +
+

No Matches

+

No products match your query.

+
+ {{/if}} +
+
+{{/freestyle-usage}} +{{#freestyle-annotation}} +

Tables compose with boxed-section and boxed-section composes with search box.

+{{/freestyle-annotation}} + +{{#freestyle-usage "table-sortable-columns" title="Table Sortable Columns"}} + {{#list-table + source=sortedShortList + sortProperty=sortProperty + sortDescending=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}} + Description + {{/t.head}} + {{#t.body key="model.name" as |row|}} + + {{row.model.name}} + {{row.model.lang}} + {{row.model.desc}} + + {{/t.body}} + {{/list-table}} +{{/freestyle-usage}} +{{#freestyle-annotation}} +

The list-table component provides a sort-by contextual component for building link-to components with the appropriate query params.

+

This leaves the component stateless, relying on data to be passed down and sending actions back up via the router (via link-to).

+{{/freestyle-annotation}} + +{{#freestyle-usage "table-multi-row" title="Table Multi-row"}} + {{#list-table + source=sortedShortList + sortProperty=sortProperty + sortDescending=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|}} + + {{row.model.name}} + {{row.model.lang}} + + + {{row.model.desc}} + + {{/t.body}} + {{/list-table}} +{{/freestyle-usage}} +{{#freestyle-annotation}} +

THe list-table component attempts to be as flexible as possible. For this reason, t.body does not provide the typical tr element. It's sometimes desired to have multiple elements per record.

+{{/freestyle-annotation}} + +{{#freestyle-usage "table-pagination" title="Table Pagination"}} + {{#list-pagination source=longList size=5 page=currentPage as |p|}} + {{#list-table source=p.list class="with-foot" as |t|}} + {{#t.head}} + Rank + City + State + Population + Growth + {{/t.head}} + {{#t.body key="model.rank" as |row|}} + + {{row.model.rank}} + {{row.model.city}} + {{row.model.state}} + {{row.model.population}} + {{format-percentage row.model.growth total=1}} + + {{/t.body}} + {{/list-table}} +
+ +
+ {{/list-pagination}} +{{/freestyle-usage}} +{{#freestyle-annotation}} +

Pagination works like sorting: using link-tos to set a query param.

+

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.

+

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.

+{{/freestyle-annotation}} + +{{#freestyle-usage "table-row-links" title="Table Row Links"}} + {{#list-table source=shortList as |t|}} + {{#t.head}} + Name + Language + Description + {{/t.head}} + {{#t.body key="model.name" as |row|}} + + {{row.model.name}} + {{row.model.lang}} + {{row.model.desc}} + + {{/t.body}} + {{/list-table}} +{{/freestyle-usage}} +{{#freestyle-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 is-interactive on the tr makes table rows have a pointer cursor. The helper class is-primary on the a 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.

+

A few rules for using table row links:

+
    +
  1. The is-primary cell should always be the first cell
  2. +
  3. The is-primary cell should always contain a link to the destination in the form of an a element. This is to support opening a link in a new tab.
  4. +
  5. 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.
  6. +
+{{/freestyle-annotation}} + +{{#freestyle-usage "table-cell-links" title="Table Cell Links"}} + {{#list-table source=shortList as |t|}} + {{#t.head}} + Name + Language + Description + {{/t.head}} + {{#t.body key="model.name" as |row|}} + + {{row.model.name}} + {{row.model.lang}} + {{row.model.desc}} + + {{/t.body}} + {{/list-table}} +{{/freestyle-usage}} +{{#freestyle-annotation}} +

Links in table cells are just links.

+{{/freestyle-annotation}} + +{{#freestyle-usage "table-cell-decorations" title="Table Cell Decorations"}} + {{#list-table source=shortList as |t|}} + {{#t.head}} + Name + Language + Description + {{/t.head}} + {{#t.body key="model.name" as |row|}} + + {{row.model.name}} + + + {{row.model.lang}} + + {{row.model.desc}} + + {{/t.body}} + {{/list-table}} +{{/freestyle-usage}} +{{#freestyle-annotation}} +

Small icons and accents of color make tables easier to scan.

+{{/freestyle-annotation}} + +{{#freestyle-usage "table-cell-icons" title="Table Cell Icons"}} + {{#list-pagination source=longList size=5 page=currentPage as |p|}} + {{#list-table source=p.list class="with-foot" as |t|}} + {{#t.head}} + + Rank + City + State + Population + Growth + {{/t.head}} + {{#t.body key="model.rank" as |row|}} + + + {{#if (lt row.model.growth 0)}} + {{x-icon "warning" class="is-warning"}} + {{/if}} + + {{row.model.rank}} + {{row.model.city}} + {{row.model.state}} + {{row.model.population}} + {{format-percentage row.model.growth total=1}} + + {{/t.body}} + {{/list-table}} +
+ +
+ {{/list-pagination}} +{{/freestyle-usage}} diff --git a/ui/app/templates/components/freestyle/sg-timeline.hbs b/ui/app/templates/components/freestyle/sg-timeline.hbs new file mode 100644 index 000000000000..21b6c96751ef --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-timeline.hbs @@ -0,0 +1,204 @@ +{{#freestyle-usage 'timeline' title="Simple Timeline"}} +
    +
  1. + {{moment-format yesterday "MMMM D, YYYY"}} +
  2. +
  3. +
    +
    + Object number one +
    +
    +
  4. +
  5. +
    +
    + Object number two +
    +
    +
  6. +
  7. + {{moment-format today "MMMM D, YYYY"}} +
  8. +
  9. +
    +
    + Object number three +
    +
    +
  10. +
+{{/freestyle-usage}} + +{{#freestyle-annotation}} +

Timelines are a combination of objects and notes. Objects compose with boxed sections to create structure.

+

Timeline notes should be used sparingly when possible. In this example there is a note per day rather than a note per object.

+{{/freestyle-annotation}} + +{{#freestyle-usage 'timeline-intricate' title="Detailed Timeline"}} +
    +
  1. + {{moment-format today "MMMM D, YYYY"}} +
  2. +
  3. +
    +
    + Running + + Stable + a387e243 + + + Submitted + {{moment-from-now (now)}} + +
    +
    +
  4. +
  5. +
    +
    + Complete + + Expired + b3220efb + + + Submitted + {{moment-format yesterday}} + +
    +
    +
  6. +
  7. + {{moment-format yesterday "MMMM D, YYYY"}} +
  8. +
  9. +
    +
    + Failed + + Reverted + fec9218e + + + Submitted + {{moment-format yesterday}} + +
    +
    +
  10. +
+{{/freestyle-usage}} + +{{#freestyle-usage 'timeline-toggles' title='Toggling Timeline Objects'}} +
    +
  1. + {{moment-format today "MMMM D, YYYY"}} +
  2. +
  3. +
    +
    + Running + + Stable + a387e243 + + +
    + {{#if toggle1}} +
    +

    Some details for the timeline object.

    +
    + {{/if}} +
    +
  4. +
  5. + {{moment-format yesterday "MMMM D, YYYY"}} +
  6. +
  7. +
    +
    + Complete + + Expired + b3220efb + + +
    + {{#if toggle2}} +
    +

    Some details for the timeline object.

    +
    + {{/if}} +
    +
  8. +
+{{/freestyle-usage}} + +{{#freestyle-usage 'timeline-emphasis' title='Emphasizing a Timeline Object'}} +
    +
  1. + {{moment-format today "MMMM D, YYYY"}} +
  2. +
  3. +
    +
    + + Stable + a387e243 + + + Submitted + {{moment-from-now (now)}} + +
    +
    +
  4. +
  5. +
    +
    + Pay attention here +
    +
    + + Expired + b3220efb + + + Submitted + {{moment-format yesterday}} + +
    +
    +
  6. +
  7. + {{moment-format yesterday "MMMM D, YYYY"}} +
  8. +
  9. +
    +
    + + Reverted + fec9218e + + + Submitted + {{moment-format yesterday}} + +
    +
    +
  10. +
+{{/freestyle-usage}} +{{#freestyle-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. +{{/freestyle-annotation}} diff --git a/ui/app/templates/components/freestyle/sg-two-step-button.hbs b/ui/app/templates/components/freestyle/sg-two-step-button.hbs new file mode 100644 index 000000000000..f39096b0e1e1 --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-two-step-button.hbs @@ -0,0 +1,22 @@ +{{#freestyle-usage "two-step-button" title="Two Step Button"}} +
+ {{two-step-button + idleText="Scary Action" + cancelText="Nvm" + confirmText="Yep" + confirmationMessage="Wait, really? Like...seriously?"}} +
+{{/freestyle-usage}} + +{{#freestyle-usage "two-step-button-title" title="Two Step Button in Title"}} +
+

+ This is a page title + {{two-step-button + idleText="Scary Action" + cancelText="Nvm" + confirmText="Yep" + confirmationMessage="Wait, really? Like...seriously?"}} +

+
+{{/freestyle-usage}} diff --git a/ui/app/templates/components/two-step-button.hbs b/ui/app/templates/components/two-step-button.hbs index a7762124b099..e9fe906b59e8 100644 --- a/ui/app/templates/components/two-step-button.hbs +++ b/ui/app/templates/components/two-step-button.hbs @@ -1,16 +1,16 @@ {{#if isIdle}} - {{else if isPendingConfirmation}} {{confirmationMessage}} - -