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:56:59 UTC
[1/3] ambari git commit: AMBARI-10063. CapSched View: Add Support for
Node labels and versions (alexantonenko)
Repository: ambari
Updated Branches:
refs/heads/trunk a5f99f4f4 -> 95103a905
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/schedulerPanel.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/schedulerPanel.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/schedulerPanel.hbs
index 12ad8a3..b30f05b 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/schedulerPanel.hbs
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/schedulerPanel.hbs
@@ -16,10 +16,19 @@
* limitations under the License.
}}
-<div class="panel panel-default panel-capacity">
+<div class="panel panel-default panel-scheduler">
<div class="panel-heading">
<div class="panel-title">
Scheduler
+ {{#if scheduler.isDirty}}
+ {{#if scheduler.isSaving}}
+ <i class="fa fa-fw fa-lg gray fa-spinner fa-spin"></i>
+ {{else}}
+ {{diff-tooltip queue=scheduler}}
+ {{/if}}
+ {{else}}
+ <i class="fa fa-fw fa-lg green fa-check"></i>
+ {{/if}}
</div>
</div>
<div class="panel-body">
@@ -29,20 +38,34 @@
{{#if isOperator}}
<div class="col-xs-6 control-value">
{{int-input value=scheduler.maximum_applications maxlength=15 class="input-sm input-int"}}
+ {{#if schedulerDirtyFilelds.maximum_applications}}
+ <div class="btn-group btn-group-xs" >
+ <a {{action 'rollbackProp' 'maximum_applications' scheduler}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a>
+ </div>
+ {{/if}}
+ </div>
{{else}}
<div class="col-xs-6">
<p class="form-control-static">{{scheduler.maximum_applications}}</p>
- {{/if}}
</div>
+ {{/if}}
</div>
<div class="form-group">
<label class="col-xs-5 control-label">Maximum AM Resource</label>
{{#if isOperator}}
- <div class="col-xs-6 control-value">
- <div class="input-group input-percent">
- {{int-input value=scheduler.maximum_am_resource_percent class="input-sm" maxVal=100}}
- <span class="input-group-addon">%</span>
+ <div class="col-xs-6 control-value input-percent-wrap">
+ <div>
+ <div class="input-group input-percent">
+ {{int-input value=scheduler.maximum_am_resource_percent class="input-sm" maxVal=100}}
+ <span class="input-group-addon">%</span>
+ </div>
</div>
+ {{#if schedulerDirtyFilelds.maximum_am_resource_percent}}
+ <div class="btn-group btn-group-xs" >
+ <a {{action 'rollbackProp' 'maximum_am_resource_percent' scheduler}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a>
+ </div>
+ {{/if}}
+ </div>
{{else}}
<div class="col-xs-6">
{{#if scheduler.maximum_am_resource_percent}}
@@ -50,34 +73,45 @@
{{else}}
<p class="form-control-static">-</p>
{{/if}}
- {{/if}}
</div>
+ {{/if}}
</div>
<div class="form-group">
<label class="col-xs-5 control-label">Node Locality Delay</label>
{{#if isOperator}}
- <div class="col-xs-6 control-value">
+ <div class="col-xs-6 control-value">
{{int-input value=scheduler.node_locality_delay maxlength=10 class="input-sm input-int"}}
+ {{#if schedulerDirtyFilelds.node_locality_delay}}
+ <div class="btn-group btn-group-xs" >
+ <a {{action 'rollbackProp' 'node_locality_delay' scheduler}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a>
+ </div>
+ {{/if}}
+ </div>
{{else}}
- <div class="col-xs-6">
- {{#if scheduler.node_locality_delay}}
- <p class="form-control-static">{{scheduler.node_locality_delay}} %</p>
- {{else}}
- <p class="form-control-static">-</p>
- {{/if}}
+ <div class="col-xs-6">
+ {{#if scheduler.node_locality_delay}}
+ <p class="form-control-static">{{scheduler.node_locality_delay}} %</p>
+ {{else}}
+ <p class="form-control-static">-</p>
+ {{/if}}
+ </div>
{{/if}}
- </div>
- </div>
- {{#if isOperator}}
- {{#if scheduler.resource_calculator}}
- <div class="form-group">
- <label class="col-xs-5 control-label">Calculator</label>
- <div class="col-xs-12 control-value">
- {{input value=scheduler.resource_calculator class="input-sm form-control"}}
- </div>
</div>
- {{/if}}
- {{/if}}
+ {{#if isOperator}}
+ {{#if scheduler.resource_calculator}}
+ <div class="form-group">
+ <label class="col-xs-5 control-label">Calculator</label>
+ <div class="col-xs-12 control-value">
+ {{input value=scheduler.resource_calculator class="input-sm form-control"}}
+ {{#if schedulerDirtyFilelds.resource_calculator}}
+ <div class="btn-group btn-group-xs" >
+ <a {{action 'rollbackProp' 'resource_calculator' scheduler}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a>
+ </div>
+ {{/if}}
+ </div>
+ </div>
+ {{/if}}
+ {{/if}}
</form>
</div>
</div>
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/views/queues.js
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/views/queues.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/views/queues.js
index ca98e43..7014482 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/views/queues.js
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/views/queues.js
@@ -27,7 +27,8 @@ App.QueuesView = Em.View.extend({
},
saveMode:'',
clearSaveMode:function () {
- $('#noteModal').on('hidden.bs.modal', function (e) {
+ this.$('#versions-table-wrap').perfectScrollbar();
+ $('#noteModal').on('hidden.bs.modal', function () {
this.set('saveMode','');
}.bind(this));
}.on('didInsertElement')
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/bower.json
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/bower.json b/contrib/views/capacity-scheduler/src/main/resources/ui/bower.json
index 18bef15..ebfafe7 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/bower.json
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/bower.json
@@ -3,18 +3,19 @@
"version": "0.0.1",
"main": "public/app.js",
"dependencies": {
- "ember": "1.6.0-beta.1",
+ "ember": "1.7.0",
"ember-data": "1.0.0-beta.8",
- "jquery": "1.9.0",
+ "jquery": "2.x",
"bootstrap": "3.1.x",
"moment": "~2.5.1",
"ember-i18n": "~1.6.0",
"font-awesome": "~4.1",
- "bootstrap3-typeahead": "~3.0.3"
+ "bootstrap3-typeahead": "~3.0.3",
+ "perfect-scrollbar": "~0.5.8"
},
"overrides": {
"jquery": {
- "main": "jquery.js"
+ "main": "dist/jquery.js"
},
"ember-uploader": {
"main": "dist/ember-uploader.js"
@@ -25,6 +26,11 @@
"ember-i18n": {
"scripts": []
},
+ "bootstrap": {
+ "main": [
+ "./dist/js/bootstrap.js"
+ ]
+ },
"font-awesome": {
"main": "css/font-awesome.css"
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/config.coffee
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/config.coffee b/contrib/views/capacity-scheduler/src/main/resources/ui/config.coffee
index 3f0e672..5474aec 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/config.coffee
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/config.coffee
@@ -15,23 +15,23 @@
# limitations under the License.
-exports.config =
+exports.config =
watcher:
usePolling: true
fileListInterval: 512
- files:
-
- javascripts:
+ files:
+
+ javascripts:
defaultExtension: 'js'
- joinTo:
+ joinTo:
'javascripts/app.js': /^app/
'javascripts/vendor.js': /^bower_components|vendor/
order:
before: [
- 'bower_components/jquery/jquery.js',
+ 'bower_components/jquery/dist/jquery.js',
'bower_components/handlebars/handlebars.js',
'bower_components/ember/ember.js',
'bower_components/ember-data/ember-data.js'
@@ -56,7 +56,7 @@ exports.config =
defaultExtension: 'hbs'
joinTo: 'javascripts/app.js' : /^app/
paths:
- jquery: 'bower_components/jquery/jquery.js'
+ jquery: 'bower_components/jquery/dist/jquery.js'
handlebars: 'bower_components/handlebars/handlebars.js'
ember: 'bower_components/ember/ember.js'
[3/3] ambari git commit: AMBARI-10063. CapSched View: Add Support for
Node labels and versions (alexantonenko)
Posted by al...@apache.org.
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'
+ )
});
[2/3] ambari git commit: AMBARI-10063. CapSched View: Add Support for
Node labels and versions (alexantonenko)
Posted by al...@apache.org.
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/router.js
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/router.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/router.js
index 33b3977..b4250fb 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/router.js
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/router.js
@@ -44,13 +44,28 @@ App.TraceRoute = Ember.Route.extend({
*/
App.QueuesRoute = Ember.Route.extend({
actions:{
+ rollbackProp:function (prop, item) {
+ var attributes = item.changedAttributes();
+ if (attributes.hasOwnProperty(prop)) {
+ item.set(prop,attributes[prop][0]);
+ }
+ }
},
model: function() {
- return this.store.find('queue');
+ var store = this.get('store');
+ return new Ember.RSVP.Promise(function (resolve,reject) {
+ store.get('nodeLabels').then(function () {
+ return store.find('queue');
+ }).then(function (queues) {
+ resolve(queues);
+ }).catch(function (e) {
+ reject(e);
+ });
+ });
},
setupController:function (c,model) {
this.store.checkOperator().then(function (isOperator) {
- c.set('isOperator',isOperator);
+ c.set('isOperator', isOperator);
});
c.set('model',model);
this.store.find('scheduler','scheduler').then(function (s) {
@@ -68,9 +83,8 @@ App.QueueRoute = Ember.Route.extend({
model: function(params,tr) {
var queues = this.modelFor('queues') || this.store.find('queue'),
filterQueues = function (queues) {
- return queues.filterBy('id',params.queue_id).get('firstObject');
+ return queues.findBy('id',params.queue_id);
};
-
return (queues instanceof DS.PromiseArray)?queues.then(filterQueues):filterQueues(queues);
},
afterModel:function (model) {
@@ -78,7 +92,7 @@ App.QueueRoute = Ember.Route.extend({
this.transitionTo('queues');
}
},
-
+
actions: {
willTransition: function (tr) {
if (this.get('controller.isRenaming')) {
@@ -111,7 +125,15 @@ App.LoadingRoute = Ember.Route.extend();
*/
App.ErrorRoute = Ember.Route.extend({
setupController:function (controller,model) {
- var response = JSON.parse(model.responseText);
+ //TODO Handle Ember Error!
+ var response;
+ try {
+ response = JSON.parse(model.responseText);
+ } catch (e) {
+ throw model;
+ response = model;
+ }
+ model.trace = model.stack;
controller.set('model',response);
}
});
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/serializers.js
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/serializers.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/serializers.js
new file mode 100644
index 0000000..a746eb8
--- /dev/null
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/serializers.js
@@ -0,0 +1,285 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+/**
+ * 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;
+}
+
+App.SerializerMixin = Em.Mixin.create({
+
+ PREFIX:"yarn.scheduler.capacity",
+
+ serializeConfig:function (records) {
+ var config = {},
+ note = this.get('store.configNote');
+
+ Em.EnumerableUtils.forEach(records,function (record) {
+ this.serializeIntoHash(config, record.constructor, record);
+ },this);
+
+ 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};
+
+ },
+
+ serializeIntoHash: function(hash, type, record, options) {
+ Em.merge(hash,this.store.serializerFor(type).serialize(record, options));
+ },
+
+ extractUpdateRecord: function(store, type, payload) {
+ return this.extractArray(store, App.Queue, payload);
+ },
+
+ extractCreateRecord: function(store, type, payload) {
+ this._setupLabels({},[payload.queue],payload.label);
+ return this.extractSave(store, type, payload);
+ },
+ 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();
+
+ var base_path = prefix + "." + q.path;
+
+ var labelsPath = base_path + ".accessible-node-labels";
+ var qLabels;
+
+ q.unfunded_capacity = props[base_path + ".unfunded.capacity"] || null;
+
+ q.state = props[base_path + ".state"] || null;
+ q.acl_administer_queue = props[base_path + ".acl_administer_queue"] || null;
+ q.acl_submit_applications = props[base_path + ".acl_submit_applications"] || null;
+
+ q.capacity = (props[base_path + ".capacity"])?+props[base_path + ".capacity"]:null;
+ q.maximum_capacity = (props[base_path + ".maximum-capacity"])?+props[base_path + ".maximum-capacity"]:null;
+
+ q.user_limit_factor = (props[base_path + ".user-limit-factor"])?+props[base_path + ".user-limit-factor"]:null;
+ q.minimum_user_limit_percent = (props[base_path + ".minimum-user-limit-percent"])?+props[base_path + ".minimum-user-limit-percent"]:null;
+ q.maximum_applications = (props[base_path + ".maximum-applications"])?+props[base_path + ".maximum-applications"]:null;
+ q.maximum_am_resource_percent = (props[base_path + ".maximum-am-resource-percent"])?+props[base_path + ".maximum-am-resource-percent"]:null;
+
+ //TODO what if didn't set??
+
+ switch ((props.hasOwnProperty(labelsPath))?props[labelsPath]:'') {
+ case '*':
+ q.labels = this.get('store.nodeLabels.content').map(function(item) {
+ return [q.id,item.name].join('.');
+ });
+ q._accessAllLabels = true;
+ break;
+ case '':
+ q.labels = [];
+ q._accessAllLabels = false;
+ break;
+ default:
+ q._accessAllLabels = false;
+ q.labels = props[labelsPath].split(',').map(function(labelName) {
+ if (!this.get('store.nodeLabels.content').isAny('name',labelName)) {
+ this.get('store.nodeLabels.content').pushObject({ name:labelName, notExist:true });
+ }
+ return [q.id,labelName].join('.');
+ }.bind(this)).compact();
+ break;
+
+ }
+
+ if (q.maximum_am_resource_percent)
+ q.maximum_am_resource_percent = q.maximum_am_resource_percent*100; // convert to percent
+
+ q.queues = props[prefix + "." + q.path + ".queues"] || null;
+
+ return q;
+ },
+ normalizePayload: function (properties) {
+ if (properties.hasOwnProperty('queue')) {
+ return properties;
+ }
+ var labels = [], queues = [];
+
+ var scheduler = [{
+ id:'scheduler',
+ maximum_am_resource_percent:properties[this.PREFIX + ".maximum-am-resource-percent"]*100, // convert to percent
+ maximum_applications:properties[this.PREFIX + ".maximum-applications"],
+ node_locality_delay:properties[this.PREFIX + ".node-locality-delay"],
+ resource_calculator:properties[this.PREFIX + ".resource-calculator"]
+ }];
+ _recurseQueues(null, "root", 0, properties, queues, this.get('store'));
+ this._setupLabels(properties,queues,labels,this.PREFIX);
+
+ return {'queue':queues,'scheduler':scheduler,'label':labels};
+ },
+ _setupLabels :function (properties, queues, labels) {
+ var prefix = this.PREFIX;
+ var nodeLabels = this.get('store.nodeLabels.content');
+ queues.forEach(function(queue) {
+ nodeLabels.forEach(function(label) {
+ var labelId = [queue.id,label.name].join('.'),
+ cp = [prefix, queue.path, 'accessible-node-labels',label.name,'capacity'].join('.'),
+ mcp = [prefix, queue.path, 'accessible-node-labels',label.name,'maximum-capacity'].join('.');
+ labels.push({
+ id:labelId,
+ capacity:properties.hasOwnProperty(cp)?+properties[cp]:0,
+ maximum_capacity:properties.hasOwnProperty(mcp)?+properties[mcp]:100
+ });
+ });
+
+ if (queue._accessAllLabels) {
+ queue.labels = nodeLabels.map(function(label) {
+ return [queue.id,label.name].join('.');
+ }.bind(this)).compact();
+ }
+ });
+ return labels;
+ }
+});
+
+App.SchedulerSerializer = DS.RESTSerializer.extend(App.SerializerMixin,{
+ serialize:function (record, options) {
+ var json = {};
+
+ json[this.PREFIX + ".maximum-am-resource-percent"] = record.get('maximum_am_resource_percent')/100; // convert back to decimal
+ json[this.PREFIX + ".maximum-applications"] = record.get('maximum_applications');
+ json[this.PREFIX + ".node-locality-delay"] = record.get('node_locality_delay');
+ json[this.PREFIX + ".resource-calculator"] = record.get('resource_calculator');
+
+ return json;
+ }
+});
+
+App.QueueSerializer = DS.RESTSerializer.extend(App.SerializerMixin,{
+ serialize:function (record, options) {
+ var json = {};
+
+ if (options && options.clone) {
+ Em.merge(json,record.toJSON({ includeId:true }));
+
+ record.eachRelationship(function(key, relationship) {
+ if (relationship.kind === 'belongsTo') {
+ //TODO will implement if need
+ } else if (relationship.kind === 'hasMany') {
+ json[key] = record.get(key).mapBy('id');
+ }
+ }, this);
+
+ return json;
+ }
+
+ json[this.PREFIX + "." + record.get('path') + ".unfunded.capacity"] = record.get('unfunded_capacity');
+ json[this.PREFIX + "." + record.get('path') + ".acl_administer_queue"] = record.get('acl_administer_queue');
+ json[this.PREFIX + "." + record.get('path') + ".acl_submit_applications"] = record.get('acl_submit_applications');
+ json[this.PREFIX + "." + record.get('path') + ".minimum-user-limit-percent"] = record.get('minimum_user_limit_percent');
+ json[this.PREFIX + "." + record.get('path') + ".maximum-capacity"] = record.get('maximum_capacity');
+ json[this.PREFIX + "." + record.get('path') + ".user-limit-factor"] = record.get('user_limit_factor');
+ json[this.PREFIX + "." + record.get('path') + ".state"] = record.get('state');
+ json[this.PREFIX + "." + record.get('path') + ".capacity"] = record.get('capacity');
+ json[this.PREFIX + "." + record.get('path') + ".queues"] = record.get('queues')||null;
+
+ // do not set property if not set
+ var ma = record.get('maximum_applications')||'';
+ if (ma) {
+ json[this.PREFIX + "." + record.get('path') + ".maximum-applications"] = ma;
+ }
+
+ // do not set property if not set
+ var marp = record.get('maximum_am_resource_percent')||'';
+ if (marp) {
+ marp = marp/100; // convert back to decimal
+ json[this.PREFIX + "." + record.get('path') + ".maximum-am-resource-percent"] = marp;
+ }
+
+ record.eachRelationship(function(key, relationship) {
+ if (relationship.kind === 'belongsTo') {
+ this.serializeBelongsTo(record, json, relationship);
+ } else if (relationship.kind === 'hasMany') {
+ this.serializeHasMany(record, json, relationship);
+ }
+ }, this);
+
+ return json;
+ },
+ serializeHasMany:function (record, json, relationship) {
+ var key = relationship.key;
+ record.get(key).map(function (l,idx,labels) {
+ json[[this.PREFIX, record.get('path'), 'accessible-node-labels'].join('.')] = (record.get('accessAllLabels'))?'*':labels.mapBy('name').join(',');
+ if (!record.get('store.nodeLabels').findBy('name',l.get('name')).notExist) {
+ json[[this.PREFIX, record.get('path'), 'accessible-node-labels', l.get('name'), 'capacity'].join('.')] = l.get('capacity');
+ json[[this.PREFIX, record.get('path'), 'accessible-node-labels', l.get('name'), 'maximum-capacity'].join('.')] = l.get('maximum_capacity');
+ }
+ },this);
+ }
+});
+
+App.LabelSerializer = DS.RESTSerializer.extend({
+ serialize:function () {
+ return {};
+ }
+});
+
+App.TagSerializer = DS.RESTSerializer.extend({
+ extractFindAll: function(store, type, payload){
+ return this.extractArray(store, type, {'tag':payload.items});
+ },
+ normalizeHash: {
+ tag: function(hash) {
+ hash.id = hash.version;
+ delete hash.version;
+ delete hash.href;
+ delete hash.Config;
+ delete hash.type;
+ return hash;
+ }
+ }
+});
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/store.js
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/store.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/store.js
new file mode 100644
index 0000000..1eb867e
--- /dev/null
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/store.js
@@ -0,0 +1,164 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+function _fetchTagged(adapter, store, type, sinceToken) {
+ var promise = adapter.findAllTagged(store, type, sinceToken),
+ serializer = store.serializerFor('queue'),
+ label = "DS: Handle Adapter#findAllTagged of " + type;
+
+
+ return Em.RSVP.Promise.cast(promise, label).then(function(adapterPayload) {
+ var config = serializer.normalizePayload(adapterPayload.items[0].properties);
+ var v = (store.get('current_tag') !== store.get('tag'))?adapterPayload.items[0].version:'';
+
+ store.set('tag',store.get('current_tag'));
+
+ if (!Em.isArray(config.queue)) {
+ return;
+ }
+
+ store.all('queue').filterBy('isNewQueue',true).forEach(function (q) {
+ q.store.get('nodeLabels').forEach(function (nl) {
+ var label = q.store.getById('label',[q.get('id'),nl].join('.'));
+ if (label) {
+ label.unloadRecord();
+ }
+ });
+ q.unloadRecord();
+ });
+
+ store.findAll('queue').then(function (queues) {
+ queues.forEach(function (queue) {
+ var new_version = config.queue.findBy('id',queue.id);
+ if (new_version) {
+ new_version['isNewQueue'] = queue.get('isNewQueue');
+ store.findByIds('label',new_version.labels).then(function(labels) {
+ labels.forEach(function (label){
+ label.setProperties(config.label.findBy('id',label.get('id')));
+ });
+ queue.updateHasMany('labels',labels);
+ queue.set('version',v);
+ });
+ delete new_version.labels;
+ queue.setProperties(new_version);
+ } else {
+ store.unloadRecord(queue);
+ }
+ config.queue.removeObject(new_version);
+ });
+
+ config.label.forEach(function (label) {
+ if (!store.hasRecordForId('label',label.id)) {
+ store.push('label',label);
+ }
+ });
+
+ config.queue.setEach('isNewQueue',true);
+ store.pushMany(type,config.queue);
+ config.queue.forEach(function(item) {
+ store.recordForId('queue',item.id).set('version',v);
+ });
+ store.didUpdateAll(type);
+ return store.recordForId('scheduler','scheduler');
+ }).then(function (scheduler) {
+ scheduler.setProperties(config.scheduler.objectAt(0));
+ scheduler.set('version',v);
+ });
+
+ }, null, "DS: Extract payload of findAll " + type);
+}
+
+App.ApplicationStore = DS.Store.extend({
+
+ adapter: App.QueueAdapter,
+
+ configNote:'',
+
+ clusterName: '',
+
+ tag: '',
+
+ current_tag: '',
+
+ nodeLabels: function () {
+ var adapter = this.get('defaultAdapter');
+ return Ember.ArrayProxy.extend(Ember.PromiseProxyMixin).create({
+ promise: adapter.getNodeLabels()
+ });
+ }.property(),
+
+ isInitialized: Ember.computed.and('tag', 'clusterName'),
+
+ markForRefresh:function () {
+ this.set('defaultAdapter.saveMark','saveAndRefresh');
+ },
+
+ markForRestart:function () {
+ this.set('defaultAdapter.saveMark','saveAndRestart');
+ },
+
+ flushPendingSave: function() {
+ var pending = this._pendingSave.slice(),
+ newPending = [[]];
+
+ if (pending.length == 1) {
+ this._super();
+ return;
+ }
+
+ pending.forEach(function (tuple) {
+ var record = tuple[0], resolver = tuple[1];
+ 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();
+ }
+ },
+ fetchTagged: function(type, tag) {
+ var adapter = this.adapterFor(type),
+ sinceToken = this.typeMapFor(type).metadata.since;
+
+ this.set('tag',tag);
+
+ return _fetchTagged(adapter, this, type, sinceToken);
+ },
+ checkOperator:function () {
+ return this.get('defaultAdapter').getPrivilege();
+ }
+});
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/styles/application.less
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/styles/application.less b/contrib/views/capacity-scheduler/src/main/resources/ui/app/styles/application.less
index df8148e..4499097 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/styles/application.less
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/styles/application.less
@@ -16,141 +16,706 @@
* limitations under the License.
*/
-.queue-capacity .peer-toggle {
- width: 100%;
- // background: #f7f7f7;
- //border: 1px solid #e4e4e4;
- text-align: left;
- padding: 3px;
-}
+@import './../../bower_components/bootstrap/less/variables.less';
-a .peer-toggle {
- color: #555;
- text-decoration: none;
- font-size: .8em;
- }
+//== Media queries breakpoints
+//
+//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
-.row.queue-acl-row {
- padding-left:15px;
-}
+// Extra small screen / phone
+// Note: Deprecated @screen-xs and @screen-phone as of v3.0.1
+@screen-xs: 480px;
+@screen-xs-min: @screen-xs;
+@screen-phone: @screen-xs-min;
-.row .queue-resources {
- padding-left: 0px;
- padding-right: 30px;
-}
+// Small screen / tablet
+// Note: Deprecated @screen-sm and @screen-tablet as of v3.0.1
+@screen-sm: 768px;
+@screen-sm-min: @screen-sm;
+@screen-tablet: @screen-sm-min;
-.wrap {
- padding: 0px;
-}
+// Medium screen / desktop
+// Note: Deprecated @screen-md and @screen-desktop as of v3.0.1
+@screen-md: 900px;
+@screen-md-min: @screen-md;
+@screen-desktop: @screen-md-min;
+
+// Large screen / wide desktop
+// Note: Deprecated @screen-lg and @screen-lg-desktop as of v3.0.1
+@screen-lg: 1100px;
+@screen-lg-min: @screen-lg;
+@screen-lg-desktop: @screen-lg-min;
+
+// So media queries don't overlap when required, provide a maximum
+@screen-xs-max: (@screen-sm-min - 1);
+@screen-sm-max: (@screen-md-min - 1);
+@screen-md-max: (@screen-lg-min - 1);
+
+@import './../../bower_components/bootstrap/less/bootstrap.less';
+
+// ----- TWO BUTTONS -----
.add-queue {
+
+ margin-bottom: 5px;
+
.tooltip.bottom .tooltip-arrow {
border-bottom-color: #a94442;
}
.tooltip-inner {
background-color: #a94442;
}
+ .btn-group-save {
+ table-layout: auto;
+ & > .btn-group:last-child > .btn:first-child {
+ border-top-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ }
+ & > .btn-group:first-child > .dropdown-toggle {
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ }
+ .dropdown-menu .btn {
+ border-radius: 0;
+ text-align: left;
+ }
+ .btn-group {
+ width: 100%;
+ .btn-save {
+ border-top-left-radius: 4px !important;
+ border-bottom-left-radius: 4px !important;
+ }
+ }
+ }
}
-.input-int {
- width: 110px;
-}
-
-.input-percent {
- width: 65px;
-}
-
-.form-horizontal {
- padding-top: 10px;
-}
-
-.form-horizontal .capacity-edit-form {
- padding-top: 0px;
-}
+// ----- QUEUE LIST -----
-.form-horizontal .control-value {
- padding-top: 0px;
+.queue-list{
+ .badge {
+ color: #999;
+ background-color: #fff;
+ margin-left: 5px;
+ .tooltip-inner{
+ white-space: pre;
+ max-width: none;
+ font-weight: bold;
+ }
+ }
+ .progress {
+ height: 3px;
+ margin: 0;
+ }
+ .spacer {
+ height: 25px;
+ }
+ .spacer.col-md-0 {
+ height: 0;
+ }
+ .capacity-bar {
+ display: inline-block;
+ float: right;
+ margin-top: 8px;
+ width: 20%;
+ }
+ .list-group-item {
+ cursor: pointer;
+ .base-transition (background-color .1s linear 0s);
+ }
+ .list-group-item.last {
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ }
+ .list-group-item.first {
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+ }
}
-.form-horizontal .form-control-static {
- padding-top: 5px;
-}
-.queue-label {
- padding-right: 10px;
- min-width: 80px;
- font-weight: bold;
- font-size: 1.1em;
- padding-top: 5px;
-}
+// ----- SHEDULER PANEL -----
-.control-label.queue-label {
- padding-top: 7px;
+.panel-scheduler {
+ .panel-title {
+ .tooltip-inner{
+ white-space: pre;
+ max-width: none;
+ font-weight: bold;
+ }
+ i {
+ float: right;
+ margin: 3px 6px;
+ }
+ }
+ .panel-body {
+ .form-group {
+ .control-label {
+ @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
+ width: 100%;
+ text-align: left;
+ }
+ }
+ .control-value {
+ @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
+ width: 100%;
+ }
+ }
+ }
+ .input-int {
+ display: inline-block;
+ .input-int-width;
+ }
+ .input-percent-wrap {
+ & > div {
+ display: table-cell;
+ .input-percent {
+ .input-percent-width;
+ }
+ &.btn-group {
+ padding-left: 5px;
+ }
+ }
+ }
+ }
}
-.capacity-edit {
- font-weight: normal;
-}
+// ----- VERSIONS PANEL -----
-.max-capacity-edit {
- padding-left: 10px;
+.panel-versions {
+ #versions-table-wrap {
+ position: relative;
+ max-height: 400px;
+ overflow: hidden;
+ table {
+ margin-bottom: 0;
+ }
+ }
}
-.queue-heading-row .input-group h3 {
- margin-bottom: 0;
- margin-top: 3px;
-}
+// ----- QUEUE -----
-.form-horizontal .control-value {
- .btn-group{
- padding-top: 5px;
- }
-}
+.queue-area {
-.edit-link {
- font-size: .75em;
- padding-top: 4px;
-}
+ padding-left: 0;
-.control-label {
- font-size: .9em;
-}
-
-.input-group-addon {
- padding: 3px;
-}
-
-.well-queue {
+ .well-queue {
background-color: #ffffff;
border: 1px solid #e3e3e3;
border-radius: 4px;
- box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05) inset;
+ .base-shadow(0 1px 1px rgba(0, 0, 0, 0.05) inset);
margin-bottom: 20px;
min-height: 20px;
padding: 15px;
padding-top: 0px;
+
+ // ----- QUEUE -> HEADING ROW -----
+
+ .queue-heading-row {
+ h3 {
+ margin:10px;
+ margin-left: 0;
+ }
+ .rm-queue {
+ cursor: pointer;
+ .gray;
+ .base-transition (color .1s linear);
+ &:hover {
+ .red;
+ }
+ .fa-stack-bottom {
+ text-align: right;
+ bottom: 0;
+ line-height: 1em;
+ //opacity: 0.9;
+ text-shadow: 0px -1px 3px #111;
+ }
+ }
+
+ }
+
+ // ----- QUEUE -> CAPACITY ROW -----
+
+ .queue-capacity-row {
+ .panel-capacity {
+ .new-queue {
+ .input-row {
+ margin-bottom: 5px;
+ }
+ }
+ .progress {
+ background-color: #ddd;
+ .marks {
+ width: 100%;
+ top: 20px;
+ position: absolute;
+ label {
+ width: 25%;
+ display: block;
+ text-align: center;
+ position: absolute;
+ margin-left: -12.5%;
+ div {
+ position: absolute;
+ left: 50%;
+ top: -5px;
+ height: 20px;
+ border-left: solid 1px #fff;
+ .base-shadow(0px 0px 2px 1px rgba(0, 0, 0, .15));
+ }
+ }
+ }
+ }
+ .panel-title {
+ font-weight: 500;
+ position: relative;
+ a {
+ position: absolute;
+ right: 0;
+ color: #428bca;
+ }
+ .total {
+ float: right;
+ width: 400px;
+ .level-total-label {
+ float: left;
+ padding-right: 10px;
+ font-size:.9em;
+ font-weight: normal;
+ }
+ }
+ }
+ .panel-body {
+ padding: 0;
+ .queue-container {
+ padding: 15px;
+ background-color: #fefefe;
+ .base-shadow(inset 0 0 2px #ccc);
+ }
+
+ .input-percent {
+ .input-percent-width;
+ }
+ .progress {
+ margin: 0;
+ }
+ .peer-toggle {
+ width: 100%;
+ text-align: center;
+ padding: 3px;
+ a {
+ color: #555;
+ text-decoration: none;
+ }
+ }
+ .queue-capacity {
+
+ padding-bottom: 4px;
+
+ &.active {
+ .progress {
+ .base-shadow(0px 0px 0px #888888);
+ .base-transition (box-shadow .25s linear 0s);
+ }
+ .progress.in {
+ .base-shadow(3px 5px 5px #888888);
+ }
+ }
+ .rollback-wrap {
+ width: 30px;
+ display: inline-block;
+ }
+ .rollback-wrap-ghost {
+ @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
+ display: block;
+ }
+ @media (min-width: @screen-lg-min) {
+ display: inline-block;
+ }
+ }
+
+ .form-inline > .form-group {
+ vertical-align: top;
+ margin-bottom: 5px;
+ &:nth-of-type(odd) {
+ @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
+ width: 35%;
+ & label {
+ width: 55%;
+ }
+ }
+ }
+ &:nth-of-type(even) {
+ width: 22%;
+ @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
+ width: 55%;
+ }
+ }
+ label {
+ font-weight: normal;
+ }
+ }
+ .help-block {
+ display: inline;
+ font-size: .8em;
+ }
+ }
+ .capacity-edit-form {
+
+ .progress-striped {
+ #gradient > .striped();
+ background-size: 20px 20px;
+ border-left: 1px solid darken(@brand-success, 5%);
+ }
+
+ .btn-group {
+ padding: 4px;
+ }
+
+ input[type=range] {
+ -webkit-appearance: none;
+ width: 100%;
+ background-color: #fefefe;
+ //margin: 7.3px 0;
+ &:focus {
+ outline: none;
+ }
+ &::-webkit-slider-runnable-track {
+ width: 100%;
+ height: 2px;
+ cursor: pointer;
+ background: #555;
+ border-radius: 1.3px;
+ border: 0.2px solid #010101;
+ &:focus {
+ background: #555;
+ }
+ }
+ &::-webkit-slider-thumb {
+ border: 1.8px solid #00001e;
+ height: 15px;
+ width: 20px;
+ border-radius: 15px;
+ background: #ffffff;
+ cursor: pointer;
+ -webkit-appearance: none;
+ margin-top: -7px;
+ }
+
+ &::-moz-range-track {
+ width: 100%;
+ height: 2px;
+ cursor: pointer;
+ background: #555;
+ border-radius: 1.3px;
+ border: 0.2px solid #010101;
+ }
+ &::-moz-range-thumb {
+ border: 1.8px solid #00001e;
+ height: 15px;
+ width: 20px;
+ border-radius: 15px;
+ background: #ffffff;
+ cursor: pointer;
+ }
+
+ &::-ms-track {
+ width: 100%;
+ height: 2px;
+ cursor: pointer;
+ background: transparent;
+ border-color: transparent;
+ color: transparent;
+ }
+ &::-ms-thumb {
+ border: 1.8px solid #00001e;
+ width: 20px;
+ border-radius: 15px;
+ background: #ffffff;
+ cursor: pointer;
+ height: 11.4px;
+ }
+ &::-ms-fill-lower {
+ background: rgba(42, 100, 149, 0.78);
+ border: 0.2px solid #010101;
+ border-radius: 2.6px;
+ &:focus {
+ background: #555;
+ }
+ }
+ &::-ms-fill-upper {
+ background: #555;
+ border: 0.2px solid #010101;
+ border-radius: 2.6px;
+ &:focus {
+ background: #555;
+ }
+ }
+ }
+ }
+ .labels-capacity-wrap {
+ border: solid 1px #ccc;
+ border-radius: 3px;
+ padding: 10px;
+ .base-shadow (0px 1px 1px #ccc);
+ .label-capacity {
+ .progress {
+ margin-bottom: 5px;
+ }
+ .queue-capacity {
+ padding: 0;
+ small {
+ color: #737373;
+ }
+ }
+ //padding: 0px 15px;
+ input[type=range] {
+ //background-color: #d9edf7;
+ }
+ }
+ }
+
+ @toggle-inactive-bg: #fff;
+ @toggle-inactive-color: #555;
+ @toggle-inactive-border-color: #ccc;
+ @toggle-inactive-bar-bg: #ddd;
+ @toggle-inactive-bar-color: #555;
+
+ @toggle-active-bg: lighten(#5cb85c, 20%);
+ @toggle-active-color: #fff;
+ @toggle-active-bar-bg: #5cb85c;
+ @toggle-active-bar-color: #fff;
+ @toggle-active-bar-bcolor: darken(#5cb85c, 15%);
+
+ @toggle-active-warn-bg: lighten(#d9534f, 30%);
+ @toggle-active-bar-warn-bg: #d9534f;
+ @toggle-active-bar-warn-bcolor:darken(#d9534f, 15%);
+
+ @toggle-not-exist-bar-bg: #eea236;
+ @toggle-not-exist-bar-bcolor: darken(#eea236, 15%);
+
+ @toggle-active-not-exist-bg: lighten(#eea236, 20%);
+ @toggle-active-not-exist-bar-bg: #eea236;
+ @toggle-active-not-exist-bar-bcolor: darken(#eea236, 15%);
+
+ .labels-toggle-wrap {
+ text-align: right;
+ margin: 10px 0 5px 0;
+ .fa.sign {
+ padding: 0 10px 5px 0;
+ &.fa-asterisk {
+ color: darken(@brand-success,10%);
+ }
+ &.fa-sliders {
+ color: #737373;
+ }
+ }
+ small {
+ margin-right: 10px;
+ color: #737373;
+ }
+ .tooltip {
+ margin-top: 2px;
+ opacity: .8;
+ }
+ .tooltip-arrow {
+ display: none;
+ }
+ .labels-toggle-all {
+ display: inline-table;
+ //width: 5%;
+ float: right;
+ height: 0;
+ margin-bottom: 5px;
+ .btn {
+ padding: 0 8px;
+ }
+ }
+ .labels-toggle {
+ margin-bottom: 5px;
+ display: inline-table;
+
+ label {
+ span {
+ position: absolute;
+ width: 100%;
+ right: 0;
+ }
+ padding: 0;
+ color: @toggle-inactive-color;
+ background-color: @toggle-inactive-bg;
+ border-color: @toggle-inactive-border-color;
+ &:hover {
+ color: darken(@toggle-inactive-color, 10%);
+ background-color: darken(@toggle-inactive-bg, 8%);
+ border-color: darken(@toggle-inactive-border-color, 12%);
+ }
+ .progress-bar {
+ height: 18px;
+ background-color: @toggle-inactive-bar-bg;
+ color: @toggle-inactive-bar-color;
+ &:hover {
+ background-color: darken(@toggle-inactive-bar-bg, 8%);
+ }
+ .base-shadow(none);
+ }
+ .progress-bar.ghost, &.active .progress-bar.ghost, &.active.warning .progress-bar.ghost {
+ .base-shadow(inset 0 -4px 0 rgba(0, 0, 0, .25));
+ background-color: transparent !important;
+ position: absolute;
+ }
+
+ &.not-exist {
+ //border-color: @toggle-not-exist-bar-bcolor;
+ border-style: dashed;
+ .progress-bar {
+ //background-color: @toggle-not-exist-bar-bg;
+ }
+ /*&.active {
+ background-color: @toggle-active-not-exist-bg;
+ border-color: @toggle-active-not-exist-bar-bcolor;
+ .progress-bar {
+ background-color:@toggle-active-not-exist-bar-bg;
+ }
+ }*/
+ }
+
+ &.active {
+ color: @toggle-active-color;
+ background-color: @toggle-active-bg;
+ border-color: @toggle-active-bar-bcolor;
+ .progress-bar {
+ color: @toggle-active-bar-color;
+ background-color: @toggle-active-bar-bg;
+ }
+ &:hover {
+ color: lighten(@toggle-active-color, 8%);
+ background-color: darken(@toggle-active-bg, 8%);
+ border-color: darken(@toggle-active-bar-bcolor, 12%);
+ .progress-bar {
+ color: lighten(@toggle-active-bar-color, 8%);
+ background-color: darken(@toggle-active-bar-bg,8%);
+ }
+ }
+
+ &.not-exist {
+ border-style: solid;
+ background-color: @toggle-active-not-exist-bg;
+ border-color: @toggle-active-not-exist-bar-bcolor;
+ .progress-bar {
+ background-color:@toggle-active-not-exist-bar-bg;
+ }
+ }
+
+ &.warning {
+ background-color: @toggle-active-warn-bg;
+ border-color: @toggle-active-bar-warn-bcolor;
+ &:hover {
+ background-color: darken(@toggle-active-warn-bg,8%);
+ border-color: darken( @toggle-active-bar-warn-bcolor, 12%);
+ .progress-bar {
+ background-color: darken(@toggle-active-bar-warn-bg,8%);
+ }
+ }
+ .progress-bar {
+ background-color: @toggle-active-bar-warn-bg;
+ }
+ }
+ }
+ }
+ .btn.first {
+ border-top-left-radius: 3px;
+ border-bottom-left-radius: 3px;
+ }
+ .btn.last {
+ border-top-right-radius: 3px;
+ border-bottom-right-radius: 3px;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // ----- QUEUE -> ACL AND RESOURCES ROW -----
+ .queue-acl-row {
+
+ // ----- QUEUE -> ACL AND RESOURCES ROW -> ACL PANEL -----
+ .queue-acl{
+ .form-acl {
+ .users .label,.groups .label {
+ border-radius: 0;
+ }
+ .btn-group{
+ padding-top: 5px;
+ }
+ .users .label:first-child,.groups .label:first-child {
+ border-top-left-radius: .25em;
+ border-bottom-left-radius: .25em;
+ }
+ .users .label:last-child,.groups .label:last-child {
+ border-top-right-radius: .25em;
+ border-bottom-right-radius: .25em;
+ }
+ .label {
+ vertical-align: text-top;
+ }
+ .user-group-input.has-feedback {
+ .form-control-feedback {
+ top: -2px;
+ right: 15px;
+ opacity: 0.4;
+ }
+ }
+ }
+ }
+
+ // ----- QUEUE -> ACL AND RESOURCES ROW -> RESOURCES PANEL -----
+ .queue-resources {
+ .input-int {
+ display: inline-block;
+ .input-int-width;
+ }
+ .input-percent-wrap {
+ & > div {
+ display: table-cell;
+ .input-percent {
+ .input-percent-width;
+ }
+ &.btn-group {
+ padding-left: 5px;
+ }
+ }
+ }
+ }
+
+ }
+
}
+}
+
+// ----- COMPONENTS -----
-.col-md-8.queue-area {
- padding-left:0px;
+
+.base-shadow (@shadow) {
+ -webkit-box-shadow: @shadow;
+ -moz-box-shadow: @shadow;
+ box-shadow: @shadow;
}
-.btn {
- padding: 6px 10px;
- transition: background-color .08s linear 0s;
+.base-transition (@transition) {
+ -webkit-transition: @transition;
+ -moz-transition: @transition;
+ -o-transition: @transition;
+ -ms-transition: @transition;
+ transition: @transition;
}
-.input-error {
- color: #a94442;
- border-color: #a94442;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+.common-shadow {
+ .base-shadow(0 5px 10px rgba(0, 0, 0, 0.2));
}
-.input-error:focus {
- border-color: #843534;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
+.common-shadow-inset {
+ .base-shadow(inset 0 1px 1px rgba(0, 0, 0, 0.05));
}
.gray {
@@ -169,228 +734,35 @@ a .peer-toggle {
color: #d9534f;
}
-.add-queue {
- margin-bottom: 5px;
-}
-
-.rm-queue {
- cursor: pointer;
- .gray;
- transition: color .1s linear;
- &:hover {
- .red;
- }
-}
-
-.fa-stack-bottom {
- text-align: right;
- bottom: 0;
- line-height: 1em;
- //opacity: 0.9;
- text-shadow: 0px -1px 3px #111;
-}
-
-.queue-list{
- .badge {
- color: #999;
- background-color: #fff;
- margin-left: 5px;
- }
- .progress {
- height: 3px;
- margin: 0;
- }
- .spacer {
- height: 25px;
- }
- .spacer.col-md-0 {
- height: 0;
- }
- .capacity-bar {
- display: inline-block;
- float: right;
- margin-top: 8px;
- width: 20%;
- }
- .list-group-item {
- cursor: pointer;
- transition: background-color .1s linear 0s;
- }
- .list-group-item.last {
- border-bottom-left-radius: 4px;
- border-bottom-right-radius: 4px;
- }
- .list-group-item.first {
- border-top-left-radius: 4px;
- border-top-right-radius: 4px;
- }
-}
-
-.path-input {
- .btn-cancel {
- border-radius: 0;
- }
-}
-
-.user-group-input.has-feedback {
- .form-control-feedback {
- top: -2px;
- right: 15px;
- opacity: 0.4;
- }
-}
-
-.btn-group-save {
- table-layout: auto;
- & > .btn-group:last-child > .btn:first-child {
- border-top-left-radius: 4px;
- border-bottom-left-radius: 4px;
- }
- & > .btn-group:first-child > .dropdown-toggle {
- border-top-right-radius: 4px;
- border-bottom-right-radius: 4px;
- }
- .dropdown-menu .btn {
- border-radius: 0;
- text-align: left;
- }
- .btn-group {
- width: 100%;
- .btn-save {
- border-top-left-radius: 4px !important;
- border-bottom-left-radius: 4px !important;
- }
- }
-}
-
-.form-acl {
- .users .label,.groups .label {
- border-radius: 0;
- }
- .users .label:first-child,.groups .label:first-child {
- border-top-left-radius: .25em;
- border-bottom-left-radius: .25em;
- }
- .users .label:last-child,.groups .label:last-child {
- border-top-right-radius: .25em;
- border-bottom-right-radius: .25em;
- }
- .label {
- vertical-align: text-top;
- }
+.input-int-width {
+ width: 110px;
}
-.panel-capacity {
- .new-queue {
- .input-row {
- margin-bottom: 5px;
- }
- }
- .progress{
- background-color: #ddd;
- .marks {
- width: 100%;
- top: 20px;
- position: absolute;
- label {
- width: 25%;
- display: block;
- text-align: center;
- position: absolute;
- margin-left: -12.5%;
- div {
- position: absolute;
- left: 50%;
- top: -5px;
- height: 20px;
- border-left: solid 1px #fff;
- -webkit-box-shadow:0px 0px 2px 1px rgba(0, 0, 0, .15);
- box-shadow:0px 0px 2px 1px rgba(0, 0, 0, .15);
- }
- }
- }
- }
- .panel-title {
- font-weight: 500;
- position: relative;
- a {
- position: absolute;
- right: 0;
- color: #428bca;
- }
- .total {
- float: right;
- width: 400px;
- .level-total-label {
- float: left;
- padding-right: 10px;
- font-size:.9em;
- font-weight: normal;
- }
- }
- }
- .panel-body.total{
- .progress {
- margin: 0;
- }
- }
- .panel-body.queues {
- padding-left: 0;
- padding-right: 0;
- }
- .panel-body {
- padding-top: 0px;
- }
-
- .queues {
- padding-top: 0px;
- }
-
- .queue-capacity-peer {
- }
-
- .queue-capacity {
- padding-bottom: 4px;
- &.active {
- .progress {
- -webkit-box-shadow: 0px 0px 0px #888888;
- -moz-shadow: 0px 0px 0px #888888;
- box-shadow: 0px 0px 0px #888888;
- transition: box-shadow .25s linear 0s;
- }
- .progress.in {
- -webkit-box-shadow: 3px 5px 5px #888888;
- -moz-shadow: 3px 5px 5px #888888;
- box-shadow: 3px 5px 5px #888888;
- }
- }
- .form-inline .form-group {
- display: inline-block;
- margin-bottom: 0;
- vertical-align: top;
- }
- .help-block {
- display: inline;
- font-size: .8em;
- }
- /*input {
- width: auto;
- }*/
+.input-percent-width {
+ width: 65px;
+ .input-group-addon {
+ padding: 3px;
}
}
-.control-label.capacity-edit {
- width: 75px;
+.control-label {
+ font-size: .9em;
}
-.capacity-input {
- float: left !important;
+.btn {
+ padding: 6px 10px;
+ .base-transition(background-color .08s linear 0s)
}
-.queue-heading-row {
- h3 {
- margin:10px;
- margin-left: 0px;
+.input-error {
+ color: #a94442;
+ border-color: #a94442;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ &:focus {
+ border-color: #843534;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
}
}
@@ -403,88 +775,3 @@ a .peer-toggle {
.modal-backdrop.in {
opacity: 0;
}
-
-
-
-input[type=range] {
- -webkit-appearance: none;
- width: 100%;
- margin: 7.3px 0;
-}
-input[type=range]:focus {
- outline: none;
-}
-input[type=range]::-webkit-slider-runnable-track {
- width: 100%;
- height: 2px;
- cursor: pointer;
- background: #555;
- border-radius: 1.3px;
- border: 0.2px solid #010101;
-}
-input[type=range]::-webkit-slider-thumb {
- border: 1.8px solid #00001e;
- height: 15px;
- width: 20px;
- border-radius: 15px;
- background: #ffffff;
- cursor: pointer;
- -webkit-appearance: none;
- margin-top: -7px;
-}
-input[type=range]:focus::-webkit-slider-runnable-track {
- background: #555;
-}
-input[type=range]::-moz-range-track {
- width: 100%;
- height: 2px;
- cursor: pointer;
- background: #555;
- border-radius: 1.3px;
- border: 0.2px solid #010101;
-}
-input[type=range]::-ms-fill-lower {
- background: red;
- border-radius: 10px;
-}
-input[type=range]::-moz-range-thumb {
- border: 1.8px solid #00001e;
- height: 15px;
- width: 20px;
- border-radius: 15px;
- background: #ffffff;
- cursor: pointer;
-}
-input[type=range]::-ms-track {
- width: 100%;
- height: 2px;
- cursor: pointer;
- background: transparent;
- border-color: transparent;
- color: transparent;
-}
-input[type=range]::-ms-fill-lower {
- background: rgba(42, 100, 149, 0.78);
- border: 0.2px solid #010101;
- border-radius: 2.6px;
-}
-input[type=range]::-ms-fill-upper {
- background: #555;
- border: 0.2px solid #010101;
- border-radius: 2.6px;
-}
-input[type=range]::-ms-thumb {
- border: 1.8px solid #00001e;
- height: 15px;
- width: 20px;
- border-radius: 15px;
- background: #ffffff;
- cursor: pointer;
- height: 11.4px;
-}
-input[type=range]:focus::-ms-fill-lower {
- background: #555;
-}
-input[type=range]:focus::-ms-fill-upper {
- background: #555;
-}
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates.js
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates.js
index 18c06a7..4f08004 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates.js
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates.js
@@ -34,3 +34,5 @@ require('templates/components/totalCapacity');
require('templates/components/queueListItem');
require('templates/components/pathInput');
require('templates/components/userGroupInput');
+require('templates/components/queueContainer');
+require('templates/components/dropdownConfirmation');
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capacityEditForm.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capacityEditForm.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capacityEditForm.hbs
index 535a18f..d5a3fb6 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capacityEditForm.hbs
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capacityEditForm.hbs
@@ -16,30 +16,65 @@
* limitations under the License.
}}
-<form {{bind-attr class=":form-horizontal :capacity-edit-form " }} role="form">
- <div {{bind-attr class=":form-group" }}>
- <label class="col-xs-1 control-label capacity-edit">Capacity: </label>
- <div class="col-xs-2 capacity-input control-value input-group input-percent">
-{{capacity-input class='input-sm' value=this.capacity totalCapacity=view.totalCapacity queue=this maxVal=100}}
-<span class="input-group-addon">%</span>
- </div>
- <div class="col-xs-3 control-value">
-{{input-range min="0" max="100" step="1" value=this.capacity}}
- </div>
- <label style="padding-top: 0px" class="col-xs-1 control-label capacity-edit">Max Capacity: </label>
- <div {{bind-attr class="this.isValid::has-error" }}>
- <div class="col-xs-2 capacity-input control-value input-group input-percent">
-{{max-capacity-input class='input-sm' value=this.maximum_capacity totalCapacity=view.totalCapacity queue=this maxVal=100}}
-<span class="input-group-addon">%</span>
- </div>
- <div class="col-xs-3 control-value">
-{{input-range min="0" max="100" step="1" value=this.maximum_capacity}}
- {{#each this.errors.maximum_capacity}}
- <span class="help-block">
- {{message}}
- </span>
- {{/each}}
- </div>
- </div>
- </div>
+<form {{bind-attr class=":form-inline :capacity-edit-form parentController.isOperator::text-center" }} role="form">
+
+ {{#if parentController.isOperator}}
+ <div class="form-group">
+ <label>Capacity:</label>
+ <div class="form-group">
+ <div class="input-group input-percent">
+ {{capacity-input class='input-sm' value=this.capacity totalCapacity=view.totalCapacity queue=this maxVal=100}}
+ <span class="input-group-addon">%</span>
+ </div>
+ </div>
+ </div>
+ <div class="form-group">
+ {{input-range min="0" max="100" step="1" value=this.capacity class="input-sm"}}
+ </div>
+ <div class="rollback-wrap">
+ {{#if view.dirty_capacity}}
+ <div class="btn-group btn-group-xs pull-left" >
+ <a {{action 'rollbackProp' 'capacity' this}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a>
+ </div>
+ {{/if}}
+ </div>
+ <div class="rollback-wrap-ghost"></div>
+
+ <div {{bind-attr class=":form-group this.isValid::has-error" }}>
+ <label>Max Capacity:</label>
+ <div class="form-group">
+ <div class="input-group input-percent">
+ {{max-capacity-input class='input-sm' value=this.maximum_capacity totalCapacity=view.totalCapacity queue=this maxVal=100}}
+ <span class="input-group-addon">%</span>
+ </div>
+ </div>
+ </div>
+ <div {{bind-attr class=":form-group this.isValid::has-error" }}>
+ {{input-range min="0" max="100" step="1" value=this.maximum_capacity class="input-sm "}}
+ {{#each this.errors.maximum_capacity}}
+ <span class="help-block">{{message}}</span>
+ {{/each}}
+ </div>
+ <div class="rollback-wrap">
+ {{#if view.dirty_maxcapacity}}
+ <div class="btn-group btn-group-xs pull-left" >
+ <a {{action 'rollbackProp' 'maximum_capacity' this}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a>
+ </div>
+ {{/if}}
+ </div>
+ {{else}}
+ <div class="form-group">
+ <label>Capacity:
+ <span>{{capacity}}%</span>
+ </label>
+ </div>
+ <div class="form-group">
+ <label>Max Capacity:
+ <span>{{maximum_capacity}}%</span>
+ </label>
+ </div>
+ {{capacity-bar capacityValue=capacity maxCapacityValue=maximum_capacity}}
+
+ {{/if}}
+
</form>
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/capacityBar.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/capacityBar.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/capacityBar.hbs
index 05741f0..2216c8f 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/capacityBar.hbs
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/capacityBar.hbs
@@ -22,9 +22,9 @@
{{capacityValue}}%
</span>
</div>
- <div class="progress-bar progress-bar-warning" {{bind-attr style="maxCapacityWidth"}}>
+ <div class="progress-bar progress-bar-success progress-striped" {{bind-attr style="maxCapacityWidth"}}>
</div>
-
+
<div class="marks">
<label style="left: 25%;"><div></div></label>
<label style="left: 50%;"><div></div></label>
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/dropdownConfirmation.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/dropdownConfirmation.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/dropdownConfirmation.hbs
new file mode 100644
index 0000000..8f3193b
--- /dev/null
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/dropdownConfirmation.hbs
@@ -0,0 +1,29 @@
+{{!
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+}}
+
+{{#view view.button action="showRestartConfirmation" classBinding=":btn needRestart::disabled"}}<i class="fa fa-fw fa fa-cogs"></i> Save and Restart ResourceManager{{/view}}
+{{#if view.restartConfirming}}
+ <div class="btn-group btn-group-justified">
+ <div class="btn-group">
+ <a {{action showRestartConfirmation target="view"}} class="btn btn-sm btn-danger"><i class="fa fa-fw fa-lg fa-times"></i> Cancel</a>
+ </div>
+ <div class="btn-group">
+ <a {{action 'confirm' target="view"}} class="btn btn-sm btn-success"><i class="fa fa-fw fa-lg fa-check"></i> Restart</a>
+ </div>
+ </div>
+{{/if}}
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/pathInput.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/pathInput.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/pathInput.hbs
index d1bcd11..54c67f7 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/pathInput.hbs
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/pathInput.hbs
@@ -19,10 +19,10 @@
<div {{bind-attr class=":input-group :path-input isError:has-error" }} >
{{view inputFieldView pathSource=pathMap value=path}}
<span class="input-group-btn">
- <button {{action cancel}} class="btn btn-danger" type="button">
+ <button {{action 'cancel'}} class="btn btn-danger" type="button">
<i class="fa fa-times"></i>
</button>
- <button {{action add}} class="btn btn-success" type="button">
+ <button {{action 'add'}} class="btn btn-success" type="button">
<i class="fa fa-check"></i>
</button>
</span>
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueContainer.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueContainer.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueContainer.hbs
new file mode 100644
index 0000000..77c33f6
--- /dev/null
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueContainer.hbs
@@ -0,0 +1,67 @@
+{{!
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+}}
+
+
+<div class="queue-container">
+ <div class="queue-capacity">
+ <h5><strong>{{this.name}}</strong></h5>
+ {{render "capacityEditForm" this}}
+ </div>
+ <div class="labels-toggle-wrap">
+ <small> Node Labels Access</small>
+ {{#if controller.isOperator}}
+ <div class="btn-group btn-group-xs labels-toggle-all" >
+ <button {{action 'toggleProperty' 'accessAllLabels' this}} type="button" {{bind-attr class=":btn this.accessAllLabels:btn-success:btn-default"}}><i class="fa fa-asterisk"></i></button>
+ </div>
+ {{else}}
+ {{#if accessAllLabels}}
+ <i class="fa fa-asterisk sign"></i>
+ {{else}}
+ <i class="fa fa-sliders sign"></i>
+ {{/if}}
+ {{/if}}
+ {{#each row in controller.arrangedNodeLabels}}
+ <div class="btn-group btn-group-xs btn-group-justified labels-toggle">
+ {{#each label in row}}
+ {{#view 'view.nodeLabelsToggles' labelName=label.name queue=this leaf=controller.leafQueues notExist=label.notExist}}
+ <span> {{label.name}} </span>
+ {{view 'view.nodeLabelsBar'}}
+ {{view 'view.currentQueueBar'}}
+ {{/view}}
+ {{/each}}
+ </div>
+ {{/each}}
+ </div>
+ {{#if this.labels}}
+ <div class="labels-capacity-wrap">
+ {{#each label in this.sortedLabels}}
+ <div class="label-capacity">
+ <div class="queue-capacity">
+ {{#if label.isNotExist}}
+ <span {{bind-attr class=":label label.overCapacity:label-danger:label-warning"}}>{{label.name}}</span>
+ <small>Label is not exist on cluster</small>
+ {{else}}
+ <span {{bind-attr class=":label label.overCapacity:label-danger:label-success"}}>{{label.name}}</span>
+ {{/if}}
+ {{render "capacityEditForm" label}}
+ </div>
+ </div>
+ {{/each}}
+ </div>
+ {{/if}}
+</div>
\ 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/templates/components/queueListItem.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueListItem.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueListItem.hbs
index 9c23541..423139e 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueListItem.hbs
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueListItem.hbs
@@ -16,45 +16,49 @@
* limitations under the License.
}}
-{{#each view.leaf}}
- {{#link-to "queue" this classNameBindings=":list-group-item overCapacity:list-group-item-danger isNew:list-group-item-info" }}
- <div class="col-md-offset-{{unbound this.depth}} col-sm-offset-{{unbound this.depth}} col-xs-offset-{{unbound this.depth}}">
-
- {{name}} ({{capacity}}%)
+{{#each queue in view.leaf }}
+ {{#link-to "queue" queue classNameBindings=":list-group-item queue.overCapacity:list-group-item-danger queue.isNew:list-group-item-info" }}
+ <span class="col-md-offset-{{unbound queue.depth}} col-sm-offset-{{unbound queue.depth}} col-xs-offset-{{unbound queue.depth}}">
+
+ {{queue.name}} ({{queue.capacity}}%)
+
+ {{#if queue.version}}
+ <span class="label label-info">v{{queue.version}}</span>
+ {{/if}}
<span class="badge pull-right">
- {{#if overCapacity}}
+ {{#if queue.overCapacity}}
<i class="fa fa-fw fa-lg red fa-warning"></i>
{{/if}}
- {{#if isNewQueue }}
- {{#if isSaving}}
+ {{#if queue.isNewQueue }}
+ {{#if queue.isSaving}}
<i class="fa fa-fw fa-lg gray fa-spinner fa-spin"></i>
{{else}}
- <i class="fa fa-fw fa-lg blue fa-plus"></i>
+ <i class="fa fa-fw fa-lg blue fa-refresh"></i>
{{/if}}
{{else}}
- {{#if isError}}
- {{#if isSaving}}
+ {{#if queue.isError}}
+ {{#if queue.isSaving}}
<i class="fa fa-fw fa-lg gray fa-spinner fa-spin"></i>
{{else}}
<span> queue was not saved </span> <i class="fa fa-fw fa-lg red fa-warning"></i>
{{/if}}
- {{else}}
- {{#if isDirty}}
- {{#if isSaving}}
+ {{else}}
+ {{#if queue.isAnyDirty}}
+ {{#if queue.isSaving}}
<i class="fa fa-fw fa-lg gray fa-spinner fa-spin"></i>
{{else}}
- <i class="fa fa-fw fa-lg blue fa-pencil"></i>
+ {{diff-tooltip queue=queue}}
{{/if}}
{{else}}
- <i class="fa fa-fw fa-lg green fa-check"></i>
+ <i class="fa fa-fw fa-lg green fa-check"></i>
{{/if}}
{{/if}}
{{/if}}
</span>
-
- </div>
+
+ </span>
{{/link-to}}
- {{recurce-queues depth=view.childDepth parent=this.path}}
+ {{recurce-queues depth=view.childDepth parent=queue.path}}
{{/each}}
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/totalCapacity.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/totalCapacity.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/totalCapacity.hbs
index 6e50328..06394ff 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/totalCapacity.hbs
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/totalCapacity.hbs
@@ -19,51 +19,34 @@
<div class="panel panel-default panel-capacity">
<div class="panel-heading">
<div class="panel-title">
- Capacity
- <div class="total">
-<div class="level-total-label">Level Total</div>
-{{capacity-bar capacityValue=totalCapacity maxCapacityValue=totalCapacity warn=leafQueues.firstObject.overCapacity}}
- </div>
+ Capacity
+ <div class="total">
+ <div class="level-total-label">Level Total</div>
+ {{capacity-bar capacityValue=totalCapacity maxCapacityValue=totalCapacity warn=currentQueue._overCapacity}}
+ </div>
</div>
</div>
- <div class="panel-body total">
- <div class="panel-body queues">
- {{#each leafQueues}}
- {{#if this.isCurrent}}
- <div {{bind-attr class=":queue-capacity this.isCurrent:active"}}>
- <div class="queue-label">{{this.name}}</div>
- {{render "capacityEditForm" this}}
- <a href="#" {{action toggleProperty "showPeerQueues"}}>
+ <div class="panel-body">
+ {{#with currentQueue}}
+ {{partial 'components/queueContainer'}}
+ {{/with}}
<div class="peer-toggle">
-Show/Hide Peer Level Queues
+ <a href="#" {{action 'toggleProperty' 'showPeerQueues'}}>
+ {{#if showPeerQueues}}
+ <small><i class="fa fa-chevron-up"></i> Hide Peer Level Queues </small>
+ {{else}}
+ <small><i class="fa fa-chevron-down"></i> Show Peer Level Queues </small>
+ {{/if}}
+ </a>
</div>
- </a>
- </div>
- {{/if}}
- {{/each}}
-{{#if showPeerQueues}}
- {{#each leafQueues}}
- {{#unless this.isCurrent}}
- <div {{bind-attr class=":queue-capacity :queue-capacity-peer"}}>
- <p>
- <div class="queue-label">{{this.name}}</div>
- </p>
- {{render "capacityEditForm" this}}
- </div>
- {{/unless}}
- {{/each}}
-{{/if}}
- {{#each newLeafQueues}}
- <div {{bind-attr class=":queue-capacity :queue-capacity-peer :new-queue this.isCurrent:active"}} >
- <div class="input-row row" >
- <div class="col-md-5">
- {{#each this.errors.path}}
- <p class="help-block red">{{this.message}}</p>
- {{/each}}
- </div>
- </div>
- {{render "capacityEditForm" this}}
- </div>
- {{/each}}
+ {{#if showPeerQueues}}
+ {{#each queue in leafQueues}}
+ {{#unless queue.isCurrent}}
+ {{#with queue}}
+ {{partial 'components/queueContainer'}}
+ {{/with}}
+ {{/unless}}
+ {{/each}}
+ {{/if}}
</div>
</div>
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/queue.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/queue.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/queue.hbs
index 93e069e..395f436 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/queue.hbs
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/queue.hbs
@@ -34,7 +34,7 @@
{{#unless isRoot}}
{{#unless isNotOperator}}
<small>
- <a href="#" {{action "renameQ" 'ask'}}>
+ <a href="#" {{action 'renameQ' 'ask'}}>
<i class="fa fa-edit"></i>
</a>
</small>
@@ -47,8 +47,8 @@
<div class="input-group col-md-8">
{{focus-input value=content.name length="250" class="form-control" action="renameQ" revert="renameQ" classBinding="content.isValid::input-error" placeholder="Enter Queue Name"}}
<span class="input-group-btn">
- <button {{action "renameQ" 'cancel'}} {{bind-attr class=":btn :btn-danger"}} type="button">Cancel</button>
- <button {{action "renameQ" 'rename'}} {{bind-attr class=":btn :btn-success content.isValid::disabled"}} type="button">Rename</button>
+ <button {{action 'renameQ' 'cancel'}} {{bind-attr class=":btn :btn-danger"}} type="button">Cancel</button>
+ <button {{action 'renameQ' 'rename'}} {{bind-attr class=":btn :btn-success content.isValid::disabled"}} type="button">Rename</button>
</span>
</div>
{{/unless}}
@@ -62,21 +62,18 @@
</div>
<div class="row queue-capacity-row">
<div class="col-md-12">
- {{total-capacity
- currentQueue=content
- allQueues=allQueues
- allQueuesArranged=allQueuesArranged
- addQueue="addQ"
- createQueue="createQ"
- deleteQueue="delQ"
- hasNew=controllers.queues.hasNewQueue
+ {{total-capacity
+ currentQueue=content
+ allQueues=allQueues
+ allQueuesArranged=controllers.queues.arrangedContent
isOperator=isOperator
+ rollbackProp="rollbackProp"
}}
</div>
</div>
<div class="row queue-acl-row">
- <div class="col-md-6">
- <div class="panel panel-default panel-capacity">
+ <div class="col-md-12 col-lg-6 queue-acl">
+ <div class="panel panel-default">
<div class="panel-heading">
<div class="panel-title">
Access Control and Status
@@ -95,9 +92,14 @@
<input type="radio" name="options"> Stopped
</label>
</div>
+ {{#if queueDirtyFilelds.state}}
+ <div class="btn-group btn-group-xs" >
+ <a {{action 'rollbackProp' 'state'}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a>
+ </div>
+ {{/if}}
</div>
</div>
-
+
{{#if isOperator}}
<div class="form-group row">
@@ -107,8 +109,10 @@
{{radio-button label="Anyone" selectionBinding="acl_administer_queue" value="*"}}
{{radio-button label="Custom" selectionBinding="acl_administer_queue" value="custom"}}
</div>
- {{#if aaq_dirty}}
- <a {{action 'rollbackProp' 'acl_administer_queue'}} href="#"><i class="fa fa-undo"></i></a>
+ {{#if queueDirtyFilelds.acl_administer_queue}}
+ <div class="btn-group btn-group-xs" >
+ <a {{action 'rollbackProp' 'acl_administer_queue'}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a>
+ </div>
{{/if}}
</div>
</div>
@@ -123,22 +127,24 @@
{{radio-button label="Anyone" selectionBinding="acl_submit_applications" value="*"}}
{{radio-button label="Custom" selectionBinding="acl_submit_applications" value="custom"}}
</div>
- {{#if asa_dirty}}
- <a {{action 'rollbackProp' 'acl_submit_applications'}} href="#"><i class="fa fa-undo"></i></a>
+ {{#if queueDirtyFilelds.acl_submit_applications}}
+ <div class="btn-group btn-group-xs" >
+ <a {{action 'rollbackProp' 'acl_submit_applications'}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a>
+ </div>
{{/if}}
</div>
</div>
{{#unless asa_anyone}}
{{user-group-input ug=content.acl_submit_applications disabled=asa_anyone}}
{{/unless}}
-
+
{{else}}
<div class="form-group">
<label class="col-lg-4 col-xs-4 control-label">Administer Queue</label>
<div class="col-lg-8 col-xs-8 control-value">
<p class="form-control-static">
{{escapeACL content.acl_administer_queue}}
- </p>
+ </p>
</div>
</div>
@@ -147,7 +153,7 @@
<div class="col-lg-8 col-xs-8 control-value">
<p class="form-control-static">
{{escapeACL content.acl_submit_applications}}
- </p>
+ </p>
</div>
</div>
{{/if}}
@@ -156,8 +162,8 @@
</div>
</div>
- <div class="col-md-6 queue-resources">
- <div class="panel panel-default panel-capacity">
+ <div class="col-md-12 col-lg-6 queue-resources">
+ <div class="panel panel-default">
<div class="panel-heading">
<div class="panel-title">
Resources
@@ -168,64 +174,92 @@
<div class="form-group">
<label class="col-xs-6 control-label">User Limit Factor</label>
{{#if isOperator}}
- <div class="col-xs-6 control-value">
- {{int-input value=content.user_limit_factor maxlength=10 class="input-sm input-int"}}
+ <div class="col-xs-6 control-value">
+ {{int-input value=content.user_limit_factor maxlength=10 class="input-sm input-int" defaultVal=1 placeholder=1}}
+ {{#if queueDirtyFilelds.user_limit_factor}}
+ <div class="btn-group btn-group-xs" >
+ <a {{action 'rollbackProp' 'user_limit_factor'}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a>
+ </div>
+ {{/if}}
+ </div>
{{else}}
- <div class="col-xs-6">
- <p class="form-control-static">{{content.user_limit_factor}}</p>
+ <div class="col-xs-6">
+ <p class="form-control-static">{{content.user_limit_factor}}</p>
+ </div>
{{/if}}
- </div>
</div>
<div class="form-group">
<label class="col-xs-6 control-label">Minimum User Limit</label>
- {{#if isOperator}}
- <div class="col-xs-6 control-value">
- <div class="input-group input-percent">
- {{int-input value=content.minimum_user_limit_percent class="input-sm" maxVal=100}}
- <span class="input-group-addon">%</span>
+ {{#if isOperator}}
+ <div class="col-xs-6 control-value input-percent-wrap">
+ <div>
+ <div class="input-group input-percent">
+ {{int-input value=content.minimum_user_limit_percent class="input-sm" maxVal=100 placeholder=100}}
+ <span class="input-group-addon">%</span>
+ </div>
</div>
- {{else}}
- <div class="col-xs-6">
+ {{#if queueDirtyFilelds.minimum_user_limit_percent}}
+ <div class="btn-group btn-group-xs" >
+ <a {{action 'rollbackProp' 'minimum_user_limit_percent'}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a>
+ </div>
+ {{/if}}
+ </div>
+ {{else}}
+ <div class="col-xs-6">
{{#if content.minimum_user_limit_percent}}
<p class="form-control-static">{{content.minimum_user_limit_percent}} %</p>
{{else}}
- <p class="form-control-static">-</p>
+ <p class="form-control-static">100</p>
{{/if}}
- {{/if}}
- </div>
+ </div>
+ {{/if}}
</div>
<div class="form-group">
<label class="col-xs-6 control-label">Maximum Applications</label>
{{#if isOperator}}
- <div class="col-xs-6 control-value">
- {{int-input placeholder="Inherited" maxlength=15 value=content.maximum_applications class="input-sm input-int"}}
+ <div class="col-xs-6 control-value">
+ {{int-input placeholder="Inherited" maxlength=15 value=content.maximum_applications class="input-sm input-int"}}
+ {{#if queueDirtyFilelds.maximum_applications}}
+ <div class="btn-group btn-group-xs" >
+ <a {{action 'rollbackProp' 'maximum_applications'}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a>
+ </div>
+ {{/if}}
+ </div>
{{else}}
- <div class="col-xs-6">
+ <div class="col-xs-6">
{{#if content.maximum_applications}}
<p class="form-control-static">{{content.maximum_applications}}</p>
- {{else}}
- <p class="form-control-static">-</p>
+ {{else}}
+ <p class="form-control-static">Inherited</p>
{{/if}}
- {{/if}}
</div>
+ {{/if}}
</div>
<div class="form-group">
<label class="col-xs-6 control-label">Maximum AM Resource</label>
{{#if isOperator}}
- <div class="col-xs-6 control-value">
- <div class="input-group input-percent">
- {{int-input placeholder="Inherited" value=content.maximum_am_resource_percent class="input-sm" maxVal=100}}
- <span class="input-group-addon">%</span>
+ <div class="col-xs-6 control-value input-percent-wrap">
+ <div>
+ <div class="input-group input-percent">
+ {{int-input placeholder="Inherited" value=content.maximum_am_resource_percent class="input-sm" maxVal=100}}
+ <span class="input-group-addon">%</span>
+ </div>
</div>
+ {{#if queueDirtyFilelds.maximum_am_resource_percent}}
+ <div class="btn-group btn-group-xs" >
+ <a {{action 'rollbackProp' 'maximum_am_resource_percent'}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a>
+ </div>
+ {{/if}}
+ </div>
{{else}}
- <div class="col-xs-6">
+ <div class="col-xs-6">
{{#if content.maximum_am_resource_percent}}
<p class="form-control-static">{{content.maximum_am_resource_percent}} %</p>
{{else}}
- <p class="form-control-static">-</p>
+ <p class="form-control-static">Inherited</p>
{{/if}}
+ </div>
{{/if}}
- </div>
</div>
</form>
</div>
http://git-wip-us.apache.org/repos/asf/ambari/blob/95103a90/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/queues.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/queues.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/queues.hbs
index f0b80d1..2591fa5 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/queues.hbs
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/queues.hbs
@@ -25,7 +25,7 @@
</div>
{{else}}
<div class="add-queue col-sm-6">
- <button {{action askPath}} {{bind-attr class=":btn :btn-default :btn-block isNotOperator:disabled hasNewQueue:disabled"}} ><i class="fa fa-plus"></i> Add Queue</button>
+ <button {{action 'askPath'}} {{bind-attr class=":btn :btn-default :btn-block isNotOperator:disabled hasNewQueue:disabled"}} ><i class="fa fa-plus"></i> Add Queue</button>
</div>
<div class="add-queue col-sm-6">
<div class="btn-group btn-group-justified btn-group-save">
@@ -35,8 +35,8 @@
<span class="caret"></span>
</button>
<ul class="dropdown-menu pull-right" role="menu">
- {{#dropdown-confirmation action='saveModal' targetObject=view}}
- {{#view view.button action='showRestartConfirmation' classBinding=":btn needRestart::disabled"}}<i class="fa fa-fw fa fa-cogs"></i> Save and Restart ResourceManager{{/view}}
+ {{dropdown-confirmation action='saveModal' targetObject=view needRestart=needRestart}}
+ {{!-- {{#view view.button action='showRestartConfirmation' classBinding=":btn needRestart::disabled"}}<i class="fa fa-fw fa fa-cogs"></i> Save and Restart ResourceManager{{/view}}
{{#if view.restartConfirming}}
<div class="btn-group btn-group-justified">
<div class="btn-group">
@@ -47,12 +47,12 @@
</div>
</div>
{{/if}}
- {{/dropdown-confirmation}}
+ {{/dropdown-confirmation}} --}}
<li >
- <a href="#" {{action saveModal 'refresh' target="view"}} {{bind-attr class=":btn needRefresh::disabled"}}><i class="fa fa-fw fa-refresh"></i> Save and Refresh Queues</a>
+ <a href="#" {{action 'saveModal' 'refresh' target="view"}} {{bind-attr class=":btn needRefresh::disabled"}}><i class="fa fa-fw fa-refresh"></i> Save and Refresh Queues</a>
</li>
<li>
- <a href="#" {{action saveModal target="view"}} {{bind-attr class=":btn needSave::disabled"}}><i class="fa fa-fw fa-save"></i> Save Only</a>
+ <a href="#" {{action 'saveModal' target="view"}} {{bind-attr class=":btn needSave::disabled"}}><i class="fa fa-fw fa-save"></i> Save Only</a>
</li>
</ul>
</div>
@@ -68,6 +68,39 @@
<div class="hidden-sm hidden-xs">
{{partial "schedulerPanel"}}
</div>
+
+ <div class="hidden-sm hidden-xs">
+ <div class="panel panel-default panel-versions">
+ <div class="panel-heading">
+ <div class="panel-title">
+ Versions
+ </div>
+ </div>
+ <div id="versions-table-wrap">
+ <table class="table table-condensed">
+ <tbody>
+ {{#each sortedTags}}
+ <tr>
+ <td>
+ <span class="label label-info">v{{id}}</span>
+ {{#if isCurrent}}
+ <span class="label label-success">Current</span>
+ {{/if}}
+ </td>
+ <td>{{changed}}</td>
+ <td>
+ <div class="btn-group btn-group-xs btn-block">
+ <button type="button" class="btn btn-default btn-xs btn-block" {{action 'loadTagged' tag}} >load</button>
+ </div>
+ </td>
+ </tr>
+ {{/each}}
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+
{{!-- ALERT --}}
{{#if alertMessage}}
<div class="alert alert-danger">
@@ -102,7 +135,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
- <button {{action saveConfig view.saveMode}} type="button" class="btn btn-success" data-dismiss="modal">Save changes</button>
+ <button {{action 'saveConfig' view.saveMode}} type="button" class="btn btn-success" data-dismiss="modal">Save changes</button>
</div>
</div>
</div>