~ya-bo-ng/juju-gui/test-prototype

« back to all changes in this revision

Viewing changes to app/views/inspector.js

  • Committer: Benjamin Saller
  • Author(s): Benjamin Saller
  • Date: 2013-08-08 19:02:30 UTC
  • mfrom: (932.3.6 viewlet-breakout)
  • Revision ID: bcsaller@gmail.com-20130808190230-r2n3a0lcryp2d284
Viewlet breakout

Viewlets all live in their own modules. DEFAULT_VIEWLETS is gone, we use
the viewlets namespace to hold things now and the viewlet list to select which
belong to which inspector.

R=jeff.pihach, gary.poster
CC=
https://codereview.appspot.com/12634045

Show diffs side-by-side

added added

removed removed

Lines of Context:
977
977
    }
978
978
  };
979
979
 
 
980
  // Mixin Conflict Handling.
 
981
  viewletNS.config = Y.merge(viewletNS.config, ConflictMixin);
 
982
  viewletNS.constraints = Y.merge(viewletNS.constraints, ConflictMixin);
 
983
 
 
984
 
980
985
  /**
981
986
    Service Inspector Viewlet Manager Controller
982
987
 
984
989
   */
985
990
  views.ServiceInspector = (function() {
986
991
    var juju = Y.namespace('juju');
987
 
 
988
 
    var unitListNameMap = {
989
 
      error: 'Error',
990
 
      pending: 'Pending',
991
 
      running: 'Running',
992
 
      'landscape-needs-reboot': 'Needs Reboot',
993
 
      'landscape-security-upgrades': 'Security Upgrade'
994
 
    };
995
 
 
996
 
    /**
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.
1000
 
 
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, ...]}].
1006
 
    */
1007
 
    function updateUnitList(values) {
1008
 
      var statuses = [],
1009
 
          unitByStatus = {};
1010
 
 
1011
 
      values.each(function(value) {
1012
 
        var category = utils.simplifyState(value);
1013
 
        if (!unitByStatus[category]) {
1014
 
          unitByStatus[category] = [];
1015
 
        }
1016
 
        unitByStatus[category].push(value);
1017
 
 
1018
 
        // landscape annotations
1019
 
        var lIds = utils.landscapeAnnotations(value);
1020
 
        lIds.forEach(function(annotation) {
1021
 
          if (!unitByStatus[annotation]) {
1022
 
            unitByStatus[annotation] = [];
1023
 
          }
1024
 
          unitByStatus[annotation].push(value);
1025
 
        });
1026
 
      });
1027
 
 
1028
 
      // This will generate a list with all categories.
1029
 
      Y.Object.each(unitListNameMap, function(value, key) {
1030
 
        var unit = {};
1031
 
        if (unitByStatus[key]) {
1032
 
          unit = unitByStatus[key];
1033
 
        }
1034
 
        statuses.push({category: key, units: unit});
1035
 
      });
1036
 
 
1037
 
      return statuses;
1038
 
    }
1039
 
 
1040
 
    /**
1041
 
      Generates the list of allowable buttons for the
1042
 
      different inspector unit lists.
1043
 
 
1044
 
      @method generateActionButtonList
1045
 
      @param {String} category The unit status category.
1046
 
    */
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
1052
 
          buttons = {
1053
 
            error: ['resolve', 'retry', 'replace'],
1054
 
            pending: ['retry', 'replace'],
1055
 
            running: ['replace'],
1056
 
            'landscape-needs-reboot': ['landscape'],
1057
 
            'landscape-security-upgrades': ['landscape']
1058
 
          };
1059
 
 
1060
 
      buttonTypes.forEach(function(buttonType) {
1061
 
        buttons[category].forEach(function(allowedButton) {
1062
 
          if (buttonType === allowedButton) {
1063
 
            showingButtons[buttonType] = true;
1064
 
          }
1065
 
        });
1066
 
      });
1067
 
      return showingButtons;
1068
 
    }
1069
 
 
1070
 
    /**
1071
 
      Binds the statuses data set to d3
1072
 
 
1073
 
      @method generateAndBindUnitHeaders
1074
 
      @param {Array} statuses A key value pair of categories to unit list.
1075
 
    */
1076
 
    function generateAndBindUnitHeaders(node, statuses) {
1077
 
      /*jshint validthis:true */
1078
 
      var self = this,
1079
 
          buttonHeight;
1080
 
 
1081
 
      var categoryWrapperNodes = d3.select(node.getDOMNode())
1082
 
                                   .selectAll('.unit-list-wrapper')
1083
 
                                   .data(statuses, function(d) {
1084
 
                                       return d.category;
1085
 
                                     });
1086
 
 
1087
 
      // D3 header enter section
1088
 
      var unitStatusWrapper = categoryWrapperNodes
1089
 
                                  .enter()
1090
 
                                  .append('div')
1091
 
                                  .classed('unit-list-wrapper hidden', true);
1092
 
 
1093
 
      var unitStatusHeader = unitStatusWrapper
1094
 
                                  .append('div')
1095
 
                                  .attr('class', function(d) {
1096
 
                                   return 'status-unit-header ' +
1097
 
                                          'closed-unit-list ' + d.category;
1098
 
                                 });
1099
 
 
1100
 
      var unitStatusContentForm = unitStatusWrapper
1101
 
                                  .append('div')
1102
 
                                  .attr('class', function(d) {
1103
 
                                    return 'status-unit-content ' +
1104
 
                                           'close-unit ' + d.category;
1105
 
                                  })
1106
 
                                  .append('form');
1107
 
 
1108
 
      unitStatusContentForm.append('li')
1109
 
                            .append('input')
1110
 
                            .attr('type', 'checkbox')
1111
 
                            .classed('toggle-select-all', true);
1112
 
 
1113
 
      unitStatusContentForm.append('ul');
1114
 
 
1115
 
      unitStatusContentForm.append('div')
1116
 
                           .classed('action-button-wrapper', true)
1117
 
                           .html(
1118
 
          function(d) {
1119
 
                                 var tmpl = Templates['unit-action-buttons'](
1120
 
                                     generateActionButtonList(d.category));
1121
 
                                 buttonHeight = tmpl.offsetHeight;
1122
 
                                 return tmpl;
1123
 
          });
1124
 
 
1125
 
      unitStatusHeader.append('span')
1126
 
                      .classed('unit-qty', true);
1127
 
 
1128
 
      unitStatusHeader.append('span')
1129
 
                      .classed('category-label', true);
1130
 
 
1131
 
      unitStatusHeader.append('span')
1132
 
                      .classed('chevron', true);
1133
 
 
1134
 
      // D3 header update section
1135
 
      categoryWrapperNodes.select('.unit-qty')
1136
 
                          .text(function(d) {
1137
 
                                 return d.units.length;
1138
 
                               });
1139
 
 
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);
1144
 
 
1145
 
      categoryWrapperNodes.filter(function(d) {
1146
 
                                 return d.units.length === undefined;
1147
 
                               })
1148
 
                                  .classed('hidden', true);
1149
 
 
1150
 
      // Add the category label to each heading
1151
 
      categoryWrapperNodes.select('.category-label')
1152
 
                          .text(function(d) {
1153
 
                                 return unitListNameMap[d.category];
1154
 
                               });
1155
 
 
1156
 
      var unitsList = categoryWrapperNodes.select('ul')
1157
 
                                    .selectAll('li')
1158
 
                                    .data(function(d) {
1159
 
                                     return d.units;
1160
 
                                   }, function(unit) {
1161
 
                                     return unit.id;
1162
 
                                   });
1163
 
 
1164
 
      // D3 content enter section
1165
 
      var unitItem = unitsList.enter()
1166
 
                              .append('li');
1167
 
 
1168
 
      unitItem.append('input')
1169
 
           .attr({
1170
 
            'type': 'checkbox',
1171
 
            'name': function(unit) {
1172
 
              return unit.id;
1173
 
            }});
1174
 
 
1175
 
      unitItem.append('a').text(
1176
 
          function(d) {
1177
 
            return d.id;
1178
 
          })
1179
 
          .attr('data-unit', function(d) {
1180
 
            return d.service + '/' + d.number;
1181
 
          });
1182
 
 
1183
 
      // D3 content update section
1184
 
      unitsList.sort(
1185
 
          function(a, b) {
1186
 
            return a.number - b.number;
1187
 
          });
1188
 
 
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');
1195
 
            }
