Break the host settings into a generic settings page

* Componentize host settings
* Add vm and catalog settings
* Add catalog filter on the catalog page
This commit is contained in:
Westly Wright 2015-12-08 14:56:46 -07:00
parent dc3e9fa905
commit 53f3a98648
21 changed files with 430 additions and 213 deletions

View File

@ -1,96 +0,0 @@
import Ember from 'ember';
function isPublic(name) {
if ( (name||'').trim().replace(/^https?:\/\//,'').match(/^(localhost|192\.168\.|172\.1[6789]\.|172\.2[0123456789]\.|172\.3[01]\.|10\.)/) )
{
return false;
}
return true;
}
export default Ember.Controller.extend({
queryParams: ['backToAdd'],
backToAdd: false,
thisPage: '', // Set by route
errors: null,
customRadio: null,
custom: Ember.computed.equal('customRadio', 'yes'),
editing: true,
saving: false,
customValue: '',
looksPublic: function() {
return isPublic(this.get('activeValue'));
}.property('activeValue'),
activeValue: function() {
if ( this.get('custom') )
{
return this.get('customValue').trim();
}
else
{
return this.get('thisPage');
}
}.property('custom','customValue','thisPage'),
actions: {
save: function() {
var model = this.get('model');
var value = this.get('activeValue');
if ( !value )
{
this.set('errors', ['Please provide a DNS name or IP address.']);
return;
}
// If your really want to set it to nothing...
if ( value === '__NONE__' )
{
value = ' ';
}
model.set('value', value);
this.set('saving', true);
model.save().then(() => {
if ( this.get('backToAdd') )
{
this.transitionToRoute('hosts.new');
}
else
{
this.send('goToPrevious');
}
}).catch((err) => {
this.set('errors', [err]);
}).finally(() => {
this.set('saving', false);
});
},
cancel: function() {
this.send('goToPrevious');
}
},
customValueDidChange: function() {
var val = this.get('customValue')||''.trim();
var idx = val.indexOf('/', 8); // 8 is enough for "https://"
if ( idx !== -1 )
{
// Trim paths off of the URL
this.set('customValue', val.substr(0,idx));
return; // We'll be back...
}
if ( val )
{
this.set('customRadio','yes');
}
}.observes('customValue'),
});

View File

@ -1,56 +0,0 @@
import Ember from 'ember';
import C from 'ui/utils/constants';
import { denormalizeName } from 'ui/services/settings';
export default Ember.Route.extend({
endpoint: Ember.inject.service(),
model: function() {
return this.get('store').find('setting', denormalizeName(C.SETTING.API_HOST));
},
setupController: function(controller, model) {
var thisPage = window.location.origin;
controller.set('thisPage', thisPage);
var endpoint = this.get('endpoint.origin');
var isDifferent = endpoint !== thisPage;
if ( endpoint !== thisPage )
{
controller.set('customValue', endpoint);
}
controller.set('model', model);
controller.set('error', null);
var value = model.get('value');
if ( value )
{
if ( value === thisPage )
{
controller.set('customValue', '');
controller.set('customRadio', 'no');
}
else
{
controller.set('customValue', value);
controller.set('customRadio', 'yes');
}
}
else if ( isDifferent )
{
controller.set('customValue', endpoint);
controller.set('customRadio', 'yes');
}
else
{
controller.set('customValue', '');
controller.set('customRadio', 'no');
}
},
resetController: function (controller, isExiting/*, transition*/) {
if (isExiting)
{
controller.set('backToAdd', false);
}
}
});

View File

@ -1,37 +0,0 @@
<section class="header">
<ol class="breadcrumb right-divider">
<li class="active">Host Registration</li>
</ol>
</section>
<section>
{{top-errors errors=errors}}
<h5>We need to know a little about how your environment is set up before you can register hosts.</h5>
</section>
<section class="well">
<h5>What base URL should hosts use to connect to the Rancher API?</h5>
<div style="margin-top: 15px;">
<label>{{radio-button selection=customRadio value="no"}}&nbsp;This site's address:&nbsp;&nbsp;</label>
<code class="form-control-static">{{thisPage}}</code>
</div>
<div style="margin-top: 10px;">
<label>{{radio-button selection=customRadio value="yes"}}&nbsp;Something else:&nbsp;&nbsp;</label>
{{input type="text" class="form-control" value=customValue placeholder="e.g. rancher.mydomain.com" safeStyle="width: calc(100% - 150px); display: inline-block; min-width: 300px;"}}
<h6 style="margin-top: 10px;">Don't include <code>/v1</code> or any other path, but if you are doing <a href="http://docs.rancher.com/rancher/installing-rancher/installing-server/basic-ssl-config/" target="_blank">SSL termination</a> in front of Rancher, be sure to use <code>https://</code>.</h6>
</div>
{{#unless looksPublic}}
<div class="alert alert-info" style="margin-top: 30px;">
<i class="icon icon-info" style="font-size: 35px; float: left;"></i>
<p style="margin-left: 50px;">
Are you sure all the hosts you will create will be able to reach <code>{{activeValue}}</code> ?<br/>It looks like a private IP or local network.
</p>
</div>
{{/unless}}
</section>
<div class="footer-actions">
{{partial "save-cancel"}}
</div>

View File

@ -1,3 +0,0 @@
import Ember from 'ember';
export default Ember.View.extend({
});

View File

@ -0,0 +1,77 @@
import Ember from 'ember';
import C from 'ui/utils/constants';
export default Ember.Controller.extend({
settings: Ember.inject.service(),
queryParams: ['backToAdd'],
backToAdd: false,
errors: null,
editing: true,
saving: false,
actions: {
setActiveCatalog: function(value) {
var out = [];
Object.keys(value).forEach((item) => {
if (item) {
out.push(`${item}=${value[item]}`);
}
});
this.get('model').set('catalog', out.join(','));
},
save: function() {
var model = this.get('model');
var value = this.get('model.host');
var propsOut = {};
if (!value) {
this.set('errors', ['Please provide a DNS name or IP address.']);
return;
}
Object.keys(model).forEach((item) => {
switch (item) {
case 'host':
propsOut[C.SETTING.API_HOST] = model[item];
break;
case 'catalog':
propsOut[C.SETTING.CATALOG_URL] = model[item];
break;
case 'vm':
propsOut[C.SETTING.VM_ENABLED] = model[item];
break;
default:
break;
}
});
this.set('saving', true);
this.get('settings').setProperties(propsOut).one('settingsPromisesResolved', () => {
this.set('saving', false);
if (this.get('backToAdd')) {
this.transitionToRoute('hosts.new');
} else {
this.send('goToPrevious');
}
});
},
cancel: function() {
this.send('goToPrevious');
}
},
});

View File

@ -0,0 +1,29 @@
import Ember from 'ember';
import C from 'ui/utils/constants';
export default Ember.Route.extend({
endpoint: Ember.inject.service(),
settings: Ember.inject.service(),
model: function() {
return this.get('store').findAll('setting').then((/* response */) => {
return Ember.Object.create({
host: this.get('settings').get(C.SETTING.API_HOST),
catalog: this.get('settings').get(C.SETTING.CATALOG_URL),
vm: this.get('settings').get(C.SETTING.VM_ENABLED),
});
});
},
setupController: function(controller, model) {
/*not sure we need this anymore except maybe to set error to null?*/
controller.set('model', model);
controller.set('error', null);
},
resetController: function(controller, isExiting /*, transition*/ ) {
if (isExiting) {
controller.set('backToAdd', false);
}
}
});

View File

@ -0,0 +1,49 @@
<section class="header">
<ol class="breadcrumb right-divider">
<li class="active">Settings</li>
</ol>
</section>
<section>
{{top-errors errors=errors}}
</section>
<section class="well">
{{host-settings host=model.host sendActiveValue=(action (mut model.host))}}
</section>
{{#unless backToAdd}}
<section class="well">
{{catalog-settings catalog=model.catalog keymapChanged="setActiveCatalog"}}
</section>
<section class="well">
<h4>Virtual Machine</h4>
<hr>
<h5>Here you can enable access to virtual machines.</h5>
<div class="row r-mt20">
<div class="col-md-2">
<label>Virtual Machines Access</label>
</div>
<div class="col-md-10">
<div class="radio small">
<label>
{{radio-button selection=model.vm value=true}}
Enable
</label>
</div>
<div class="radio small">
<label>
{{radio-button selection=model.vm value=false}}
Disable
</label>
</div>
</div>
</div>
</section>
{{/unless}}
<div class="footer-actions">
{{partial "save-cancel"}}
</div>

View File

@ -1,6 +1,7 @@
import Ember from 'ember';
export default Ember.Controller.extend({
queryParams: ['category'],
queryParams: ['category', 'catalogid'],
category: 'all',
catalogid: 'library'
});

View File

@ -4,6 +4,7 @@ export default Ember.Controller.extend({
application: Ember.inject.controller(),
catalogController: Ember.inject.controller('applications-tab.catalog'),
category: Ember.computed.alias('catalogController.category'),
selectedCatalog: Ember.computed.alias('catalogController.catalogid'),
search: '',
actions: {
@ -18,6 +19,8 @@ export default Ember.Controller.extend({
categories: Ember.computed.alias('model.categories'),
catalogIds: Ember.computed.alias('model.catalogIds'),
arrangedContent: function() {
var search = this.get('search').toUpperCase();
var result = [];

View File

@ -3,6 +3,19 @@
<li class="active">Catalog</li>
</ol>
<div class="btn-group pull-right left-divider">
<button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Catalog: <span class="text-capitalize">{{selectedCatalog}}</span> <i class="icon icon-chevron-down"></i></button>
<ul class="dropdown-menu dropdown-menu-right">
{{#each catalogIds as |catalog|}}
<li>
{{#link-to "applications-tab.catalog" (query-params catalogid=catalog)}}
<span class="text-capitalize">{{catalog}}</span>
{{/link-to}}
</li>
{{/each}}
</ul>
</div>
<div class="btn-group pull-right left-divider">
<button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Category: <span class="text-capitalize">{{category}}</span> <i class="icon icon-chevron-down"></i></button>
<ul class="dropdown-menu dropdown-menu-right">

View File

@ -1,6 +1,13 @@
import Ember from 'ember';
import { addQueryParams } from 'ui/utils/util';
function uniqKeys(data, name) {
var out = data.map((item) => item[name]);
out = out.uniq().sort((a,b) => a.localeCompare(b, 'en', {sensitivity: 'base'}));
out.unshift('all');
return out;
}
export default Ember.Route.extend({
settings: Ember.inject.service(),
@ -9,19 +16,37 @@ export default Ember.Route.extend({
queryParams: {
category: {
refreshModel: true
},
catalogid: {
refreshModel: true
}
},
catalogIds: null,
deactivate() {
// Clear the cache when leaving the route so that it will be reloaded when you come back.
this.set('cache', null);
},
beforeModel: function() {
return this.get('store').request({url: `${this.get('app.catalogEndpoint')}/catalogs`}).then((response) => {
var catalogs = uniqKeys(response, 'id');
this.set('catalogIds', catalogs);
});
},
model(params) {
var cache = this.get('cache');
if ( cache )
// If the catalogIds dont match we need to go get the other catalog from the store since we do not cache all catalogs
if ( cache && cache.catalogId === params.catalogid)
{
return filter(cache, params.category);
return filter(cache, params.category, this.get('catalogIds'));
}
if (params.catalogid) {
this.controllerFor('applications-tab.catalog.index').set('selectedCatalog', params.catalogid);
}
var version = this.get('settings.rancherVersion');
@ -29,6 +54,10 @@ export default Ember.Route.extend({
'category_ne': 'system',
};
if (params.catalogid !== 'all') {
qp['catalogId'] = params.catalogid;
}
if ( version )
{
qp['minimumRancherVersion_lte'] = version;
@ -37,14 +66,17 @@ export default Ember.Route.extend({
var url = addQueryParams(this.get('app.catalogEndpoint')+'/templates', qp);
return this.get('store').request({url: url}).then((response) => {
response.catalogId = params.catalogid;
this.set('cache', response);
return filter(response, params.category);
return filter(response, params.category, this.get('catalogIds'));
});
function filter(data, category) {
function filter(data, category, catalogIds) {
data = data.sortBy('name');
var out = Ember.Object.create({
categories: categories(data),
categories: uniqKeys(data, 'category'),
catalogIds: catalogIds,
});
@ -56,12 +88,5 @@ export default Ember.Route.extend({
return out;
}
function categories(data) {
var out = data.map((item) => item.category);
out = out.uniq().sort((a,b) => a.localeCompare(b, 'en', {sensitivity: 'base'}));
out.unshift('all');
return out;
}
},
});

View File

@ -0,0 +1,45 @@
import Ember from 'ember';
export default Ember.Component.extend({
keymap: null,
didInitAttrs: function() {
var catalogs = this.get('catalog');
var catalogsSplit = catalogs.split(',');
var keymapOut = {};
if (catalogsSplit.length > 1) {
// its not default so we need to parse it
catalogsSplit.forEach((item) => {
var split = item.split('=');
keymapOut[split[0]] = split[1];
});
this.set('keymap', keymapOut);
} else {
var catalogItem = catalogsSplit[0];
if (catalogItem.indexOf('=') >= 0) {
var split = catalogItem.split('=');
var obj = {};
obj[split[0]] = split[1];
this.set('keymap', obj);
} else {
this.set('keymap', {'library': catalogsSplit[0]});
}
}
},
keymapObserver: function() {
this.sendAction('keymapChanged', this.get('keymap.asMap'));
}.observes('keymap.asMap'),
});

View File

@ -0,0 +1,12 @@
<h4>Catalog</h4>
<hr>
<h5>You can add and remove catalogs here. When adding ensure you give your catalog a name and git url. The correct format of git urls can be found <a href="https://git-scm.com/docs/git-clone#_git_urls_a_id_urls_a" target="_blank">here</a>.</h5>
<div class="">
{{form-key-value
nameLabel="Catalog URL"
initialMap=keymap
keyLabel="Catalog Name"
valueLabel="Catalog URL"
changed=(action (mut keymap))
}}
</div>

View File

@ -1,5 +1,7 @@
import Ember from 'ember';
// @@TODO@@ - Dec 8, 2015 - need to add callback to this service.
export default Ember.Component.extend({
// Inputs
initialMap: null,
@ -10,6 +12,7 @@ export default Ember.Component.extend({
valuePlaceholder: 'Value',
ary: null,
asMap: null,
actions: {
add() {
@ -92,14 +95,15 @@ export default Ember.Component.extend({
this.set('ary', ary);
},
asMap: function() {
asMapObserver: function() {
var out = {};
this.get('ary').forEach((row) => {
out[row.get('key')] = row.get('value');
out[row.get('key').trim()] = row.get('value').trim();
});
return out;
}.property('ary.@each.{key,value}'),
this.set('asMap', out);
}.observes('ary.@each.{key,value}'),
changed: function() {
this.sendAction('changed', this.get('asMap'));

View File

@ -0,0 +1,112 @@
import Ember from 'ember';
function isPublic(name) {
if ( (name||'').trim().replace(/^https?:\/\//,'').match(/^(localhost|192\.168\.|172\.1[6789]\.|172\.2[0123456789]\.|172\.3[01]\.|10\.)/) )
{
return false;
}
return true;
}
export default Ember.Component.extend({
endpoint: Ember.inject.service(),
customRadio: null,
customValue: '',
thisPage: null,
actions: {
sendActiveValue: function(value) {
this.sendAction('sendActiveValue', value);
}
},
didInitAttrs: function() {
var thisPage = window.location.origin;
var endpoint = this.get('endpoint.origin');
var isDifferent = endpoint !== thisPage;
this.set('thisPage', thisPage);
if ( endpoint !== thisPage )
{
this.set('customValue', endpoint);
}
var value = this.get('host');
if ( value )
{
if ( value === thisPage )
{
this.set('customValue', '');
this.set('customRadio', 'no');
}
else
{
this.set('customValue', value);
this.set('customRadio', 'yes');
}
}
else if ( isDifferent )
{
// for some reason the activeValueObserver doesnt recognize setting this value unless
// we run with ember.run.next
Ember.run.next(() => {
this.set('customValue', endpoint);
this.set('customRadio', 'yes');
});
}
else
{
this.set('customValue', '');
this.set('customRadio', 'no');
}
},
looksPublic: function() {
return isPublic(this.get('activeValue'));
}.property('activeValue'),
parseActiveValue: function(value) {
var out;
if ( this.get('customRadio') === 'yes' )
{
out = value.trim();
}
else
{
out = this.get('thisPage');
}
return out;
},
activeValueObserver: function() {
this.send('sendActiveValue', this.parseActiveValue(this.get('customValue')));
}.observes('customRadio','customValue','thisPage'),
activeValue: function() {
return this.parseActiveValue(this.get('customValue'));
}.property('customRadio','customValue','thisPage'),
customValueDidChange: function() {
var val = this.get('customValue')||''.trim();
var idx = val.indexOf('/', 8); // 8 is enough for "https://"
if ( idx !== -1 )
{
// Trim paths off of the URL
this.set('customValue', val.substr(0,idx));
return; // We'll be back...
}
if ( val )
{
this.set('customRadio','yes');
}
}.observes('customValue'),
});

View File

@ -0,0 +1,23 @@
<h4>Host</h4>
<hr>
<h5>We need to know a little about how your environment is set up before you can register hosts. What base URL should hosts use to connect to the Rancher API?</h5>
<div style="margin-top: 15px;">
<label>{{radio-button selection=customRadio value="no"}}&nbsp;This site's address:&nbsp;&nbsp;</label>
<code class="form-control-static">{{thisPage}}</code>
</div>
<div style="margin-top: 10px;">
<label>{{radio-button selection=customRadio value="yes"}}&nbsp;Something else:&nbsp;&nbsp;</label>
{{input type="text" class="form-control" value=customValue placeholder="e.g. rancher.mydomain.com" safeStyle="width: calc(100% - 150px); display: inline-block; min-width: 300px;"}}
<h6 style="margin-top: 10px;">Don't include <code>/v1</code> or any other path, but if you are doing <a href="http://docs.rancher.com/rancher/installing-rancher/installing-server/basic-ssl-config/" target="_blank">SSL termination</a> in front of Rancher, be sure to use <code>https://</code>.</h6>
</div>
{{#unless looksPublic}}
<div class="alert alert-info" style="margin-top: 30px;">
<i class="icon icon-info" style="font-size: 35px; float: left;"></i>
<p style="margin-left: 50px;">
Are you sure all the hosts you will create will be able to reach <code>{{activeValue}}</code> ?<br/>It looks like a private IP or local network.
</p>
</div>
{{/unless}}

View File

@ -135,7 +135,7 @@
{{#if isAdminTab}}
{{#link-to "admin-tab.accounts"}}<i class="icon icon-users"></i>Accounts{{/link-to}}
{{#link-to "admin-tab.auth"}}<i class="icon icon-key"></i>Access Control{{/link-to}}
{{#link-to "admin-tab.host"}}<i class="icon icon-network"></i>Host Registration{{/link-to}}
{{#link-to "admin-tab.settings"}}<i class="icon icon-network"></i>Settings{{/link-to}}
{{#link-to "admin-tab.processes"}}<i class="icon icon-processes"></i>Processes{{/link-to}}
{{/if}}
</nav>

View File

@ -16,7 +16,7 @@ export default Ember.Route.extend({
}
else
{
this.transitionTo('admin-tab.host', {queryParams: {backToAdd: true}});
this.transitionTo('admin-tab.settings', {queryParams: {backToAdd: true}});
}
});
}

View File

@ -35,7 +35,7 @@ Router.map(function() {
this.route('localauth', {path: 'local'});
});
this.route('host');
this.route('settings');
this.route('accounts', {path: '/accounts'}, function() {
this.route('index', {path: '/'});

View File

@ -9,9 +9,11 @@ export function denormalizeName(str) {
return str.replace(C.SETTING.DOT_CHAR,'.').toLowerCase();
}
export default Ember.Service.extend({
export default Ember.Service.extend(Ember.Evented, {
all: null,
promiseCount: 0,
init() {
this._super();
this.set('all', this.get('store').allUnremoved('activesetting'));
@ -56,14 +58,27 @@ export default Ember.Service.extend({
});
}
this.incrementProperty('promiseCount');
obj.set('value', value+''); // Values are all strings in settings.
obj.save().then(() => {
this.notifyPropertyChange(normalizeName(key));
}).catch((err) => {
this.trigger('gotError', err);
}).finally(() => {
this.decrementProperty('promiseCount');
});
return value;
},
promiseCountObserver: function() {
if (this.get('promiseCount') <= 0) {
this.trigger('settingsPromisesResolved');
}
}.observes('promiseCount'),
findByName(name) {
return this.get('asMap')[normalizeName(name)];
},

View File

@ -71,6 +71,7 @@ var C = {
VERSION_MACHINE: 'docker$machine$version',
VERSION_GMS: 'go$machine$service$version',
API_HOST: 'api$host',
CATALOG_URL: 'catalog$url',
VM_ENABLED: 'vm$enabled',
},