1.3.1
by Andres Rodriguez
Import upstream version 1.7.0~beta7+bzr3266 |
1 |
/* Copyright 2014 Canonical Ltd. This software is licensed under the
|
2 |
* GNU Affero General Public License version 3 (see the file LICENSE).
|
|
3 |
*
|
|
4 |
* Image views.
|
|
5 |
*
|
|
6 |
* @module Y.maas.image_views
|
|
7 |
*/
|
|
8 |
||
9 |
YUI.add('maas.image_views', function(Y) { |
|
10 |
||
11 |
Y.log('loading maas.image_views'); |
|
12 |
var module = Y.namespace('maas.image_views'); |
|
13 |
||
14 |
var BOOT_RESOURCE_TYPE = Y.maas.enums.BOOT_RESOURCE_TYPE; |
|
15 |
||
16 |
||
17 |
/**
|
|
18 |
* A base view class to display a set of Images (Y.maas.image.Image).
|
|
19 |
*
|
|
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
|
|
22 |
* re-rendering.
|
|
23 |
*
|
|
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).
|
|
29 |
*
|
|
30 |
*/
|
|
31 |
module.ImageListLoader = Y.Base.create('imageListLoader', Y.View, [], { |
|
32 |
||
33 |
initializer: function(config) { |
|
34 |
this.modelList = new Y.maas.image.ImageList(); |
|
35 |
this.loaded = false; |
|
36 |
},
|
|
37 |
||
38 |
render: function () { |
|
39 |
},
|
|
40 |
||
41 |
/**
|
|
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.
|
|
44 |
*
|
|
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.
|
|
49 |
*
|
|
50 |
* @method addLoader
|
|
51 |
*/
|
|
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); |
|
58 |
}, this); |
|
59 |
},
|
|
60 |
||
61 |
/**
|
|
62 |
* Load the images from the given data.
|
|
63 |
*
|
|
64 |
* @method loadImages
|
|
65 |
*/
|
|
66 |
loadImages: function(data) { |
|
67 |
try { |
|
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); |
|
72 |
}
|
|
73 |
catch(e) { |
|
74 |
this.loadImagesFailed(); |
|
75 |
}
|
|
76 |
this.loaded = true; |
|
77 |
this.render(); |
|
78 |
},
|
|
79 |
||
80 |
/**
|
|
81 |
* Process an array of images, merging them into modelList with the
|
|
82 |
* fewest modifications possible.
|
|
83 |
*
|
|
84 |
* @method mergeImages
|
|
85 |
*/
|
|
86 |
mergeImages: function(images) { |
|
87 |
var self = this; |
|
88 |
var imagesByID = {}; |
|
89 |
Y.Array.each(images, function(image) { |
|
90 |
imagesByID[image.id] = image; |
|
91 |
});
|
|
92 |
var modelsByID = {}; |
|
93 |
this.modelList.each(function(model) { |
|
94 |
modelsByID[model.get("id")] = model; |
|
95 |
});
|
|
96 |
||
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]; |
|
106 |
}
|
|
107 |
});
|
|
108 |
// Update the image.
|
|
109 |
model.setAttrs(modelChanges); |
|
110 |
}
|
|
111 |
else { |
|
112 |
// Add the image.
|
|
113 |
self.modelList.add(image); |
|
114 |
}
|
|
115 |
});
|
|
116 |
||
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); |
|
121 |
}
|
|
122 |
});
|
|
123 |
},
|
|
124 |
||
125 |
/**
|
|
126 |
* Function called if an error occurs during the initial loading.
|
|
127 |
*
|
|
128 |
* @method displayGlobalError
|
|
129 |
*/
|
|
130 |
displayGlobalError: function (error_message) { |
|
131 |
},
|
|
132 |
||
133 |
/**
|
|
134 |
* Function called when the Image list starts loading.
|
|
135 |
*
|
|
136 |
* @method loadImagesStarted
|
|
137 |
*/
|
|
138 |
loadImagesStarted: function() { |
|
139 |
},
|
|
140 |
||
141 |
/**
|
|
142 |
* Function called when the Image list has loaded.
|
|
143 |
*
|
|
144 |
* @method loadImagesEnded
|
|
145 |
*/
|
|
146 |
loadImagesEnded: function() { |
|
147 |
},
|
|
148 |
||
149 |
/**
|
|
150 |
* Function called when the Image list failed to load.
|
|
151 |
*
|
|
152 |
* @method loadImagesFailed
|
|
153 |
*/
|
|
154 |
loadImagesFailed: function() { |
|
155 |
this.displayGlobalError('Unable to load boot images.'); |
|
156 |
}
|
|
157 |
||
158 |
});
|
|
159 |
||
160 |
/**
|
|
161 |
* A customized view based on ImageListLoader that will display the
|
|
162 |
* images view.
|
|
163 |
*/
|
|
164 |
module.ImagesView = Y.Base.create( |
|
165 |
'imagesView', module.ImageListLoader, [], { |
|
166 |
||
167 |
regionImportingText: 'Step 1/2: Region importing', |
|
168 |
clusterImportingText: 'Step 2/2: Clusters importing', |
|
169 |
||
170 |
||
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); |
|
181 |
},
|
|
182 |
||
183 |
/**
|
|
184 |
* Return all Ubuntu images.
|
|
185 |
*
|
|
186 |
* @ method getUbuntuImages
|
|
187 |
*/
|
|
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; |
|
192 |
});
|
|
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'))); |
|
196 |
});
|
|
197 |
return images; |
|
198 |
},
|
|
199 |
||
200 |
/**
|
|
201 |
* Display images page.
|
|
202 |
*
|
|
203 |
* @method render
|
|
204 |
*/
|
|
205 |
render: function () { |
|
206 |
if(!this.loaded) { |
|
207 |
this.loader.removeClass('hidden'); |
|
208 |
this.content.addClass('hidden'); |
|
209 |
} else { |
|
210 |
this.loader.addClass('hidden'); |
|
211 |
this.content.removeClass('hidden'); |
|
212 |
}
|
|
213 |
this.renderImporting(); |
|
214 |
this.renderUbuntuView(); |
|
215 |
},
|
|
216 |
||
217 |
/**
|
|
218 |
* Render the importing header.
|
|
219 |
*
|
|
220 |
* @method renderUbuntuView
|
|
221 |
*/
|
|
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); |
|
233 |
}
|
|
234 |
},
|
|
235 |
||
236 |
/**
|
|
237 |
* Render the Ubuntu section of the view.
|
|
238 |
*
|
|
239 |
* @method renderUbuntuView
|
|
240 |
*/
|
|
241 |
renderUbuntuView: function() { |
|
242 |
if(this.regionImportRunning) { |
|
243 |
if(Y.Lang.isValue(this.ubuntuOptions)) { |
|
244 |
this.ubuntuOptions.addClass('hidden'); |
|
245 |
}
|
|
246 |
if(Y.Lang.isValue(this.ubuntuButton)) { |
|
247 |
this.ubuntuButton.addClass('hidden'); |
|
248 |
}
|
|
249 |
} else { |
|
250 |
if(Y.Lang.isValue(this.ubuntuOptions)) { |
|
251 |
this.ubuntuOptions.removeClass('hidden'); |
|
252 |
}
|
|
253 |
if(Y.Lang.isValue(this.ubuntuButton)) { |
|
254 |
this.ubuntuButton.removeClass('hidden'); |
|
255 |
}
|
|
256 |
}
|
|
257 |
var ubuntuImages = this.getUbuntuImages(); |
|
258 |
if(ubuntuImages.length === 0) { |
|
259 |
this.ubuntuMissingImages.removeClass('hidden'); |
|
260 |
this.ubuntuTable.addClass('hidden'); |
|
261 |
this.updateUbuntuButton(false); |
|
262 |
} else { |
|
263 |
this.ubuntuMissingImages.addClass('hidden'); |
|
264 |
this.ubuntuTable.removeClass('hidden'); |
|
265 |
this.updateUbuntuButton(true); |
|
266 |
}
|
|
267 |
var self = this; |
|
268 |
var innerTable = ""; |
|
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>"; |
|
277 |
});
|
|
278 |
this.ubuntuTable.one('tbody').setHTML(innerTable); |
|
279 |
},
|
|
280 |
||
281 |
/**
|
|
282 |
* Update the value of the ubuntuButton.
|
|
283 |
*
|
|
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"
|
|
286 |
*
|
|
287 |
* @method updateUbuntuButton
|
|
288 |
*/
|
|
289 |
updateUbuntuButton: function(showApply) { |
|
290 |
if(!Y.Lang.isValue(this.ubuntuButton)) { |
|
291 |
return; |
|
292 |
}
|
|
293 |
if(this.ubuntuButton.getData('lock-value') === "true") { |
|
294 |
return; |
|
295 |
}
|
|
296 |
if(showApply) { |
|
297 |
this.ubuntuButton.set('value', 'Apply changes'); |
|
298 |
}
|
|
299 |
else { |
|
300 |
this.ubuntuButton.set('value', 'Import images'); |
|
301 |
}
|
|
302 |
},
|
|
303 |
||
304 |
/**
|
|
305 |
* Return the HTML for the downloading spinner for the given model.
|
|
306 |
*
|
|
307 |
* @method getSpinner
|
|
308 |
*/
|
|
309 |
getSpinner: function(model) { |
|
310 |
// Spinner is not rendered when the model is complete.
|
|
311 |
if(model.get('complete')) { |
|
312 |
return ''; |
|
313 |
}
|
|
314 |
html = '<div title="' + model.get('status') + '" class="spinner'; |
|
315 |
if(model.get('downloading')) { |
|
316 |
html += ' spin'; |
|
317 |
}
|
|
318 |
html += '"></div>'; |
|
319 |
return html; |
|
320 |
}
|
|
321 |
});
|
|
322 |
||
323 |
}, '0.1', {'requires': [ |
|
324 |
'view', 'io', 'maas.enums', 'maas.image']} |
|
325 |
);
|