You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by al...@apache.org on 2015/03/14 21:57:01 UTC
[3/3] ambari git commit: AMBARI-10063. CapSched View: Add Support for
Node labels and versions (alexantonenko)
AMBARI-10063. CapSched View: Add Support for Node labels and versions (alexantonenko)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/95103a90
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/95103a90
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/95103a90
Branch: refs/heads/trunk
Commit: 95103a905df36745b8e9f01768a612193626c221
Parents: a5f99f4
Author: Alex Antonenko <hi...@gmail.com>
Authored: Sat Mar 14 22:05:21 2015 +0200
Committer: Alex Antonenko <hi...@gmail.com>
Committed: Sat Mar 14 22:56:52 2015 +0200
----------------------------------------------------------------------
.../capacityscheduler/ConfigurationService.java | 115 +-
.../src/main/resources/ui/app/adapters.js | 439 +++----
.../src/main/resources/ui/app/assets/index.html | 2 -
.../resources/ui/app/components/capacityBar.js | 22 +-
.../ui/app/components/capacityInput.js | 45 +-
.../ui/app/components/confirmDelete.js | 6 +-
.../ui/app/components/dropdownConfirmation.js | 11 +-
.../resources/ui/app/components/escapeAcl.js | 11 +-
.../resources/ui/app/components/pathInput.js | 6 +-
.../ui/app/components/queueListItem.js | 67 +-
.../resources/ui/app/components/radioButton.js | 6 +-
.../ui/app/components/totalCapacity.js | 411 ++++++-
.../main/resources/ui/app/controllers/queue.js | 284 +++--
.../main/resources/ui/app/controllers/queues.js | 287 ++++-
.../src/main/resources/ui/app/initialize.js | 6 +
.../src/main/resources/ui/app/models/queue.js | 148 ++-
.../src/main/resources/ui/app/router.js | 34 +-
.../src/main/resources/ui/app/serializers.js | 285 +++++
.../src/main/resources/ui/app/store.js | 164 +++
.../resources/ui/app/styles/application.less | 1069 +++++++++++-------
.../src/main/resources/ui/app/templates.js | 2 +
.../ui/app/templates/capacityEditForm.hbs | 87 +-
.../ui/app/templates/components/capacityBar.hbs | 4 +-
.../components/dropdownConfirmation.hbs | 29 +
.../ui/app/templates/components/pathInput.hbs | 4 +-
.../app/templates/components/queueContainer.hbs | 67 ++
.../app/templates/components/queueListItem.hbs | 42 +-
.../app/templates/components/totalCapacity.hbs | 67 +-
.../main/resources/ui/app/templates/queue.hbs | 136 ++-
.../main/resources/ui/app/templates/queues.hbs | 47 +-
.../ui/app/templates/schedulerPanel.hbs | 84 +-
.../src/main/resources/ui/app/views/queues.js | 3 +-
.../src/main/resources/ui/bower.json | 14 +-
.../src/main/resources/ui/config.coffee | 14 +-
34 files changed, 2869 insertions(+), 1149 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/ConfigurationService.java
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/ConfigurationService.java b/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/ConfigurationService.java
index e6d81d0..f703f65 100644
--- a/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/ConfigurationService.java
+++ b/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/ConfigurationService.java
@@ -114,9 +114,12 @@ public class ConfigurationService {
// ================================================================================
private final String versionTagUrl = "%s?fields=Clusters/desired_configs/capacity-scheduler";
- private final String configurationUrl = "%%s/configurations?type=capacity-scheduler&tag=%s";
+ private final String configurationUrl = "%s/configurations?type=capacity-scheduler";
+ private final String configurationUrlByTag = "%%s/configurations?type=capacity-scheduler&tag=%s";
private final String rmHostUrl = "%s/services/YARN/components/RESOURCEMANAGER?fields=host_components/host_name";
+ private final String rmGetNodeLabelUrl = "http://%s:8088/ws/v1/cluster/get-node-labels";
+
// ================================================================================
// Privilege Reading
// ================================================================================
@@ -131,7 +134,7 @@ public class ConfigurationService {
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
- public Response readConfiguration() {
+ public Response readLatestConfiguration() {
Response response = null;
try {
validateViewConfiguration();
@@ -149,6 +152,55 @@ public class ConfigurationService {
}
/**
+ * Gets capacity scheduler configuration by all tags.
+ *
+ * @return scheduler configuration
+ */
+ @GET
+ @Path("all")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response readAllConfigurations() {
+ Response response = null;
+ try {
+ validateViewConfiguration();
+
+ String url = String.format(configurationUrl, baseUrl);
+ JSONObject configurations = proxy.request(url).get().asJSON();
+ response = Response.ok(configurations).build();
+ } catch (WebApplicationException ex) {
+ throw ex;
+ } catch (Exception ex) {
+ throw new ServiceFormattedException(ex.getMessage(), ex);
+ }
+
+ return response;
+ }
+
+ /**
+ * Gets capacity scheduler configuration by specific tag.
+ *
+ * @return scheduler configuration
+ */
+ @GET
+ @Path("byTag/{tag}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response readConfigurationByTag(@PathParam("tag") String tag) {
+ Response response = null;
+ try {
+ validateViewConfiguration();
+
+ JSONObject configurations = getConfigurationFromAmbari(tag);
+ response = Response.ok(configurations).build();
+ } catch (WebApplicationException ex) {
+ throw ex;
+ } catch (Exception ex) {
+ throw new ServiceFormattedException(ex.getMessage(), ex);
+ }
+
+ return response;
+ }
+
+ /**
* Gets the privilege for this user.
*
* @return scheduler configuration
@@ -173,6 +225,31 @@ public class ConfigurationService {
}
/**
+ * Gets node labels from RM
+ *
+ * @return node labels
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/nodeLabels")
+ public Response getNodeLabels() {
+ Response response;
+
+ try {
+ String nodeLabels = proxy.request(String.format(rmGetNodeLabelUrl, getRMHost())).get().asString();
+
+ response = Response.ok(nodeLabels).build();
+ } catch (WebApplicationException ex) {
+ throw ex;
+ } catch (Exception ex) {
+ throw new ServiceFormattedException(ex.getMessage(), ex);
+ }
+
+ return response;
+ }
+
+
+ /**
* Checks if the user is an operator.
*
* @return if <code>true</code>, the user is an operator; otherwise <code>false</code>
@@ -183,7 +260,7 @@ public class ConfigurationService {
// first check if the user is an CLUSTER.OPERATOR
String url = String.format(clusterOperatorPrivilegeUrl, baseUrl, context.getUsername());
JSONObject json = proxy.request(url).get().asJSON();
-
+
if (json == null || json.size() <= 0) {
// user is not a CLUSTER.OPERATOR but might be an AMBARI.ADMIN
url = String.format(ambariAdminPrivilegeUrl, serverUrl, context.getUsername());
@@ -215,7 +292,7 @@ public class ConfigurationService {
}
private JSONObject getConfigurationFromAmbari(String versionTag) {
- String urlTemplate = String.format(configurationUrl, versionTag);
+ String urlTemplate = String.format(configurationUrlByTag, versionTag);
String url = String.format(urlTemplate, baseUrl);
return proxy.request(url).get().asJSON();
}
@@ -267,6 +344,7 @@ public class ConfigurationService {
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response writeConfiguration(JSONObject request) {
+ JSONObject response;
try {
validateViewConfiguration();
@@ -274,9 +352,9 @@ public class ConfigurationService {
return Response.status(401).build();
}
- proxy.request(baseUrl).
- setData(makeConfigUpdateData(request)).
- put();
+ response = proxy.request(baseUrl).
+ setData(request).
+ put().asJSON();
} catch (WebApplicationException ex) {
throw ex;
@@ -284,7 +362,7 @@ public class ConfigurationService {
throw new ServiceFormattedException(ex.getMessage(), ex);
}
- return readConfiguration();
+ return Response.ok(response).build();
}
/**
@@ -302,8 +380,6 @@ public class ConfigurationService {
if (isOperator() == false) {
return Response.status(401).build();
}
-
- writeConfiguration(request);
String rmHost = getRMHost();
JSONObject data = (JSONObject) JSONValue.parse(String.format(refreshRMRequestData, rmHost));
@@ -316,7 +392,7 @@ public class ConfigurationService {
} catch (Exception ex) {
throw new ServiceFormattedException(ex.getMessage(), ex);
}
- return readConfiguration();
+ return readLatestConfiguration();
}
/**
@@ -335,8 +411,6 @@ public class ConfigurationService {
return Response.status(401).build();
}
- writeConfiguration(request);
-
String rmHost = getRMHost();
JSONObject data = (JSONObject) JSONValue.parse(String.format(restartRMRequestData, rmHost, rmHost));
proxy.request(baseUrl + "/requests/").
@@ -348,7 +422,7 @@ public class ConfigurationService {
} catch (Exception ex) {
throw new ServiceFormattedException(ex.getMessage(), ex);
}
- return readConfiguration();
+ return readLatestConfiguration();
}
private String getRMHost() {
@@ -367,17 +441,4 @@ public class ConfigurationService {
return rmHost;
}
- private JSONObject makeConfigUpdateData(JSONObject request) {
- JSONObject desiredConfigs = (JSONObject) request.clone();
- desiredConfigs.put("type", "capacity-scheduler");
- desiredConfigs.put("tag", "version" + String.valueOf(System.currentTimeMillis()));
-
- JSONObject clusters = new JSONObject();
- clusters.put("desired_configs", desiredConfigs);
-
- JSONObject data = new JSONObject();
- data.put("Clusters", clusters);
- return data;
- }
-
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/adapters.js
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/adapters.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/adapters.js
index 3c47a90..07d6fbd 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/adapters.js
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/adapters.js
@@ -18,13 +18,36 @@
var App = require('app');
+/**
+ * Gets the view backend URI
+ *
+ * @return view backend URI
+ */
+function _getCapacitySchedulerViewUri(adapter) {
+ if (App.testMode)
+ return "/data";
+
+ var parts = window.location.pathname.match(/[^\/]*/g).filterBy('').removeAt(0),
+ view = parts[0],
+ version = parts[1],
+ instance = parts[2];
+ if (parts.length == 2) { // version is not present
+ instance = parts[1];
+ version = '';
+ }
+
+ return '/' + [adapter.namespace,'views',view,'versions',version,'instances',instance,'resources','scheduler','configuration'].join('/');
+}
+
+
App.QueueAdapter = DS.Adapter.extend({
+ defaultSerializer:'queue',
PREFIX: "yarn.scheduler.capacity",
+ namespace: 'api/v1',
queues: [],
- clusterName: '',
- tag: '',
+
createRecord: function(store, type, record) {
- var data = this.serialize(record, { includeId: true });
+ var data = record.toJSON({ includeId: true });
return new Ember.RSVP.Promise(function(resolve, reject) {
return store.filter('queue',function (q) {
return q.id === record.id;
@@ -34,50 +57,68 @@ App.QueueAdapter = DS.Adapter.extend({
message = "Field can not be empty";
} else if (queues.get('length') > 1) {
message = "Queue already exists";
- };
+ }
if (message) {
var error = new DS.InvalidError({path:[message]});
store.recordWasInvalid(record, error.errors);
return;
- };
-
- Ember.run(record, resolve, {'queue':data})
+ }
+ Ember.run(record, resolve, {'queue':data,'label':[]});
});
});
},
+ deleteRecord:function (store, type, record) {
+ return new Ember.RSVP.Promise(function(resolve, reject) {
+ Ember.run(null, resolve, {'queue':record.serialize({ includeId: true , clone: true })});
+ });
+ },
+
saveMark:'',
updateRecord:function (store,type,record) {
- var adapter = this,
- saveMark = this.get('saveMark'),
- uri = _getCapacitySchedulerUri(adapter.clusterName, adapter.tag),
+ var adapter = this;
+ var saveMark = this.get('saveMark'),
+ uri = _getCapacitySchedulerViewUri(this),
serializer = store.serializerFor('queue'),
- props = serializer.serializeConfig(record);
+ props = serializer.serializeConfig(record),
+ new_tag = 'version' + Math.floor(+moment()),
+ postSaveUri,data;
if (saveMark) {
- uri = [uri,saveMark].join('/');
+ postSaveUri = [_getCapacitySchedulerViewUri(this),saveMark].join('/');
this.set('saveMark','');
- };
+ }
+
+ data = JSON.stringify({'Clusters':
+ {'desired_config':
+ [{
+ 'type': 'capacity-scheduler',
+ 'tag': new_tag,
+ 'service_config_version_note': props.service_config_version_note,
+ 'properties': props.properties
+ }]
+ }
+ });
return new Ember.RSVP.Promise(function(resolve, reject) {
- adapter.ajax(uri,'PUT',{data:props}).then(function(data) {
- Ember.run(null, resolve, data);
+ adapter.ajax(uri,'PUT',{contentType:'application/json; charset=utf-8',data:data}).then(function(data) {
+ store.setProperties({'current_tag':new_tag,'tag':new_tag});
+ Ember.run(null, resolve, data.resources.objectAt(0).configurations.objectAt(0).configs);
}, function(jqXHR) {
jqXHR.then = null;
Ember.run(null, reject, jqXHR);
+ }).then(function () {
+ if (postSaveUri) {
+ adapter.postSave(postSaveUri);
+ }
});
});
},
-
- /**
- * Finds queue by id.
- *
- */
- findQueue: function(id) {
- var result = $.grep(App.Adapter.queues, function(e){ return e.get('id') == id; });
- return result[0];
+
+ postSave:function(uri){
+ this.ajax(uri,'PUT',{contentType:'application/json; charset=utf-8',data:JSON.stringify({save:true})});
},
/**
@@ -85,9 +126,18 @@ App.QueueAdapter = DS.Adapter.extend({
*
*/
find: function(store, type, id) {
- return store.findAll('queue').then(function (queues) {
- return {"queue":queues.findBy('id',id).toJSON({includeId:true})};
- });
+ id = id.toLowerCase();
+ var record = store.getById(type,id);
+ var key = type.typeKey;
+ if (record) {
+ return new Ember.RSVP.Promise(function(resolve, reject) {
+ resolve({key:record.toJSON({includeId:true})});
+ });
+ } else {
+ return store.findAll('queue').then(function (queues) {
+ resolve({key:store.getById(type,id).toJSON({includeId:true})});
+ });
+ }
},
/**
@@ -96,23 +146,62 @@ App.QueueAdapter = DS.Adapter.extend({
*/
findAll: function(store, type) {
var adapter = this;
- var uri = _getCapacitySchedulerUri(adapter.clusterName, adapter.tag);
+ var uri = _getCapacitySchedulerViewUri(this);
if (App.testMode)
uri = uri + "/scheduler-configuration.json";
+
return new Ember.RSVP.Promise(function(resolve, reject) {
- adapter.ajax(uri).then(function(data) {
+ adapter.ajax(uri,'GET').then(function(data) {
+ var config = data.items.objectAt(0);
+ if (!store.get('isInitialized')) {
+ store.set('current_tag',config.tag);
+ }
+ store.setProperties({'clusterName':config.Config.cluster_name,'tag':config.tag});
+ Ember.run(null, resolve, data.items.objectAt(0).properties);
+ }, function(jqXHR) {
+ jqXHR.then = null;
+ Ember.run(null, reject, jqXHR);
+ });
+ });
+ },
+
+ findAllTagged: function(store, type) {
+ var adapter = this,
+ uri = [_getCapacitySchedulerViewUri(this),'byTag',store.get('tag')].join('/');
+
+ return new Ember.RSVP.Promise(function(resolve, reject) {
+ adapter.ajax(uri,'GET').then(function(data) {
Ember.run(null, resolve, data);
}, function(jqXHR) {
jqXHR.then = null;
Ember.run(null, reject, jqXHR);
});
});
+ },
+
+ getNodeLabels:function () {
+ var uri = [_getCapacitySchedulerViewUri(this),'nodeLabels'].join('/');
+ return new Ember.RSVP.Promise(function(resolve, reject) {
+ this.ajax(uri,'GET').then(function(data) {
+ var parsedData = JSON.parse(data), labels;
+ if (parsedData && Em.isArray(parsedData.nodeLabels)) {
+ labels = parsedData.nodeLabels;
+ } else {
+ labels = (parsedData && parsedData.nodeLabels)?[parsedData.nodeLabels]:[];
+ }
+ Ember.run(null, resolve, labels.map(function (label) {
+ return {name:label};
+ }));
+ }, function(jqXHR) {
+ jqXHR.then = null;
+ Ember.run(null, reject, jqXHR);
+ });
+ }.bind(this));
},
getPrivilege:function () {
- var uri = _getCapacitySchedulerUri(this.clusterName, this.tag);
- uri = [uri,'privilege'].join('/');
+ var uri = [_getCapacitySchedulerViewUri(this),'privilege'].join('/');
if (App.testMode)
uri = uri + ".json";
return new Ember.RSVP.Promise(function(resolve, reject) {
@@ -124,7 +213,7 @@ App.QueueAdapter = DS.Adapter.extend({
});
}.bind(this));
},
-
+
ajax: function(url, type, hash) {
var adapter = this;
@@ -150,10 +239,6 @@ App.QueueAdapter = DS.Adapter.extend({
hash.dataType = 'json';
hash.context = this;
- if (hash.data && type !== 'GET') {
- hash.contentType = 'application/json; charset=utf-8';
- hash.data = JSON.stringify(hash.data);
- }
hash.beforeSend = function (xhr) {
xhr.setRequestHeader('X-Requested-By', 'view-capacity-scheduler');
};
@@ -177,277 +262,23 @@ App.SchedulerAdapter = App.QueueAdapter.extend({
}
});
-App.ApplicationStore = DS.Store.extend({
-
- adapter: App.QueueAdapter,
-
- configNote:'',
-
- markForRefresh:function (mark) {
- this.set('defaultAdapter.saveMark','saveAndRefresh');
- },
-
- markForRestart:function (mark) {
- this.set('defaultAdapter.saveMark','saveAndRestart');
- },
-
- flushPendingSave: function() {
- var pending = this._pendingSave.slice(),
- newPending = [[]],scheduler;
-
- if (pending.length == 1) {
- this._super();
- return;
- };
-
- pending.forEach(function (tuple) {
- var record = tuple[0], resolver = tuple[1],
- operation;
- newPending[0].push(record)
- newPending[1] = resolver;
- });
-
- this._pendingSave = [newPending];
- this._super();
- },
- didSaveRecord: function(record, data) {
- if (Em.isArray(record)) {
- for (var i = 0; i < record.length; i++) {
- this._super(record[i],data.findBy('id',record[i].id));
- };
- } else {
- this._super(record, data);
- };
- },
- recordWasError: function(record) {
- if (Em.isArray(record)) {
- for (var i = 0; i < record.length; i++) {
- record[i].adapterDidError();
- };
- } else {
- record.adapterDidError();
- }
- },
- checkOperator:function () {
- return this.get('defaultAdapter').getPrivilege();
- }
-});
-
-App.ApplicationSerializer = DS.RESTSerializer.extend({
-
- PREFIX:"yarn.scheduler.capacity",
-
- serializeConfig:function (records) {
- var config = {},
- note = this.get('store.configNote'),
- prefix = this.PREFIX;
- records.forEach(function (record) {
- if (record.id == 'scheduler') {
- config[prefix + ".maximum-am-resource-percent"] = record.get('maximum_am_resource_percent')/100; // convert back to decimal
- config[prefix + ".maximum-applications"] = record.get('maximum_applications');
- config[prefix + ".node-locality-delay"] = record.get('node_locality_delay');
- config[prefix + ".resource-calculator"] = record.get('resource_calculator');
- } else {
- config[prefix + "." + record.get('path') + ".unfunded.capacity"] = record.get('unfunded_capacity');
- config[prefix + "." + record.get('path') + ".acl_administer_queue"] = record.get('acl_administer_queue');
- config[prefix + "." + record.get('path') + ".acl_submit_applications"] = record.get('acl_submit_applications');
- config[prefix + "." + record.get('path') + ".minimum-user-limit-percent"] = record.get('minimum_user_limit_percent');
- config[prefix + "." + record.get('path') + ".maximum-capacity"] = record.get('maximum_capacity');
- config[prefix + "." + record.get('path') + ".user-limit-factor"] = record.get('user_limit_factor');
- config[prefix + "." + record.get('path') + ".state"] = record.get('state');
- config[prefix + "." + record.get('path') + ".capacity"] = record.get('capacity');
- config[prefix + "." + record.get('path') + ".queues"] = record.get('queueNames')||'';
-
- // do not set property if not set
- var ma = record.get('maximum_applications')||'';
- if (ma) {
- config[prefix + "." + record.get('path') + ".maximum-applications"] = ma;
- }
+App.TagAdapter = App.QueueAdapter.extend({
+ /**
+ * Finds all tags.
+ *
+ */
+ findAll: function(store, type) {
+ var adapter = this;
+ var uri = [_getCapacitySchedulerViewUri(this),'all'].join('/');
- // do not set property if not set
- var marp = record.get('maximum_am_resource_percent')||'';
- if (marp) {
- marp = marp/100; // convert back to decimal
- config[prefix + "." + record.get('path') + ".maximum-am-resource-percent"] = marp;
- }
- };
+ return new Ember.RSVP.Promise(function(resolve, reject) {
+ adapter.ajax(uri ,'GET').then(function(data) {
+ Ember.run(null, resolve, data);
+ }, function(jqXHR) {
+ jqXHR.then = null;
+ Ember.run(null, reject, jqXHR);
+ });
});
-
- for (var i in config) {
- if (config[i] === null || config[i] === undefined) {
- delete config[i];
- }
- }
-
- this.set('store.configNote','');
-
- return {properties : config, service_config_version_note: note};
-
- },
- normalizeConfig:function (store, payload) {
- var queues = [];
- var props = payload.items[0].properties;
-
- var scheduler = [{
- id:'scheduler',
- maximum_am_resource_percent:props[this.PREFIX + ".maximum-am-resource-percent"]*100, // convert to percent
- maximum_applications:props[this.PREFIX + ".maximum-applications"],
- node_locality_delay:props[this.PREFIX + ".node-locality-delay"],
- resource_calculator:props[this.PREFIX + ".resource-calculator"]
- }];
- queues = _recurseQueues(null, "root", 0, props, queues, store);
-
- return {'queue':queues,'scheduler':scheduler};
- },
-
- extractFindAll: function(store, type, payload){
- var queues = [];
- var props = payload.items[0].properties;
-
- var scheduler = {
- id:'scheduler',
- maximum_am_resource_percent:props[this.PREFIX + ".maximum-am-resource-percent"]*100, // convert to percent
- maximum_applications:props[this.PREFIX + ".maximum-applications"],
- node_locality_delay:props[this.PREFIX + ".node-locality-delay"],
- resource_calculator:props[this.PREFIX + ".resource-calculator"]
- };
- queues = _recurseQueues(null, "root", 0, props, queues, store);
-
- var config = this.normalizeConfig(store, payload);
-
- return this.extractArray(store, type, config);
- },
-
- extractUpdateRecord:function (store, type, payload, id, requestType) {
- var queues = [];
- var props = payload.items[0].properties;
-
- var scheduler = {
- id:'scheduler',
- maximum_am_resource_percent:props[this.PREFIX + ".maximum-am-resource-percent"]*100, // convert to percent
- maximum_applications:props[this.PREFIX + ".maximum-applications"],
- node_locality_delay:props[this.PREFIX + ".node-locality-delay"],
- resource_calculator:props[this.PREFIX + ".resource-calculator"]
- };
- queues = _recurseQueues(null, "root", 0, props, queues, store);
-
- var config = this.normalizeConfig(store, payload);
-
- return this.extractConfig(store, App.Queue, {'queue':queues,'scheduler':[scheduler]});
- },
-
- extractConfig: function(store, primaryType, payload) {
- payload = this.normalizePayload(payload);
-
- var primaryTypeName = primaryType.typeKey,
- primaryArray = [],
- scheduler,queues;
-
- for (var prop in payload) {
- var typeKey = prop,
- forcedSecondary = false;
-
- if (prop.charAt(0) === '_') {
- forcedSecondary = true;
- typeKey = prop.substr(1);
- }
-
- var typeName = this.typeForRoot(typeKey),
- type = store.modelFor(typeName),
- typeSerializer = store.serializerFor(type),
- isPrimary = (!forcedSecondary && (type.typeKey === primaryTypeName));
-
- var normalizedArray = Ember.ArrayPolyfills.map.call(payload[prop], function(hash) {
- return typeSerializer.normalize(type, hash, prop);
- }, this);
-
- if (typeKey == App.Scheduler.typeKey) {
- scheduler = normalizedArray;
- } else {
- queues = normalizedArray;
- }
- }
-
- return scheduler.concat(queues);
- },
-
- extractQueue: function(data, props) {
- var q = { name: data.name, parentPath: data.parentPath, depth: data.depth };
- var prefix = this.PREFIX;
-
- if (q.parentPath == null || q.parentPath.length == 0){
- q.path = q.name;
- } else {
- q.path = q.parentPath + '.' + q.name;
- }
- q.id = q.path.dasherize();
-
- q.unfunded_capacity = props[prefix + "." + q.path + ".unfunded.capacity"];
-
- q.state = props[prefix + "." + q.path + ".state"];
- q.acl_administer_queue = props[prefix + "." + q.path + ".acl_administer_queue"];
- q.acl_submit_applications = props[prefix + "." + q.path + ".acl_submit_applications"];
-
- q.capacity = props[prefix + "." + q.path + ".capacity"];
- q.maximum_capacity = props[prefix + "." + q.path + ".maximum-capacity"];
-
- q.user_limit_factor = props[prefix + "." + q.path + ".user-limit-factor"];
- q.minimum_user_limit_percent = props[prefix + "." + q.path + ".minimum-user-limit-percent"];
- q.maximum_applications = props[prefix + "." + q.path + ".maximum-applications"];
- q.maximum_am_resource_percent = props[prefix + "." + q.path + ".maximum-am-resource-percent"]
-
- if (q.maximum_am_resource_percent)
- q.maximum_am_resource_percent = q.maximum_am_resource_percent*100; // convert to percent
-
- q.queueNames = props[prefix + "." + q.path + ".queues"];
-
- return q;
}
});
-/**
- * Recursively builds the list of queues.
- *
- */
-function _recurseQueues(parentQueue, queueName, depth, props, queues, store) {
- var serializer = store.serializerFor('queue');
- var prefix = serializer.PREFIX;
- var parentPath = '';
- if (parentQueue != null) {
- parentPath = parentQueue.path;
- prefix += ".";
- }
-
- var queue = serializer.extractQueue({ name: queueName, parentPath: parentPath, depth: depth}, props);
- queues.push(queue);
-
- var queueProp = prefix + parentPath + "." + queueName + ".queues";
- if (props[queueProp]) {
- var qs = props[queueProp].split(',');
- for (var i=0; i < qs.length; i++) {
- queues = _recurseQueues(queue, qs[i], depth+1, props, queues, store)
- }
- }
-
- return queues;
-}
-
-/**
- * Gets the cluster name URI based on test mode
- *
- * @return cluster name URI
- */
-function _getCapacitySchedulerUri() {
- if (App.testMode)
- return "/data";
-
- var parts = window.location.pathname.match(/\/[^\/]*/g);
- var view = parts[1];
- var version = '/versions' + parts[2];
- var instance = parts[3];
- if (parts.length == 4) { // version is not present
- instance = parts[2];
- version = '';
- }
- return '/api/v1/views' + view + version + '/instances' + instance+'/resources/scheduler/configuration';
-}
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/assets/index.html
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/assets/index.html b/contrib/views/capacity-scheduler/src/main/resources/ui/app/assets/index.html
index ce0972c..2cdfe39 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/assets/index.html
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/assets/index.html
@@ -34,13 +34,11 @@
</head>
<body>
<script type="text/x-handlebars">
- <div class="wrap">
<div class="container-fluid">
<div class="row">
{{outlet}}
</div>
</div>
- </div>
</script>
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/capacityBar.js
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/capacityBar.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/capacityBar.js
index 22d511b..6f35e28 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/capacityBar.js
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/capacityBar.js
@@ -20,22 +20,36 @@ var App = require('app');
App.CapacityBarComponent = Em.Component.extend({
layoutName:'components/capacityBar',
+
classNames:['capacity-bar'],
+
+ capacityValue:null,
+
+ maxCapacityValue:null,
+
+ warn:false,
+
+ pattern:'width: %@%;',
+
mark:function () {
if (this.$().parent().hasClass('active')) {
Ember.run.next(this, 'addInClass');
- };
+ }
}.on('didInsertElement'),
+
addInClass:function () {
this.$().children().addClass('in');
},
+
capacityWidth: function() {
- return (Number(this.get('capacityValue'))<=100)?('width: '+this.get('capacityValue')+'%'):'width: 100%';
+ return this.get('pattern').fmt(( +this.get('capacityValue') <= 100 ) ? +this.get('capacityValue') : 100);
}.property('capacityValue'),
+
maxCapacityWidth: function() {
- var val = Number(this.get('maxCapacityValue')) - Number(this.get('capacityValue'));
- return (val<=100)?('width: '+val+'%'):'width: 100%';
+ var val = this.get('maxCapacityValue') - this.get('capacityValue');
+ return this.get('pattern').fmt((val<=100)?val:100);
}.property('maxCapacityValue','capacityValue'),
+
showMaxCapacity:function () {
return this.get('maxCapacityValue') > this.get('capacityValue');
}.property('maxCapacityValue','capacityValue')
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/capacityInput.js
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/capacityInput.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/capacityInput.js
index 0bf4b85..8ac766d 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/capacityInput.js
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/capacityInput.js
@@ -24,8 +24,7 @@ App.InputRangeComponent = Em.TextField.extend({
action: 'mouseUp',
mouseUp: function () {
- var value = this.get('value');
- this.sendAction('action', value);
+ this.sendAction('action', this.get('value'));
}
});
@@ -42,35 +41,29 @@ App.IntInputComponent = Ember.TextField.extend({
classNames:['form-control'],
maxVal:null,
intVal:function () {
- var val = parseFloat(this.get('value'))||0;
+ var val = (!Em.isBlank(this.get('value'))) ? parseFloat(this.get('value')) : null;
var maxVal = this.get('maxVal');
- val = (maxVal && maxVal < val)?maxVal:val;
- this.set('value', val);
+ this.set('value', (maxVal && maxVal < val)?maxVal:val);
}.on('change'),
checkNumber:function () {
- var val = this.get('value'),
- num = Number(val),
- str = String(val);
- if (typeof val !== "number" && !isNaN(num) && str == num.toString()) {
- this.set('value', Number(val));
- };
+ this.set('value', (!Em.isBlank(this.get('value')) && !isNaN(parseFloat(this.get('value')))) ? parseFloat(this.get('value')): null);
}.observes('value')
});
App.CapacityInputComponent = App.IntInputComponent.extend({
-
+
totalCapacity:null,
queue:null,
keyDown: function(evt) {
var newChar, val = this.get('value')||0;
val = val.toString();
-
- if ((evt.keyCode > 64 && evt.keyCode < 91) ||
- (evt.keyCode > 185 && evt.keyCode < 193) ||
+
+ if ((evt.keyCode > 64 && evt.keyCode < 91) ||
+ (evt.keyCode > 185 && evt.keyCode < 193) ||
(evt.keyCode > 218 && evt.keyCode < 223)) {
return false;
- };
+ }
if (evt.keyCode > 95 && evt.keyCode < 106) {
newChar = (evt.keyCode - 96).toString();
@@ -80,25 +73,31 @@ App.CapacityInputComponent = App.IntInputComponent.extend({
if (newChar.match(/[0-9]/)) {
val = val.substring(0, evt.target.selectionStart) + newChar + val.substring(evt.target.selectionEnd);
- };
+ }
- return parseFloat(val)<=100;
- },
+ return parseFloat(val) <= 100;
+ }
});
App.MaxCapacityInputComponent = App.CapacityInputComponent.extend({
isInvalid:false,
invalid:function (c,o) {
- var queue = this.get('queue'),
- max_capacity = Number(queue.get('maximum_capacity')),
- capacity = Number(queue.get('capacity'));
+ var queue = this.get('queue'), max_capacity, capacity;
+
+ if (queue.get('maximum_capacity') === null) return;
+
+ max_capacity = +queue.get('maximum_capacity');
+ capacity = +queue.get('capacity');
+
if (o == 'queue.capacity' && max_capacity < capacity) {
return queue.set('maximum_capacity',capacity);
- };
+ }
+
if (max_capacity < capacity && queue.get('isDirty')) {
queue.get('errors').add('maximum_capacity', 'Maximum must be equal or greater than capacity');
} else {
queue.get('errors').remove('maximum_capacity');
}
+
}.observes('queue.maximum_capacity','queue.capacity')
});
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/confirmDelete.js
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/confirmDelete.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/confirmDelete.js
index 8b61ca3..5ada051 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/confirmDelete.js
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/confirmDelete.js
@@ -36,16 +36,18 @@ App.ConfirmDeleteComponent = Em.Component.extend(App.ClickElsewhereMixin,{
tagName:'a',
tooltip:function () {
var element = this.$();
+
if (this.get('confirm')) {
element.tooltip({
placement:'left',
title:'Click again to confirm'
}).tooltip('show');
} else {
- element.tooltip('destroy')
+ element.tooltip('destroy');
}
+
}.observes('confirm'),
- click:function (e) {
+ click:function () {
this.send('delete');
},
didChange:function () {
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/dropdownConfirmation.js
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/dropdownConfirmation.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/dropdownConfirmation.js
index 105d960..d86e3a0 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/dropdownConfirmation.js
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/dropdownConfirmation.js
@@ -20,6 +20,7 @@ var App = require('app');
App.DropdownConfirmationComponent = Em.Component.extend(App.ClickElsewhereMixin,{
+ layoutName:'components/dropdownConfirmation',
tagName:'li',
restartConfirming:false,
actions:{
@@ -36,20 +37,20 @@ App.DropdownConfirmationComponent = Em.Component.extend(App.ClickElsewhereMixin,
this.$().parents('.dropdown-menu').parent().removeClass('open');
},
dropdownHideControl:function () {
- var _this = this;
this.$().parents('.dropdown-menu').parent().on(
"hide.bs.dropdown", function() {
- return !_this.get('restartConfirming');
- });
+ return !this.get('restartConfirming');
+ }.bind(this));
}.on('didInsertElement'),
button:Em.Component.extend({
tagName:'a',
- click:function (e) {
+ click:function () {
this.triggerAction({
action: 'showRestartConfirmation',
target: this.get('parentView'),
actionContext: this.get('context')
});
}
- })
+ }),
+ needRestart:false
});
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/escapeAcl.js
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/escapeAcl.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/escapeAcl.js
index b5a3e07..125b037 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/escapeAcl.js
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/escapeAcl.js
@@ -19,7 +19,7 @@
Ember.Handlebars.helper('escapeACL', function(value) {
var output = '';
-
+
value = value || '';
if (value.trim() == '') {
@@ -28,21 +28,22 @@ Ember.Handlebars.helper('escapeACL', function(value) {
output = '<label class="label label-success"> <i class="fa fa-asterisk fa-fw"></i> Anyone</label>';
} else {
var ug = value.split(' ');
- var users = ug[0].split(',')||[];
+ var users = ug[0].split(',')||[];
var groups = (ug.length == 2)?ug[1].split(',')||[]:[];
- output += ' <span class="users"> '
+ output += ' <span class="users"> ';
users.forEach(function (user) {
output += (user)?'<span class="label label-primary"><i class="fa fa-user fa-fw"></i> '+ user +'</span> ':'';
});
- output += ' </span> <span class="groups"> '
+ output += ' </span> <span class="groups"> ';
groups.forEach(function (group) {
output += (group)?'<span class="label label-primary"><i class="fa fa-users fa-fw"></i> '+ group +'</span> ':'';
});
- output += ' </span> '
+
+ output += ' </span> ';
}
return new Ember.Handlebars.SafeString(output);
});
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/pathInput.js
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/pathInput.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/pathInput.js
index 92bfb31..fc8f77c 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/pathInput.js
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/pathInput.js
@@ -39,7 +39,7 @@ App.PathInputComponent = Em.Component.extend({
cancel:function () {
this.set('activeFlag',false);
}
- },
+ },
queues:[],
activeFlag:false,
pathMap:Em.computed.mapBy('queues','path'),
@@ -60,7 +60,7 @@ App.PathInputComponent = Em.Component.extend({
this.$().typeahead({
source: this.get('pathSource'),
matcher: function (item) {
- return ~item.toLowerCase().indexOf(this.query.toLowerCase())
+ return ~item.toLowerCase().indexOf(this.query.toLowerCase());
},
minLength:2,
items:100,
@@ -82,4 +82,4 @@ App.PathInputComponent = Em.Component.extend({
this.$().tooltip('destroy');
}
})
-});
\ No newline at end of file
+});
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/queueListItem.js
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/queueListItem.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/queueListItem.js
index c9bf813..fc52eb9 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/queueListItem.js
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/queueListItem.js
@@ -18,7 +18,7 @@
var App = require('app');
-App.RecurceQueuesComponent = Ember.View.extend({
+App.RecurceQueuesComponent = Em.View.extend({
templateName: "components/queueListItem",
depth:0,
parent:'',
@@ -40,3 +40,68 @@ App.RecurceQueuesComponent = Ember.View.extend({
}
});
+
+App.DiffTooltipComponent = Em.Component.extend({
+ classNames:'fa fa-fw fa-lg blue fa-pencil'.w(),
+ tagName:'i',
+ queue:null,
+ initTooltip:function () {
+ var queue = this.get('queue');
+ this.$().tooltip({
+ title:function () {
+ var caption = '',
+ fmtString = '<span>%@: %@ -> %@</span>\n',
+ emptyValue = '<small><em>not set</em></small>',
+ changes = queue.changedAttributes(),
+ idsToNames = function (l) {
+ return l.split('.').get('lastObject');
+ },
+ formatChangedAttributes = function (prefix,item) {
+ // don't show this to user.
+ if (item == '_accessAllLabels') return;
+
+ var oldV = this[item].objectAt(0),
+ newV = this[item].objectAt(1);
+
+ caption += fmtString.fmt(
+ [prefix,item].compact().join('.'),
+ (oldV != null && '\'%@\''.fmt(oldV)) || emptyValue,
+ (newV != null && '\'%@\''.fmt(newV)) || emptyValue
+ );
+ },
+ initialLabels,
+ currentLabels,
+ isAllChanged,
+ oldV,
+ newV;
+
+ Em.keys(changes).forEach(Em.run.bind(changes,formatChangedAttributes,null));
+
+ if (queue.constructor.typeKey === 'queue') {
+ //cpmpare labels
+ isAllChanged = changes.hasOwnProperty('_accessAllLabels');
+ initialLabels = queue.get('initialLabels').sort();
+ currentLabels = queue.get('labels').mapBy('id').sort();
+
+ if (queue.get('isLabelsDirty') || isAllChanged) {
+
+ oldV = ((isAllChanged && changes._accessAllLabels.objectAt(0)) || (!isAllChanged && queue.get('_accessAllLabels')))?'*':initialLabels.map(idsToNames).join(',') || emptyValue;
+ newV = ((isAllChanged && changes._accessAllLabels.objectAt(1)) || (!isAllChanged && queue.get('_accessAllLabels')))?'*':currentLabels.map(idsToNames).join(',') || emptyValue;
+
+ caption += fmtString.fmt('accessible-node-labels', oldV, newV);
+ }
+
+ queue.get('labels').forEach(function (label) {
+ var labelsChanges = label.changedAttributes(),
+ prefix = ['accessible-node-labels',label.get('name')].join('.');
+ Em.keys(labelsChanges).forEach(Em.run.bind(labelsChanges,formatChangedAttributes,prefix));
+ });
+ }
+
+ return caption;
+ },
+ html:true,
+ placement:'bottom'
+ });
+ }.on('didInsertElement')
+});
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/radioButton.js
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/radioButton.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/radioButton.js
index 30fd531..8be8919 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/radioButton.js
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/radioButton.js
@@ -23,7 +23,7 @@ App.RadioButtonInputComponent = Ember.View.extend({
type : "radio",
attributeBindings : [ "type", "value", "checked:checked" ],
click : function() {
- this.set("selection", this.get('value'))
+ this.set("selection", this.get('value'));
}
});
@@ -35,12 +35,12 @@ App.RadioButtonComponent = Em.Component.extend({
//arguments
selection:null,
label:null,
- value:null,
+ value:null,
click : function() {
this.set("selection", this.get('value'))
},
isActive : function() {
- return this.get("value") == this.get("selection");
+ return this.get("value") == this.get("selection");
}.property("selection"),
layout:Em.Handlebars.compile('{{label}} {{radio-button-input selection=selection value=value checked=isActive}}')
});
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/totalCapacity.js
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/totalCapacity.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/totalCapacity.js
index 806cf3b..d4fc80b 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/totalCapacity.js
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/totalCapacity.js
@@ -21,55 +21,149 @@ var App = require('app');
App.TotalCapacityComponent = Ember.Component.extend({
layoutName:'components/totalCapacity',
+ // TAKEN VALUES
+
+ /**
+ * Speaks for itself.
+ * @type {App.Queue}
+ */
+ currentQueue:null,
+
+ /**
+ * All queues in store.
+ * @type {DS.RecordArray}
+ */
+ allQueues:[],
+
+ /**
+ * All queues in store sorted by App.QueuesController
+ * @type {Array}
+ */
+ allQueuesArranged:[],
+
+ /**
+ * User admin status.
+ * @type {Boolean}
+ */
+ isOperator:false,
+
+ /**
+ * Target for rollbackProp action.
+ * @type {String}
+ */
+ rollbackProp:'',
+
+ // ACTIONS
+
actions:{
- toggleEdit:function () {
- this.toggleProperty('isEdit');
- },
- addQueue:function (path) {
- this.sendAction('addQueue',path);
+ toggleProperty:function (property,target) {
+ target = target || this;
+ target.toggleProperty(property);
},
- createQueue:function (queue) {
- this.sendAction('createQueue',queue);
+ rollbackProp:function (prop, item) {
+ this.sendAction('rollbackProp', prop, item);
},
- deleteQueue:function (queue) {
- this.sendAction('deleteQueue',queue);
+ toggleLabel:function (labelName, queue) {
+ var q = queue || this.get('currentQueue'),
+ labelRecord = q.store.getById('label',[q.get('path'),labelName].join('.').toLowerCase());
+
+ if (q.get('labels').contains(labelRecord)) {
+ this.recurseRemoveLabel(q,labelRecord);
+ } else {
+ q.get('labels').pushObject(labelRecord);
+ }
+ q.notifyPropertyChange('labels');
}
},
/**
- * passed params
+ * @param {App.Queue} target queue
+ * @param {App.Label} label ralated to queue. All labels with it's name will be removed from child queues.
+ * @method recurseRemoveLabel
*/
- currentQueue:null,
- allQueues:[],
- allQueuesArranged:[],
+ recurseRemoveLabel:function(queue,label) {
+ label = queue.get('labels').findBy('name',label.get('name'));
+ if (label) {
+ queue.get('labels').removeObject(label);
+ this.get('allQueues').filterBy('parentPath',queue.get('path')).forEach(function (child) {
+ this.recurseRemoveLabel(child,label);
+ }.bind(this));
+ }
+ },
- isEdit: true,
-
- disableEdit:function () {
- this.set('isEdit',false);
- }.observes('allQueues'),
+ // COMPUTED PROPERTIES
+ /**
+ * Path for current queue parent.
+ * @type {String}
+ */
currentPrPath:Em.computed.alias('currentQueue.parentPath'),
- leafQueuesCapacity: Ember.computed.map('leafQueues.@each.capacity', function (queue) {
- return Number(queue.get('capacity'));
- }),
+ /**
+ * Current queue parent.
+ * @type {App.Queue}
+ */
+ parentQueue:function () {
+ return this.allQueues.findBy('path',this.get('currentPrPath'));
+ }.property('allQueues','currentPrPath'),
- totalCapacity: Ember.computed.sum('leafQueuesCapacity'),
-
+ /**
+ * Leaf queues.
+ * @type {Array}
+ */
leafQueues:function () {
return this.allQueuesArranged.filterBy('parentPath',this.get('currentPrPath')).filterBy('isNew',false);
}.property('allQueuesArranged.length','currentPrPath'),
- newLeafQueues:function () {
- return this.allQueues.filterBy('parentPath',this.get('currentPrPath')).filterBy('isNew',true);
- }.property('allQueues.length','currentPrPath'),
-
- parentQueue:function () {
- return this.allQueues.findBy('path',this.get('currentPrPath'));
- }.property('allQueues','currentPrPath'),
+ /**
+ * Array of leaf queues capacities.
+ * @type {Array}
+ */
+ leafQueuesCapacity: Ember.computed.map('leafQueues.@each.capacity', function (queue) {
+ return +queue.get('capacity');
+ }),
+
+ /**
+ * Sum of leaf queues capacities.
+ * @type {Number}
+ */
+ totalCapacity: Ember.computed.sum('leafQueuesCapacity'),
+
+ /**
+ * Node labels stored in store.
+ * @type {Array}
+ */
+ nodeLabels:Ember.computed.alias('currentQueue.store.nodeLabels.content'),
- currentInLeaf:function (argument) {
+ /**
+ * Array of arrays of node labels.
+ * Made to split node labels in several rows.
+ *
+ * @return {Array}
+ */
+ arrangedNodeLabels:function () {
+ var llength = this.get('nodeLabels.length'),
+ cols = (llength % 4 === 1)?3:4,
+ arranged = [];
+
+ this.get('nodeLabels').forEach(function (l, idx, all) {
+ if (idx%cols === 0) {
+ arranged.pushObject([l]);
+ } else {
+ arranged.get('lastObject').pushObject(l)
+ }
+ });
+
+ return arranged;
+ }.property('nodeLabels.length'),
+
+ // OBSERVABLES
+
+ /**
+ * Marks currentQueue with 'isCurrent' flag.
+ * @method currentInLeaf
+ */
+ currentInLeaf:function () {
var queues = this.get('allQueues');
queues.setEach('isCurrent',false);
if(!this.get('currentQueue.currentState.stateName').match(/root.deleted/)) {
@@ -77,22 +171,251 @@ App.TotalCapacityComponent = Ember.Component.extend({
}
}.observes('leafQueues','currentQueue').on('init'),
- newQueueNameField: Em.TextField.extend({
+ // CHILD VIEWS
+
+ /**
+ * Toggle buttons for node labels.
+ * @type {Em.View}
+ */
+ nodeLabelsToggles: Em.View.extend(Ember.ViewTargetActionSupport,{
+
+ // TAKEN VALUES
+
+ /**
+ * Name of node label.
+ * @type {[type]}
+ */
+ labelName:null,
+ /**
+ * Node label's existence flag.
+ * @type {Boolean}
+ */
+ notExist:false,
+
+ /**
+ * Queue related to label.
+ * @type {App.Queue}
+ */
queue:null,
- classNames:['form-control'],
- classNameBindings:['isValid::input-error'],
- isValid:Em.computed.bool('queue.isValid')
+
+ /**
+ * Queue's leaf.
+ * @type {Array}
+ */
+ leaf:null,
+
+ // VIEW'S ATTRIBUTES
+
+ tagName:'label',
+
+ classNames:['btn','btn-default'],
+
+ classNameBindings:['isActive:active','warning','lockedByParent:disabled','notExist:not-exist'],
+
+ action:'toggleLabel',
+
+ click:function () {
+ if (this.get('parentView.isOperator')) {
+ this.triggerAction({
+ actionContext: [this.get('labelName'),this.get('queue')]
+ });
+ Em.run.next(function() {
+ this.notifyPropertyChange('currentLabel');
+ }.bind(this));
+ }
+ },
+
+ // COMPUTED PROPERTIES
+
+ /**
+ * Returns true if parent queue has no access to label with 'labelName'.
+ * @return {Boolean}
+ */
+ lockedByParent:function () {
+ var parent = this.get('controller.parentQueue');
+ return (Em.isEmpty(parent))?false:!parent.get('labels').findBy('name',this.get('labelName'));
+ }.property('controller.parentQueue'),
+
+ /**
+ * Returns true if current queue has access to label with 'labelName'.
+ * @return {Boolean}
+ */
+ isActive:function () {
+ return this.get('queue.labels').mapBy('name').contains(this.get('labelName'));
+ }.property('queue.labels.[]'),
+
+ /**
+ * Label record of type App.Label related to current queue.
+ * @return {App.Label}
+ */
+ currentLabel:function () {
+ return this.get('queue.labels').findBy('name',this.get('labelName'));
+ }.property('labelName','queue.labels.length'),
+
+ /**
+ * Label records from leaf queues, that has label with 'labelName'.
+ * @return {Array}
+ */
+ labels:function () {
+ return this.get('leaf').map(function (item) {
+ return item.get('labels').findBy('name',this.get('labelName'));
+ }.bind(this)).compact();
+ }.property('leaf.@each.labels.[]'),
+
+ /**
+ * Sum of label records from leaf queues, that has label with 'labelName'.
+ * @return {Number}
+ */
+ capacityValue:function () {
+ return this.get('labels').reduce(function (prev, label) {
+ return prev + ((label && label.get('capacity'))?+label.get('capacity'):0);
+ }, 0);
+ }.property('labels.@each.capacity','labels.[]'),
+
+ /**
+ * Returns true if total capacity of node labels in leaf are greater than 100.
+ * @type {Boolean}
+ */
+ warning:Em.computed.gt('capacityValue',100),
+
+ // OBSERVABLES
+
+ /**
+ * Marks labels with 'overCapacity' mark if they can't have such capacity value.
+ * @method capacityWatcher
+ */
+ capacityWatcher:function () {
+ this.get('labels').setEach('overCapacity',this.get('warning'));
+ }.observes('warning').on('didInsertElement'),
+
+ /**
+ * Initializes tooltip on button when inserted to DOM.
+ * @method capacityTooltip
+ */
+ capacityTooltip:function () {
+ this.$().tooltip({
+ title: function () {
+ return this.get('capacityValue') + '';
+ }.bind(this),
+ container:"#"+this.elementId,
+ animation:false
+ });
+ }.on('didInsertElement'),
+
+ /**
+ * Marks buttons with 'first' and 'last' classes when inserted to DOM.
+ * @method setFirstAndLast
+ */
+ setFirstAndLast:function (item) {
+ var items = this.$().parents('.labels-toggle').find('label');
+ items.first().addClass('first');
+ items.last().addClass('last');
+ }.on('didInsertElement'),
+
+ /**
+ * Timer information with callback that hides tooltip.
+ * @type {Array}
+ */
+ isShownTimer:null,
+
+ /**
+ * Shows tooltip when label's capacity value changes.
+ * @method showCapacityTooltip
+ */
+ showCapacityTooltip: function() {
+ Em.run.next(this,function () {
+ if (Em.isNone(this.$())) return;
+
+ this.$().tooltip({
+ title: function () {
+ return this.get('capacityValue') + '';
+ }.bind(this),
+ container:"#"+this.elementId,
+ animation:false
+ }).tooltip('show');
+
+ Em.run.cancel(this.get('isShownTimer'));
+ this.set('isShownTimer',Em.run.debounce(this,function() {
+ if (this.$()) this.$().tooltip('hide');
+ },500));
+ })
+ }.observes('currentLabel.capacity'),
+
+ /**
+ * Destrioy tooltips when element destroys.
+ * @method willClearRender
+ */
+ willClearRender:function() {
+ this.$().tooltip('destroy');
+ },
+
+ // CHILD VIEWS
+
+ /**
+ * Bar that shows total capcity value on node label.
+ * @type {Em.View}
+ */
+ nodeLabelsBar:Em.View.extend({
+
+ classNameBindings:[':progress-bar'],
+
+ attributeBindings:['capacityWidth:style'],
+
+ /**
+ * Formatting pattern.
+ * @type {String}
+ */
+ pattern:'width: %@%;',
+
+ /**
+ * Alias for parentView.capacityValue.
+ * @type {String}
+ */
+ value:Em.computed.alias('parentView.capacityValue'),
+
+ /**
+ * Formats pattern whit value.
+ * @return {String}
+ */
+ capacityWidth: function(c,o) {
+ return this.get('pattern').fmt((+this.get('value')<=100)?this.get('value'):100);
+ }.property('value')
+ }),
+
+ /**
+ * Bar that shows current label capcity on node label.
+ * @type {Em.View}
+ */
+ currentQueueBar:Em.View.extend({
+
+ classNameBindings:[':progress-bar',':ghost'],
+
+ attributeBindings:['capacityWidth:style'],
+
+ /**
+ * Formatting pattern.
+ * @type {String}
+ */
+ pattern:'width: %@%;',
+
+ /**
+ * Alias for parentView.currentLabel.capacity.
+ * @type {String}
+ */
+ value:Em.computed.alias('parentView.currentLabel.capacity'),
+
+ capacityWidth: function(c,o) {
+ return this.get('pattern').fmt((+this.get('value')<=100)?this.get('value'):0);
+ }.property('parentView.currentLabel.capacity','parentView.queue.labels.length')
+ })
})
});
App.CapacityEditFormView = Em.View.extend({
- mark:function () {
- this.addObserver('controller.target.isEdit',this,'slide');
- if (!this.get('controller.target.isEdit')) {
- this.$('.capacity-edit-form').hide();
- }
- }.on('didInsertElement'),
- slide:function () {
- this.$('.capacity-edit-form').slideToggle(100);
- }
+ dirty_capacity:function () {
+ return this.get('controller.content').changedAttributes().hasOwnProperty('capacity');
+ }.property('controller.content.capacity'),
+ dirty_maxcapacity:function () {
+ return this.get('controller.content').changedAttributes().hasOwnProperty('maximum_capacity');
+ }.property('controller.content.maximum_capacity')
});
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queue.js
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queue.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queue.js
index 1bffbea..41a4dfd 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queue.js
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queue.js
@@ -23,129 +23,265 @@ var _stopState = 'STOPPED';
App.QueueController = Ember.ObjectController.extend({
needs:['queues'],
- isOperator:Em.computed.alias('controllers.queues.isOperator'),
- isNotOperator:Em.computed.alias('controllers.queues.isNotOperator'),
actions:{
setState:function (state) {
- this.content.set('state', (state === "running") ? _runState : _stopState );
+ this.set('content.state', (state === "running") ? (this.get('content.state') == null) ? null : _runState : _stopState );
},
- createQ:function (record) {
+ createQ:function () {
this.get('controllers.queues').send('createQ',this.get('content'));
},
addQ:function (path) {
this.get('controllers.queues').send('addQ',path);
},
delQ:function (record) {
- this.get('controllers.queues').send('delQ',this.get('content'));
+ this.get('controllers.queues').send('delQ',record);
},
renameQ:function (opt) {
if (opt == 'ask') {
- this.content.addObserver('name',this,this.setQueuePath);
+ this.set('tmpName',{name:this.get('content.name'),path:this.get('content.path')});
+ this.get('content').addObserver('name',this,this.setQueuePath);
this.toggleProperty('isRenaming');
return;
}
if (opt == 'cancel') {
- this.send('rollbackProp','name');
- this.send('rollbackProp','id');
- this.send('rollbackProp','path');
- this.content.removeObserver('name',this,this.setQueuePath);
+ this.get('content').removeObserver('name',this,this.setQueuePath);
+ this.get('content').setProperties({
+ name:this.get('tmpName.name'),
+ id:this.get('tmpName.path'),
+ path:this.get('tmpName.path')
+ });
this.toggleProperty('isRenaming');
return;
}
- if (opt) {
- var self = this;
- this.store.filter('queue',function (q) {
- return q.id === self.content.id;
- }).then(function(queues){
- if (queues.get('length') > 1) {
- return self.content.get('errors').add('path', 'Queue already exists');
- }
- self.toggleProperty('isRenaming');
- self.content.removeObserver('name',self,self.setQueuePath);
- self.transitionToRoute('queue',self.content.id);
- })
+ if (opt && !this.get('content').get('errors.path')) {
+
+ this.store.filter('label',function (label){
+ return label.get('forQueue') == this.get('tmpName.path');
+ }.bind(this)).then(function (labels) {
+ labels.forEach(function (label) {
+ label.materializeId([this.get('id'),label.get('name')].join('.'));
+ label.store.updateId(label,label);
+ }.bind(this))
+ }.bind(this));
+ this.toggleProperty('isRenaming');
+ this.get('content').removeObserver('name',this,this.setQueuePath);
+ this.store.updateId(this.get('content'),this.get('content'));
+ this.transitionToRoute('queue',this.get('content.id'));
+
}
},
- rollbackProp:function(prop){
- attributes = this.content.changedAttributes();
+ // TODO bubble to route
+ rollbackProp:function(prop, queue){
+ queue = queue || this.get('content');
+ attributes = queue.changedAttributes();
if (attributes.hasOwnProperty(prop)) {
- this.content.set(prop,attributes[prop][0]);
+ queue.set(prop,attributes[prop][0]);
}
}
},
- setQueuePath:function (queue,o) {
- var name = queue.get(o);
- var parentPath = queue.get('parentPath');
-
- queue.setProperties({
- name:name.replace(/\s|\./g, ''),
- path:parentPath+'.'+name,
- id:(parentPath+'.'+name).dasherize()
- });
- if (name == '') {
- queue.get('errors').add('path', 'This field is required');
- }
- },
+ /**
+ * Collection of modified fields in queue.
+ * @type {Object} - { [fileldName] : {Boolean} }
+ */
+ queueDirtyFilelds:{},
+ /**
+ * Represents renaming status.
+ * @type {Boolean}
+ */
isRenaming:false,
- unsetRenaming:function () {
- this.set('isRenaming',false);
- }.observes('content'),
+ /**
+ * Object contains temporary name and path while renaming the queue.
+ * @type {Object} - { name : {String}, path : {String} }
+ */
+ tmpName:{},
+
+
+
+ // COMPUTED PROPERTIES
- isRoot:Ember.computed.equal('content.id', 'root'),
- isRunning:Ember.computed.equal('content.state', _runState),
+ /**
+ * Alias for user admin status.
+ * @type {Boolean}
+ */
+ isOperator:Em.computed.alias('controllers.queues.isOperator'),
+
+ /**
+ * Inverted user admin status.
+ * @type {Boolean}
+ */
+ isNotOperator:Em.computed.alias('controllers.queues.isNotOperator'),
+
+ /**
+ * All queues in store.
+ * @type {DS.RecordArray}
+ */
allQueues:Em.computed.alias('controllers.queues.content'),
- allQueuesArranged:Em.computed.alias('controllers.queues.arrangedContent'),
- handleAcl:function (key,value) {
- if (value) {
- this.set(key,(value == '*')?'*':' ');
- }
- return (this.get(key) == '*')? '*':'custom';
- },
+ /**
+ * Array of leaf queues.
+ * @return {Array}
+ */
+ leafQueues:function () {
+ return this.get('allQueues').filterBy('parentPath',this.get('content.parentPath'));
+ }.property('allQueues.length','content.parentPath'),
+
+ /**
+ * Parent of current queue.
+ * @return {App.Queue}
+ */
+ parentQueue: function () {
+ return this.store.getById('queue',this.get('content.parentPath'));
+ }.property('content.parentPath'),
- acl_administer_queue: function (key, value, previousValue) {
+ /**
+ * Returns true if queue is root.
+ * @type {Boolean}
+ */
+ isRoot:Ember.computed.match('content.id', /^(root|root.default)$/),
+
+ /**
+ * Represents queue run state. Returns true if state is null.
+ * @return {Boolean}
+ */
+ isRunning: function() {
+ return this.get('content.state') == _runState || this.get('content.state') == null;
+ }.property('content.state'),
+
+ /**
+ * Queue's acl_administer_queue property can be set to '*' (everyone) or ' ' (nobody) thru this property.
+ *
+ * @param {String} key
+ * @param {String} value
+ * @return {String} - '*' if equal to '*' or 'custom' in other case.
+ */
+ acl_administer_queue: function (key, value) {
return this.handleAcl('content.acl_administer_queue',value);
}.property('content.acl_administer_queue'),
+
+ /**
+ * Returns true if acl_administer_queue is set to '*'
+ * @type {Boolean}
+ */
aaq_anyone:Ember.computed.equal('acl_administer_queue', '*'),
- aaq_dirty:function () {
- var attributes = this.content.changedAttributes();
- return attributes.hasOwnProperty('acl_administer_queue');
- }.property('content.acl_administer_queue'),
- acl_submit_applications: function (key, value, previousValue) {
+ /**
+ * Queue's acl_submit_applications property can be set to '*' (everyone) or ' ' (nobody) thru this property.
+ *
+ * @param {String} key
+ * @param {String} value
+ * @return {String} - '*' if equal to '*' or 'custom' in other case.
+ */
+ acl_submit_applications: function (key, value) {
return this.handleAcl('content.acl_submit_applications',value);
}.property('content.acl_submit_applications'),
+
+ /**
+ * Returns true if acl_submit_applications is set to '*'
+ * @type {Boolean}
+ */
asa_anyone:Ember.computed.equal('acl_submit_applications', '*'),
- asa_dirty:function () {
- var attributes = this.content.changedAttributes();
- return attributes.hasOwnProperty('acl_submit_applications');
- }.property('content.acl_submit_applications'),
+ /**
+ * Error messages for queue path.
+ * @type {[type]}
+ */
+ pathErrors:Ember.computed.mapBy('content.errors.path','message'),
+
+
+
+ // OBSERVABLES
+
+ /**
+ * Marks each queue in leaf with 'overCapacity' if sum if their capacity values is greater then 100.
+ * @method capacityControl
+ */
capacityControl:function () {
- var leafQueues = this.get('leafQueues');
- var total = 0;
+ var leafQueues = this.get('leafQueues'),
+ total = leafQueues.reduce(function (prev, queue) {
+ return +queue.get('capacity') + prev;
+ },0);
- leafQueues.forEach(function (queue) {
- total+=Number(queue.get('capacity'));
- });
leafQueues.setEach('overCapacity',total>100);
}.observes('content.capacity','leafQueues.@each.capacity'),
- leafQueues:function () {
- return this.get('allQueues').filterBy('parentPath',this.get('content.parentPath'));
- }.property('allQueues.length','content.parentPath'),
- queueNamesControl:function (c,o) {
- var leaf = c.get('leafQueues');
- var parent = c.get('allQueues').filterBy('path',c.get('content.parentPath')).get('firstObject');
- if (parent) parent.set('queueNames',leaf.mapBy('name').join() || null);
+ /**
+ * Keeps track of leaf queues and sets 'queues' value of parent to list of their names.
+ * @method queueNamesControl
+ */
+ queueNamesControl:function () {
+ if (this.get('parentQueue')) this.set('parentQueue.queuesArray',this.get('leafQueues').mapBy('name'));
}.observes('allQueues.length','allQueues.@each.name','content'),
- pathErrors:Ember.computed.mapBy('content.errors.path','message')
-
+ /**
+ * Adds observers for each queue attribute.
+ * @method dirtyObserver
+ */
+ dirtyObserver:function () {
+ this.get('content.constructor.transformedAttributes.keys.list').forEach(function(item) {
+ this.addObserver('content.' + item,this,'propertyBecomeDirty');
+ }.bind(this));
+ }.observes('content'),
+
+
+
+ // METHODS
+
+ /**
+ * Sets ACL value to '*' or ' ' and returns '*' and 'custom' respectively.
+ * @param {String} key - ACL attribute
+ * @param {String} value - ACL value
+ * @return {String}
+ */
+ handleAcl:function (key,value) {
+ if (value) {
+ this.set(key,(value == '*')?'*':' ');
+ }
+ return (this.get(key) == '*' || this.get(key) == null) ? '*' : 'custom';
+ },
+
+ /**
+ * Sets queue path and id in accordance with the name.
+ * Also adds errors to queue if name is incorrect.
+ *
+ * @method setQueuePath
+ */
+ setQueuePath:function (queue) {
+ var name = queue.get('name').replace(/\s|\./g, ''),
+ parentPath = queue.get('parentPath');
+
+ queue.setProperties({
+ name:name,
+ path:parentPath+'.'+name,
+ id:(parentPath+'.'+name).dasherize()
+ });
+
+ if (name == '') {
+ queue.get('errors').add('path', 'This field is required');
+ }
+
+ this.store.filter('queue',function (q) {
+ return q.get('id') === queue.get('id');
+ }.bind(this)).then(function (queues){
+ if (queues.get('length') > 1) {
+ return this.get('content').get('errors').add('path', 'Queue already exists');
+ }
+ }.bind(this));
+ },
+
+ /**
+ * Adds modified queue fileds to q queueDirtyFilelds collection.
+ * @param {String} controller
+ * @param {String} property
+ * @method propertyBecomeDirty
+ */
+ propertyBecomeDirty:function (controller, property) {
+ var queueProp = property.split('.').objectAt(1);
+ this.set('queueDirtyFilelds.' + queueProp, this.get('content').changedAttributes().hasOwnProperty(queueProp));
+ }
});
App.ErrorController = Ember.ObjectController.extend();
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queues.js
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queues.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queues.js
index b03e633..aecd265 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queues.js
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queues.js
@@ -24,8 +24,13 @@ App.QueuesController = Ember.ArrayController.extend({
sortProperties: ['name'],
sortAscending: true,
actions:{
+ loadTagged:function (tag) {
+ this.transitionToRoute('queue','root').then(function() {
+ this.store.fetchTagged(App.Queue,tag);
+ }.bind(this));
+ },
goToQueue:function (queue) {
- this.transitionToRoute('queue',queue)
+ this.transitionToRoute('queue',queue);
},
askPath:function () {
this.set('isWaitingPath',true);
@@ -53,6 +58,9 @@ App.QueuesController = Ember.ArrayController.extend({
record.save().then(Em.run.bind(this,this.set,'newQueue',null));
},
delQ:function (record) {
+ var queues = this.get('content'),
+ parentPath = record.get('parentPath'),
+ name = record.get('name');
if (record.get('isNew')) {
this.set('newQueue',null);
}
@@ -60,9 +68,15 @@ App.QueuesController = Ember.ArrayController.extend({
this.set('hasDeletedQueues',true);
}
if (record.isCurrent) {
- this.transitionToRoute('queue',record.get('parentPath'));
+ this.transitionToRoute('queue',parentPath.toLowerCase())
+ .then(Em.run.schedule('afterRender', function () {
+ record.destroyRecord().then(function() {
+ queues.findBy('path',parentPath).set('queuesArray',{'exclude':name});
+ });
+ }));
+ } else {
+ record.destroyRecord();
}
- this.store.deleteRecord(record);
},
saveConfig:function (mark) {
if (mark == 'restart') {
@@ -70,11 +84,15 @@ App.QueuesController = Ember.ArrayController.extend({
} else if (mark == 'refresh') {
this.get('store').markForRefresh();
}
+ var collectedLabels = this.get('model').reduce(function (prev,q) {
+ return prev.pushObjects(q.get('labels.content'));
+ },[]);
var hadDeletedQueues = this.get('hasDeletedQueues'),
scheduler = this.get('scheduler').save(),
model = this.get('model').save(),
- all = Em.RSVP.Promise.all([model,scheduler]);
+ labels = DS.ManyArray.create({content:collectedLabels}).save(),
+ all = Em.RSVP.Promise.all([labels,model,scheduler]);
all.catch(Em.run.bind(this,this.saveError,hadDeletedQueues));
@@ -85,18 +103,147 @@ App.QueuesController = Ember.ArrayController.extend({
}
},
+ /**
+ * User admin status.
+ * @type {Boolean}
+ */
+ isOperator:false,
+
+ /**
+ * Inverted isOperator value.
+ * @type {Boolean}
+ */
+ isNotOperator:cmp.not('isOperator'),
+
+ /**
+ * Flag to show input for adding queue.
+ * @type {Boolean}
+ */
+ isWaitingPath:false,
+
+ /**
+ * Property for error message which may appear when saving queue.
+ * @type {Object}
+ */
alertMessage:null,
+
+ /**
+ * Temporary filed for new queue
+ * @type {App.Queue}
+ */
+ newQueue:null,
+
+ /**
+ * True if newQueue is not empty.
+ * @type {Boolean}
+ */
+ hasNewQueue: cmp.bool('newQueue'),
+
+ /**
+ * Current configuration version tag.
+ * @type {[type]}
+ */
+ current_tag: cmp.alias('store.current_tag'),
+
+ /**
+ * Scheduler record
+ * @type {App.Scheduler}
+ */
+ scheduler:null,
+
+ /**
+ * Collection of modified fields in Scheduler.
+ * @type {Object} - { [fileldName] : {Boolean} }
+ */
+ schedulerDirtyFilelds:{},
+
+
+ configNote: cmp.alias('store.configNote'),
+
+ /*configNote:function (arg,val) {
+ if (arguments.length > 1) {
+ this.set('store.configNote',val);
+ }
+ return this.get('store.configNote');
+ }.property('store.configNote'),*/
+
+ tags:function () {
+ return this.store.find('tag');
+ }.property('store.current_tag'),
+
+ sortedTags: cmp.sort('tags', function(a, b){
+ return (+a.id > +b.id)?(+a.id < +b.id)?0:-1:1;
+ }),
+
+
saveError:function (hadDeletedQueues,error) {
this.set('hasDeletedQueues',hadDeletedQueues);
var response = JSON.parse(error.responseText);
this.set('alertMessage',response);
},
- isOperator:false,
- isNotOperator:cmp.not('isOperator'),
+ propertyBecomeDirty:function (controller,property) {
+ var schedProp = property.split('.').objectAt(1);
+ this.set('schedulerDirtyFilelds.' + schedProp, this.get('scheduler').changedAttributes().hasOwnProperty(schedProp));
+ },
+
+ dirtyObserver:function () {
+ this.get('scheduler.constructor.transformedAttributes.keys.list').forEach(function(item) {
+ this.addObserver('scheduler.' + item,this,'propertyBecomeDirty');
+ }.bind(this));
+ }.observes('scheduler'),
+
+
+ trackNewQueue:function () {
+ var newQueue = this.get('newQueue');
+ if (Em.isEmpty(newQueue)) {
+ return;
+ }
+ var name = newQueue.get('name');
+ var parentPath = newQueue.get('parentPath');
+
+ this.get('newQueue').setProperties({
+ name:name.replace(/\s/g, ''),
+ path:parentPath+'.'+name,
+ id:(parentPath+'.'+name).dasherize()
+ });
+
+ }.observes('newQueue.name'),
+
+
+
+ // TRACKING OF RESTART REQUIREMENT
+
+ /**
+ * check if RM needs restart
+ * @type {bool}
+ */
+ needRestart: cmp.any('hasDeletedQueues', 'hasRenamedQueues'),
+
+ /**
+ * True if some queue of desired configs was removed.
+ * @type {Boolean}
+ */
+ hasDeletedQueues:false,
+
+ /**
+ * List of queues with modified name.
+ * @type {Array}
+ */
+ renamedQueues:cmp.filter('content.@each.name',function (queue){
+ return queue.changedAttributes().hasOwnProperty('name') && !queue.get('isNewQueue');
+ }),
+
+ /**
+ * True if renamedQueues is not empty.
+ * @type {Boolean}
+ */
+ hasRenamedQueues: cmp.notEmpty('renamedQueues.[]'),
+
+
+
+ // TRACKING OF REFRESH REQUIREMENT
- isWaitingPath:false,
-
/**
* check if RM needs refresh
* @type {bool}
@@ -104,34 +251,59 @@ App.QueuesController = Ember.ArrayController.extend({
needRefresh: cmp.and('needRefreshProps','noNeedRestart'),
/**
- * props for 'needRefresh'
+ * Inverted needRestart value.
+ * @type {Boolean}
+ */
+ noNeedRestart: cmp.not('needRestart'),
+
+ /**
+ * Check properties for refresh requirement
+ * @type {Boolean}
+ */
+ needRefreshProps: cmp.any('hasChanges', 'hasNewQueues','dirtyScheduler'),
+
+ /**
+ * List of modified queues.
+ * @type {Array}
+ */
+ dirtyQueues:function () {
+ return this.get('content').filter(function (q) {
+ return q.get('isAnyDirty');
+ });
+ }.property('content.@each.isAnyDirty'),
+
+ /**
+ * True if dirtyQueues is not empty.
+ * @type {Boolean}
*/
- dirtyQueues: cmp.filterBy('content', 'isDirty', true),
- dirtyScheduler: cmp.bool('scheduler.isDirty'),
- newQueues: cmp.filterBy('content', 'isNewQueue', true),
hasChanges: cmp.notEmpty('dirtyQueues.[]'),
+
+ /**
+ * List of new queues.
+ * @type {Array}
+ */
+ newQueues: cmp.filterBy('content', 'isNewQueue', true),
+
+ /**
+ * True if newQueues is not empty.
+ * @type {Boolean}
+ */
hasNewQueues: cmp.notEmpty('newQueues.[]'),
- needRefreshProps: cmp.any('hasChanges', 'hasNewQueues','dirtyScheduler'),
- noNeedRestart: cmp.not('needRestart'),
-
/**
- * check if RM needs restart
- * @type {bool}
+ * True if scheduler is modified.
+ * @type {[type]}
*/
- needRestart: cmp.any('hasDeletedQueues', 'hasRenamedQueues'),
-
+ dirtyScheduler: cmp.bool('scheduler.isDirty'),
+
+
+ // TRACKING OF PRESERVATION POSSIBILITY
+
/**
- * props for 'needRestart'
+ * check there is some changes for save
+ * @type {bool}
*/
- hasDeletedQueues:false,
- hasRenamedQueues: cmp.notEmpty('renamedQueues.[]'),
- renamedQueues:function () {
- return this.content.filter(function(queue){
- var attr = queue.changedAttributes();
- return attr.hasOwnProperty('name') && !queue.get('isNewQueue');
- });
- }.property('content.@each.name'),
+ needSave: cmp.any('needRestart', 'needRefresh'),
/**
* check if can save configs
@@ -140,43 +312,42 @@ App.QueuesController = Ember.ArrayController.extend({
canNotSave: cmp.any('hasOverCapacity', 'hasUncompetedAddings','hasNotValid','isNotOperator'),
/**
- * props for canNotSave
+ * List of not valid queues.
+ * @type {Array}
*/
notValid:cmp.filterBy('content','isValid',false),
- overCapacityQ:cmp.filterBy('content','overCapacity',true),
- uncompetedAddings:cmp.filterBy('content', 'isNew', true),
- hasNotValid:cmp.notEmpty('notValid.[]'),
- hasOverCapacity:cmp.notEmpty('overCapacityQ.[]'),
- hasUncompetedAddings:cmp.notEmpty('uncompetedAddings.[]'),
/**
- * check there is some changes for save
- * @type {bool}
+ * True if notValid is not empty.
+ * @type {Boolean}
*/
- needSave: cmp.any('needRestart', 'needRefresh'),
-
- newQueue:null,
- hasNewQueue: cmp.bool('newQueue'),
- trackNewQueue:function () {
- var newQueue = this.get('newQueue');
- if (Em.isEmpty(newQueue)){
- return;
- }
- var name = newQueue.get('name');
- var parentPath = newQueue.get('parentPath');
+ hasNotValid:cmp.notEmpty('notValid.[]'),
- this.get('newQueue').setProperties({
- name:name.replace(/\s/g, ''),
- path:parentPath+'.'+name,
- id:(parentPath+'.'+name).dasherize()
+ /**
+ * List of queues with excess of capacity
+ * @type {Array}
+ */
+ overCapacityQ:function () {
+ return this.get('content').filter(function (q) {
+ return q.get('overCapacity');
});
+ }.property('content.@each.overCapacity'),
- }.observes('newQueue.name'),
+ /**
+ * True if overCapacityQ is not empty.
+ * @type {Boolean}
+ */
+ hasOverCapacity:cmp.notEmpty('overCapacityQ.[]'),
- configNote:function (arg,val) {
- if (arguments.length > 1) {
- this.set('store.configNote',val);
- }
- return this.get('store.configNote');
- }.property('store.configNote')
+ /**
+ * List of queues with incompete adding process
+ * @type {[type]}
+ */
+ uncompetedAddings:cmp.filterBy('content', 'isNew', true),
+
+ /**
+ * True if uncompetedAddings is not empty.
+ * @type {Boolean}
+ */
+ hasUncompetedAddings:cmp.notEmpty('uncompetedAddings.[]')
});
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/initialize.js
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/initialize.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/initialize.js
index dda4c83..57f57a0 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/initialize.js
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/initialize.js
@@ -24,6 +24,12 @@ App.testMode = false;
// adapters
require('adapters');
+//serializers
+require('serializers');
+
+//store
+require('store');
+
//components
require('components');
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/models/queue.js
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/models/queue.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/models/queue.js
index cf22437..2044ff1 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/models/queue.js
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/models/queue.js
@@ -18,11 +18,49 @@
var App = require('app');
+App.Label = DS.Model.extend({
+ queue: DS.belongsTo('queue'),
+ capacity: DS.attr('number', { defaultValue: 0 }),
+ maximum_capacity: DS.attr('number', { defaultValue: 0 }),
+ forQueue:function() {
+ return this.get('id').substring(0,this.get('id').lastIndexOf('.'));
+ }.property('id'),
+ name:function() {
+ return this.get('id').substr(this.get('id').lastIndexOf('.')+1);
+ }.property('id'),
+
+ overCapacity:false,
+ isNotExist:function () {
+ return this.get('store.nodeLabels.content').findBy('name',this.get('name')).notExist;
+ }.property('store.nodeLabels.content.@each.notExist')
+});
+
App.Scheduler = DS.Model.extend({
maximum_am_resource_percent: DS.attr('number', { defaultValue: 0 }),
maximum_applications: DS.attr('number', { defaultValue: 0 }),
node_locality_delay: DS.attr('number', { defaultValue: 0 }),
- resource_calculator: DS.attr('string', { defaultValue: '' }),
+ resource_calculator: DS.attr('string', { defaultValue: '' })
+});
+
+
+/**
+ * Represents tagged of configuraion vresion.
+ *
+ */
+App.Tag = DS.Model.extend({
+ tag:DS.attr('string'),
+ isCurrent:function () {
+ return this.get('tag') === this.get('store.current_tag');
+ }.property('store.current_tag'),
+ changed:function () {
+ return (this.get('tag').match(/version[1-9]+/))?moment(+this.get('tag').replace('version','')).fromNow():'';
+ }.property('tag'),
+ updateTime:function () {
+ Em.run.later(this,function () {
+ this.trigger('tick');
+ this.notifyPropertyChange('tag');
+ },5000);
+ }.on('init','tick')
});
/**
@@ -30,6 +68,55 @@ App.Scheduler = DS.Model.extend({
*
*/
App.Queue = DS.Model.extend({
+ labels: DS.hasMany('label'),
+ sortBy:['name'],
+ sortedLabels:Em.computed.sort('labels','sortBy'),
+
+ _accessAllLabels:DS.attr('boolean'),
+ accessAllLabels:function (key,val) {
+ var labels = this.get('store.nodeLabels').map(function(label) {
+ return this.store.getById('label',[this.get('id'),label.name].join('.'));
+ }.bind(this));
+
+ if (arguments.length > 1) {
+ this.set('_accessAllLabels',val);
+
+ if (this.get('_accessAllLabels')) {
+ labels.forEach(function(lb) {
+
+ var containsByParent = (Em.isEmpty(this.get('parentPath')))?true:this.store.getById('queue',this.get('parentPath')).get('labels').findBy('name',lb.get('name'));
+ if (!this.get('labels').contains(lb) && !!containsByParent) {
+ this.get('labels').pushObject(lb);
+ this.notifyPropertyChange('labels');
+ }
+ }.bind(this));
+ }
+ }
+
+ if (this.get('labels.length') != labels.get('length')) {
+ this.set('_accessAllLabels',false);
+ }
+
+ return this.get('_accessAllLabels');
+ }.property('_accessAllLabels','labels.[]'),
+
+ isAnyDirty: function () {
+ return this.get('isDirty') || !Em.isEmpty(this.get('labels').findBy('isDirty',true)) || this.get('isLabelsDirty');
+ }.property('isDirty','labels.@each.isDirty','initialLabels','isLabelsDirty'),
+
+ initialLabels:[],
+ labelsLoad:function() {
+ this.set('initialLabels',this.get('labels').mapBy('id'));
+ }.on('didLoad','didUpdate','didCreate'),
+
+ isLabelsDirty:function () {
+ var il = this.get('initialLabels').sort();
+ var cl = this.get('labels').mapBy('id').sort();
+ return !((il.length == cl.length) && il.every(function(element, index) {
+ return element === cl[index];
+ }));
+ }.property('initialLabels', 'labels.[]', '_accessAllLabels'),
+
name: DS.attr('string'),
parentPath: DS.attr('string'),
depth: DS.attr('number'),
@@ -42,20 +129,59 @@ App.Queue = DS.Model.extend({
capacity: DS.attr('number', { defaultValue: 0 }),
maximum_capacity: DS.attr('number', { defaultValue: 0 }),
- unfunded_capacity: DS.attr('number', { defaultValue: 0 }),
-
+ //unfunded_capacity: DS.attr('number', { defaultValue: 0 }),
+
user_limit_factor: DS.attr('number', { defaultValue: 1 }),
minimum_user_limit_percent: DS.attr('number', { defaultValue: 100 }),
- maximum_applications: DS.attr('number', { defaultValue: '' }),
- maximum_am_resource_percent: DS.attr('number', { defaultValue: '' }),
+ maximum_applications: DS.attr('number', { defaultValue: null }),
+ maximum_am_resource_percent: DS.attr('number', { defaultValue: null }),
- queueNames: DS.attr('string'),
- queueNamesArray:function () {
- return (this.get('queueNames.length')>0)?this.get('queueNames').split(','):[];
- }.property('queueNames'),
+ queues: DS.attr('string'),
+ queuesArray:function (key,val) {
+ var qrray;
+ if (arguments.length > 1) {
+ if (typeof val === 'object' && val.hasOwnProperty('exclude')) {
+ qrray = (this.get('queues'))?this.get('queues').split(','):[];
+ this.set('queues',qrray.removeObject(val.exclude).join(',') || null);
+ } else {
+ this.set('queues',val.join(',') || null);
+ }
+ }
+ return (this.get('queues'))?this.get('queues').split(','):[];
+ }.property('queues'),
- overCapacity:false,
+ _overCapacity:false,
+ overCapacity:function(key,val) {
+ if (arguments.length > 1) {
+ this.set('_overCapacity',val);
+ }
+
+ return this.get('_overCapacity') || !Em.isEmpty(this.get('labels').filterBy('overCapacity'));
+ }.property('_overCapacity','labels.@each.overCapacity'),
//new queue flag
- isNewQueue:DS.attr('boolean', {defaultValue: false})
+ isNewQueue:DS.attr('boolean', {defaultValue: false}),
+
+ version:null,
+ clearTag:function () {
+ this.set('version', null);
+ }.observes(
+ 'name',
+ 'parentPath',
+ 'depth',
+ 'path',
+ 'state',
+ 'acl_administer_queue',
+ 'acl_submit_applications',
+ 'capacity',
+ 'maximum_capacity',
+ 'unfunded_capacity',
+ 'user_limit_factor',
+ 'minimum_user_limit_percent',
+ 'maximum_applications',
+ 'maximum_am_resource_percent',
+ 'queues',
+ 'labels.@each.capacity',
+ 'labels.@each.maximum_capacity'
+ )
});