985
990
views.ServiceInspector = (function() {
986
991
var juju = Y.namespace('juju');
988
var unitListNameMap = {
992
'landscape-needs-reboot': 'Needs Reboot',
993
'landscape-security-upgrades': 'Security Upgrade'
997
Generates the unit list sorted by status category and landscape
998
annotation key and returns an array with the data to
999
generate the unit list UI.
1001
@method updateUnitList
1002
@param {Object} values From the databinding update method.
1003
@return {Array} An array of objects with agent_state or landscape
1004
annotation id as category and an array of units
1005
[{ category: 'started', units: [model, model, ...]}].
1007
function updateUnitList(values) {
1011
values.each(function(value) {
1012
var category = utils.simplifyState(value);
1013
if (!unitByStatus[category]) {
1014
unitByStatus[category] = [];
1016
unitByStatus[category].push(value);
1018
// landscape annotations
1019
var lIds = utils.landscapeAnnotations(value);
1020
lIds.forEach(function(annotation) {
1021
if (!unitByStatus[annotation]) {
1022
unitByStatus[annotation] = [];
1024
unitByStatus[annotation].push(value);
1028
// This will generate a list with all categories.
1029
Y.Object.each(unitListNameMap, function(value, key) {
1031
if (unitByStatus[key]) {
1032
unit = unitByStatus[key];
1034
statuses.push({category: key, units: unit});
1041
Generates the list of allowable buttons for the
1042
different inspector unit lists.
1044
@method generateActionButtonList
1045
@param {String} category The unit status category.
1047
function generateActionButtonList(category) {
1048
var showingButtons = {},
1049
buttonTypes = ['resolve', 'retry', 'replace', 'landscape'],
1050
// if you adjust this list don't forget to edit
1051
// the list in the unit tests
1053
error: ['resolve', 'retry', 'replace'],
1054
pending: ['retry', 'replace'],
1055
running: ['replace'],
1056
'landscape-needs-reboot': ['landscape'],
1057
'landscape-security-upgrades': ['landscape']
1060
buttonTypes.forEach(function(buttonType) {
1061
buttons[category].forEach(function(allowedButton) {
1062
if (buttonType === allowedButton) {
1063
showingButtons[buttonType] = true;
1067
return showingButtons;
1071
Binds the statuses data set to d3
1073
@method generateAndBindUnitHeaders
1074
@param {Array} statuses A key value pair of categories to unit list.
1076
function generateAndBindUnitHeaders(node, statuses) {
1077
/*jshint validthis:true */
1081
var categoryWrapperNodes = d3.select(node.getDOMNode())
1082
.selectAll('.unit-list-wrapper')
1083
.data(statuses, function(d) {
1087
// D3 header enter section
1088
var unitStatusWrapper = categoryWrapperNodes
1091
.classed('unit-list-wrapper hidden', true);
1093
var unitStatusHeader = unitStatusWrapper
1095
.attr('class', function(d) {
1096
return 'status-unit-header ' +
1097
'closed-unit-list ' + d.category;
1100
var unitStatusContentForm = unitStatusWrapper
1102
.attr('class', function(d) {
1103
return 'status-unit-content ' +
1104
'close-unit ' + d.category;
1108
unitStatusContentForm.append('li')
1110
.attr('type', 'checkbox')
1111
.classed('toggle-select-all', true);
1113
unitStatusContentForm.append('ul');
1115
unitStatusContentForm.append('div')
1116
.classed('action-button-wrapper', true)
1119
var tmpl = Templates['unit-action-buttons'](
1120
generateActionButtonList(d.category));
1121
buttonHeight = tmpl.offsetHeight;
1125
unitStatusHeader.append('span')
1126
.classed('unit-qty', true);
1128
unitStatusHeader.append('span')
1129
.classed('category-label', true);
1131
unitStatusHeader.append('span')
1132
.classed('chevron', true);
1134
// D3 header update section
1135
categoryWrapperNodes.select('.unit-qty')
1137
return d.units.length;
1140
// Toggles the sections visible or hidden based on
1141
// whether there are units in their list.
1142
categoryWrapperNodes.filter(function(d) { return d.units.length > 0; })
1143
.classed('hidden', false);
1145
categoryWrapperNodes.filter(function(d) {
1146
return d.units.length === undefined;
1148
.classed('hidden', true);
1150
// Add the category label to each heading
1151
categoryWrapperNodes.select('.category-label')
1153
return unitListNameMap[d.category];
1156
var unitsList = categoryWrapperNodes.select('ul')
1164
// D3 content enter section
1165
var unitItem = unitsList.enter()
1168
unitItem.append('input')
1171
'name': function(unit) {
1175
unitItem.append('a').text(
1179
.attr('data-unit', function(d) {
1180
return d.service + '/' + d.number;
1183
// D3 content update section
1186
return a.number - b.number;
1189
categoryWrapperNodes
1190
.select('.status-unit-content')
1191
.style('max-height', function(d) {
1192
if (!self._unitItemHeight) {
1193
self._unitItemHeight =
1194
d3.select(this).select('li').property('offsetHeight');
1196
return ((self._unitItemHeight *
1197
(d.units.length + 1)) + buttonHeight) + 'px';
1201
// D3 content exit section
1202
unitsList.exit().remove();
1204
// D3 header exit section
1205
categoryWrapperNodes.exit().remove();
1208
var DEFAULT_VIEWLETS = {
1211
template: Templates.serviceOverview,
1213
aggregated_status: {
1214
'update': function(node, value) {
1215
var bar = this._statusbar;
1217
bar = this._statusbar = new views.StatusBar({
1218
target: node.getDOMNode()
1225
depends: ['aggregated_status'],
1226
'update': function(node, value) {
1227
// Called under the databinding context.
1228
// Subordinates may not have a value.
1230
var statuses = this.viewlet.updateUnitList(value);
1231
this.viewlet.generateAndBindUnitHeaders(node, statuses);
1236
// These methods are exposed here to allow us access for testing.
1237
updateUnitList: updateUnitList,
1238
generateAndBindUnitHeaders: generateAndBindUnitHeaders,
1239
generateActionButtonList: generateActionButtonList
1243
template: Templates['service-configuration'],
1244
'render': function(service, viewContainerAttrs) {
1246
var db = viewContainerAttrs.db;
1247
var charm = db.charms.getById(service.get('charm'));
1248
var charmOptions = charm.get('options');
1249
Y.Object.each(service.get('config'), function(value, key) {
1255
var option = charmOptions[key];
1257
setting.description = option.description;
1258
setting.type = option.type;
1261
settings.push(setting);
1263
this.container = Y.Node.create(this.templateWrapper);
1265
this.container.setHTML(
1269
exposed: service.get('exposed')}));
1270
this.container.all('textarea.config-field')
1271
.plug(plugins.ResizingTextarea,
1278
'update': function(node, value) {
1279
node.one('input').set('checked', value);
1284
// Service constraints viewlet.
1286
name: 'constraints',
1287
template: Templates['service-constraints-viewlet'],
1288
readOnlyConstraints: ['provider-type', 'ubuntu-series'],
1289
constraintDescriptions: {
1290
arch: {title: 'Architecture'},
1291
cpu: {title: 'CPU', unit: 'Ghz'},
1292
'cpu-cores': {title: 'CPU Cores'},
1293
'cpu-power': {title: 'CPU Power', unit: 'Ghz'},
1294
mem: {title: 'Memory', unit: 'GB'}
1299
'format': function(value) {
1300
// Display undefined constraints as empty strings.
1301
// This method is inherited when using all nested
1302
// constraints binding.
1308
'render': function(service, options) {
1309
var constraints = utils.getConstraints(
1310
service.get('constraints') || {},
1311
options.env.genericConstraints,
1312
this.readOnlyConstraints,
1313
this.constraintDescriptions);
1314
var contents = this.template({
1316
constraints: constraints
1318
this.container = Y.Node.create(this.templateWrapper);
1319
this.container.setHTML(contents);
1324
name: 'ghostConfig',
1325
template: Templates['ghost-config-viewlet'],
1328
'update': function(node, val) {
1329
var newVal = (val['default'] === undefined) ? '' : val['default'];
1330
node.set('value', newVal);
1334
'render': function(model) {
1335
this.container = Y.Node.create(this.templateWrapper);
1337
// This is to allow for data binding on the ghost settings
1338
// while using a shared template across both inspectors
1339
var options = model.getAttrs();
1342
// not sure this should be done like this
1343
// but this will allow us to use the old template.
1344
options.settings = utils.extractServiceSettings(options.options);
1346
// Signalling to the shared templates that this is the ghost view.
1347
options.ghost = true;
1348
this.container.setHTML(this.template(options));
1350
this.container.all('textarea.config-field')
1351
.plug(plugins.ResizingTextarea,
1359
// Add any imported viewlets into this DEFAULT_VIEWLETS from doom.
1360
DEFAULT_VIEWLETS = Y.merge(DEFAULT_VIEWLETS, viewletNS);
1361
// Mixin Conflict Handling.
1362
DEFAULT_VIEWLETS.config = Y.merge(DEFAULT_VIEWLETS.config, ConflictMixin);
1363
DEFAULT_VIEWLETS.constraints = Y.merge(DEFAULT_VIEWLETS.constraints,
1366
992
// This variable is assigned an aggregate collection of methods and
1367
993
// properties provided by various controller objects in the
1368
994
// ServiceInspector constructor.