1
/* Copyright 2014 Canonical Ltd. This software is licensed under the
2
* GNU Affero General Public License version 3 (see the file LICENSE).
6
* @module Y.maas.image_views
9
YUI.add('maas.image_views', function(Y) {
11
Y.log('loading maas.image_views');
12
var module = Y.namespace('maas.image_views');
14
var BOOT_RESOURCE_TYPE = Y.maas.enums.BOOT_RESOURCE_TYPE;
18
* A base view class to display a set of Images (Y.maas.image.Image).
20
* It will load the list of images (in this.modelList) when rendered
21
* for the first time and changes to this.modelList will trigger
24
* You can provide your custom rendering method by defining a 'render'
25
* method (also, you can provide methods named 'loadImagesStarted' and
26
* 'loadImagesEnded' to customize the display during the initial loading of the
27
* visible images and a method named 'displayGlobalError' to display a message
28
* when errors occur during loading).
31
module.ImageListLoader = Y.Base.create('imageListLoader', Y.View, [], {
33
initializer: function(config) {
34
this.modelList = new Y.maas.image.ImageList();
42
* Add a loader, a Y.IO object. Events fired by this IO object will
43
* be followed, and will drive updates to this object's model.
45
* It may be wiser to remodel this to consume a YUI DataSource. That
46
* would make testing easier, for one, but it would also mean we can
47
* eliminate our polling code: DataSource has support for polling
48
* via the datasource-polling module.
52
addLoader: function(loader) {
53
loader.on("io:start", this.loadImagesStarted, this);
54
loader.on("io:end", this.loadImagesEnded, this);
55
loader.on("io:failure", this.loadImagesFailed, this);
56
loader.on("io:success", function(id, request) {
57
this.loadImages(request.responseText);
62
* Load the images from the given data.
66
loadImages: function(data) {
68
var parsed = JSON.parse(data);
69
this.regionImportRunning = parsed.region_import_running;
70
this.clusterImportRunning = parsed.cluster_import_running;
71
this.mergeImages(parsed.resources);
74
this.loadImagesFailed();
81
* Process an array of images, merging them into modelList with the
82
* fewest modifications possible.
86
mergeImages: function(images) {
89
Y.Array.each(images, function(image) {
90
imagesByID[image.id] = image;
93
this.modelList.each(function(model) {
94
modelsByID[model.get("id")] = model;
97
Y.each(imagesByID, function(image, id) {
98
var model = modelsByID[id];
99
if (Y.Lang.isValue(model)) {
100
// Compare the image and the model.
101
var modelAttrs = model.getAttrs();
102
var modelChanges = {};
103
Y.each(modelAttrs, function(value, key) {
104
if (image[key] !== value) {
105
modelChanges[key] = image[key];
109
model.setAttrs(modelChanges);
113
self.modelList.add(image);
117
Y.each(modelsByID, function(model, id) {
118
// Remove models that don't correspond to a image.
119
if (!Y.Object.owns(imagesByID, id)) {
120
self.modelList.remove(model);
126
* Function called if an error occurs during the initial loading.
128
* @method displayGlobalError
130
displayGlobalError: function (error_message) {
134
* Function called when the Image list starts loading.
136
* @method loadImagesStarted
138
loadImagesStarted: function() {
142
* Function called when the Image list has loaded.
144
* @method loadImagesEnded
146
loadImagesEnded: function() {
150
* Function called when the Image list failed to load.
152
* @method loadImagesFailed
154
loadImagesFailed: function() {
155
this.displayGlobalError('Unable to load boot images.');
161
* A customized view based on ImageListLoader that will display the
164
module.ImagesView = Y.Base.create(
165
'imagesView', module.ImageListLoader, [], {
167
regionImportingText: 'Step 1/2: Region importing',
168
clusterImportingText: 'Step 2/2: Clusters importing',
171
initializer: function(config) {
172
this.srcNode = Y.one(config.srcNode);
173
this.loader = this.srcNode.one(config.loader);
174
this.content = this.srcNode.one(config.content);
175
this.importer = this.srcNode.one(config.importer);
176
this.ubuntuOptions = this.srcNode.one(config.ubuntuOptions);
177
this.ubuntuSpinner = this.srcNode.one(config.ubuntuSpinner);
178
this.ubuntuTable = this.srcNode.one(config.ubuntuTable);
179
this.ubuntuMissingImages = this.srcNode.one(config.ubuntuMissingImages);
180
this.ubuntuButton = this.srcNode.one(config.ubuntuButton);
184
* Return all Ubuntu images.
186
* @ method getUbuntuImages
188
getUbuntuImages: function() {
189
images = this.modelList.filter(function(model) {
190
return model.get('rtype') === BOOT_RESOURCE_TYPE.SYNCED &&
191
model.get('name').indexOf('ubuntu/') === 0;
193
// Sort the images decending, so newest Ubuntu version is on top.
194
images.sort(function(a, b) {
195
return -(a.get('title').localeCompare(b.get('title')));
201
* Display images page.
205
render: function () {
207
this.loader.removeClass('hidden');
208
this.content.addClass('hidden');
210
this.loader.addClass('hidden');
211
this.content.removeClass('hidden');
213
this.renderImporting();
214
this.renderUbuntuView();
218
* Render the importing header.
220
* @method renderUbuntuView
222
renderImporting: function() {
223
var importingText = this.importer.one('.importing-text');
224
if(!this.regionImportRunning && !this.clusterImportRunning) {
225
this.importer.addClass('hidden');
226
importingText.setContent('');
227
} else if (this.regionImportRunning) {
228
this.importer.removeClass('hidden');
229
importingText.setContent(this.regionImportingText);
230
} else if (this.clusterImportRunning) {
231
this.importer.removeClass('hidden');
232
importingText.setContent(this.clusterImportingText);
237
* Render the Ubuntu section of the view.
239
* @method renderUbuntuView
241
renderUbuntuView: function() {
242
if(this.regionImportRunning) {
243
if(Y.Lang.isValue(this.ubuntuOptions)) {
244
this.ubuntuOptions.addClass('hidden');
246
if(Y.Lang.isValue(this.ubuntuButton)) {
247
this.ubuntuButton.addClass('hidden');
250
if(Y.Lang.isValue(this.ubuntuOptions)) {
251
this.ubuntuOptions.removeClass('hidden');
253
if(Y.Lang.isValue(this.ubuntuButton)) {
254
this.ubuntuButton.removeClass('hidden');
257
var ubuntuImages = this.getUbuntuImages();
258
if(ubuntuImages.length === 0) {
259
this.ubuntuMissingImages.removeClass('hidden');
260
this.ubuntuTable.addClass('hidden');
261
this.updateUbuntuButton(false);
263
this.ubuntuMissingImages.addClass('hidden');
264
this.ubuntuTable.removeClass('hidden');
265
this.updateUbuntuButton(true);
269
Y.each(ubuntuImages, function(model) {
270
innerTable += "<tr><td>" + self.getSpinner(model) + "</td>";
271
innerTable += "<td>" + model.get('title') + "</td>";
272
innerTable += "<td>" + model.get('arch') + "</td>";
273
innerTable += "<td>" + model.get('size') + "</td>";
274
innerTable += "<td>" + model.get('numberOfNodes') + "</td>";
275
innerTable += "<td>" + model.get('lastUpdate') + "</td>";
276
innerTable += "</tr>";
278
this.ubuntuTable.one('tbody').setHTML(innerTable);
282
* Update the value of the ubuntuButton.
284
* The value of the button can be locked meaning it should not change, this
285
* is done using the data attribute lock-value. data-lock-value="true"
287
* @method updateUbuntuButton
289
updateUbuntuButton: function(showApply) {
290
if(!Y.Lang.isValue(this.ubuntuButton)) {
293
if(this.ubuntuButton.getData('lock-value') === "true") {
297
this.ubuntuButton.set('value', 'Apply changes');
300
this.ubuntuButton.set('value', 'Import images');
305
* Return the HTML for the downloading spinner for the given model.
309
getSpinner: function(model) {
310
// Spinner is not rendered when the model is complete.
311
if(model.get('complete')) {
314
html = '<div title="' + model.get('status') + '" class="spinner';
315
if(model.get('downloading')) {
323
}, '0.1', {'requires': [
324
'view', 'io', 'maas.enums', 'maas.image']}