├── .bowerrc ├── .editorconfig ├── .ember-cli ├── .gitignore ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── 200.jade ├── Brocfile.js ├── CONTRIBUTING.md ├── LICENCE.md ├── README.md ├── app ├── adapters │ ├── config-last.js │ ├── config-next.js │ ├── hook.js │ ├── host.js │ ├── journal.js │ ├── orchestrator.js │ ├── scheduler.js │ ├── service.js │ └── unit.js ├── app.js ├── components │ ├── .gitkeep │ ├── health-buttons.js │ ├── health-state.js │ ├── quick-chart.js │ └── service-edit.js ├── controllers │ ├── .gitkeep │ ├── application.js │ ├── host.js │ ├── load-balancers.js │ ├── service.js │ ├── services.js │ └── unit.js ├── helpers │ ├── .gitkeep │ ├── format-date.js │ └── validators.js ├── index.html ├── models │ ├── .gitkeep │ ├── config-edit.js │ ├── config-last.js │ ├── config-next.js │ ├── config.js │ ├── hook.js │ ├── host.js │ ├── journal.js │ ├── load-balancer.js │ ├── service.js │ └── unit.js ├── router.js ├── routes │ ├── .gitkeep │ ├── config-last.js │ ├── config-next.js │ ├── dashboard.js │ ├── dashboard │ │ └── index.js │ ├── history.js │ ├── hosts.js │ ├── index.js │ ├── load-balancers.js │ ├── logs.js │ ├── monitoring.js │ ├── monitoring │ │ ├── host.js │ │ └── unit.js │ ├── service.js │ ├── services.js │ ├── services │ │ └── new.js │ ├── styleguide.js │ └── units.js ├── serializers │ ├── config-last.js │ ├── config-next.js │ ├── config.js │ ├── hook.js │ ├── host.js │ ├── journal.js │ ├── load-balancer.js │ ├── service.js │ └── unit.js ├── styles │ ├── .gitkeep │ ├── _animations.scss │ ├── _buttons.scss │ ├── _card.scss │ ├── _form.scss │ ├── _health.scss │ ├── _helpers.scss │ ├── _layout.scss │ ├── _navigation.scss │ ├── _pills.scss │ ├── _progress-bar.scss │ ├── _range.scss │ ├── _ss-standard.scss │ ├── _table.scss │ ├── _typography.scss │ ├── _variables.scss │ ├── app.scss │ ├── container.scss │ └── history.scss ├── templates │ ├── .gitkeep │ ├── application.hbs │ ├── components │ │ ├── .gitkeep │ │ ├── health-buttons.hbs │ │ ├── health-state.hbs │ │ ├── progress-bar.hbs │ │ ├── quick-chart.hbs │ │ ├── service-config.hbs │ │ └── service-edit.hbs │ ├── dashboard.hbs │ ├── dashboard │ │ ├── index.hbs │ │ └── loading.hbs │ ├── error.hbs │ ├── history.hbs │ ├── hosts.hbs │ ├── index.hbs │ ├── load-balancers.hbs │ ├── logs.hbs │ ├── monitoring │ │ ├── host.hbs │ │ ├── index.hbs │ │ └── unit.hbs │ ├── service.hbs │ ├── service │ │ └── edit.hbs │ ├── services.hbs │ ├── services │ │ ├── index.hbs │ │ └── new.hbs │ ├── styleguide.hbs │ └── units.hbs └── views │ └── .gitkeep ├── bin ├── dockerBuild.sh └── run.sh ├── bower.json ├── config └── environment.js ├── docker └── Dockerfile ├── environment.sh ├── package.json ├── paz-ember.sublime-project ├── public ├── .gitkeep ├── assets │ ├── fonts │ │ ├── ss-standard.eot │ │ ├── ss-standard.js │ │ ├── ss-standard.svg │ │ ├── ss-standard.ttf │ │ └── ss-standard.woff │ └── images │ │ └── logo.svg ├── crossdomain.xml └── robots.txt ├── testem.json └── tests ├── .jshintrc ├── helpers ├── resolver.js └── start-app.js ├── index.html ├── test-helper.js └── unit ├── .gitkeep ├── adapters └── load-balancer-test.js ├── components ├── health-buttons-test.js ├── health-state-test.js ├── quick-chart-test.js ├── service-config-test.js └── service-edit-test.js ├── controllers ├── application-test.js ├── host-test.js ├── services-test.js └── unit-test.js ├── helpers └── validators-test.js ├── models ├── config-test.js ├── host-test.js ├── load-balancer-test.js ├── service-test.js └── unit-test.js ├── routes ├── dashboard-test.js ├── dashboard │ └── index-test.js ├── hosts-test.js ├── index-test.js ├── monitoring-test.js ├── services-test.js ├── services │ └── new-test.js ├── styleguide-test.js └── units-test.js └── serializers └── config-test.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.js] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.hbs] 21 | indent_style = space 22 | indent_size = 2 23 | 24 | [*.css] 25 | indent_style = space 26 | indent_size = 2 27 | 28 | [*.html] 29 | indent_style = space 30 | indent_size = 2 31 | 32 | [*.{diff,md}] 33 | trim_trailing_whitespace = false 34 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/* 15 | /libpeerconnection.log 16 | npm-debug.log 17 | testem.log 18 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | dist 4 | public/assets/fonts 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": { 3 | "document": true, 4 | "window": true, 5 | "-Promise": true, 6 | "PazEmberENV": true, 7 | "Em": true, 8 | "EmberSockets": true, 9 | "ok": true, 10 | "expect": true, 11 | "equal": true 12 | }, 13 | "browser": true, 14 | "boss": true, 15 | "curly": true, 16 | "debug": false, 17 | "devel": true, 18 | "eqeqeq": true, 19 | "evil": true, 20 | "forin": false, 21 | "immed": false, 22 | "laxbreak": false, 23 | "newcap": true, 24 | "noarg": true, 25 | "noempty": false, 26 | "nonew": false, 27 | "nomen": false, 28 | "onevar": false, 29 | "plusplus": false, 30 | "regexp": false, 31 | "undef": true, 32 | "sub": true, 33 | "strict": false, 34 | "white": false, 35 | "eqnull": true, 36 | "esnext": true, 37 | "unused": true 38 | } 39 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | 4 | sudo: false 5 | 6 | cache: 7 | directories: 8 | - node_modules 9 | 10 | before_install: 11 | - "npm config set spin false" 12 | - "npm install -g npm@^2" 13 | 14 | install: 15 | - npm install -g bower 16 | - npm install 17 | - bower install 18 | 19 | script: 20 | - npm test 21 | -------------------------------------------------------------------------------- /200.jade: -------------------------------------------------------------------------------- 1 | include ./index.html 2 | -------------------------------------------------------------------------------- /Brocfile.js: -------------------------------------------------------------------------------- 1 | /* global require, module */ 2 | 3 | var EmberApp = require('ember-cli/lib/broccoli/ember-app'); 4 | 5 | var app = new EmberApp(); 6 | 7 | // Use `app.import` to add additional libraries to the generated 8 | // output files. 9 | // 10 | // If you need to use different assets in different 11 | // environments, specify an object as the first parameter. That 12 | // object's keys should be the environment name and the values 13 | // should be the asset to use in that environment. 14 | // 15 | // If the library that you are including contains AMD or ES6 16 | // modules that you would like to import into your application 17 | // please specify an object with the list of modules as keys 18 | // along with the exports of each module as its value. 19 | 20 | app.import('bower_components/socket.io-client/socket.io.js'); 21 | app.import('bower_components/ember-sockets/dist/ember-sockets.js'); 22 | app.import('bower_components/jQuery-Collapse/src/jquery.collapse.js'); 23 | 24 | module.exports = app.toTree(); 25 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Submitting a Pull Request 4 | 5 | Please ensure that your pull request meets the following criteria: 6 | 7 | * Existing tests pass 8 | * New tests are added to cover the issue or feature address by your pull request 9 | * Code style conforms to that of the rest of the project (ie. es-lint reports no errors) 10 | 11 | Other requirements will be added in the future (e.g. code coverage, specific forking/branching rules, etc.), so do check back here. 12 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | # Licence 2 | 3 | Copyright 2015 YLD Ltd. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Paz-ember 2 | 3 | This README outlines the details of collaborating on this Ember application. 4 | A short introduction of this app could easily go here. 5 | 6 | ## Prerequisites 7 | 8 | You will need the following things properly installed on your computer. 9 | 10 | * [Git](http://git-scm.com/) 11 | * [Node.js](http://nodejs.org/) (with NPM) 12 | * [Bower](http://bower.io/) 13 | * [Ember CLI](http://www.ember-cli.com/) 14 | * [PhantomJS](http://phantomjs.org/) 15 | 16 | ## Installation 17 | 18 | * `git clone ` this repository 19 | * change into the new directory 20 | * `npm install` 21 | * `bower install` 22 | 23 | ## Running / Development 24 | 25 | * export PAZ_ORCHESTRATOR_URL=http://your_orchestrator_url 26 | * `ember server` 27 | * Visit your app at [http://localhost:4200](http://localhost:4200). 28 | 29 | ### Code Generators 30 | 31 | Make use of the many generators for code, try `ember help generate` for more details 32 | 33 | ### Running Tests 34 | 35 | * `ember test` 36 | * `ember test --server` 37 | 38 | ### Building 39 | 40 | * `ember build` (development) 41 | * `ember build --environment production` (production) 42 | 43 | ### Deploying 44 | 45 | Specify what it takes to deploy your app. 46 | 47 | ## Further Reading / Useful Links 48 | 49 | * [ember.js](http://emberjs.com/) 50 | * [ember-cli](http://www.ember-cli.com/) 51 | * Development Browser Extensions 52 | * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) 53 | * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) 54 | 55 | -------------------------------------------------------------------------------- /app/adapters/config-last.js: -------------------------------------------------------------------------------- 1 | import Adapter from './orchestrator'; 2 | import Ember from 'ember'; 3 | 4 | var get = Ember.get; 5 | 6 | export default Adapter.extend({ 7 | namespace: 'services', 8 | buildURL: function(type, id) { 9 | var url = [], 10 | host = get(this, 'host'), 11 | prefix = this.urlPrefix(); 12 | 13 | if (id && !Ember.isArray(id)) { url.push(id); } 14 | url.push('config', 'last'); 15 | 16 | if (prefix) { url.unshift(prefix); } 17 | 18 | url = url.join('/'); 19 | if (!host && url) { url = '/' + url; } 20 | 21 | return url; 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /app/adapters/config-next.js: -------------------------------------------------------------------------------- 1 | import Adapter from './orchestrator'; 2 | import Ember from 'ember'; 3 | 4 | var get = Ember.get; 5 | 6 | export default Adapter.extend({ 7 | namespace: 'services', 8 | buildURL: function(type, id) { 9 | var url = [], 10 | host = get(this, 'host'), 11 | prefix = this.urlPrefix(); 12 | 13 | if (id && !Ember.isArray(id)) { url.push(id); } 14 | url.push('config', 'next'); 15 | 16 | if (prefix) { url.unshift(prefix); } 17 | 18 | url = url.join('/'); 19 | if (!host && url) { url = '/' + url; } 20 | 21 | return url; 22 | }, 23 | // Override to PATCH instead of PUT 24 | updateRecord: function(store, type, record) { 25 | var data = {}; 26 | var serializer = store.serializerFor(type.typeKey); 27 | 28 | serializer.serializeIntoHash(data, type, record._createSnapshot()); 29 | 30 | var service_name = get(record, 'id'); 31 | 32 | data.configNext.numInstances = Number(data.configNext.numInstances); 33 | 34 | return this.ajax(this.buildURL(type.typeKey, service_name, record), 'PATCH', { data: data.configNext }); 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /app/adapters/hook.js: -------------------------------------------------------------------------------- 1 | import Adapter from './scheduler'; 2 | 3 | export default Adapter.extend({ 4 | namespace: 'hooks', 5 | buildURL: function(type, id, snapshot) { 6 | var s = this._super(type, id, snapshot); 7 | return s; 8 | }, 9 | createRecord: function(store, type, snapshot) { 10 | var serializer = store.serializerFor(type.typeKey); 11 | var data = serializer.serialize(snapshot.toJSON()); 12 | return this.ajax( this.buildURL() + '/deploy', 'POST', {data: data}); 13 | }, 14 | updateRecord: function(store, type, snapshot) { 15 | return this.createRecord(store, type, snapshot); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /app/adapters/host.js: -------------------------------------------------------------------------------- 1 | import Adapter from './orchestrator'; 2 | 3 | export default Adapter.extend({ 4 | namespace: 'cluster' 5 | }); 6 | -------------------------------------------------------------------------------- /app/adapters/journal.js: -------------------------------------------------------------------------------- 1 | import Adapter from './scheduler'; 2 | 3 | export default Adapter.extend({ 4 | pathForType: function(type) { 5 | return (type); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /app/adapters/orchestrator.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | import config from '../config/environment'; 3 | 4 | export default DS.RESTAdapter.extend({ 5 | host: config.APP.ORCHESTRATOR_URL, 6 | }); 7 | -------------------------------------------------------------------------------- /app/adapters/scheduler.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | import config from '../config/environment'; 3 | 4 | export default DS.RESTAdapter.extend({ 5 | host: config.APP.SCHEDULER_URL, 6 | }); 7 | -------------------------------------------------------------------------------- /app/adapters/service.js: -------------------------------------------------------------------------------- 1 | import Adapter from './orchestrator'; 2 | 3 | export default Adapter.extend({ 4 | buildURL: function(type, id) { 5 | var url = this._super(type, id); 6 | 7 | // Just add a noEmit flag so the orchestrator knows not to tell us something happened 8 | return url + '?noEmit=true'; 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /app/adapters/unit.js: -------------------------------------------------------------------------------- 1 | import Adapter from './host'; 2 | 3 | export default Adapter.extend({ 4 | }); 5 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resolver from 'ember/resolver'; 3 | import loadInitializers from 'ember/load-initializers'; 4 | import config from './config/environment'; 5 | 6 | Ember.MODEL_FACTORY_INJECTIONS = true; 7 | 8 | // Register handlebars helper to print booleans 9 | Ember.Handlebars.helper('toString', function returnToString(x){ 10 | return ( x === void 0 ) ? 'undefined' : x.toString(); 11 | }); 12 | 13 | var App = Ember.Application.extend({ 14 | modulePrefix: config.modulePrefix, 15 | podModulePrefix: config.podModulePrefix, 16 | Resolver: Resolver, 17 | 18 | // ember-sockets stuff for Socket.io 19 | Socket: EmberSockets.extend({ 20 | host: config.APP.ORCHESTRATOR_SOCKET.split(':')[0], 21 | port: Number(config.APP.ORCHESTRATOR_SOCKET.split(':')[1]), 22 | controllers: ['services', 'host', 'unit'], 23 | autoConnect: true 24 | }) 25 | }); 26 | 27 | loadInitializers(App, 'paz-ember'); 28 | 29 | export default App; 30 | -------------------------------------------------------------------------------- /app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paz-sh/paz-web/21fbb43be813a5931a982a417b6be97147317c8d/app/components/.gitkeep -------------------------------------------------------------------------------- /app/components/health-buttons.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import layout from '../templates/components/health-buttons'; 3 | 4 | export default Ember.Component.extend({ 5 | layout: layout 6 | }); 7 | -------------------------------------------------------------------------------- /app/components/health-state.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import layout from '../templates/components/health-state'; 3 | 4 | export default Ember.Component.extend({ 5 | layout: layout 6 | }); 7 | -------------------------------------------------------------------------------- /app/components/quick-chart.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Component.extend({ 4 | style: function() { 5 | var current = this.get('current'); 6 | var total = current + this.get('next'); 7 | var percent = (current / total) * 100; 8 | 9 | return 'width:' + percent + '%;'; 10 | }.property('current') 11 | }); 12 | -------------------------------------------------------------------------------- /app/components/service-edit.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Component.extend({ 4 | actions: { 5 | addPorts: function(config) { 6 | if(!config.get('ports')) { 7 | config.set('ports', [{ 8 | container: '', 9 | host: '' 10 | }]); 11 | } else { 12 | config.get('ports').pushObject({ 13 | container: '', 14 | host: '' 15 | }); 16 | } 17 | }, 18 | removePort: function(config, port) { 19 | config.get('ports').removeObject(port); 20 | }, 21 | addEnvKey: function(config) { 22 | if(!config.get('env')) { 23 | config.set('env', [{ 24 | key: '', 25 | value: '' 26 | }]); 27 | } else { 28 | config.get('env').pushObject({ 29 | key: '', 30 | value: '' 31 | }); 32 | } 33 | }, 34 | removeEnvKey: function(config, envKey) { 35 | config.get('env').removeObject(envKey); 36 | } 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paz-sh/paz-web/21fbb43be813a5931a982a417b6be97147317c8d/app/controllers/.gitkeep -------------------------------------------------------------------------------- /app/controllers/application.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import config from '../config/environment'; 3 | 4 | export default Ember.Controller.extend({ 5 | developmentMode: function() { 6 | return config.environment === 'development'; 7 | }.property() 8 | }); 9 | -------------------------------------------------------------------------------- /app/controllers/host.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Controller.extend({ 4 | sockets: { 5 | 'unit.add': function(unit) { 6 | var units = this.store.all('unit'), 7 | newUnit = true; 8 | 9 | units.forEach(function(localUnit) { 10 | if(localUnit.get('unitHash') === unit.hash && 11 | localUnit.get('instance' === unit.instance)) { 12 | newUnit = false; 13 | } 14 | }); 15 | 16 | if(newUnit) { 17 | unit.id = unit.name; 18 | var toPush = this.store.push('unit', unit); 19 | var hosts = this.store.all('host'); 20 | 21 | hosts.forEach(function(host) { 22 | if(host.get('id') === unit.machineState.ID) { 23 | host.get('units').addObject(toPush); 24 | } 25 | }); 26 | } 27 | }, 28 | 'unit.remove': function(name) { 29 | var shouldDestroy = true; 30 | 31 | var unit = this.store.getById('unit', name), 32 | hosts = this.store.all('host'); 33 | 34 | if(unit.get('activeState') === 'activating') { 35 | shouldDestroy = false; 36 | } 37 | if(unit.get('activeState') === 'inactive') { 38 | shouldDestroy = false; 39 | } 40 | 41 | 42 | if(shouldDestroy) { 43 | hosts.forEach(function(host) { 44 | if(host.get('id') === unit.get('machineState.ID')) { 45 | host.get('units').removeObject(unit); 46 | } 47 | }); 48 | } 49 | } 50 | } 51 | }); 52 | -------------------------------------------------------------------------------- /app/controllers/load-balancers.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | function toArray(obj) { 4 | var arr = []; 5 | 6 | Object.keys(obj).forEach(function(key) { 7 | obj[key].order = Ember.String.capitalize(key); 8 | arr.push(obj[key]); 9 | }); 10 | 11 | return arr; 12 | } 13 | 14 | export default Ember.ArrayController.extend({ 15 | sockets: { 16 | // This is just a heartbeat to send stats info over a socket to the Ember app every second. 17 | 'haproxy': function(balancers) { 18 | var self = this; 19 | 20 | var storedBalancers = this.store.all('load-balancer'); 21 | 22 | balancers.forEach(function(balancer) { 23 | storedBalancers.forEach(function(storedBalancer) { 24 | var id = storedBalancer.get('id'); 25 | var hasBackends = false; 26 | 27 | if(id === balancer.service) { 28 | balancer.id = balancer.service; 29 | delete balancer.service; 30 | 31 | balancer.backends = toArray(balancer.backends); 32 | 33 | self.store.update('load-balancer', balancer); 34 | } 35 | 36 | storedBalancer.get('backends').forEach(function() { 37 | hasBackends = true; 38 | }); 39 | 40 | if(!hasBackends) { 41 | self.store.unloadRecord(storedBalancer); 42 | } 43 | }); 44 | 45 | if(!balancer.id && Object.keys(balancer.backends).length > 0) { 46 | self.store.pushPayload('load-balancer', [balancer]); 47 | } 48 | }); 49 | } 50 | } 51 | }); 52 | -------------------------------------------------------------------------------- /app/controllers/service.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.ObjectController.extend({ 4 | actions: { 5 | deploy: function() { 6 | if(this.get('isDeploying')) { 7 | return; 8 | } 9 | this.set('isDeploying', true); 10 | 11 | var service = this.get('model'); 12 | 13 | if(service.get('hook.content') == null) { 14 | service.set('hook', this.store.createRecord('hook')); 15 | } 16 | var hook = service.get('hook.content'); 17 | hook.set('serviceName', service.get('id')); 18 | hook.set('dockerRepository', service.get('dockerRepository')); 19 | hook.set('pushedAt', 0); 20 | 21 | var that = this; 22 | hook.save().then(function(hook) { 23 | // On success 24 | if (hook.get('statusCode') === 200) { 25 | // Reload last config 26 | that.store.find('configLast', service.get('id')).then(function(configLast) { 27 | service.set('configLast', configLast); 28 | }); 29 | 30 | that.set('isDeploying', false); 31 | that.set('isDeployFailed', false); 32 | that.set('isDeploySuccess', true); 33 | 34 | } else { 35 | // On error 36 | that.set('isDeploying', false); 37 | that.set('isDeployFailed', true); 38 | that.set('isDeploySuccess', false); 39 | } 40 | }, function(){ 41 | // On error 42 | that.set('isDeploying', false); 43 | that.set('isDeployFailed', true); 44 | that.set('isDeploySuccess', false); 45 | }); 46 | } 47 | } 48 | }); 49 | -------------------------------------------------------------------------------- /app/controllers/services.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.ArrayController.extend({ 4 | sockets: { 5 | 'service.create': function(service) { 6 | this.store.push('service', { 7 | id: service.name, 8 | description: service.description, 9 | configNext: service.name 10 | }); 11 | }, 12 | 'service.del': function(name) { 13 | var that = this; 14 | 15 | this.store.find('service', name).then(function(service) { 16 | that.store.unloadRecord(service); 17 | }); 18 | }, 19 | 'service.modifyConfig': function(name, config) { 20 | var serializer = this.store.serializerFor('configNext'); 21 | config = serializer.normalizePayload({doc: config}).configNext; 22 | 23 | this.store.find('service', name).then(function(model) { 24 | model.get('configNext').then(function(model) { 25 | for(var attr in config) { 26 | if(config.hasOwnProperty(attr)) { 27 | model.set(attr, config[attr]); 28 | } 29 | } 30 | }); 31 | }); 32 | } 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /app/controllers/unit.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Controller.extend({ 4 | sockets: { 5 | 'unit.add': function(unit) { 6 | var that = this; 7 | 8 | var units = this.store.all('unit'), 9 | newUnit = true; 10 | 11 | units.forEach(function(localUnit) { 12 | if(localUnit.get('unitHash') === unit.hash && 13 | localUnit.get('instance' === unit.instance)) { 14 | newUnit = false; 15 | } 16 | }); 17 | 18 | if(newUnit) { 19 | unit.id = unit.name; 20 | var services = this.store.all('service'); 21 | 22 | services.forEach(function(service) { 23 | if(service.get('id') === unit.service.get('id')) { 24 | service.get('units').addObject(that.store.push('unit', unit)); 25 | } 26 | }); 27 | } 28 | }, 29 | 'unit.remove': function(name) { 30 | var shouldDestroy = true; 31 | 32 | var unit = this.store.getById('unit', name), 33 | services = this.store.all('service'); 34 | 35 | if(unit.get('activeState') === 'activating') { 36 | shouldDestroy = false; 37 | } 38 | if(unit.get('activeState') === 'inactive') { 39 | shouldDestroy = false; 40 | } 41 | 42 | if(shouldDestroy) { 43 | services.forEach(function(service) { 44 | var unitService = unit.get('service'); 45 | if(service.id === unitService.id) { 46 | service.get('units').removeObject(unit); 47 | } 48 | }); 49 | } 50 | } 51 | } 52 | }); 53 | -------------------------------------------------------------------------------- /app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paz-sh/paz-web/21fbb43be813a5931a982a417b6be97147317c8d/app/helpers/.gitkeep -------------------------------------------------------------------------------- /app/helpers/format-date.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | import momentjs from 'moment'; 3 | 4 | export default Ember.Handlebars.makeBoundHelper(function(value) { 5 | return momentjs.apply(this, [value]).format('YYYY-MM-DD HH:mm:ss'); 6 | }); 7 | -------------------------------------------------------------------------------- /app/helpers/validators.js: -------------------------------------------------------------------------------- 1 | function validatePorts(ports) { 2 | var validateport = function (port) { 3 | return(port > 0 && port < 65536); 4 | }; 5 | for(var p=0; p 2 | 3 | 4 | 5 | 6 | PazEmber 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | 12 | 13 | 14 | 15 | {{content-for 'head-footer'}} 16 | 17 | 18 | {{content-for 'body'}} 19 | 20 | 21 | 22 | 23 | {{content-for 'body-footer'}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paz-sh/paz-web/21fbb43be813a5931a982a417b6be97147317c8d/app/models/.gitkeep -------------------------------------------------------------------------------- /app/models/config-edit.js: -------------------------------------------------------------------------------- 1 | import Config from './config'; 2 | 3 | var ConfigEdit = Config.extend({ 4 | }); 5 | 6 | export default ConfigEdit; 7 | -------------------------------------------------------------------------------- /app/models/config-last.js: -------------------------------------------------------------------------------- 1 | import Config from './config'; 2 | 3 | var ConfigLast = Config.extend({ 4 | }); 5 | 6 | export default ConfigLast; 7 | -------------------------------------------------------------------------------- /app/models/config-next.js: -------------------------------------------------------------------------------- 1 | import Config from './config'; 2 | 3 | var ConfigNext = Config.extend({ 4 | }); 5 | 6 | export default ConfigNext; 7 | -------------------------------------------------------------------------------- /app/models/config.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | import EmberValidations from 'ember-validations'; 3 | 4 | var Config = DS.Model.extend(EmberValidations.Mixin, { 5 | publicFacing: DS.attr('boolean'), 6 | ports: DS.attr(), 7 | env: DS.attr(), 8 | numInstances: DS.attr('number'), 9 | validations: { 10 | numInstances: { 11 | numericality: { 12 | allowBlank: true, 13 | onlyInteger: true 14 | } 15 | } 16 | } 17 | }); 18 | 19 | export default Config; 20 | -------------------------------------------------------------------------------- /app/models/hook.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | var Hook = DS.Model.extend({ 4 | serviceName: DS.attr('string'), 5 | dockerRepository: DS.attr('string'), 6 | pushedAt: DS.attr('number'), 7 | statusCode: DS.attr('number') 8 | }); 9 | 10 | export default Hook; 11 | -------------------------------------------------------------------------------- /app/models/host.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | var Host = DS.Model.extend({ 4 | PublicIP: DS.attr('string'), 5 | CadvisorIP: DS.attr('string'), 6 | Metadata: DS.attr(), 7 | Version: DS.attr('string'), 8 | TotalResources: DS.attr(), 9 | Services: DS.attr(), 10 | Healthy: DS.attr('boolean'), 11 | LoadedUnits: DS.attr('number'), 12 | units: DS.hasMany('unit', {async: true}), 13 | isExpanded: true, 14 | 15 | computedHealth: function() { 16 | return this.get('units').every(function(u) { 17 | return u.get('healthy'); 18 | }); 19 | }.property('units.@each.healthy'), 20 | 21 | cadvisorGet: function() { 22 | var url = 'http://' + this.get('PublicIP') + ':8080'; 23 | return url; 24 | }.property('CadvisorIP') 25 | }); 26 | 27 | export default Host; 28 | -------------------------------------------------------------------------------- /app/models/journal.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | var Journal = DS.Model.extend({ 4 | serviceName: DS.attr('string'), 5 | event: DS.attr('string'), 6 | version: DS.attr('string'), 7 | timestamp: DS.attr('number'), 8 | config: DS.belongsTo('config') 9 | }); 10 | 11 | export default Journal; 12 | -------------------------------------------------------------------------------- /app/models/load-balancer.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | var LoadBalancer = DS.Model.extend({ 4 | service: DS.attr('string'), // TODO: I think this should be a relation? 5 | backends: DS.attr() 6 | 7 | // TODO: none of these are really model properties are they? 8 | //displayName: function() { 9 | //var service = this.get('service'), 10 | //dns = this.get('dns'), 11 | //publicFacing = this.get('publicFacing'); 12 | 13 | //if (publicFacing) { 14 | //return service + '.' + dns; 15 | //} else { 16 | //return service; 17 | //} 18 | //}.property('service', 'dns', 'publicFacing'), 19 | 20 | //currentSessions: function() { 21 | //return this.get('backends.current.hosts').reduce(function(prev, host) { 22 | //return prev + host.sessions; 23 | //}, 0); 24 | //}.property('backends.current.hosts.@each.sessions'), 25 | 26 | //nextSessions: function() { 27 | //return this.get('backends.next.hosts').reduce(function(prev, host) { 28 | //return prev + host.sessions; 29 | //}, 0); 30 | //}.property('backends.next.hosts.@each.sessions'), 31 | 32 | //currentQueued: function() { 33 | //return this.get('backends.current.hosts').reduce(function(prev, host) { 34 | //return prev + host.queuedRequests; 35 | //}, 0); 36 | //}.property('backends.current.hosts.@each.queuedRequests'), 37 | 38 | //nextQueued: function() { 39 | //return this.get('backends.next.hosts').reduce(function(prev, host) { 40 | //return prev + host.queuedRequests; 41 | //}, 0); 42 | //}.property('backends.next.hosts.@each.queuedRequests') 43 | }); 44 | 45 | /* jshint quotmark: false */ 46 | 47 | LoadBalancer.reopenClass({ 48 | FIXTURES: [ 49 | { 50 | "id": "6770361ab0b049eda35d4ac58d9043d5", 51 | "service": "api", 52 | "dns": "lukeb0nd.com", 53 | "publicFacing": true, 54 | "backends": { 55 | "current": { 56 | "version": "1.0.1", 57 | "weighting": 85, 58 | "hosts": [ 59 | { 60 | "unit": "api-1.0.1-1", 61 | "host": "172.17.9.101", 62 | "port": "49165", 63 | "sessions": 5, 64 | "queuedRequests": 3 65 | }, 66 | { 67 | "unit": "api-1.0.1-2", 68 | "host": "172.17.8.102", 69 | "port": "49166", 70 | "sessions": 3, 71 | "queuedRequests": 2 72 | }, 73 | { 74 | "unit": "api-1.0.1-3", 75 | "host": "172.17.8.103", 76 | "port": "49167", 77 | "sessions": 2, 78 | "queuedRequests": 1 79 | } 80 | ] 81 | }, 82 | "next": { 83 | "version": "1.0.2", 84 | "weighting": 15, 85 | "hosts": [ 86 | { 87 | "unit": "api-1.0.2-1", 88 | "host": "172.17.9.101", 89 | "port": "49168", 90 | "sessions": 3, 91 | "queuedRequests": 2 92 | }, 93 | { 94 | "unit": "api-1.0.2-2", 95 | "host": "172.17.8.102", 96 | "port": "49169", 97 | "sessions": 2, 98 | "queuedRequests": 1 99 | }, 100 | { 101 | "unit": "api-1.0.2-3", 102 | "host": "172.17.8.103", 103 | "port": "49170", 104 | "sessions": 1, 105 | "queuedRequests": 0 106 | } 107 | ] 108 | } 109 | } 110 | } 111 | ] 112 | }); 113 | 114 | /* jshint quotmark: true */ 115 | 116 | export default LoadBalancer; 117 | -------------------------------------------------------------------------------- /app/models/service.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | import EmberValidations from 'ember-validations'; 3 | 4 | var Service = DS.Model.extend(EmberValidations.Mixin, { 5 | name: DS.attr('string'), 6 | description: DS.attr('string'), 7 | dockerRepository: DS.attr('string'), 8 | configNext: DS.belongsTo('configNext', {async: true}), 9 | configLast: DS.belongsTo('configLast', {async: true}), 10 | configEdit: DS.belongsTo('configEdit', {async: true}), 11 | hook: DS.belongsTo('hook', {async: true}), 12 | isExpanded: true, 13 | dockerLink: function() { 14 | var repo = this.get('dockerRepository'); 15 | var url = 'https://registry.hub.docker.com/u/' + repo; 16 | return url; 17 | }.property('dockerRepository'), 18 | 19 | // Properties only used on service submission (stored in configs on retrieval). Maybe should be separate model? 20 | publicFacing: DS.attr('boolean'), 21 | numInstances: DS.attr('number'), 22 | ports: DS.attr(), 23 | env: DS.attr(), 24 | volume: DS.attr(), 25 | units: DS.hasMany('unit'), 26 | computedHealth: function() { 27 | return this.get('units').every(function(u) { 28 | return u.get('healthy'); 29 | }); 30 | }.property('units.@each.healthy'), 31 | 32 | validations: { 33 | name: { 34 | presence: true, 35 | length: { minimum: 3 }, 36 | format: { 37 | with: /^([a-zA-Z]|\d|\-)+$/, 38 | message: 'Only letters and numbers allowed.' 39 | } 40 | }, 41 | description: { 42 | presence: true 43 | }, 44 | dockerRepository: { 45 | presence: true 46 | }, 47 | numInstances: { 48 | numericality: { 49 | allowBlank: true, 50 | onlyInteger: true 51 | } 52 | } 53 | } 54 | }); 55 | 56 | export default Service; 57 | -------------------------------------------------------------------------------- /app/models/unit.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | var Unit = DS.Model.extend({ 4 | service: DS.belongsTo('service', {async: false}), 5 | version: DS.attr('string'), 6 | instance: DS.attr('number'), 7 | activeState: DS.attr('string'), 8 | loadState: DS.attr('string'), 9 | machineState: DS.attr(), 10 | subState: DS.attr('string'), 11 | host: DS.belongsTo('host', {async: false}), 12 | unitHash: DS.attr('string'), 13 | CadvisorIP: DS.attr('string'), 14 | 15 | loadHealthy: function() { 16 | return Em.isEqual(this.get('loadState'), 'loaded'); 17 | }.property('loadState'), 18 | 19 | activeHealthy: function() { 20 | return (Em.isEqual(this.get('activeState'), 'active') || Em.isEqual(this.get('activeState'), 'activating')); 21 | }.property('activeState'), 22 | 23 | subHealthy: function() { 24 | return (Em.isEqual(this.get('subState'), 'running') || Em.isEqual(this.get('subState'), 'start-pre')); 25 | }.property('subState'), 26 | 27 | healthy: function() { 28 | var healths = [this.get('loadHealthy'), this.get('activeHealthy'), this.get('subHealthy')]; 29 | 30 | return healths.every(function(health) { 31 | return health === true; 32 | }); 33 | }.property('loadHealthy', 'activeHealthy', 'subHealthy'), 34 | 35 | name: function() { 36 | return this.get('id').split('-').filter(function(part) { 37 | return isNaN(parseInt(part)); 38 | }).join('-'); 39 | }.property('id'), 40 | 41 | cadvisorGet: function() { 42 | var host = this.get('host'); 43 | var url = 'http://' + host.get('PublicIP') + ':8080/containers/system.slice/' + this.get('id') + '.service'; 44 | return url; 45 | }.property('CadvisorIP') 46 | }); 47 | 48 | export default Unit; 49 | -------------------------------------------------------------------------------- /app/router.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import config from './config/environment'; 3 | 4 | var Router = Ember.Router.extend({ 5 | location: config.locationType 6 | }); 7 | 8 | Router.map(function() { 9 | this.resource('dashboard', function() { 10 | this.resource('hosts'); 11 | this.resource('units'); 12 | }); 13 | 14 | this.resource('services', function() { 15 | this.route('new'); 16 | this.resource('service', { path: '/:service_name' }, function() { 17 | this.route('edit'); 18 | }); 19 | }); 20 | this.route('services/new'); 21 | 22 | this.resource('monitoring', function() { 23 | this.route('host', { path: '/:machine_id' }); 24 | this.route('unit', { path: '/:unit_id' }); 25 | }); 26 | 27 | this.resource('history', function() { 28 | }); 29 | 30 | if (config.environment === 'development') { 31 | this.route('styleguide'); 32 | } 33 | }); 34 | 35 | export default Router; 36 | -------------------------------------------------------------------------------- /app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paz-sh/paz-web/21fbb43be813a5931a982a417b6be97147317c8d/app/routes/.gitkeep -------------------------------------------------------------------------------- /app/routes/config-last.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | model: function(params) { 5 | return this.store.find('configlast', params.service_name); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /app/routes/config-next.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | model: function(params) { 5 | return this.store.find('confignext', params.service_name); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /app/routes/dashboard.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | actions: { 5 | 'expand': function(model) { 6 | model.toggleProperty('isExpanded'); 7 | } 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /app/routes/dashboard/index.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | beforeModel: function() { 5 | this.transitionTo('hosts'); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /app/routes/history.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | model: function() { 5 | return this.store.findAll('journal'); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /app/routes/hosts.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | model: function() { 5 | return this.store.findAll('host'); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /app/routes/index.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | beforeModel: function() { 5 | this.transitionTo('dashboard'); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /app/routes/load-balancers.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | model: function() { 5 | return this.store.findAll('load-balancer'); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /app/routes/logs.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | }); 5 | -------------------------------------------------------------------------------- /app/routes/monitoring.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | model: function() { 5 | return this.store.find('host'); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /app/routes/monitoring/host.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | model: function(params) { 5 | return this.store.find('host', params.machine_id); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /app/routes/monitoring/unit.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | model: function(params) { 5 | return this.store.find('unit', params.unit_id); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /app/routes/service.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { validatePorts, validateEnvKeys, validateVolumeKeys } from 'paz-ember/helpers/validators'; 3 | 4 | export default Ember.Route.extend({ 5 | model: function(params) { 6 | return this.store.find('service', params.service_name); 7 | }, 8 | deactivate: function() { 9 | var configEdit = this.currentModel.get('configEdit.content'); 10 | if(configEdit != null) { 11 | configEdit.destroyRecord(); 12 | } 13 | }, 14 | actions: { 15 | edit: function(model) { 16 | // Copy configNext to configEdit 17 | var configCopy = model.get('configNext.content').toJSON(); 18 | configCopy = JSON.stringify(configCopy); 19 | configCopy = JSON.parse(configCopy); 20 | configCopy = Ember.Object.create(configCopy); 21 | if(model.get('configEdit.content') == null) { 22 | model.set('configEdit', this.store.createRecord('configEdit')); 23 | } 24 | var configEdit = model.get('configEdit'); 25 | configEdit.content.set('id', model.get('id')); 26 | configEdit.content.set('isEditing', true); 27 | configEdit.content.setProperties(configCopy); 28 | }, 29 | save: function(model) { 30 | var configEdit = model.get('configEdit.content'); 31 | var configNext = model.get('configNext.content'); 32 | 33 | if(configEdit.get('isValid')) { 34 | // Check ports/env/volume (can't get validator to run on them) 35 | var ports = configEdit.get('ports'), 36 | portsValid = true, 37 | envKeys = configEdit.get('env'), 38 | envKeysValid = true, 39 | volumeKeys = configEdit.get('volume'), 40 | volumeKeysValid = true; 41 | 42 | if(ports) { 43 | portsValid = validatePorts(ports); 44 | } 45 | 46 | if(envKeys) { 47 | envKeysValid = validateEnvKeys(envKeys); 48 | } 49 | 50 | if(volumeKeys) { 51 | volumeKeysValid = validateVolumeKeys(volumeKeys); 52 | } 53 | 54 | if(portsValid && envKeysValid && volumeKeysValid) { 55 | // Copy configEdit to configNext and save 56 | configEdit.set('isEditing', false); 57 | configNext.setProperties(configEdit.toJSON()); 58 | configNext.save(); 59 | } else if (!portsValid) { 60 | Ember.$('#other-errors').html('Ports invalid'); 61 | } else if (!envKeysValid) { 62 | Ember.$('#other-errors').html('Environment keys invalid'); 63 | } else if (!volumeKeysValid) { 64 | Ember.$('#other-errors').html('Volumes invalid'); 65 | } 66 | } else { 67 | Ember.$('#other-errors').html('Instances invalid'); 68 | } 69 | }, 70 | cancel: function(model) { 71 | model.set('configEdit.isEditing', false); 72 | } 73 | } 74 | }); 75 | -------------------------------------------------------------------------------- /app/routes/services.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | model: function() { 5 | return this.store.findAll('service'); 6 | }, 7 | actions: { 8 | destroy: function(model) { 9 | if(confirm('Are you sure you want to delete this service?')) { 10 | model.destroyRecord(); 11 | this.transitionTo('services'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /app/routes/services/new.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { validatePorts, validateEnvKeys, validateVolumeKeys } from 'paz-ember/helpers/validators'; 3 | 4 | export default Ember.Route.extend({ 5 | model: function() { 6 | var service = this.store.createRecord('service'); 7 | 8 | // Set default values 9 | service.set('numInstances', '1'); 10 | 11 | return service; 12 | }, 13 | 14 | isNew: true, 15 | 16 | deactivate: function() { 17 | var model = this.currentModel; 18 | if (model && model.get('isNew') && !model.get('isSaving')) { 19 | model.destroyRecord(); 20 | } 21 | }, 22 | 23 | actions: { 24 | save: function(model) { 25 | var that = this; 26 | var id = model.get('name'); 27 | 28 | if(model.get('isValid')) { 29 | // Check ports/env/volume (can't get validator to run on them) 30 | var ports = model.get('ports'), 31 | portsValid = true, 32 | env = model.get('env'), 33 | envKeysValid = true, 34 | volume = model.get('volume'), 35 | volumeKeysValid = true; 36 | 37 | if(ports) { 38 | portsValid = validatePorts(ports); 39 | } 40 | 41 | if(env) { 42 | envKeysValid = validateEnvKeys(env); 43 | } 44 | 45 | if(volume) { 46 | volumeKeysValid = validateVolumeKeys(volume); 47 | } 48 | 49 | if(portsValid && envKeysValid && volumeKeysValid) { 50 | model.save().then(function() { 51 | that.transitionTo('service', id).then(function() { 52 | that.store.push('service', {id: id, configNext: id}); 53 | }); 54 | }); 55 | } else if (!portsValid) { 56 | Ember.$('#other-errors').html('Ports invalid'); 57 | } else if (!envKeysValid) { 58 | Ember.$('#other-errors').html('Environment keys invalid'); 59 | } else if (!volumeKeysValid) { 60 | Ember.$('#other-errors').html('Volumes invalid'); 61 | } 62 | } else { 63 | Ember.$('#other-errors').html('Name, Description and Docker Repository are required.'); 64 | } 65 | } 66 | } 67 | }); 68 | -------------------------------------------------------------------------------- /app/routes/styleguide.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | }); 5 | -------------------------------------------------------------------------------- /app/routes/units.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import config from '../config/environment'; 3 | 4 | export default Ember.Route.extend({ 5 | model: function() { 6 | var that = this; 7 | 8 | return this.store.findAll('service').then(function(services) { 9 | services.forEach(function(service) { 10 | // XXX Very hacky. Would like to fix. 11 | Ember.$.getJSON(config.APP.ORCHESTRATOR_URL + '/services/' + service.get('id') + '/units').then(function(units) { 12 | for (var u = 0; u < units.length; u++) { 13 | units[u].id = units[u].name; 14 | units[u].host = units[u].machineState.ID; 15 | var unit = that.store.push('unit', units[u]); 16 | service.get('units').addObject(unit); 17 | } 18 | }); 19 | }); 20 | return services; 21 | }); 22 | }, 23 | actions: { 24 | 'expand': function(model) { 25 | model.toggleProperty('isExpanded'); 26 | } 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /app/serializers/config-last.js: -------------------------------------------------------------------------------- 1 | import Config from './config'; 2 | 3 | var ConfigLast = Config.extend({ 4 | normalizePayload: function(payload) { 5 | return { configLast: this.transformEnvObject(payload) }; 6 | } 7 | }); 8 | 9 | export default ConfigLast; 10 | -------------------------------------------------------------------------------- /app/serializers/config-next.js: -------------------------------------------------------------------------------- 1 | import Config from './config'; 2 | 3 | var ConfigNext = Config.extend({ 4 | normalizePayload: function(payload) { 5 | return { configNext: this.transformEnvObject(payload) }; 6 | } 7 | }); 8 | 9 | export default ConfigNext; 10 | -------------------------------------------------------------------------------- /app/serializers/config.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | var Config = DS.RESTSerializer.extend({ 4 | normalizePayload: function(payload) { 5 | return { config: this.transformEnvObject(payload) }; 6 | }, 7 | transformEnvObject: function(payload) { 8 | var envKeys = []; 9 | var envkeysObject = payload.doc.env; 10 | Object.keys(envkeysObject).map(function(data) { 11 | envKeys.push({ 12 | key: data, 13 | value: envkeysObject[data] 14 | }); 15 | }); 16 | payload.doc.env = envKeys; 17 | return payload.doc; 18 | }, 19 | extractSingle: function(store, primaryType, payload, recordId) { 20 | payload.doc.id = recordId; 21 | return this._super(store, primaryType, payload, recordId); 22 | }, 23 | serialize: function(post) { 24 | var envKeys = post.attr('env') || {}; 25 | var volume = post.attr('volume') || {}; 26 | var ports = post.attr('ports') || []; 27 | var publicFacing = post.attr('publicFacing') || false; 28 | var numInstances= post.attr('numInstances') || 1; 29 | 30 | var json = { 31 | publicFacing: publicFacing, 32 | numInstances: numInstances, 33 | ports: ports.map(function(port) { 34 | return { 35 | container: Number(port.container), 36 | host: Number(port.host) 37 | }; 38 | }), 39 | env : envKeys.reduce(function(memo, k) { 40 | memo[k.key] = k.value; 41 | return memo; 42 | }, {}), 43 | volume : volume.reduce(function(memo, k) { 44 | memo[k.key] = k.value; 45 | return memo; 46 | }, {}) 47 | }; 48 | 49 | return json; 50 | } 51 | }); 52 | 53 | export default Config; 54 | -------------------------------------------------------------------------------- /app/serializers/hook.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, { 4 | normalizePayload: function(payload) { 5 | return {hook: payload}; 6 | }, 7 | serialize: function(snapshot) { 8 | delete(snapshot['statusCode']); 9 | return snapshot; 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /app/serializers/host.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | export default DS.RESTSerializer.extend({ 4 | primaryKey: 'ID', 5 | normalizePayload: function(payload) { 6 | return { hosts: payload }; 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /app/serializers/journal.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, { 4 | normalizePayload: function(payload) { 5 | payload.doc.forEach(function(journal) { 6 | // set id 7 | journal.id = journal.config.id = journal.timestamp; 8 | // transform environment variable objects 9 | var envKeys = []; 10 | var envkeysObject = journal.config.env; 11 | Object.keys(envkeysObject).map(function(data) { 12 | envKeys.push({ 13 | key: data, 14 | value: envkeysObject[data] 15 | }); 16 | }); 17 | journal.config.env = envKeys; 18 | }); 19 | return { journal: payload.doc }; 20 | }, 21 | attrs: { 22 | config: {embedded: 'always'}, 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /app/serializers/load-balancer.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | import Ember from 'ember'; 3 | 4 | export default DS.RESTSerializer.extend({ 5 | primaryKey: 'service', 6 | normalizePayload: function(payload) { 7 | payload.forEach(function(balancer) { 8 | var arr = []; 9 | 10 | Object.keys(balancer.backends).forEach(function(key) { 11 | balancer.backends[key].order = Ember.String.capitalize(key); 12 | arr.push(balancer.backends[key]); 13 | }); 14 | 15 | balancer.backends = arr; 16 | }); 17 | 18 | return { 'load-balancer': payload }; 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /app/serializers/service.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | import Ember from 'ember'; 3 | 4 | export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, { 5 | primaryKey: 'name', 6 | normalizePayload: function(payload) { 7 | for (var i = 0; i < payload.doc.length; i++) { 8 | payload.doc[i].configNext = payload.doc[i].name; 9 | payload.doc[i].configLast = payload.doc[i].name; 10 | } 11 | 12 | return { services: payload.doc }; 13 | }, 14 | serializeIntoHash: function(hash, type, record, options) { 15 | Ember.merge(hash, this.serialize(record, options)); 16 | }, 17 | 18 | // Custom serialize function for POST data since ember would give other properties in model by default 19 | serialize: function(post) { 20 | var volume = post.attr('volume') || []; 21 | var env = post.attr('env') || []; 22 | 23 | var ports = post.attr('ports') || []; 24 | 25 | var json = { 26 | name: post.attr('name'), 27 | description: post.attr('description') || '', 28 | dockerRepository: post.attr('dockerRepository'), 29 | publicFacing: post.attr('publicFacing') || false, 30 | numInstances: Number(post.attr('numInstances')) || 1, 31 | ports: ports.map(function(port) { 32 | return { 33 | container: Number(port.container), 34 | host: Number(port.host) 35 | }; 36 | }), 37 | env : env.reduce(function(memo, k) { 38 | memo[k.key] = k.value; 39 | return memo; 40 | }, {}), 41 | volume : volume.reduce(function(memo, k) { 42 | memo[k.key] = k.value; 43 | return memo; 44 | }, {}) 45 | }; 46 | 47 | return json; 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /app/serializers/unit.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | export default DS.RESTSerializer.extend({ 4 | primaryKey: 'name', 5 | normalizePayload: function(payload) { 6 | payload.host = payload.machineState.ID; 7 | 8 | return { unit: payload }; 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /app/styles/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paz-sh/paz-web/21fbb43be813a5931a982a417b6be97147317c8d/app/styles/.gitkeep -------------------------------------------------------------------------------- /app/styles/_animations.scss: -------------------------------------------------------------------------------- 1 | @include keyframes(fadeIn) { 2 | from { 3 | @include transform(scale(1.05)); 4 | opacity: 0.4; 5 | } 6 | 7 | to { 8 | @include transform(scale(1)); 9 | opacity: 1; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/styles/_buttons.scss: -------------------------------------------------------------------------------- 1 | %button { 2 | // -webkit-font-smoothing: antialiased; 3 | background-color: $base-button-background; 4 | border-radius: $border-radius; 5 | color: $base-button-color; 6 | display: inline-block; 7 | font-size: $base-font-size; 8 | font-weight: bold; 9 | line-height: em(36); 10 | padding: 0 1em; 11 | text-decoration: none; 12 | cursor: pointer; 13 | text-align: center; 14 | border: none; 15 | 16 | &:hover { 17 | background-color: $hover-button-color; 18 | color: $base-button-color; 19 | } 20 | 21 | > .btn__icon { 22 | display: inline-block; 23 | vertical-align: middle; 24 | width: em(30); 25 | text-align: center; 26 | } 27 | } 28 | 29 | .btn { 30 | @extend %button; 31 | } 32 | 33 | %btn--light { 34 | color: #fff; 35 | 36 | &:hover { 37 | color: #fff; 38 | } 39 | } 40 | 41 | .btn--full { 42 | display: block; 43 | width: 100%; 44 | } 45 | 46 | .btn--sm { 47 | line-height: em(24); 48 | } 49 | 50 | .btn--lg { 51 | line-height: em(48); 52 | padding-left: 2em; 53 | padding-right: 2em; 54 | } 55 | 56 | .btn--primary { 57 | @extend .btn; 58 | @extend %btn--light; 59 | @include primaryGradient; 60 | &:hover { 61 | background: darken($primary, 10%); 62 | } 63 | } 64 | 65 | .btn--happy { 66 | @extend .btn; 67 | @extend %btn--light; 68 | @include happyGradient; 69 | &:hover { 70 | background: darken($happy, 10%); 71 | } 72 | } 73 | 74 | .btn--sad { 75 | @extend .btn; 76 | @extend %btn--light; 77 | @include sadGradient; 78 | &:hover { 79 | background: darken($sad, 10%); 80 | } 81 | } 82 | 83 | .btn-group { 84 | @include display(flex); 85 | 86 | > .btn { 87 | border-radius: 0; 88 | @include flex(1); 89 | 90 | &:first-child { 91 | border-radius: $border-radius 0 0 $border-radius; 92 | } 93 | 94 | &:last-child { 95 | border-radius: 0 $border-radius $border-radius 0; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/styles/_card.scss: -------------------------------------------------------------------------------- 1 | %cards { 2 | @include row; 3 | } 4 | .card { 5 | background: #fff; 6 | border-radius: $border-radius; 7 | border-radius: 3px; 8 | box-shadow: 0px 3px 0px 0px rgba(0,0,0,0.05); 9 | margin-bottom: em(24); 10 | @include animation(fadeIn .2s ease-out); 11 | } 12 | 13 | .card__header { 14 | @include primaryGradient; 15 | border-radius: $border-radius $border-radius 0 0; 16 | padding-left: 2em; 17 | padding-right: 2em; 18 | &:last-child { 19 | border-radius: $border-radius; 20 | } 21 | } 22 | 23 | .card__header--happy { 24 | @extend .card__header; 25 | @include happyGradient; 26 | color: #fff; 27 | text-align: center; 28 | } 29 | 30 | .card__header--sad { 31 | @extend .card__header; 32 | @include sadGradient; 33 | color: #fff; 34 | text-align: center; 35 | } 36 | 37 | .card__header-title { 38 | line-height: 60px; 39 | color: #fff; 40 | a { 41 | color: inherit; 42 | } 43 | } 44 | 45 | .card__body { 46 | padding: 1em 1em; 47 | border: $base-border; 48 | border-top-width: 0; 49 | & + .card__body, & + .card__footer { 50 | border-top-width: 0; 51 | } 52 | &:last-child { 53 | border-radius: 0 0 $border-radius $border-radius; 54 | } 55 | } 56 | 57 | .card__footer { 58 | background: $base-background-color; 59 | @extend .card__body; 60 | border: $base-border; 61 | border-radius: 0 0 $border-radius $border-radius; 62 | } 63 | 64 | .cards { 65 | @extend %cards; 66 | .card { 67 | .halves { 68 | @include clearfix; 69 | } 70 | .half { 71 | @include span-columns(3 of 6); 72 | @include omega(2n); 73 | } 74 | } 75 | } 76 | 77 | .cards--thirds { 78 | @extend %cards; 79 | 80 | .card { 81 | @media screen and (min-width: 600px) and (max-width: 960px) { 82 | @include span-columns(3 of 6); 83 | @include omega(2n); 84 | } 85 | @media screen and (min-width: 960px) { 86 | @include span-columns(3 of 9); 87 | @include omega(3n); 88 | } 89 | } 90 | 91 | .card__header { 92 | text-align: center; 93 | } 94 | } 95 | 96 | .cards--one-up { 97 | @extend %cards; 98 | .card { 99 | @media screen and (min-width: 600px) and (max-width: 960px) { 100 | @include span-columns(3 of 6); 101 | @include omega(2n); 102 | } 103 | @media screen and (min-width: 960px) { 104 | @include span-columns(6 of 9); 105 | @include omega(3n); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/styles/_form.scss: -------------------------------------------------------------------------------- 1 | // form {} 2 | 3 | .input { 4 | margin-bottom: $form-field-spacing; 5 | } 6 | 7 | // .input > label { 8 | // display: block; 9 | // } 10 | 11 | // .input > input { 12 | // display: block; 13 | // } 14 | 15 | fieldset { 16 | // background: $form-input-background-color; 17 | border-color: $form-input-background-color; 18 | margin: 0 0 $base-spacing 0; 19 | padding: $base-spacing; 20 | } 21 | 22 | legend { 23 | color: $header-font-color; 24 | font-weight: bold; 25 | } 26 | 27 | input, 28 | label, 29 | select { 30 | display: block; 31 | font-family: $form-font-family; 32 | font-size: $form-font-size; 33 | } 34 | 35 | label { 36 | font-weight: bold; 37 | color: $header-font-color; 38 | margin-bottom: $base-spacing / 4; 39 | 40 | &.required:after { 41 | content: "*"; 42 | } 43 | 44 | abbr { 45 | display: none; 46 | } 47 | } 48 | 49 | textarea, 50 | #{$all-text-inputs}, 51 | select[multiple=multiple] { 52 | @include box-sizing(border-box); 53 | @include transition(border-color); 54 | color: $form-input-font-color; 55 | background-color: $form-input-background-color; 56 | border-radius: $border-radius; 57 | border: 1px solid $form-input-border-color; 58 | font-family: $form-font-family; 59 | font-size: $form-font-size; 60 | margin-bottom: $base-spacing / 2; 61 | padding: $base-spacing; 62 | width: 100%; 63 | 64 | &:hover { 65 | background-color: $form-input-background-color-hover; 66 | border-color: $form-input-border-color-hover; 67 | } 68 | 69 | &:focus { 70 | background-color: $form-input-background-color-focus; 71 | border-color: $form-input-border-color-focus; 72 | outline: none; 73 | } 74 | } 75 | 76 | textarea { 77 | resize: vertical; 78 | } 79 | 80 | input[type="search"] { 81 | @include appearance(none); 82 | } 83 | 84 | input[type="checkbox"], 85 | input[type="radio"] { 86 | display: inline; 87 | margin-right: $base-spacing / 4; 88 | } 89 | 90 | input[type="file"] { 91 | padding-bottom: $base-spacing / 2; 92 | width: 100%; 93 | } 94 | 95 | select { 96 | margin-bottom: $base-spacing; 97 | max-width: 100%; 98 | width: auto; 99 | } 100 | 101 | .fieldWithErrors {} 102 | .fieldWithErrors > label {} 103 | .fieldWithErrors > input { 104 | background-color: lighten($sad, 28%); 105 | border-color: lighten($sad, 28%); 106 | color: $sad; 107 | &:hover { 108 | border-color: lighten($sad, 25%); 109 | background-color: lighten($sad, 25%); 110 | } 111 | 112 | &:focus { 113 | border-color: $sad; 114 | background-color: lighten($sad, 22%); 115 | color: $sad; 116 | } 117 | } 118 | .fieldWithErrors > .error { 119 | color: $sad; 120 | } 121 | -------------------------------------------------------------------------------- /app/styles/_health.scss: -------------------------------------------------------------------------------- 1 | .health__list { 2 | cursor: pointer; 3 | } 4 | 5 | .health__list--expanded { 6 | cursor: zoom-out; 7 | } 8 | 9 | .health__list--collapsed { 10 | cursor: zoom-in; 11 | } 12 | 13 | %health { 14 | h5 { 15 | display: inline-block; 16 | } 17 | } 18 | 19 | .health__title { 20 | h5 { 21 | display: inline-block; 22 | } 23 | } 24 | 25 | .health__title--sad { 26 | @extend .health__title; 27 | h5 { color: $sad; } 28 | } 29 | 30 | .health__title + .health { 31 | margin-top: 1em; 32 | } 33 | 34 | %health::before { 35 | content: ''; 36 | height: em(10); 37 | width: em(10); 38 | background: $base-font-color; 39 | display: inline-block; 40 | border-radius: 10px; 41 | margin-right: 1em; 42 | } 43 | 44 | .health--happy, .health--sad { 45 | @extend %health; 46 | } 47 | 48 | .health--happy::before { 49 | background: $happy; 50 | } 51 | 52 | .health--sad::before { 53 | background: $sad; 54 | } 55 | 56 | .health__buttons { 57 | margin-top: 1em; 58 | } 59 | -------------------------------------------------------------------------------- /app/styles/_helpers.scss: -------------------------------------------------------------------------------- 1 | .not-implemented { 2 | cursor: not-allowed; 3 | } 4 | 5 | #cAdvisor { 6 | width: 100%; 7 | height: 100%; 8 | border: none; 9 | } 10 | 11 | iframe::-webkit-scrollbar { 12 | display: none; 13 | } 14 | -------------------------------------------------------------------------------- /app/styles/_layout.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | @include outer-container; 3 | } 4 | 5 | .sidebar { 6 | @include span-columns(3); 7 | background: $menu; 8 | height: 100%; 9 | height: 100vh; 10 | } 11 | 12 | .content { 13 | @include span-columns(9); 14 | padding: em(24) 0; 15 | } 16 | -------------------------------------------------------------------------------- /app/styles/_navigation.scss: -------------------------------------------------------------------------------- 1 | .nav { 2 | 3 | } 4 | 5 | .nav__logo { 6 | display: block; 7 | height: em(120); 8 | background: url('images/logo.svg') center no-repeat; 9 | text-indent: -9999px; 10 | } 11 | 12 | .nav__item { 13 | display: block; 14 | line-height: em(80); 15 | font-size: em(16); 16 | font-weight: bold; 17 | color: $base-font-color; 18 | &:hover { 19 | 20 | } 21 | &.active { 22 | background: #fff; 23 | color: $header-font-color; 24 | } 25 | } 26 | 27 | .nav__icon { 28 | width: em(60, 16); 29 | text-align: center; 30 | display: inline-block; 31 | // font-size: em(16); 32 | vertical-align: middle; 33 | } 34 | 35 | // subnav 36 | 37 | .sub-nav { 38 | @include row; 39 | margin-bottom: em(24); 40 | } 41 | -------------------------------------------------------------------------------- /app/styles/_pills.scss: -------------------------------------------------------------------------------- 1 | .pills { 2 | $pills-background: #98B0C3; 3 | $pills-color: white; 4 | $pills-border: #98B0C3; 5 | $pills-active-color: #5E778B; 6 | $pills-radius: $border-radius; 7 | margin-bottom: 1em; 8 | 9 | .pill { 10 | @extend %button; 11 | display: inline-block; 12 | background: $pills-background; 13 | color: $pills-color; 14 | margin: 0; 15 | border: solid $pills-border; 16 | border-width: 2px 0; 17 | border-radius: 0; 18 | margin-right: -5px; 19 | font-weight: bold; 20 | 21 | &:hover { 22 | background-color: darken($pills-background, 5%); 23 | border-color: darken($pills-border, 5%); 24 | } 25 | 26 | &:first-child { 27 | border-radius: $border-radius 0 0 $border-radius; 28 | border-left-width: 2px; 29 | } 30 | 31 | &:last-child { 32 | border-radius: 0 $border-radius $border-radius 0; 33 | border-right-width: 2px; 34 | } 35 | 36 | &.active { 37 | background: #fff; 38 | color: $pills-active-color; 39 | } 40 | 41 | + .pill { 42 | border-left-width: 0; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/styles/_progress-bar.scss: -------------------------------------------------------------------------------- 1 | #followingBallsG { 2 | position:relative; 3 | width:100px; 4 | height:8px; 5 | } 6 | 7 | .followingBallsG { 8 | background-color: $primary; 9 | position:absolute; 10 | top:0; 11 | left:0; 12 | width:8px; 13 | height:8px; 14 | -moz-border-radius:6px; 15 | -moz-animation-name:bounce_followingBallsG; 16 | -moz-animation-duration:2.6s; 17 | -moz-animation-iteration-count:infinite; 18 | -moz-animation-direction:normal; 19 | -webkit-border-radius:6px; 20 | -webkit-animation-name:bounce_followingBallsG; 21 | -webkit-animation-duration:2.6s; 22 | -webkit-animation-iteration-count:infinite; 23 | -webkit-animation-direction:normal; 24 | -o-border-radius:6px; 25 | -o-animation-name:bounce_followingBallsG; 26 | -o-animation-duration:2.6s; 27 | -o-animation-iteration-count:infinite; 28 | -o-animation-direction:normal; 29 | -ms-border-radius:6px; 30 | -ms-animation-name:bounce_followingBallsG; 31 | -ms-animation-duration:2.6s; 32 | -ms-animation-iteration-count:infinite; 33 | -ms-animation-direction:normal; 34 | border-radius:6px; 35 | animation-name:bounce_followingBallsG; 36 | animation-duration:2.6s; 37 | animation-iteration-count:infinite; 38 | animation-direction:normal; 39 | } 40 | 41 | #followingBallsG_1 { 42 | -moz-animation-delay:0s; 43 | } 44 | 45 | #followingBallsG_1 { 46 | -webkit-animation-delay:0s; 47 | } 48 | 49 | #followingBallsG_1 { 50 | -o-animation-delay:0s; 51 | } 52 | 53 | #followingBallsG_1 { 54 | -ms-animation-delay:0s; 55 | } 56 | 57 | #followingBallsG_1 { 58 | animation-delay:0s; 59 | } 60 | 61 | #followingBallsG_2 { 62 | -moz-animation-delay:0.26s; 63 | -webkit-animation-delay:0.26s; 64 | -o-animation-delay:0.26s; 65 | -ms-animation-delay:0.26s; 66 | animation-delay:0.26s; 67 | } 68 | 69 | #followingBallsG_3 { 70 | -moz-animation-delay:0.52s; 71 | -webkit-animation-delay:0.52s; 72 | -o-animation-delay:0.52s; 73 | -ms-animation-delay:0.52s; 74 | animation-delay:0.52s; 75 | } 76 | 77 | #followingBallsG_4 { 78 | -moz-animation-delay:0.78s; 79 | -webkit-animation-delay:0.78s; 80 | -o-animation-delay:0.78s; 81 | -ms-animation-delay:0.78s; 82 | animation-delay:0.78s; 83 | } 84 | 85 | @-moz-keyframes bounce_followingBallsG { 86 | 0% { 87 | left:0px; 88 | background-color:$primary; 89 | } 90 | 91 | 50% { 92 | left:139px; 93 | background-color:$base-background-color; 94 | } 95 | 96 | 100% { 97 | left:0px; 98 | background-color:$primary; 99 | } 100 | 101 | } 102 | 103 | @-webkit-keyframes bounce_followingBallsG { 104 | 0% { 105 | left:0px; 106 | background-color:$primary; 107 | } 108 | 109 | 50% { 110 | left:139px; 111 | background-color:$base-background-color; 112 | } 113 | 114 | 100% { 115 | left:0px; 116 | background-color:$primary; 117 | } 118 | 119 | } 120 | 121 | @-o-keyframes bounce_followingBallsG { 122 | 0% { 123 | left:0px; 124 | background-color:$primary; 125 | } 126 | 127 | 50% { 128 | left:139px; 129 | background-color:$base-background-color; 130 | } 131 | 132 | 100% { 133 | left:0px; 134 | background-color:$primary; 135 | } 136 | } 137 | 138 | @-ms-keyframes bounce_followingBallsG { 139 | 0% { 140 | left:0px; 141 | background-color:$primary; 142 | } 143 | 144 | 50% { 145 | left:139px; 146 | background-color:$base-background-color; 147 | } 148 | 149 | 100% { 150 | left:0px; 151 | background-color:$primary; 152 | } 153 | } 154 | 155 | @keyframes bounce_followingBallsG { 156 | 0% { 157 | left:0px; 158 | background-color:$primary; 159 | } 160 | 161 | 50% { 162 | left:139px; 163 | background-color:$base-background-color; 164 | } 165 | 166 | 100% { 167 | left:0px; 168 | background-color:$primary; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /app/styles/_ss-standard.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /* 4 | * Symbolset 5 | * www.symbolset.com 6 | * Copyright © 2012 Oak Studios LLC 7 | * 8 | * Upload this file to your web server 9 | * and place this within your tags. 10 | * 11 | */ 12 | 13 | @font-face { 14 | font-family: "SSStandard"; 15 | src: url('fonts/ss-standard.eot'); 16 | src: url('fonts/ss-standard.eot?#iefix') format('embedded-opentype'), 17 | url('fonts/ss-standard.woff') format('woff'), 18 | url('fonts/ss-standard.ttf') format('truetype'), 19 | url('fonts/ss-standard.svg#SSStandard') format('svg'); 20 | font-weight: normal; 21 | font-style: normal; 22 | } 23 | 24 | /* This triggers a redraw in IE to Fix IE8's :before content rendering. */ 25 | html:hover [class^="ss-"]{-ms-zoom: 1;} 26 | 27 | .ss-icon, .ss-icon.ss-standard, 28 | [class^="ss-"]:before, [class*=" ss-"]:before, 29 | [class^="ss-"].ss-standard:before, [class*=" ss-"].ss-standard:before, 30 | [class^="ss-"].right:after, [class*=" ss-"].right:after, 31 | [class^="ss-"].ss-standard.right:after, [class*=" ss-"].ss-standard.right:after { 32 | font-family: "SSStandard"; 33 | font-style: normal; 34 | font-weight: normal; 35 | text-decoration: none; 36 | text-rendering: optimizeLegibility; 37 | white-space: nowrap; 38 | /*-webkit-font-feature-settings: "liga"; Currently broken in Chrome >= v22. Falls back to text-rendering. Safari is unaffected. */ 39 | -moz-font-feature-settings: "liga=1"; 40 | -moz-font-feature-settings: "liga"; 41 | -ms-font-feature-settings: "liga" 1; 42 | -o-font-feature-settings: "liga"; 43 | font-feature-settings: "liga"; 44 | -webkit-font-smoothing: antialiased; 45 | } 46 | 47 | [class^="ss-"].right:before, 48 | [class*=" ss-"].right:before{display:none;content:'';} 49 | 50 | .ss-cursor:before,.ss-cursor.right:after{content:''}.ss-crosshair:before,.ss-crosshair.right:after{content:'⌖'}.ss-search:before,.ss-search.right:after{content:'🔎'}.ss-zoomin:before,.ss-zoomin.right:after{content:''}.ss-zoomout:before,.ss-zoomout.right:after{content:''}.ss-view:before,.ss-view.right:after{content:'👀'}.ss-attach:before,.ss-attach.right:after{content:'📎'}.ss-link:before,.ss-link.right:after{content:'🔗'}.ss-move:before,.ss-move.right:after{content:''}.ss-write:before,.ss-write.right:after{content:'✎'}.ss-writingdisabled:before,.ss-writingdisabled.right:after{content:''}.ss-erase:before,.ss-erase.right:after{content:'✐'}.ss-compose:before,.ss-compose.right:after{content:'📝'}.ss-lock:before,.ss-lock.right:after{content:'🔒'}.ss-unlock:before,.ss-unlock.right:after{content:'🔓'}.ss-key:before,.ss-key.right:after{content:'🔑'}.ss-backspace:before,.ss-backspace.right:after{content:'⌫'}.ss-ban:before,.ss-ban.right:after{content:'🚫'}.ss-trash:before,.ss-trash.right:after{content:''}.ss-target:before,.ss-target.right:after{content:'◎'}.ss-tag:before,.ss-tag.right:after{content:''}.ss-bookmark:before,.ss-bookmark.right:after{content:'🔖'}.ss-flag:before,.ss-flag.right:after{content:'⚑'}.ss-like:before,.ss-like.right:after{content:'👍'}.ss-dislike:before,.ss-dislike.right:after{content:'👎'}.ss-heart:before,.ss-heart.right:after{content:'♥'}.ss-halfheart:before,.ss-halfheart.right:after{content:''}.ss-star:before,.ss-star.right:after{content:'⋆'}.ss-halfstar:before,.ss-halfstar.right:after{content:''}.ss-sample:before,.ss-sample.right:after{content:''}.ss-crop:before,.ss-crop.right:after{content:''}.ss-layers:before,.ss-layers.right:after{content:''}.ss-fill:before,.ss-fill.right:after{content:''}.ss-stroke:before,.ss-stroke.right:after{content:''}.ss-phone:before,.ss-phone.right:after{content:'📞'}.ss-phonedisabled:before,.ss-phonedisabled.right:after{content:''}.ss-rss:before,.ss-rss.right:after{content:''}.ss-facetime:before,.ss-facetime.right:after{content:''}.ss-reply:before,.ss-reply.right:after{content:'↩'}.ss-send:before,.ss-send.right:after{content:''}.ss-mail:before,.ss-mail.right:after{content:'✉'}.ss-inbox:before,.ss-inbox.right:after{content:'📥'}.ss-chat:before,.ss-chat.right:after{content:'💬'}.ss-ellipsischat:before,.ss-ellipsischat.right:after{content:''}.ss-ellipsis:before,.ss-ellipsis.right:after{content:'…'}.ss-user:before,.ss-user.right:after{content:'👤'}.ss-femaleuser:before,.ss-femaleuser.right:after{content:'👧'}.ss-users:before,.ss-users.right:after{content:'👥'}.ss-cart:before,.ss-cart.right:after{content:''}.ss-creditcard:before,.ss-creditcard.right:after{content:'💳'}.ss-dollarsign:before,.ss-dollarsign.right:after{content:'💲'}.ss-barchart:before,.ss-barchart.right:after{content:'📊'}.ss-piechart:before,.ss-piechart.right:after{content:''}.ss-box:before,.ss-box.right:after{content:'📦'}.ss-home:before,.ss-home.right:after{content:'⌂'}.ss-buildings:before,.ss-buildings.right:after{content:'🏢'}.ss-warehouse:before,.ss-warehouse.right:after{content:''}.ss-globe:before,.ss-globe.right:after{content:'🌎'}.ss-navigate:before,.ss-navigate.right:after{content:''}.ss-compass:before,.ss-compass.right:after{content:''}.ss-signpost:before,.ss-signpost.right:after{content:''}.ss-map:before,.ss-map.right:after{content:''}.ss-location:before,.ss-location.right:after{content:''}.ss-pin:before,.ss-pin.right:after{content:'📍'}.ss-database:before,.ss-database.right:after{content:''}.ss-hdd:before,.ss-hdd.right:after{content:''}.ss-music:before,.ss-music.right:after{content:'♫'}.ss-mic:before,.ss-mic.right:after{content:'🎤'}.ss-volume:before,.ss-volume.right:after{content:'🔈'}.ss-lowvolume:before,.ss-lowvolume.right:after{content:'🔉'}.ss-highvolume:before,.ss-highvolume.right:after{content:'🔊'}.ss-airplay:before,.ss-airplay.right:after{content:''}.ss-camera:before,.ss-camera.right:after{content:'📷'}.ss-picture:before,.ss-picture.right:after{content:'🌄'}.ss-video:before,.ss-video.right:after{content:'📹'}.ss-play:before,.ss-play.right:after{content:'▶'}.ss-pause:before,.ss-pause.right:after{content:''}.ss-stop:before,.ss-stop.right:after{content:'■'}.ss-record:before,.ss-record.right:after{content:'●'}.ss-rewind:before,.ss-rewind.right:after{content:'⏪'}.ss-fastforward:before,.ss-fastforward.right:after{content:'⏩'}.ss-skipback:before,.ss-skipback.right:after{content:'⏮'}.ss-skipforward:before,.ss-skipforward.right:after{content:'⏭'}.ss-eject:before,.ss-eject.right:after{content:'⏏'}.ss-repeat:before,.ss-repeat.right:after{content:'🔁'}.ss-replay:before,.ss-replay.right:after{content:'↺'}.ss-shuffle:before,.ss-shuffle.right:after{content:'🔀'}.ss-book:before,.ss-book.right:after{content:'📕'}.ss-openbook:before,.ss-openbook.right:after{content:'📖'}.ss-notebook:before,.ss-notebook.right:after{content:'📓'}.ss-newspaper:before,.ss-newspaper.right:after{content:'📰'}.ss-grid:before,.ss-grid.right:after{content:''}.ss-rows:before,.ss-rows.right:after{content:''}.ss-columns:before,.ss-columns.right:after{content:''}.ss-thumbnails:before,.ss-thumbnails.right:after{content:''}.ss-filter:before,.ss-filter.right:after{content:''}.ss-desktop:before,.ss-desktop.right:after{content:'💻'}.ss-laptop:before,.ss-laptop.right:after{content:''}.ss-tablet:before,.ss-tablet.right:after{content:''}.ss-cell:before,.ss-cell.right:after{content:'📱'}.ss-battery:before,.ss-battery.right:after{content:'🔋'}.ss-highbattery:before,.ss-highbattery.right:after{content:''}.ss-mediumbattery:before,.ss-mediumbattery.right:after{content:''}.ss-lowbattery:before,.ss-lowbattery.right:after{content:''}.ss-emptybattery:before,.ss-emptybattery.right:after{content:''}.ss-lightbulb:before,.ss-lightbulb.right:after{content:'💡'}.ss-downloadcloud:before,.ss-downloadcloud.right:after{content:''}.ss-download:before,.ss-download.right:after{content:''}.ss-uploadcloud:before,.ss-uploadcloud.right:after{content:''}.ss-upload:before,.ss-upload.right:after{content:''}.ss-fork:before,.ss-fork.right:after{content:''}.ss-merge:before,.ss-merge.right:after{content:''}.ss-transfer:before,.ss-transfer.right:after{content:'⇆'}.ss-refresh:before,.ss-refresh.right:after{content:'↻'}.ss-sync:before,.ss-sync.right:after{content:''}.ss-loading:before,.ss-loading.right:after{content:''}.ss-wifi:before,.ss-wifi.right:after{content:''}.ss-connection:before,.ss-connection.right:after{content:''}.ss-file:before,.ss-file.right:after{content:'📄'}.ss-folder:before,.ss-folder.right:after{content:'📁'}.ss-quote:before,.ss-quote.right:after{content:'“'}.ss-text:before,.ss-text.right:after{content:''}.ss-font:before,.ss-font.right:after{content:''}.ss-print:before,.ss-print.right:after{content:'⎙'}.ss-fax:before,.ss-fax.right:after{content:'📠'}.ss-list:before,.ss-list.right:after{content:''}.ss-layout:before,.ss-layout.right:after{content:''}.ss-action:before,.ss-action.right:after{content:''}.ss-redirect:before,.ss-redirect.right:after{content:'↪'}.ss-expand:before,.ss-expand.right:after{content:'⤢'}.ss-contract:before,.ss-contract.right:after{content:''}.ss-help:before,.ss-help.right:after{content:'❓'}.ss-info:before,.ss-info.right:after{content:'ℹ'}.ss-alert:before,.ss-alert.right:after{content:'⚠'}.ss-caution:before,.ss-caution.right:after{content:'⛔'}.ss-logout:before,.ss-logout.right:after{content:''}.ss-plus:before,.ss-plus.right:after{content:'+'}.ss-hyphen:before,.ss-hyphen.right:after{content:'-'}.ss-check:before,.ss-check.right:after{content:'✓'}.ss-delete:before,.ss-delete.right:after{content:'␡'}.ss-settings:before,.ss-settings.right:after{content:'⚙'}.ss-dashboard:before,.ss-dashboard.right:after{content:''}.ss-notifications:before,.ss-notifications.right:after{content:'🔔'}.ss-notificationsdisabled:before,.ss-notificationsdisabled.right:after{content:'🔕'}.ss-clock:before,.ss-clock.right:after{content:'⏲'}.ss-stopwatch:before,.ss-stopwatch.right:after{content:'⏱'}.ss-calendar:before,.ss-calendar.right:after{content:'📅'}.ss-addcalendar:before,.ss-addcalendar.right:after{content:''}.ss-removecalendar:before,.ss-removecalendar.right:after{content:''}.ss-checkcalendar:before,.ss-checkcalendar.right:after{content:''}.ss-deletecalendar:before,.ss-deletecalendar.right:after{content:''}.ss-plane:before,.ss-plane.right:after{content:'✈'}.ss-briefcase:before,.ss-briefcase.right:after{content:'💼'}.ss-cloud:before,.ss-cloud.right:after{content:'☁'}.ss-droplet:before,.ss-droplet.right:after{content:'💧'}.ss-flask:before,.ss-flask.right:after{content:''}.ss-up:before,.ss-up.right:after{content:'⬆'}.ss-upright:before,.ss-upright.right:after{content:'⬈'}.ss-right:before,.ss-right.right:after{content:'➡'}.ss-downright:before,.ss-downright.right:after{content:'⬊'}.ss-down:before,.ss-down.right:after{content:'⬇'}.ss-downleft:before,.ss-downleft.right:after{content:'⬋'}.ss-left:before,.ss-left.right:after{content:'⬅'}.ss-upleft:before,.ss-upleft.right:after{content:'⬉'}.ss-navigateup:before,.ss-navigateup.right:after{content:''}.ss-navigateright:before,.ss-navigateright.right:after{content:'▻'}.ss-navigatedown:before,.ss-navigatedown.right:after{content:''}.ss-navigateleft:before,.ss-navigateleft.right:after{content:'◅'}.ss-directup:before,.ss-directup.right:after{content:'▴'}.ss-directright:before,.ss-directright.right:after{content:'▹'}.ss-dropdown:before,.ss-dropdown.right:after{content:'▾'}.ss-directleft:before,.ss-directleft.right:after{content:'◃'}.ss-retweet:before,.ss-retweet.right:after{content:''} 51 | 52 | /* Legacy classes */ 53 | .ss-volumelow:before,.ss-volumelow.right:after{content:'🔉'}.ss-volumehigh:before,.ss-volumehigh.right:after{content:'🔊'}.ss-batteryhigh:before,.ss-batteryhigh.right:after{content:''}.ss-batterymedium:before,.ss-batterymedium.right:after{content:''}.ss-batterylow:before,.ss-batterylow.right:after{content:''}.ss-batteryempty:before,.ss-batteryempty.right:after{content:''}.ss-clouddownload:before,.ss-clouddownload.right:after{content:''}.ss-cloudupload:before,.ss-cloudupload.right:after{content:''}.ss-calendaradd:before,.ss-calendaradd.right:after{content:''}.ss-calendarremove:before,.ss-calendarremove.right:after{content:''}.ss-calendarcheck:before,.ss-calendarcheck.right:after{content:''}.ss-calendardelete:before,.ss-calendardelete.right:after{content:''} 54 | -------------------------------------------------------------------------------- /app/styles/_table.scss: -------------------------------------------------------------------------------- 1 | table { 2 | width: 100%; 3 | } 4 | 5 | .quick-chart { 6 | background: $base-background-color; 7 | height: 2em; 8 | width: 100%; 9 | } 10 | 11 | .quick-chart__inner { 12 | background: $primary; 13 | color: #fff; 14 | height: 100%; 15 | line-height: 2em; 16 | padding-left: 0.5em; 17 | } 18 | -------------------------------------------------------------------------------- /app/styles/_typography.scss: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: $em-base; 3 | } 4 | 5 | // Typography 6 | body { 7 | // -webkit-font-smoothing: antialiased; 8 | background-color: $base-background-color; 9 | color: $base-font-color; 10 | font-family: $base-font-family; 11 | font-size: $base-font-size; 12 | line-height: $base-line-height; 13 | } 14 | 15 | h1, 16 | h2, 17 | h3, 18 | h4, 19 | h5, 20 | h6 { 21 | font-family: $header-font-family; 22 | font-weight: bold; 23 | line-height: $header-line-height; 24 | color: $header-font-color; 25 | margin: 0; 26 | text-rendering: optimizeLegibility; // Fix the character spacing for headings 27 | } 28 | 29 | h1 { 30 | font-size: $h1-font-size; 31 | } 32 | 33 | h2 { 34 | font-size: $h2-font-size; 35 | } 36 | 37 | h3 { 38 | font-size: $h3-font-size; 39 | } 40 | 41 | h4 { 42 | font-size: $h4-font-size; 43 | } 44 | 45 | h5 { 46 | font-size: $h5-font-size; 47 | } 48 | 49 | h6 { 50 | font-size: $h6-font-size; 51 | } 52 | 53 | p { 54 | margin: 0 0 ($base-spacing / 2); 55 | 56 | &:last-child { 57 | margin-bottom: 0; 58 | } 59 | } 60 | 61 | a { 62 | // @include transition(color 0.1s linear); 63 | color: $base-link-color; 64 | text-decoration: none; 65 | 66 | &:hover { 67 | color: $hover-link-color; 68 | } 69 | 70 | &:active, &:focus { 71 | color: $hover-link-color; 72 | outline: none; 73 | } 74 | } 75 | 76 | hr { 77 | border-bottom: $base-border; 78 | border-left: none; 79 | border-right: none; 80 | border-top: none; 81 | margin: $base-spacing 0; 82 | } 83 | 84 | img, 85 | picture { 86 | margin: 0; 87 | max-width: 100%; 88 | } 89 | 90 | blockquote { 91 | border-left: 2px solid $base-border-color; 92 | color: lighten($base-font-color, 15); 93 | margin: $base-spacing 0; 94 | padding-left: $base-spacing / 2; 95 | } 96 | 97 | cite { 98 | color: lighten($base-font-color, 25); 99 | font-style: italic; 100 | 101 | &:before { 102 | content: "\2014 \00A0"; 103 | } 104 | } 105 | 106 | ul, 107 | ol { 108 | margin: 0; 109 | padding: 0; 110 | list-style-type: none; 111 | 112 | &%default-ul { 113 | list-style-type: disc; 114 | margin-bottom: $base-spacing / 2; 115 | padding-left: $base-spacing; 116 | } 117 | 118 | &%default-ol { 119 | list-style-type: decimal; 120 | margin-bottom: $base-spacing / 2; 121 | padding-left: $base-spacing; 122 | } 123 | } 124 | 125 | dl { 126 | margin-bottom: $base-spacing / 2; 127 | 128 | dt { 129 | font-weight: bold; 130 | margin-top: $base-spacing / 2; 131 | } 132 | 133 | dd { 134 | margin: 0; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /app/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | $base-background-color: rgb(249, 250, 252); 2 | $menu: rgb(237, 242, 246); 3 | $base-font-color: #98B0C3; 4 | $header-font-color: #5E778B; 5 | 6 | $primary: #6B7B9C; 7 | $happy: #74F2A7; 8 | $sad: #FF715D; 9 | 10 | $base-font-size: 1em; 11 | $h1-font-size: em(20); 12 | $h2-font-size: em(16); 13 | $h3-font-size: em(16); 14 | $h4-font-size: em(16); 15 | $h5-font-size: 1em; 16 | $h6-font-size: 1em; 17 | 18 | $base-line-height: 24px; 19 | $header-line-height: 24px; 20 | 21 | $base-border-color: $menu; 22 | $base-border: 1px solid $base-border-color; 23 | 24 | $base-spacing: 1em; 25 | 26 | $base-link-color: $header-font-color; 27 | $hover-link-color: darken($header-font-color, 5%); 28 | 29 | $base-font-family: 'Range', monospace; 30 | $header-font-family: $base-font-family; 31 | 32 | $border-radius: 3px; 33 | 34 | $base-button-color: $header-font-color; 35 | $base-button-background: $base-background-color; 36 | $hover-button-color: darken($base-button-background, 5%); 37 | 38 | $form-input-background-color: $base-background-color; 39 | $form-input-background-color-hover: darken($form-input-background-color, 5%); 40 | $form-input-background-color-focus: darken($form-input-background-color, 5%); 41 | $form-input-border-color: $form-input-background-color; 42 | $form-input-border-color-hover: $form-input-background-color-hover; 43 | $form-input-border-color-focus: $primary; 44 | $form-input-font-color: $header-font-color; 45 | $form-font-size: 1em; 46 | $form-font-family: $base-font-family; 47 | $form-font-weight: bold; 48 | $form-field-spacing: $base-line-height; 49 | 50 | @mixin primaryGradient { 51 | @include linear-gradient(-180deg, $primary 0%, #647495 100%); 52 | } 53 | 54 | @mixin happyGradient { 55 | @include linear-gradient(-180deg, $happy 0%, #71EBA1 100%); 56 | } 57 | 58 | @mixin sadGradient { 59 | @include linear-gradient(-180deg, $sad 0%, #F56A58 100%); 60 | } 61 | -------------------------------------------------------------------------------- /app/styles/app.scss: -------------------------------------------------------------------------------- 1 | // ________ ________ ________ 2 | // |\ __ \|\ __ \|\_____ \ 3 | // \ \ \|\ \ \ \|\ \\|___/ /| 4 | // \ \ ____\ \ __ \ / / / 5 | // \ \ \___|\ \ \ \ \ / /_/__ 6 | // \ \__\ \ \__\ \__\\________\ 7 | // \|__| \|__|\|__|\|_______| 8 | 9 | $em-base: 13px; 10 | 11 | @import 'bower_components/normalize-scss/_normalize'; 12 | @import 'bower_components/bourbon/app/assets/stylesheets/_bourbon'; 13 | @import 'bower_components/neat/app/assets/stylesheets/_neat'; 14 | @import '_range'; 15 | @import '_ss-standard'; 16 | 17 | @import '_variables'; 18 | @import '_layout'; 19 | @import '_typography'; 20 | 21 | @import '_animations'; 22 | @import '_buttons'; 23 | @import '_card'; 24 | @import '_form'; 25 | @import '_health'; 26 | @import '_navigation'; 27 | @import '_pills'; 28 | @import '_progress-bar'; 29 | @import '_helpers'; 30 | @import '_table'; 31 | 32 | @import 'container'; 33 | @import 'history'; 34 | -------------------------------------------------------------------------------- /app/styles/container.scss: -------------------------------------------------------------------------------- 1 | .deploy-container { 2 | @include align-items(center); 3 | @include display(flex); 4 | @include flex-direction(row); 5 | @include justify-content(flex-start); 6 | 7 | @include outer-container; 8 | button { @include span-columns(3); } 9 | .deploy-message { @include span-columns(6); } 10 | } 11 | -------------------------------------------------------------------------------- /app/styles/history.scss: -------------------------------------------------------------------------------- 1 | .table-container { 2 | @include outer-container; 3 | font-family: sans-serif; 4 | font-size: .9em; 5 | line-height: 40px; 6 | background: white; 7 | color: $header-font-color; 8 | } 9 | 10 | .table-header-main { 11 | background: $primary; 12 | color: $base-background-color; 13 | font-weight: bold; 14 | @include fill-parent(); 15 | @include row(table); 16 | } 17 | 18 | .table-header { 19 | font-weight: bold; 20 | font-style: bold; 21 | @include fill-parent(); 22 | @include row(table); 23 | } 24 | 25 | .table-row { 26 | @include fill-parent(); 27 | @include row(table); 28 | } 29 | 30 | .table-row-item { 31 | @include span-columns(2); 32 | padding: 5px 20px 5px 20px; 33 | border-bottom: 1px solid #ddd; 34 | } 35 | 36 | .table-row-item-expand { 37 | @include span-columns(2); 38 | background: darken(white, 5%); 39 | padding: 5px 20px 5px 20px; 40 | border-bottom: 1px solid #ddd; 41 | } 42 | 43 | .history-icon { 44 | padding-right: 15px; 45 | } 46 | 47 | .table-row i { 48 | font-size: 5pt; 49 | } 50 | -------------------------------------------------------------------------------- /app/templates/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paz-sh/paz-web/21fbb43be813a5931a982a417b6be97147317c8d/app/templates/.gitkeep -------------------------------------------------------------------------------- /app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |
2 | 24 | 25 |
26 | {{outlet}} 27 |
28 | 29 |
30 | -------------------------------------------------------------------------------- /app/templates/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paz-sh/paz-web/21fbb43be813a5931a982a417b6be97147317c8d/app/templates/components/.gitkeep -------------------------------------------------------------------------------- /app/templates/components/health-buttons.hbs: -------------------------------------------------------------------------------- 1 |
  • 2 | {{#link-to 'monitoring.unit' unit.id classNames='btn' title='Monitoring'}} 3 | 4 | {{/link-to}} 5 |
  • 6 | -------------------------------------------------------------------------------- /app/templates/components/health-state.hbs: -------------------------------------------------------------------------------- 1 |
  • 2 | {{yield}} 3 |
  • 4 | -------------------------------------------------------------------------------- /app/templates/components/progress-bar.hbs: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 |
    6 |
    7 | 8 |
    9 |
    10 | 11 |
    12 |
    13 |
    14 | -------------------------------------------------------------------------------- /app/templates/components/quick-chart.hbs: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | {{current}}% 4 |
    5 |
    6 | -------------------------------------------------------------------------------- /app/templates/components/service-config.hbs: -------------------------------------------------------------------------------- 1 | 35 | 36 | {{yield}} 37 | -------------------------------------------------------------------------------- /app/templates/components/service-edit.hbs: -------------------------------------------------------------------------------- 1 | {{#form-wrapper classNames='config__form'}} 2 | 3 |
    4 | {{input config.publicFacing label="Public Facing?" as="checkbox"}} 5 |
    6 |
    7 | {{input config.numInstances label="Number of instances"}} 8 |
    9 | 10 |
    11 |
    12 | Ports 13 | {{#each port in config.ports}} 14 |
    15 |
    16 | {{input port.container placeholder=9000 label="container"}} 17 |
    18 |
    19 | {{input port.host placeholder=81 pattern="^(?!.*80).*$" oninvalid="setCustomValidity('You cannot use port 80')" label="host"}} 20 | 21 |
    22 |
    23 | {{/each}} 24 | 25 | 28 |
    29 | 30 |
    31 | Environment 32 |
    33 | {{#each envKey in config.env}} 34 |
    35 | {{input envKey.key placeholder="key" label="key"}} 36 |
    37 |
    38 | {{input envKey.value placeholder="value" label="value"}} 39 | 40 |
    41 | {{/each}} 42 |
    43 | 44 | 47 |
    48 | 49 |
    50 | Volumes 51 |
    52 | {{#each volumeKey in config.volume}} 53 |
    54 | {{input volumeKey.key placeholder="key" label="key"}} 55 |
    56 |
    57 | {{input volumeKey.value placeholder="value" label="value"}} 58 | 59 |
    60 | {{/each}} 61 |
    62 | 63 | 66 |
    67 |
    68 | {{/form-wrapper}} 69 | 70 | -------------------------------------------------------------------------------- /app/templates/dashboard.hbs: -------------------------------------------------------------------------------- 1 | 7 | 8 | {{outlet}} 9 | -------------------------------------------------------------------------------- /app/templates/dashboard/index.hbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paz-sh/paz-web/21fbb43be813a5931a982a417b6be97147317c8d/app/templates/dashboard/index.hbs -------------------------------------------------------------------------------- /app/templates/dashboard/loading.hbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paz-sh/paz-web/21fbb43be813a5931a982a417b6be97147317c8d/app/templates/dashboard/loading.hbs -------------------------------------------------------------------------------- /app/templates/error.hbs: -------------------------------------------------------------------------------- 1 |

    :( something went wrong

    2 | -------------------------------------------------------------------------------- /app/templates/history.hbs: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 |
    Date
    5 |
    Service Name
    6 |
    Version
    7 |
    8 | 9 |
    10 | {{#each journal in model}} 11 |
    12 |
    13 |
    {{format-date journal.timestamp}}
    14 |
    {{journal.serviceName}}
    15 |
    {{journal.version}}
    16 |
    17 |
    18 | 19 |
    20 |
    21 |
    Public Facing
    22 |
    Ports
    23 |
    Env Vars
    24 |
    Num Instances
    25 |
    26 | 27 |
    28 |
    {{journal.config.publicFacing}}
    29 | {{#if journal.config.ports}} 30 |
    31 | {{#each port in journal.config.ports}} 32 |
    {{port.container}}:{{port.host}}
    33 | {{/each}} 34 |
    35 | {{else}} 36 |
    37 |

    None

    38 |
    39 | {{/if}} 40 | 41 |
    42 | {{#each env in journal.config.env}} 43 |

    {{env.key}}: {{env.value}}

    44 | {{/each}} 45 |
    46 |
    {{journal.config.numInstances}}
    47 |
    48 |
    49 | {{/each}} 50 |
    51 |
    52 | 53 | 62 | -------------------------------------------------------------------------------- /app/templates/hosts.hbs: -------------------------------------------------------------------------------- 1 |
    2 | {{#each host in model}} 3 |
    4 |
    5 |

    6 | {{host.PublicIP}} 7 |

    8 |
    9 | {{#if host.isExpanded}} 10 | {{#if host.units}} 11 | {{#each unit in host.units}} 12 |
    13 |
      14 |
    • 15 |
      {{unit.name}}
      v{{unit.version}} #{{unit.instance}} 16 |
    • 17 | {{#if unit.isExpanded}} 18 | 19 | {{#health-state property=unit.loadHealthy}}loading: {{unit.loadState}}{{/health-state}} 20 | 21 | {{#health-state property=unit.activeHealthy}}active: {{unit.activeState}}{{/health-state}} 22 | 23 | {{#health-state property=unit.subHealthy}}sub: {{unit.subState}}{{/health-state}} 24 | 25 | {{health-buttons unit=unit}} 26 | {{/if}} 27 |
    28 |
    29 | {{/each}} 30 | {{else}} 31 |
    32 |

    No units running.

    33 |
    34 | {{/if}} 35 | 36 | 41 | {{/if}} 42 |
    43 | {{/each}} 44 |
    45 | 46 | -------------------------------------------------------------------------------- /app/templates/index.hbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paz-sh/paz-web/21fbb43be813a5931a982a417b6be97147317c8d/app/templates/index.hbs -------------------------------------------------------------------------------- /app/templates/load-balancers.hbs: -------------------------------------------------------------------------------- 1 | {{#unless this.length}} 2 |

    No units deployed.

    3 | {{/unless}} 4 | {{#each}} 5 |
    6 |
    7 |
    8 |

    9 | Backends for 10 | {{#link-to 'service' id}} 11 | {{id}} 12 | {{/link-to}} 13 |

    14 |
    15 | {{#each backends}} 16 |
    17 | 18 | 19 | 20 | 21 |

    {{order}} (v{{version}})

    22 |
    23 |
    24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {{#each hosts}} 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {{/each}} 41 |
    Unit
    Host
    Sessions
    Queued
    Weight
    {{unit}}{{host}}{{service.scur}}{{service.qcur}}{{service.weight}}
    42 |
    43 | {{/each}} 44 |
    45 |
    46 | {{/each}} 47 | -------------------------------------------------------------------------------- /app/templates/logs.hbs: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    Logs

    5 |
    6 |
    7 |

    Can we embed Kibana here?

    8 |
    9 |
    10 |
    11 | -------------------------------------------------------------------------------- /app/templates/monitoring/host.hbs: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    Monitoring for {{PublicIP}}

    4 |
    5 |
    6 | 7 |
    8 |
    9 | -------------------------------------------------------------------------------- /app/templates/monitoring/index.hbs: -------------------------------------------------------------------------------- 1 |
    2 | {{#each}} 3 |
    4 |
    5 |

    6 | {{#link-to 'monitoring.host' id}} 7 | {{PublicIP}} 8 | {{/link-to}} 9 |

    10 |
    11 | {{#if units}} 12 |
    13 |
      14 | {{#each units}} 15 | {{#link-to 'monitoring.unit' id}} 16 |
    • {{name}}
      v{{version}} #{{instance}}
    • 17 | {{/link-to}} 18 | {{/each}} 19 |
    20 |
    21 | {{/if}} 22 |
    23 | {{/each}} 24 |
    25 | -------------------------------------------------------------------------------- /app/templates/monitoring/unit.hbs: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    Monitoring for {{id}}

    4 |
    5 |
    6 | {{#if healthy}} 7 | 8 | {{else}} 9 |

    Requested unit is down.

    10 | {{/if}} 11 |
    12 |
    13 | -------------------------------------------------------------------------------- /app/templates/service.hbs: -------------------------------------------------------------------------------- 1 | {{#link-to 'services'}}← Back to list{{/link-to}} 2 | 3 |
    4 |
    5 |
    6 |

    7 | {{id}} 8 |
    9 | 12 |
    13 |

    14 |
    15 |
    16 |

    {{description}}

    17 |

    {{dockerRepository}}

    18 |
    19 |
    20 |

    Last Config

    21 | {{#if configLast}} 22 | {{#if configLast.isFulfilled}} 23 | {{service-config config=configLast}} 24 | {{else}} 25 |

    Service hasn't been deployed yet.

    26 | {{/if}} 27 | {{else}} 28 |

    Service hasn't been deployed yet.

    29 | {{/if}} 30 | 31 |
    32 | 33 |
    34 |

    35 | Next Config 36 | {{#unless configEdit.isEditing}} 37 | 40 | {{/unless}} 41 |

    42 | {{#if configEdit.isEditing}} 43 | {{service-edit config=configEdit}} 44 | 48 | 51 | {{else}} 52 | {{service-config config=configNext}} 53 |
    54 | 57 |
    58 | {{#if isDeploying}} 59 | {{progress-bar}} 60 | {{else}} 61 | {{#if isDeploySuccess}} 62 |

    deploy successfully

    63 | {{/if}} 64 | {{#if isDeployFailed}} 65 |

    deploy failed

    66 | {{/if}} 67 | {{/if}} 68 |
    69 |
    70 | {{/if}} 71 |
    72 |
    73 |
    74 |
    75 | 76 | {{outlet}} 77 |
    78 | -------------------------------------------------------------------------------- /app/templates/service/edit.hbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paz-sh/paz-web/21fbb43be813a5931a982a417b6be97147317c8d/app/templates/service/edit.hbs -------------------------------------------------------------------------------- /app/templates/services.hbs: -------------------------------------------------------------------------------- 1 | {{outlet}} 2 | -------------------------------------------------------------------------------- /app/templates/services/index.hbs: -------------------------------------------------------------------------------- 1 | 5 | 6 |
    7 | {{#each service in model}} 8 |
    9 |
    10 |

    11 | {{#link-to 'service' service.id}} 12 | {{service.id}} 13 | {{/link-to}} 14 |

    15 |
    16 | {{#link-to 'service' service.id}} 17 |
    18 |

    {{service.description}}

    19 |
    20 | {{/link-to}} 21 |
    22 | {{/each}} 23 |
    24 | -------------------------------------------------------------------------------- /app/templates/services/new.hbs: -------------------------------------------------------------------------------- 1 | {{#link-to 'services'}}← Back to list{{/link-to}} 2 | 3 |
    4 |
    5 |
    6 |

    New Service

    7 |
    8 |
    9 | {{#form-wrapper}} 10 | {{input model.name label='Name' placeholder='my-service'}} 11 | {{input model.description label='Description' placeholder='Awesome sauce service.'}} 12 | {{input model.dockerRepository label='Docker Repository' placeholder='node:0.10'}} 13 | 14 |
    15 | Config 16 | {{service-edit config=model}} 17 |
    18 | 19 | 20 | 21 | {{/form-wrapper}} 22 |
    23 |
    24 |
    25 | -------------------------------------------------------------------------------- /app/templates/styleguide.hbs: -------------------------------------------------------------------------------- 1 | 8 | 9 |
    10 |
    11 |
    12 |

    13 | Card 14 |

    15 |
    16 |
    17 |
    This is my card
    18 |

    There are many like it, but this one is mine.

    19 |

    My card is my best friend. It is my life. I must master it as I must master my life.

    20 |
    21 |
    22 | 23 |
    24 |
    25 |

    26 | Happy Card 27 |

    28 |
    29 |
    30 |
      31 |
    • Service
      v1 #1
    • 32 |
    33 |
    34 |
    35 |
      36 |
    • Service
      v1 #1
    • 37 |
    38 |
    39 |
    40 |
      41 |
    • Service
      v1 #1
    • 42 |
    43 |
    44 |
    45 | 46 |
    47 |
    48 |

    49 | Sad Card 50 |

    51 |
    52 |
    53 |
      54 |
    • Service
      v1 #1
    • 55 |
    • loading: loaded
    • 56 |
    • active: active
    • 57 |
    • sub: failed
    • 58 |
    • 59 |
      60 |
      61 |
      62 |
    • 63 |
    64 |
    65 |
    66 |
      67 |
    • Service
      v1 #1
    • 68 |
    69 |
    70 |
    71 |
      72 |
    • Service
      v1 #1
    • 73 |
    74 |
    75 |
    76 | 77 |
    78 |
    79 |

    80 | Second row card 81 |

    82 |
    83 |
    84 |

    Look mom, no .row div!

    85 |
    86 |
    87 | 88 |
    89 |
    90 |

    91 | Card with sections 92 |

    93 |
    94 |

    Foo

    95 |

    Bar

    96 |

    Baz

    97 | 98 |
    99 | 100 |
    101 |
    102 |

    103 | Card with footer 104 |

    105 |
    106 |
    107 |

    I'm a cool card with a footer!

    108 |
    109 | 112 |
    113 | 114 |
    115 | 116 |
    117 |
    118 |
    119 |

    120 | A bigger card 121 |

    122 |
    123 |
    124 |
    This card is bigger.
    125 |

    It doesn't tile, so use it for occasions when you have lots of content.

    126 | 127 |
    Buttons
    128 |
    Default Button
    129 |
    Primary Button
    130 |
    Happy Button
    131 |
    Sad Button
    132 |
    133 | Button with icon 134 |
    135 |

    Because inline-block is finicky - make sure the <i> tag is on the same level as the button text - otherwise we get a weird margin bug.

    136 | 137 |
    Block-level buttons
    138 |
    Full-width Button
    139 |
    140 | Full-width Button with icon 141 |
    142 | 143 |
    Button sizes
    144 |
    Small button
    145 |
    Normal button
    146 |
    Large button
    147 | 148 |
    Button groups
    149 |
    150 |
    151 |
    152 |
    153 |
    154 |
    155 |
    156 | 157 |
    158 |
    159 |

    Form styling

    160 |
    161 |
    162 | {{#form-wrapper}} 163 | {{input "Username" placeholder='Paz Peterson'}} 164 | {{input Checkbox label="Load Balanced?" as="checkbox" value=loadBalanced }} 165 | {{input Numeric label="# Instances" as="number" value=numInstances placeholder='3'}} 166 | 167 | {{!--
    --}} 168 | 169 | {{#input error-testing classNames='fieldWithErrors'}} 170 | {{label-field error-testing}} 171 | {{input-field error-testing value='Invalid'}} 172 | can't be blank 173 | {{/input}} 174 | 175 | {{submit classNames='btn btn--primary'}} 176 | 177 | {{/form-wrapper}} 178 |
    179 |
    180 |
    181 | 182 |
    183 |

    Progress bar

    184 | {{progress-bar}} 185 |
    186 | -------------------------------------------------------------------------------- /app/templates/units.hbs: -------------------------------------------------------------------------------- 1 |
    2 | {{#each service in model}} 3 |
    4 |
    5 |

    6 | {{service.id}} 7 |

    8 |
    9 | {{#if service.isExpanded}} 10 | {{#if service.units}} 11 | {{#each unit in service.units}} 12 |
    13 |
      14 |
    • {{unit.name}}
      v{{unit.version}} #{{unit.instance}}
    • 15 | {{#if unit.isExpanded}} 16 | 17 | {{#health-state property=unit.loadHealthy}}loading: {{unit.loadState}}{{/health-state}} 18 | 19 | {{#health-state property=unit.activeHealthy}}active: {{unit.activeState}}{{/health-state}} 20 | 21 | {{#health-state property=unit.subHealthy}}sub: {{unit.subState}}{{/health-state}} 22 | 23 | {{health-buttons unit=unit}} 24 | {{/if}} 25 |
    26 |
    27 | {{/each}} 28 | {{else}} 29 |
    30 |

    No units running.

    31 |
    32 | {{/if}} 33 | {{/if}} 34 |
    35 | {{/each}} 36 |
    37 | -------------------------------------------------------------------------------- /app/views/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paz-sh/paz-web/21fbb43be813a5931a982a417b6be97147317c8d/app/views/.gitkeep -------------------------------------------------------------------------------- /bin/dockerBuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | npm install 3 | bower install 4 | ember build --environment production 5 | cp docker/Dockerfile dist 6 | cp bin/run.sh dist 7 | cp 200.jade dist 8 | cp -R public/assets dist 9 | cd dist 10 | docker build -t quay.io/yldio/paz-web . 11 | cd .. 12 | -------------------------------------------------------------------------------- /bin/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Connecting to orchestrator at $PAZ_ORCHESTRATOR_URL & $PAZ_ORCHESTRATOR_SOCKET" 3 | perl -pi -e "s/localhost:9000/$PAZ_ORCHESTRATOR_URL/g" assets/paz-ember-*.js 4 | perl -pi -e "s/localhost:9002/$PAZ_SCHEDULER_URL/g" assets/paz-ember-*.js 5 | perl -pi -e "s/localhost:1337/$PAZ_ORCHESTRATOR_SOCKET:80/g" assets/paz-ember-*.js 6 | perl -pi -e "s/localhost%3A9000/$PAZ_ORCHESTRATOR_URL/g" index.html 7 | perl -pi -e "s/localhost%3A9002/$PAZ_SCHEDULER_URL/g" index.html 8 | perl -pi -e "s/localhost%3A1337/$PAZ_ORCHESTRATOR_SOCKET%3A80/g" index.html 9 | harp server -p 80 . 10 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paz-ember", 3 | "dependencies": { 4 | "jquery": "^1.11.1", 5 | "bourbon": "4.2.3", 6 | "neat": "~1.7.1", 7 | "normalize-scss": "~3.0.1", 8 | "ember": "1.10.0", 9 | "ember-data": "1.0.0-beta.15", 10 | "ember-resolver": "~0.1.12", 11 | "loader.js": "ember-cli/loader.js#1.0.1", 12 | "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3", 13 | "ember-cli-test-loader": "ember-cli-test-loader#0.1.3", 14 | "ember-load-initializers": "ember-cli/ember-load-initializers#0.0.2", 15 | "ember-qunit": "0.2.8", 16 | "ember-qunit-notifications": "0.0.7", 17 | "qunit": "~1.17.1", 18 | "ember-validations": "http://builds.dockyard.com.s3.amazonaws.com/ember-validations/ember-validations-latest.js", 19 | "jQuery-Collapse": "~1.1.1", 20 | "ember-cli-moment-shim": "~0.2.0" 21 | }, 22 | "devDependencies": { 23 | "ember-sockets": "git://github.com/Wildhoney/EmberSockets#5d8d6abbe14bcd2aada5127ee3e94b637a7d44bb" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 3 | module.exports = function(environment) { 4 | var ENV = { 5 | modulePrefix: 'paz-ember', 6 | environment: environment, 7 | baseURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | } 14 | }, 15 | 16 | APP: { 17 | // Here you can pass flags/options to your application instance 18 | // when it is created 19 | ORCHESTRATOR_URL: process.env['PAZ_ORCHESTRATOR_URL'] || 'http://localhost:9000', 20 | ORCHESTRATOR_SOCKET: process.env['PAZ_ORCHESTRATOR_SOCKET'] || 'localhost:1337', 21 | SCHEDULER_URL: process.env['PAZ_SCHEDULER_URL'] || 'http://localhost:9002' 22 | } 23 | }; 24 | 25 | if (environment === 'development') { 26 | // ENV.APP.LOG_RESOLVER = true; 27 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 28 | // ENV.APP.LOG_TRANSITIONS = true; 29 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 30 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 31 | } 32 | 33 | if (environment === 'test') { 34 | // Testem prefers this... 35 | ENV.baseURL = '/'; 36 | ENV.locationType = 'none'; 37 | 38 | // keep test console output quieter 39 | ENV.APP.LOG_ACTIVE_GENERATION = false; 40 | ENV.APP.LOG_VIEW_LOOKUPS = false; 41 | 42 | ENV.APP.rootElement = '#ember-testing'; 43 | } 44 | 45 | if (environment === 'production') { 46 | 47 | } 48 | 49 | return ENV; 50 | }; 51 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM quay.io/yldio/paz-base 2 | MAINTAINER Luke Bond "luke@yld.io" 3 | 4 | RUN apk --update add bash perl 5 | RUN npm install -g harp 6 | 7 | WORKDIR /usr/src/app 8 | ADD ./assets /usr/src/app/assets 9 | ADD ./crossdomain.xml /usr/src/app/crossdomain.xml 10 | ADD ./index.html /usr/src/app/index.html 11 | ADD ./200.jade /usr/src/app/200.jade 12 | ADD ./robots.txt /usr/src/app/robots.txt 13 | ADD ./run.sh /usr/src/app/run.sh 14 | 15 | EXPOSE 80 16 | 17 | CMD [ "./run.sh" ] 18 | -------------------------------------------------------------------------------- /environment.sh: -------------------------------------------------------------------------------- 1 | #Might need to change this eventually 2 | ETCD=178.62.3.75:4001 3 | export PAZ_ORCHESTRATOR_URL=http://$(etcdctl --peers=$ETCD get /paz/services/paz-orchestrator) 4 | export PAZ_ORCHESTRATOR_SOCKET=$(etcdctl --peers=$ETCD get /paz/services/paz-orchestrator-socket) 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paz-ember", 3 | "version": "0.0.0", 4 | "description": "Small description for paz-ember goes here", 5 | "private": true, 6 | "directories": { 7 | "doc": "doc", 8 | "test": "tests" 9 | }, 10 | "scripts": { 11 | "start": "ember server", 12 | "lint": "jshint app config", 13 | "build": "ember build", 14 | "test": "ember test" 15 | }, 16 | "repository": "", 17 | "engines": { 18 | "node": ">= 0.10.0" 19 | }, 20 | "author": "", 21 | "license": "MIT", 22 | "devDependencies": { 23 | "broccoli-asset-rev": "^2.0.0", 24 | "ember-cli": "^0.2.0", 25 | "ember-cli-app-version": "0.3.2", 26 | "ember-cli-babel": "^4.3.0", 27 | "ember-cli-dependency-checker": "0.0.8", 28 | "ember-cli-htmlbars": "0.7.4", 29 | "ember-cli-ic-ajax": "0.1.1", 30 | "ember-cli-inject-live-reload": "^1.3.0", 31 | "ember-cli-qunit": "0.3.9", 32 | "ember-cli-sass": "^4.0.0", 33 | "ember-cli-uglify": "1.0.1", 34 | "ember-data": "1.0.0-beta.15", 35 | "ember-easy-form-extensions": "0.2.3", 36 | "ember-export-application-global": "^1.0.2", 37 | "ember-moment": "1.2.1", 38 | "ember-validations": "^2.0.0-alpha.3", 39 | "express": "^4.8.5", 40 | "glob": "^4.4.2", 41 | "precommit-hook": "^1.0.7" 42 | }, 43 | "precommit": [ 44 | "lint" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /paz-ember.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "follow_symlinks": true, 6 | "path": ".", 7 | "folder_exclude_patterns": ["node_modules", "tmp", "dist/"], 8 | "file_exclude_patterns": [".gitkeep"] 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paz-sh/paz-web/21fbb43be813a5931a982a417b6be97147317c8d/public/.gitkeep -------------------------------------------------------------------------------- /public/assets/fonts/ss-standard.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paz-sh/paz-web/21fbb43be813a5931a982a417b6be97147317c8d/public/assets/fonts/ss-standard.eot -------------------------------------------------------------------------------- /public/assets/fonts/ss-standard.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Symbolset 3 | * www.symbolset.com 4 | * Copyright © 2013 Oak Studios LLC 5 | * 6 | * Upload this file to your web server 7 | * and place this before the closing tag. 8 | * 9 | */ 10 | 11 | if (/(MSIE [7-9]\.|Opera.*Version\/(10\.[5-9]|(11|12)\.)|Chrome\/([1-9]|10)\.|Version\/[2-4][\.0-9]+ Safari\/|Version\/(4\.0\.[4-9]|4\.[1-9]|5\.0)[\.0-9]+? Mobile\/.*Safari\/|Android ([1-2]|4\.[2-9].*Version\/4)\.|BlackBerry.*WebKit)/.test(navigator.userAgent) && !/(IEMobile)/.test(navigator.userAgent)) { 12 | 13 | if (/Android 4\.[2-9].*Version\/4/.test(navigator.userAgent)) { 14 | var ss_android = document.createElement('style'); 15 | ss_android.innerHTML = '.ss-icon,[class^="ss-"],[class*=" ss-"],[class^="ss-"]:before,[class*=" ss-"]:before,[class^="ss-"].right:after[class*=" ss-"].right:after{text-rendering:auto!important}'; 16 | document.body.appendChild(ss_android); 17 | } 18 | 19 | var ss_set={'notifications disabled':'\uD83D\uDD15','notificationsdisabled':'\uD83D\uDD15','notification disabled':'\uD83D\uDD15','notificationdisabled':'\uD83D\uDD15','telephone disabled':'\uE300','telephonedisabled':'\uE300','writing disabled':'\uE071','writingdisabled':'\uE071','pencil disabled':'\uE071','remove calendar':'\uF071','calendar remove':'\uF071','delete calendar':'\uF073','calendar delete':'\uF073','pencildisabled':'\uE071','phone disabled':'\uE300','medium battery':'\uEA11','battery medium':'\uEA11','download cloud':'\uEB00','cloud download':'\uEB00','removecalendar':'\uF071','calendarremove':'\uF071','check calendar':'\uF072','calendar check':'\uF072','deletecalendar':'\uF073','calendardelete':'\uF073','navigate right':'\u25BB','phonedisabled':'\uE300','call disabled':'\uE300','ellipsis chat':'\uE399','female avatar':'\uD83D\uDC67','shopping cart':'\uE500','mediumbattery':'\uEA11','batterymedium':'\uEA11','empty battery':'\uEA13','battery empty':'\uEA13','downloadcloud':'\uEB00','clouddownload':'\uEB00','notifications':'\uD83D\uDD14','bell disabled':'\uD83D\uDD15','checkcalendar':'\uF072','calendarcheck':'\uF072','navigateright':'\u25BB','navigate down':'\uF501','navigate left':'\u25C5','calldisabled':'\uE300','ellipsischat':'\uE399','femaleavatar':'\uD83D\uDC67','shoppingcart':'\uE500','fast forward':'\u23E9','skip forward':'\u23ED','mobile phone':'\uD83D\uDCF1','full battery':'\uD83D\uDD0B','battery full':'\uD83D\uDD0B','high battery':'\uEA10','battery high':'\uEA10','emptybattery':'\uEA13','batteryempty':'\uEA13','upload cloud':'\uEB40','cloud upload':'\uEB40','rotate right':'\u21BB','notification':'\uD83D\uDD14','belldisabled':'\uD83D\uDD15','add calendar':'\uF070','calendar add':'\uF070','navigatedown':'\uF501','navigateleft':'\u25C5','direct right':'\u25B9','thumbs down':'\uD83D\uDC4E','male avatar':'\uD83D\uDC64','female user':'\uD83D\uDC67','credit card':'\uD83D\uDCB3','dollar sign':'\uD83D\uDCB2','high volume':'\uD83D\uDD0A','volume high':'\uD83D\uDD0A','photographs':'\uD83C\uDF04','videocamera':'\uD83D\uDCF9','fastforward':'\u23E9','skipforward':'\u23ED','rotate left':'\u21BA','mobilephone':'\uD83D\uDCF1','fullbattery':'\uD83D\uDD0B','batteryfull':'\uD83D\uDD0B','highbattery':'\uEA10','batteryhigh':'\uEA10','low battery':'\uEA12','battery low':'\uEA12','uploadcloud':'\uEB40','cloudupload':'\uEB40','rotateright':'\u21BB','information':'\u2139','addcalendar':'\uF070','calendaradd':'\uF070','remove date':'\uF071','delete date':'\uF073','navigate up':'\uF500','directright':'\u25B9','direct down':'\u25BE','direct left':'\u25C3','screenshot':'\u2316','visibility':'\uD83D\uDC40','attachment':'\uD83D\uDCCE','disapprove':'\uD83D\uDC4E','thumbsdown':'\uD83D\uDC4E','half heart':'\uE1A0','eyedropper':'\uE200','maleavatar':'\uD83D\uDC64','femaleuser':'\uD83D\uDC67','creditcard':'\uD83D\uDCB3','dollarsign':'\uD83D\uDCB2','navigation':'\uE670','directions':'\uE672','hard drive':'\uE7B0','microphone':'\uD83C\uDFA4','low volume':'\uD83D\uDD09','volume low':'\uD83D\uDD09','highvolume':'\uD83D\uDD0A','volumehigh':'\uD83D\uDD0A','photograph':'\uD83C\uDF04','rotateleft':'\u21BA','thumbnails':'\uE9A3','cell phone':'\uD83D\uDCF1','smartphone':'\uD83D\uDCF1','lowbattery':'\uEA12','batterylow':'\uEA12','connection':'\uEB85','pull quote':'\u201C','removedate':'\uF071','check date':'\uF072','deletedate':'\uF073','down right':'\u2B0A','navigateup':'\uF500','descending':'\u25BE','directdown':'\u25BE','directleft':'\u25C3','crosshair':'\u2316','paperclip':'\uD83D\uDCCE','backspace':'\u232B','thumbs up':'\uD83D\uDC4D','halfheart':'\uE1A0','half star':'\uE1A1','telephone':'\uD83D\uDCDE','male user':'\uD83D\uDC64','bar chart':'\uD83D\uDCCA','pie chart':'\uE570','buildings':'\uD83C\uDFE2','warehouse':'\uE602','harddrive':'\uE7B0','musicnote':'\u266B','lowvolume':'\uD83D\uDD09','volumelow':'\uD83D\uDD09','skip back':'\u23EE','open book':'\uD83D\uDCD6','newspaper':'\uD83D\uDCF0','cellphone':'\uD83D\uDCF1','lightbulb':'\uD83D\uDCA1','pullquote':'\u201C','checkmark':'\u2713','dashboard':'\uF000','stopwatch':'\u23F1','checkdate':'\uF072','briefcase':'\uD83D\uDCBC','downright':'\u2B0A','down left':'\u2B0B','ascending':'\u25B4','direct up':'\u25B4','zoom out':'\uE003','unlocked':'\uD83D\uDD13','insecure':'\uD83D\uDD13','trashcan':'\uE0D0','keywords':'\uE100','bookmark':'\uD83D\uDD16','thumbsup':'\uD83D\uDC4D','favorite':'\u22C6','halfstar':'\uE1A1','end call':'\uE300','facetime':'\uE320','envelope':'\u2709','ellipsis':'\u2026','maleuser':'\uD83D\uDC64','barchart':'\uD83D\uDCCA','piechart':'\uE570','navigate':'\uE670','signpost':'\uE672','location':'\uE6D0','database':'\uE7A0','pictures':'\uD83C\uDF04','skipback':'\u23EE','openbook':'\uD83D\uDCD6','notebook':'\uD83D\uDCD3','computer':'\uD83D\uDCBB','download':'\uEB01','transfer':'\u21C6','document':'\uD83D\uDCC4','typeface':'\uED01','redirect':'\u21AA','contract':'\uEE01','question':'\u2753','sign out':'\uEE02','subtract':'\u002D','settings':'\u2699','calendar':'\uD83D\uDCC5','add date':'\uF070','up right':'\u2B08','downleft':'\u2B0B','previous':'\u25C5','directup':'\u25B4','dropdown':'\u25BE','zoom in':'\uE002','zoomout':'\uE003','visible':'\uD83D\uDC40','compose':'\uD83D\uDCDD','private':'\uD83D\uDD12','keyword':'\uE100','approve':'\uD83D\uDC4D','dislike':'\uD83D\uDC4E','windows':'\uE202','endcall':'\uE300','comment':'\uD83D\uDCAC','avatars':'\uD83D\uDC65','package':'\uD83D\uDCE6','compass':'\uE671','dictate':'\uD83C\uDFA4','speaker':'\uD83D\uDD08','airplay':'\uE800','picture':'\uD83C\uDF04','shuffle':'\uD83D\uDD00','columns':'\uE9A2','desktop':'\uD83D\uDCBB','display':'\uD83D\uDCBB','monitor':'\uD83D\uDCBB','battery':'\uD83D\uDD0B','refresh':'\u21BB','syncing':'\uEB82','loading':'\uEB83','printer':'\u2399','warning':'\u26A0','caution':'\u26D4','log out':'\uEE02','signout':'\uEE02','checked':'\u2713','adddate':'\uF070','droplet':'\uD83D\uDCA7','upright':'\u2B08','forward':'\u27A1','up left':'\u2B09','descend':'\u25BE','retweet':'\uF600','cursor':'\uE001','search':'\uD83D\uDD0E','zoomin':'\uE002','attach':'\uD83D\uDCCE','pencil':'\u270E','eraser':'\u2710','locked':'\uD83D\uDD12','secure':'\uD83D\uDD12','unlock':'\uD83D\uDD13','public':'\uD83D\uDD13','target':'\u25CE','tagged':'\uE100','sample':'\uE200','layers':'\uE202','stroke':'\uE241','avatar':'\uD83D\uDC64','locate':'\uE670','volume':'\uD83D\uDD08','camera':'\uD83D\uDCF7','images':'\uD83C\uDF04','photos':'\uD83C\uDF04','videos':'\uD83D\uDCF9','record':'\u25CF','rewind':'\u23EA','repeat':'\uD83D\uDD01','replay':'\u21BA','filter':'\uE9B0','funnel':'\uE9B0','laptop':'\uEA00','tablet':'\uEA01','iphone':'\uD83D\uDCF1','mobile':'\uD83D\uDCF1','upload':'\uEB41','folder':'\uD83D\uDCC1','layout':'\uEDA0','action':'\uEE00','expand':'\u2922','logout':'\uEE02','hyphen':'\u002D','remove':'\u002D','delete':'\u2421','upleft':'\u2B09','ascend':'\u25B4','write':'\u270E','erase':'\u2710','trash':'\uE0D0','heart':'\u2665','zelda':'\uE1A0','phone':'\uD83D\uDCDE','reply':'\u21A9','email':'\u2709','inbox':'\uD83D\uDCE5','users':'\uD83D\uDC65','price':'\uD83D\uDCB2','house':'\u2302','globe':'\uD83C\uDF0E','earth':'\uD83C\uDF0E','world':'\uD83C\uDF0E','music':'\u266B','audio':'\u266B','sound':'\uD83D\uDD08','image':'\uD83C\uDF04','photo':'\uD83C\uDF04','video':'\uD83D\uDCF9','pause':'\uE8A0','eject':'\u23CF','merge':'\uEB81','nodes':'\uEB85','quote':'\u201C','print':'\u2399','share':'\uEE00','visit':'\uEE00','alert':'\u26A0','minus':'\u002D','check':'\u2713','close':'\u2421','clock':'\u23F2','timer':'\u23F1','plane':'\u2708','cloud':'\u2601','flask':'\uF4C0','right':'\u27A1','zoom':'\uE002','view':'\uD83D\uDC40','look':'\uD83D\uDC40','link':'\uD83D\uDD17','move':'\uE070','edit':'\u270E','lock':'\uD83D\uDD12','tags':'\uE100','flag':'\u2691','like':'\uD83D\uDC4D','love':'\u2665','star':'\u22C6','crop':'\uE201','fill':'\uE240','call':'\uD83D\uDCDE','send':'\uE350','mail':'\u2709','chat':'\uD83D\uDCAC','talk':'\uD83D\uDCAC','user':'\uD83D\uDC64','cart':'\uE500','cost':'\uD83D\uDCB2','home':'\u2302','city':'\uD83C\uDFE2','play':'\u25B6','stop':'\u25A0','skip':'\u23ED','undo':'\u21BA','book':'\uD83D\uDCD5','news':'\uD83D\uDCF0','grid':'\uE9A0','rows':'\uE9A1','ipad':'\uEA01','cell':'\uD83D\uDCF1','idea':'\uD83D\uDCA1','fork':'\uEB80','redo':'\u21BB','sync':'\uEB82','wifi':'\uEB84','file':'\uD83D\uDCC4','page':'\uD83D\uDCC4','text':'\uED00','font':'\uED01','list':'\uED50','help':'\u2753','info':'\u2139','exit':'\uEE02','plus':'\u002B','gear':'\u2699','bell':'\uD83D\uDD14','time':'\u23F2','date':'\uD83D\uDCC5','work':'\uD83D\uDCBC','drop':'\uD83D\uDCA7','down':'\u2B07','left':'\u2B05','back':'\u2B05','next':'\u25BB','eye':'\uD83D\uDC40','key':'\uD83D\uDD11','ban':'\uD83D\uDEAB','tag':'\uE100','rss':'\uE310','box':'\uD83D\uDCE6','map':'\uE673','pin':'\uD83D\uDCCD','hdd':'\uE7B0','mic':'\uD83C\uDFA4','fax':'\uD83D\uDCE0','out':'\uEE00','add':'\u002B','cog':'\u2699','up':'\u2B06'}; 20 | 21 | if (typeof ss_icons !== 'object' || typeof ss_icons !== 'object') { 22 | var ss_icons = ss_set; 23 | var ss_keywords = []; 24 | for (var i in ss_set) { ss_keywords.push(i); }; 25 | } else { 26 | for (var i in ss_set) { ss_icons[i] = ss_set[i]; ss_keywords.push(i); } 27 | }; 28 | 29 | if (typeof ss_legacy !== 'function') { 30 | 31 | /* domready.js */ 32 | !function(a,b){typeof module!="undefined"?module.exports=b():typeof define=="function"&&typeof define.amd=="object"?define(b):this[a]=b()}("ss_ready",function(a){function m(a){l=1;while(a=b.shift())a()}var b=[],c,d=!1,e=document,f=e.documentElement,g=f.doScroll,h="DOMContentLoaded",i="addEventListener",j="onreadystatechange",k="readyState",l=/^loade|c/.test(e[k]);return e[i]&&e[i](h,c=function(){e.removeEventListener(h,c,d),m()},d),g&&e.attachEvent(j,c=function(){/^c/.test(e[k])&&(e.detachEvent(j,c),m())}),a=g?function(c){self!=top?l?c():b.push(c):function(){try{f.doScroll("left")}catch(b){return setTimeout(function(){a(c)},50)}c()}()}:function(a){l?a():b.push(a)}}) 33 | 34 | var ss_legacy = function(node) { 35 | 36 | if (!node instanceof Object) return false; 37 | 38 | if (node.length) { 39 | for (var i=0; i 2 | 3 | 4 | logo 5 | Created with Sketch Beta. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "qunit", 3 | "test_page": "tests/index.html?hidepassed", 4 | "launch_in_ci": [ 5 | "PhantomJS" 6 | ], 7 | "launch_in_dev": [ 8 | "PhantomJS", 9 | "Chrome" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "location", 6 | "setTimeout", 7 | "$", 8 | "-Promise", 9 | "define", 10 | "console", 11 | "visit", 12 | "exists", 13 | "fillIn", 14 | "click", 15 | "keyEvent", 16 | "triggerEvent", 17 | "find", 18 | "findWithAssert", 19 | "wait", 20 | "Ember", 21 | "DS", 22 | "andThen", 23 | "currentURL", 24 | "currentPath", 25 | "currentRouteName", 26 | "equal", 27 | "notEqual", 28 | "notStrictEqual", 29 | "test", 30 | "testing", 31 | "async", 32 | "asyncTest", 33 | "testBoth", 34 | "testWithDefault", 35 | "raises", 36 | "throws", 37 | "deepEqual", 38 | "start", 39 | "stop", 40 | "ok", 41 | "strictEqual", 42 | "module", 43 | "expect", 44 | "assert" 45 | ], 46 | "node": false, 47 | "browser": false, 48 | "boss": true, 49 | "curly": false, 50 | "debug": false, 51 | "devel": false, 52 | "eqeqeq": true, 53 | "evil": true, 54 | "forin": false, 55 | "immed": false, 56 | "laxbreak": false, 57 | "newcap": true, 58 | "noarg": true, 59 | "noempty": false, 60 | "nonew": false, 61 | "nomen": false, 62 | "onevar": false, 63 | "plusplus": false, 64 | "regexp": false, 65 | "undef": true, 66 | "sub": true, 67 | "strict": false, 68 | "white": false, 69 | "eqnull": true, 70 | "esnext": true 71 | } 72 | -------------------------------------------------------------------------------- /tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember/resolver'; 2 | import config from '../../config/environment'; 3 | 4 | var resolver = Resolver.create(); 5 | 6 | resolver.namespace = { 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix 9 | }; 10 | 11 | export default resolver; 12 | -------------------------------------------------------------------------------- /tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Application from '../../app'; 3 | import Router from '../../router'; 4 | import config from '../../config/environment'; 5 | 6 | export default function startApp(attrs) { 7 | var application; 8 | 9 | var attributes = Ember.merge({}, config.APP); 10 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; 11 | 12 | Ember.run(function() { 13 | application = Application.create(attributes); 14 | application.setupForTesting(); 15 | application.injectTestHelpers(); 16 | }); 17 | 18 | return application; 19 | } 20 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | PazEmber Tests 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | {{content-for 'test-head'}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for 'head-footer'}} 18 | {{content-for 'test-head-footer'}} 19 | 20 | 21 | 22 | {{content-for 'body'}} 23 | {{content-for 'test-body'}} 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for 'body-footer'}} 31 | {{content-for 'test-body-footer'}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import resolver from './helpers/resolver'; 2 | import { 3 | setResolver 4 | } from 'ember-qunit'; 5 | 6 | setResolver(resolver); 7 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paz-sh/paz-web/21fbb43be813a5931a982a417b6be97147317c8d/tests/unit/.gitkeep -------------------------------------------------------------------------------- /tests/unit/adapters/load-balancer-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleFor } from 'ember-qunit'; 2 | 3 | moduleFor('adapter:load-balancer', 'LoadBalancerAdapter', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['serializer:foo'] 6 | }); 7 | 8 | // Replace this with your real tests. 9 | test('it exists', function() { 10 | var adapter = this.subject(); 11 | ok(adapter); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/components/health-buttons-test.js: -------------------------------------------------------------------------------- 1 | import { 2 | moduleForComponent, 3 | test 4 | } from 'ember-qunit'; 5 | 6 | moduleForComponent('health-buttons', { 7 | // specify the other units that are required for this test 8 | // needs: ['component:foo', 'helper:bar'] 9 | }); 10 | 11 | test('it renders', function(assert) { 12 | assert.expect(2); 13 | 14 | // creates the component instance 15 | var component = this.subject(); 16 | assert.equal(component._state, 'preRender'); 17 | 18 | // renders the component to the page 19 | this.render(); 20 | assert.equal(component._state, 'inDOM'); 21 | }); 22 | -------------------------------------------------------------------------------- /tests/unit/components/health-state-test.js: -------------------------------------------------------------------------------- 1 | import { 2 | moduleForComponent, 3 | test 4 | } from 'ember-qunit'; 5 | 6 | moduleForComponent('health-state', { 7 | // specify the other units that are required for this test 8 | // needs: ['component:foo', 'helper:bar'] 9 | }); 10 | 11 | test('it renders', function(assert) { 12 | assert.expect(2); 13 | 14 | // creates the component instance 15 | var component = this.subject(); 16 | assert.equal(component._state, 'preRender'); 17 | 18 | // renders the component to the page 19 | this.render(); 20 | assert.equal(component._state, 'inDOM'); 21 | }); 22 | -------------------------------------------------------------------------------- /tests/unit/components/quick-chart-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleForComponent } from 'ember-qunit'; 2 | 3 | moduleForComponent('quick-chart', 'QuickChartComponent', { 4 | // specify the other units that are required for this test 5 | // needs: ['component:foo', 'helper:bar'] 6 | }); 7 | 8 | test('it renders', function() { 9 | expect(2); 10 | 11 | // creates the component instance 12 | var component = this.subject(); 13 | equal(component.state, 'preRender'); 14 | 15 | // appends the component to the page 16 | this.append(); 17 | equal(component.state, 'inDOM'); 18 | }); 19 | -------------------------------------------------------------------------------- /tests/unit/components/service-config-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleForComponent } from 'ember-qunit'; 2 | 3 | moduleForComponent('service-config', 'ServiceConfigComponent', { 4 | // specify the other units that are required for this test 5 | // needs: ['component:foo', 'helper:bar'] 6 | }); 7 | 8 | test('it renders', function() { 9 | expect(2); 10 | 11 | // creates the component instance 12 | var component = this.subject(); 13 | equal(component.state, 'preRender'); 14 | 15 | // appends the component to the page 16 | this.append(); 17 | equal(component.state, 'inDOM'); 18 | }); 19 | -------------------------------------------------------------------------------- /tests/unit/components/service-edit-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleForComponent } from 'ember-qunit'; 2 | 3 | moduleForComponent('service-edit', 'ServiceEditComponent', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function() { 9 | var component = this.subject(); 10 | ok(component); 11 | }); 12 | 13 | test('add ports', function() { 14 | var component = this.subject(); 15 | var port = {container: '', host: ''}; 16 | var config = Ember.Object.create({ports:[]}); 17 | 18 | component.send('addPorts', config); 19 | deepEqual(config.ports, [port], 'add port'); 20 | 21 | component.send('addPorts', config); 22 | deepEqual(config.ports, [port, port], 'add another port'); 23 | }); 24 | 25 | test('remove port', function() { 26 | var component = this.subject(); 27 | var port1 = {container: '1', host: '1'}; 28 | var port2 = {container: '2', host: '2'}; 29 | var config = Ember.Object.create({ports:[port1, port2]}); 30 | 31 | component.send('removePort', config, port1); 32 | deepEqual(config.ports, [port2], 'remove port1'); 33 | 34 | component.send('removePort', config, port2); 35 | deepEqual(config.ports, [], 'remove port2'); 36 | }); 37 | 38 | test('add env key', function() { 39 | var component = this.subject(); 40 | var env = {key: '', value: ''}; 41 | var config = Ember.Object.create({env:[]}); 42 | 43 | component.send('addEnvKey', config); 44 | deepEqual(config.env, [env], 'add env key'); 45 | }); 46 | 47 | test('remove env', function() { 48 | var component = this.subject(); 49 | var env1 = {key: '', value: ''}; 50 | var env2 = {key: '', value: ''}; 51 | var config = Ember.Object.create({env:[env1, env2]}); 52 | 53 | component.send('removeEnvKey', config, env1); 54 | deepEqual(config.env, [env2], 'remove env1'); 55 | 56 | component.send('removeEnvKey', config, env2); 57 | deepEqual(config.env, [], 'remove env2'); 58 | }); 59 | 60 | 61 | test('add volume', function() { 62 | var component = this.subject(); 63 | var volume = {key: '', value: ''}; 64 | var config = Ember.Object.create({volume:[]}); 65 | 66 | component.send('addVolumeKey', config); 67 | deepEqual(config.volume, [volume], 'add volume'); 68 | }); 69 | 70 | test('remove volume', function() { 71 | var component = this.subject(); 72 | var volume1 = {key: '', value: ''}; 73 | var volume2 = {key: '', value: ''}; 74 | var config = Ember.Object.create({volume:[volume1, volume2]}); 75 | 76 | component.send('removeVolumeKey', config, volume1); 77 | deepEqual(config.volume, [volume2], 'remove volume1'); 78 | 79 | component.send('removeVolumeKey', config, volume2); 80 | deepEqual(config.volume, [], 'remove volume2'); 81 | }); 82 | -------------------------------------------------------------------------------- /tests/unit/controllers/application-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleFor } from 'ember-qunit'; 2 | 3 | moduleFor('controller:application', 'ApplicationController', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | // Replace this with your real tests. 9 | test('it exists', function() { 10 | var controller = this.subject(); 11 | ok(controller); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/controllers/host-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleFor } from 'ember-qunit'; 2 | 3 | moduleFor('controller:host', 'HostController', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | // Replace this with your real tests. 9 | test('it exists', function() { 10 | var controller = this.subject(); 11 | ok(controller); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/controllers/services-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleFor } from 'ember-qunit'; 2 | 3 | moduleFor('controller:services', 'ServicesController', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | // Replace this with your real tests. 9 | test('it exists', function() { 10 | var controller = this.subject(); 11 | ok(controller); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/controllers/unit-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleFor } from 'ember-qunit'; 2 | 3 | moduleFor('controller:unit', 'UnitController', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | // Replace this with your real tests. 9 | test('it exists', function() { 10 | var controller = this.subject(); 11 | ok(controller); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/helpers/validators-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleFor } from 'ember-qunit'; 2 | import { validatePorts, validateEnvKeys, validateVolumeKeys } from 'paz-ember/helpers/validators'; 3 | 4 | test('env validation', function() { 5 | ok(validateEnvKeys, 'validateEnvKeys exists'); 6 | var envCreator = function(key, value) { 7 | return [{key: key, value: value}]; 8 | }; 9 | equal(validateEnvKeys(envCreator('foo', 'bar')), true, 'valid env'); 10 | equal(validateEnvKeys(envCreator('1foo', 'bar')), false, 'begin with a number'); 11 | }); 12 | 13 | test('volume validation', function() { 14 | ok(validateVolumeKeys, 'validateVolumeKeys exists'); 15 | var volumeCreator = function(key, value) { 16 | return [{key: key, value: value}]; 17 | }; 18 | equal(validateVolumeKeys(volumeCreator('/foo', '/bar')), true, 'valid volume'); 19 | equal(validateVolumeKeys(volumeCreator('1/foo', '/bar')), false, 'begin with a number'); 20 | }); 21 | 22 | test('port validation', function() { 23 | ok(validatePorts, 'validatePorts exists'); 24 | var portCreator = function(container, host) { 25 | return [{container: container, host: host}]; 26 | }; 27 | equal(validatePorts(portCreator(9000, 80)), true, 'valid port'); 28 | equal(validatePorts(portCreator('foo', 'bar')), false, 'not a number'); 29 | equal(validatePorts(portCreator('80', '65536')), false, 'not in range'); 30 | }); 31 | -------------------------------------------------------------------------------- /tests/unit/models/config-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleForModel } from 'ember-qunit'; 2 | 3 | moduleForModel('config', 'Config Model', { 4 | // Specify the other units that are required for this test. 5 | needs: [] 6 | }); 7 | 8 | test('it exists', function() { 9 | var model = this.subject(); 10 | ok(model); 11 | }); 12 | 13 | test('config model', function() { 14 | var data = { 15 | publicFacing: true, 16 | ports: [{container: 9000, host: 80}], 17 | env: {foo: 'bar'}, 18 | numInstances: 3 19 | }; 20 | var config = this.subject(data); 21 | ok(config instanceof DS.Model); 22 | equal(config.get('publicFacing'), data.publicFacing, 'should set publicFacing'); 23 | equal(config.get('env'), data.env, 'should set env'); 24 | equal(config.get('ports'), data.ports, 'should set ports'); 25 | equal(config.get('numInstances'), data.numInstances, 'should set numInstances'); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/unit/models/host-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleForModel } from 'ember-qunit'; 2 | 3 | moduleForModel('host', 'Host', { 4 | // Specify the other units that are required for this test. 5 | needs: [] 6 | }); 7 | 8 | test('it exists', function() { 9 | var model = this.subject(); 10 | // var store = this.store(); 11 | ok(model); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/models/load-balancer-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleForModel } from 'ember-qunit'; 2 | 3 | moduleForModel('load-balancer', 'LoadBalancer', { 4 | // Specify the other units that are required for this test. 5 | needs: [] 6 | }); 7 | 8 | test('it exists', function() { 9 | var model = this.subject(); 10 | // var store = this.store(); 11 | ok(model); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/models/service-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleForModel } from 'ember-qunit'; 2 | 3 | moduleForModel('service', 'Service', { 4 | // Specify the other units that are required for this test. 5 | needs: [] 6 | }); 7 | 8 | test('it exists', function() { 9 | var model = this.subject(); 10 | // var store = this.store(); 11 | ok(model); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/models/unit-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleForModel } from 'ember-qunit'; 2 | 3 | moduleForModel('unit', 'Unit', { 4 | // Specify the other units that are required for this test. 5 | needs: [] 6 | }); 7 | 8 | test('it exists', function() { 9 | var model = this.subject(); 10 | // var store = this.store(); 11 | ok(model); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/routes/dashboard-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleFor } from 'ember-qunit'; 2 | 3 | moduleFor('route:dashboard', 'DashboardRoute', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function() { 9 | var route = this.subject(); 10 | ok(route); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/unit/routes/dashboard/index-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleFor } from 'ember-qunit'; 2 | 3 | moduleFor('route:dashboard/index', 'DashboardIndexRoute', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function() { 9 | var route = this.subject(); 10 | ok(route); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/unit/routes/hosts-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleFor } from 'ember-qunit'; 2 | 3 | moduleFor('route:hosts', 'HostsRoute', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function() { 9 | var route = this.subject(); 10 | ok(route); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/unit/routes/index-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleFor } from 'ember-qunit'; 2 | 3 | moduleFor('route:index', 'IndexRoute', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function() { 9 | var route = this.subject(); 10 | ok(route); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/unit/routes/monitoring-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleFor } from 'ember-qunit'; 2 | 3 | moduleFor('route:monitoring', 'MonitoringRoute', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function() { 9 | var route = this.subject(); 10 | ok(route); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/unit/routes/services-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleFor } from 'ember-qunit'; 2 | 3 | moduleFor('route:services', 'ServicesRoute', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function() { 9 | var route = this.subject(); 10 | ok(route); 11 | 12 | equal(find('h1').text(), 'Services'); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/unit/routes/services/new-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleFor } from 'ember-qunit'; 2 | 3 | moduleFor('route:services/new', 'ServicesNewRoute', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function() { 9 | var route = this.subject(); 10 | ok(route); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/unit/routes/styleguide-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleFor } from 'ember-qunit'; 2 | 3 | moduleFor('route:styleguide', 'StyleguideRoute', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function() { 9 | var route = this.subject(); 10 | ok(route); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/unit/routes/units-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleFor } from 'ember-qunit'; 2 | 3 | moduleFor('route:units', 'UnitsRoute', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function() { 9 | var route = this.subject(); 10 | ok(route); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/unit/serializers/config-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleFor } from 'ember-qunit'; 2 | 3 | moduleFor('serializer:config', 'Config Serializer', { 4 | // Specify the other units that are required for this test. 5 | needs: [] 6 | }); 7 | 8 | test('it exists', function() { 9 | var serializer = this.subject(); 10 | ok(serializer); 11 | }); 12 | 13 | test('transform env object into env array', function() { 14 | var serializer = this.subject(); 15 | var payload = { 16 | doc: { 17 | env: {foo: 'bar'} 18 | } 19 | }; 20 | var normalizedPayload = serializer.transformEnvObject(payload); 21 | var envArray = [{key: 'foo', value: 'bar'}]; 22 | deepEqual(normalizedPayload.env, envArray, 'transform env object'); 23 | }); 24 | 25 | test('transform volume object into volume array', function() { 26 | var serializer = this.subject(); 27 | var payload = { 28 | doc: { 29 | volume: {foo: 'bar'} 30 | } 31 | }; 32 | var normalizedPayload = serializer.transformVolumeObject(payload); 33 | var volumeArray = [{key: 'foo', value: 'bar'}]; 34 | deepEqual(normalizedPayload.volume, volumeArray, 'transform volume object'); 35 | }); 36 | --------------------------------------------------------------------------------