1196
 
            return ((self._unitItemHeight *
1197
 
                    (d.units.length + 1)) + buttonHeight) + 'px';
1198
 
          });
1199
 
 
1200
 
 
1201
 
      // D3 content exit section
1202
 
      unitsList.exit().remove();
1203
 
 
1204
 
      // D3 header exit section
1205
 
      categoryWrapperNodes.exit().remove();
1206
 
    }
1207
 
 
1208
 
    var DEFAULT_VIEWLETS = {
1209
 
      overview: {
1210
 
        name: 'overview',
1211
 
        template: Templates.serviceOverview,
1212
 
        bindings: {
1213
 
          aggregated_status: {
1214
 
            'update': function(node, value) {
1215
 
              var bar = this._statusbar;
1216
 
              if (!bar) {
1217
 
                bar = this._statusbar = new views.StatusBar({
1218
 
                  target: node.getDOMNode()
1219
 
                }).render();
1220
 
              }
1221
 
              bar.update(value);
1222
 
            }
1223
 
          },
1224
 
          units: {
1225
 
            depends: ['aggregated_status'],
1226
 
            'update': function(node, value) {
1227
 
              // Called under the databinding context.
1228
 
              // Subordinates may not have a value.
1229
 
              if (value) {
1230
 
                var statuses = this.viewlet.updateUnitList(value);
1231
 
                this.viewlet.generateAndBindUnitHeaders(node, statuses);
1232
 
              }
1233
 
            }
1234
 
          }
1235
 
        },
1236
 
        // These methods are exposed here to allow us access for testing.
1237
 
        updateUnitList: updateUnitList,
1238
 
        generateAndBindUnitHeaders: generateAndBindUnitHeaders,
1239
 
        generateActionButtonList: generateActionButtonList
1240
 
      },
1241
 
      config: {
1242
 
        name: 'config',
1243
 
        template: Templates['service-configuration'],
1244
 
        'render': function(service, viewContainerAttrs) {
1245
 
          var settings = [];
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) {
1250
 
            var setting = {
1251
 
              name: key,
1252
 
              value: value
1253
 
            };
1254
 
            if (charmOptions) {
1255
 
              var option = charmOptions[key];
1256
 
              if (option) {
1257
 
                setting.description = option.description;
1258
 
                setting.type = option.type;
1259
 
              }
1260
 
            }
1261
 
            settings.push(setting);
1262
 
          });
1263
 
          this.container = Y.Node.create(this.templateWrapper);
1264
 
 
1265
 
          this.container.setHTML(
1266
 
              this.template({
1267
 
                service: service,
1268
 
                settings: settings,
1269
 
                exposed: service.get('exposed')}));
1270
 
          this.container.all('textarea.config-field')
1271
 
                        .plug(plugins.ResizingTextarea,
1272
 
                              { max_height: 200,
1273
 
                                min_height: 18,
1274
 
                                single_line: 18});
1275
 
        },
