diff --git a/ui/app/components/freestyle/sg-accordion.js b/ui/app/components/freestyle/sg-accordion.js new file mode 100644 index 00000000000..84f7a7360a8 --- /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 00000000000..e81e64cf5f6 --- /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 00000000000..bc9980efc47 --- /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 00000000000..5eea5837cf1 --- /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 00000000000..a2d736975da --- /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 00000000000..613bc08e9bd --- /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 00000000000..2e5a8e3ca1d --- /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 00000000000..da8112e19ac --- /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 00000000000..c84ac3ab3c3 --- /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 a5809f613a8..f585f0460ee 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 18ed9fb01f3..b46b1a4395f 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 06fd74a5639..0811337afd9 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 f77df0655c4..36093f53cb6 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 00000000000..13975dec941 --- /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.desc}}
+ + {{/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.desc}}
+ + {{/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.desc}}
+ + {{/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 00000000000..f1d7c8c30c0 --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-alerts.hbs @@ -0,0 +1,84 @@ +{{#freestyle-usage "alert-standard" title="Alert"}} +Alerts are used for both situational and reactionary information.
+Alerts use Bulma's notification component.
+{{/freestyle-annotation}} + +{{#freestyle-usage "alert-colors" title="Alert colors"}} +Alerts are used for both situational and reactionary information.
+Alerts are used for both situational and reactionary information.
+Alerts are used for both situational and reactionary information.
+Alerts are used for both situational and reactionary information.
+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"}} +Alerts are used for both situational and reactionary information.
+Alerts are used for both situational and reactionary information.
+Alerts are used for both situational and reactionary information.
+Alerts are used for both situational and reactionary information.
+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 00000000000..1a6a11addce --- /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 00000000000..efca1ec815e --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-inline-definitions.hbs @@ -0,0 +1,80 @@ +{{#freestyle-usage "inline-definitions" title="Inline Definitions"}} +{{if (eq mode1 "stdout") sampleOutput sampleError}}
+ 12
+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"}} +1 / 2
+1,300
+dc1
+81º
+73º
+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"}} +1
+2
+3
+4
+5
+6
+7
+8
+9
+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"}} +A
+B
+C
+A
+B
+C
+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 00000000000..56fd755f40f --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-page-tabs.hbs @@ -0,0 +1,18 @@ +{{#freestyle-usage "page-tabs" title="Page Tabs"}} +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"}} +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"}} +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"}} +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 00000000000..24e34f00e6d --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-progress-bar.hbs @@ -0,0 +1,77 @@ +{{#freestyle-usage "progress-bar" title="Progress Bar"}} +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 00000000000..5c7ff65b5e9 --- /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 00000000000..fffa79ef033 --- /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}} +Tables have airy designs with a minimal amount of borders. This maximizes their utility.
+{{/freestyle-annotation}} + +{{#freestyle-usage "table-search" title="Table Search"}} +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}} +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|}} +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.
Pagination works like sorting: using link-to
s 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}} +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:
+is-primary
cell should always be the first cellis-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.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}} +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}} +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"}} +a387e243
+
+
+ Submitted
+ {{moment-from-now (now)}}
+
+ b3220efb
+
+
+ Submitted
+ {{moment-format yesterday}}
+
+ fec9218e
+
+
+ Submitted
+ {{moment-format yesterday}}
+
+ a387e243
+
+
+ Some details for the timeline object.
+b3220efb
+
+
+ Some details for the timeline object.
+a387e243
+
+
+ Submitted
+ {{moment-from-now (now)}}
+
+ b3220efb
+
+
+ Submitted
+ {{moment-format yesterday}}
+
+ fec9218e
+
+
+ Submitted
+ {{moment-format yesterday}}
+
+