1276
 
        bindings: {
1277
 
          exposed: {
1278
 
            'update': function(node, value) {
1279
 
              node.one('input').set('checked', value);
1280
 
            }
1281
 
          }
1282
 
        }
1283
 
      },
1284
 
      // Service constraints viewlet.
1285
 
      constraints: {
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'}
1295
 
        },
1296
 
 
1297
 
        bindings: {
1298
 
          'constraints': {
1299
 
            'format': function(value) {
1300
 
              // Display undefined constraints as empty strings.
1301
 
              // This method is inherited when using all nested
1302
 
              // constraints binding.
1303
 
              return value || '';
1304
 
            }
1305
 
          }
1306
 
        },
1307
 
 
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({
1315
 
            service: service,
1316
 
            constraints: constraints
1317
 
          });
1318
 
          this.container = Y.Node.create(this.templateWrapper);
1319
 
          this.container.setHTML(contents);
1320
 
        }
1321
 
      },
1322
 
      //relations: {},
1323
 
      ghostConfig: {
1324
 
        name: 'ghostConfig',
1325
 
        template: Templates['ghost-config-viewlet'],
1326
 
        bindings: {
1327
 
          'options': {
1328
 
            'update': function(node, val) {
1329
 
              var newVal = (val['default'] === undefined) ? '' : val['default'];
1330
 
              node.set('value', newVal);
1331
 
            }
1332
 
          }
1333
 
        },
1334
 
        'render': function(model) {
1335
 
          this.container = Y.Node.create(this.templateWrapper);
1336
 
 
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();
1340
 
 
1341
 
          // XXX - Jeff
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);
1345
 
 
1346
 
          // Signalling to the shared templates that this is the ghost view.
1347
 
          options.ghost = true;
1348
 
          this.container.setHTML(this.template(options));
1349
 
 
1350
 
          this.container.all('textarea.config-field')
1351
 
                        .plug(plugins.ResizingTextarea,
1352
 
                              { max_height: 200,
1353
 
                                min_height: 18,
1354
 
                                single_line: 18});
1355
 
        }
1356
 
      }
1357
 
    };
1358
 
 
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,
1364
 
                                           ConflictMixin);
1365
 
 
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.
1390
1016
      // Build a collection of viewlets from the list of required viewlets.
1391
1017
      var viewlets = {};
1392
1018
      options.viewletList.forEach(function(viewlet) {
1393
 
        viewlets[viewlet] = DEFAULT_VIEWLETS[viewlet];
1394
 
      });
 
1019
        viewlets[viewlet] = viewletNS[viewlet]; });
1395
1020
      // Mix in any custom viewlet configuration options provided by the config.
1396
1021
      options.viewlets = Y.mix(
1397
1022
          viewlets, options.viewlets, true, undefined, 0, true);
1440
1065
}, '0.1.0', {
1441
1066
  requires: [
1442
1067
    'base-build',
1443
 
    'd3-statusbar',
1444
 
    'dd',
1445
1068
    'event-key',
1446
1069
    'event-resize',
1447
1070
    'handlebars',
1457
1080
    'view',
1458
1081
    // Imported viewlets
1459
1082
    'viewlet-charm-details',
1460
 
    'viewlet-unit-details',
1461
 
    'viewlet-inspector-header'
 
1083
    'viewlet-inspector-header',
 
1084
    'viewlet-inspector-overview',
 
1085
    'viewlet-service-config',
 
1086
    'viewlet-service-constraints',
 
1087
    'viewlet-service-ghost',
 
1088
    'viewlet-unit-details'
1462
1089
  ]
1463
1090
});
1464
1091