1
|
define([
|
2
|
'dojo/_base/array',
|
3
|
'dojo/_base/connect',
|
4
|
'dojo/on',
|
5
|
'dojo/dom',
|
6
|
'dojo/dom-construct',
|
7
|
'stabile/formatters',
|
8
|
'stabile/grid',
|
9
|
'steam2/user',
|
10
|
'stabile/upload',
|
11
|
'stabile/stores',
|
12
|
'steam2/models/Server',
|
13
|
'steam2/models/Image',
|
14
|
'steam2/models/Node',
|
15
|
'helpers/uuid',
|
16
|
// used in the form
|
17
|
'dijit/form/SimpleTextarea',
|
18
|
'dijit/form/ComboBox',
|
19
|
'dijit/form/Form'
|
20
|
], function(arrayUtil, connect, on, dom, domConstruct, formatters, grid, user, upload, stores, Server, Image, Node){
|
21
|
|
22
|
var images = {
|
23
|
|
24
|
grid: {},
|
25
|
|
26
|
/** image model */
|
27
|
model : function(args){
|
28
|
return dojo.mixin(
|
29
|
{
|
30
|
// FIXME: size / virtualsize is the same
|
31
|
backup: '--',
|
32
|
bschedule: 'manually',
|
33
|
domains: '--',
|
34
|
master: '--',
|
35
|
name: "",
|
36
|
path: '--',
|
37
|
realsize: 0,
|
38
|
backupsize: 0,
|
39
|
size: 0,
|
40
|
status: "new",
|
41
|
type: "qcow2",
|
42
|
user: user.username,
|
43
|
uuid: Math.uuid().toLowerCase(),
|
44
|
virtualsize: 10737418240, //10737418240, //can grow up to 10 GB
|
45
|
storagepool: 0,
|
46
|
managementlink: "",
|
47
|
upgradelink: "",
|
48
|
terminallink: "",
|
49
|
getPath: function(){
|
50
|
this.name = this.name || console.error('name must be set before calling getPath');
|
51
|
var t = "/mnt/stabile/images/${user}/${name}.${type}";
|
52
|
return dojo.string.substitute(t, {user:user.username, name:this.name, type: this.type});
|
53
|
},
|
54
|
onSave: function(){
|
55
|
console.log("onSave");
|
56
|
}
|
57
|
}, args || {});
|
58
|
},
|
59
|
|
60
|
/** object name - for reflective lookup */
|
61
|
name: 'images',
|
62
|
sortInfo: 2,
|
63
|
|
64
|
// initial query
|
65
|
storeQuery: {
|
66
|
type: 'user'
|
67
|
// user: user.username,
|
68
|
// complexQuery: "type:qcow2 OR type:vmdk OR type:vdi OR type:vhd OR type:img"
|
69
|
},
|
70
|
|
71
|
_searchQuery: "name:*",
|
72
|
_storagepoolQuery:"storagepool:",
|
73
|
_typeQuery: "type:user",
|
74
|
_inited: false,
|
75
|
|
76
|
/** grid structure */
|
77
|
structure : [
|
78
|
{
|
79
|
field: 'name',
|
80
|
name: 'Name',
|
81
|
//width: '230px'
|
82
|
width: 'auto'
|
83
|
},
|
84
|
{
|
85
|
field: 'status',
|
86
|
name: 'Status <a href="https://www.origo.io/info/stabiledocs/web/images/status" rel="help" target="_blank" class="irigo-tooltip">help</a>',
|
87
|
width: '76px'
|
88
|
// ,
|
89
|
|
90
|
// formatter: function(val, rowIdx, cell) {
|
91
|
// var t = '<span style="color:${color}">${val}</span>';
|
92
|
// var color = statusCodes.getColor(val);
|
93
|
// return dojo.string.substitute(t, { color: color, val: val });
|
94
|
// }
|
95
|
},
|
96
|
//{ field: 'type', name: 'Type', width: '50px'},
|
97
|
{ field: 'domainnames', name: 'Server(s)', width: 'auto'},
|
98
|
{ field: 'user', name: 'Owner', width: '120px' },
|
99
|
{ field: 'type', name: 'Type', width: '40px' },
|
100
|
{ field: 'realsize', name: 'Usage (GB)', width: '70px',
|
101
|
cellStyles: "text-align:right;",
|
102
|
formatter: formatters.bytes2gbs
|
103
|
},
|
104
|
{ field: 'virtualsize', name: 'Size (GB)', width: '65px',
|
105
|
cellStyles: "text-align:right;",
|
106
|
formatter: formatters.bytes2gbs
|
107
|
},
|
108
|
{ field: 'snap1',
|
109
|
name: 'Snapshot',
|
110
|
width: '65px',
|
111
|
constraint: { formatLength: 'short'},
|
112
|
formatter: function(val, rowIds, cell){
|
113
|
if (isNaN(val)) {
|
114
|
return val;
|
115
|
}
|
116
|
var fn = dojo.hitch({formatLength: 'short'}, formatters.datetime);
|
117
|
return fn(Number(val)*1000);
|
118
|
}
|
119
|
},
|
120
|
{ field: 'action',
|
121
|
name: 'Action <a href="//www.origo.io/info/stabiledocs/web/images/image-actions" rel="help" target="_blank" class="irigo-tooltip">help</a>',
|
122
|
width: 'auto', //
|
123
|
// options: ["--", "clone","snapshot","revert","unsnap","master","unmaster","delete"]
|
124
|
formatter: function(val, rowIdx, cell) {
|
125
|
var item = this.grid.getItem(rowIdx);
|
126
|
return images.getActionButtons(item);
|
127
|
},
|
128
|
hidden: user.is_readonly
|
129
|
}
|
130
|
],
|
131
|
|
132
|
dialogStructure : [
|
133
|
{
|
134
|
field:"name",
|
135
|
name: "Name",
|
136
|
type:"dijit.form.ValidationTextBox",
|
137
|
attrs: {required:false}
|
138
|
},
|
139
|
{
|
140
|
field: "status", name: "Status", type: "dijit.form.TextBox",
|
141
|
attrs: {readonly:"readonly"}
|
142
|
},
|
143
|
{
|
144
|
field: "uuid",
|
145
|
name: "UUID",
|
146
|
type: "dijit.form.TextBox",
|
147
|
attrs: {readonly:"readonly"}
|
148
|
},
|
149
|
{
|
150
|
field:"type", name: 'Type', type:"dijit.form.Select",
|
151
|
help: "images/image-type",
|
152
|
attrs:{ store: "stores.imageTypes", searchAttr:"type" }
|
153
|
},
|
154
|
{
|
155
|
field:"virtualsize", name:'Virtual Size <span id="humanVirtualSize" style="color:gray"></span>',
|
156
|
type: "dijit.form.ComboBox",
|
157
|
help: "images/virtual-size",
|
158
|
style: "width: 150px;",
|
159
|
attrs: {store: "stores.virtualSizes",
|
160
|
searchAttr:"size",
|
161
|
onChange: "images.dialogOnSizeChangeHandler(this, 'humanVirtualSize')"},
|
162
|
extra: function(item){return " bytes";}
|
163
|
},
|
164
|
{
|
165
|
field:"realsize", name:'Usage <span id="humanRealSize" style="color:gray"></span>',
|
166
|
type: "dijit.form.TextBox",
|
167
|
style: "width: 150px;",
|
168
|
attrs:{readonly:"readonly", onChange: "images.dialogOnSizeChangeHandler(this, 'humanRealSize')"},
|
169
|
extra: function(item){return " bytes";}
|
170
|
},
|
171
|
{
|
172
|
field:"backupsize", name:'Backup <span id="humanBackupSize" style="color:gray"></span>',
|
173
|
type: "dijit.form.TextBox",
|
174
|
style: "width: 150px;",
|
175
|
attrs:{readonly:"readonly", onChange: "images.dialogOnSizeChangeHandler(this, 'humanBackupSize')"},
|
176
|
extra: function(item){return " bytes";}
|
177
|
},
|
178
|
{
|
179
|
field:"storagepool", name:'Storage pool', type: "dijit.form.Select",
|
180
|
// extra: function(item){
|
181
|
// return "<button type=\"button\" id=\"move_button\" dojoType=\"dijit.form.Button\" onclick=\"images.move()\" style=\"font-size:80%;\">Move</button>";
|
182
|
// },
|
183
|
attrs:{
|
184
|
store: "stores.storagePools",
|
185
|
searchAttr:"id",
|
186
|
sortByLabel:"false",
|
187
|
onChange: "images.dialogOnStoragepoolChangeHandler(this)"
|
188
|
}
|
189
|
},
|
190
|
{
|
191
|
field:"mac",
|
192
|
name:'<span id="imageDialogNodeDialogLink">Node</span>',
|
193
|
type: "dijit.form.FilteringSelect",
|
194
|
restricted: true,
|
195
|
attrs:{store: "stores.nodesReadOnly", searchAttr:"name", required:"false", query: "{storfree: /^\\d\\d/}" }
|
196
|
},
|
197
|
{
|
198
|
formatter: function(image){
|
199
|
if(image.status != 'new'){
|
200
|
if(image.domains != '--'){
|
201
|
// called domains, but only one server right? - NO, masters may be used by multiple domains /co
|
202
|
var doms = image.domains.split(/, {0,1}/);
|
203
|
var domnames = image.domainnames.split(/, {0,1}/);
|
204
|
var serverEditLink = "";
|
205
|
for (var i in doms) {
|
206
|
serverEditLink += '<a nohref="#images" onclick="servers.grid.dialog.show(stores.servers.fetchItemByIdentity({identity: \'' + doms[i] + '\'}));">' + domnames[i] + '</a> ';
|
207
|
}
|
208
|
return '<td>Server</td><td>' + serverEditLink + '</td>';
|
209
|
} else {
|
210
|
return '<td>Server</td><td>Not used by any of your servers</td>';
|
211
|
}
|
212
|
} else {
|
213
|
return '';
|
214
|
}
|
215
|
}
|
216
|
},
|
217
|
{
|
218
|
formatter: function(item){
|
219
|
if (item.status == "new") {
|
220
|
return "";
|
221
|
} else {
|
222
|
var dsnap1 = "--";
|
223
|
if (item.snap1 && item.snap1 != "--") {
|
224
|
dsnap1 = (new Date(Number(item.snap1)*1000)).toLocaleString();
|
225
|
}
|
226
|
return [
|
227
|
'<td>',
|
228
|
' <div>Snapshot<a href="https://www.origo.io/info/stabiledocs/web/images/snapshot"',
|
229
|
' rel="help" target="_blank" class="irigo-tooltip">help</a>',
|
230
|
' </div>',
|
231
|
'</td>',
|
232
|
'<td><input id="snap1" dojoType="dijit.form.TextBox" readonly value="' + dsnap1 + '"></td>'].join('\n');
|
233
|
}
|
234
|
}
|
235
|
//field: "snap1", name:"Snapshot", type: "dijit.form.TextBox",
|
236
|
//attrs: {readonly:"readonly"}
|
237
|
},
|
238
|
{
|
239
|
field: "master", name:"Master (path)", type: "dijit.form.TextBox",
|
240
|
restricted: true,
|
241
|
attrs: {readonly:"readonly"}
|
242
|
},
|
243
|
{
|
244
|
field: "mastername",
|
245
|
name:"<a nohref=\"#\" onClick=\"home.showImageDialog(null);\">Master</a>",
|
246
|
type: "dijit.form.TextBox",
|
247
|
attrs: {readonly:"readonly"}
|
248
|
},
|
249
|
{
|
250
|
field:"backup", name:'Backups',
|
251
|
type: "dijit.form.FilteringSelect",
|
252
|
style: "width: 250px;",
|
253
|
extra: function(item){
|
254
|
return '<button type="button" id="restore_button" nodojoType="dijit.form.Button" class="btn btn-xs btn-info" onclick="window.images.restore(); return false;" style="font-size:80%; display: none;">Restore</button>';
|
255
|
},
|
256
|
attrs:{store: "stores.backups", searchAttr:"time", sortByLabel:"false", required:"false", value:"--"}
|
257
|
},
|
258
|
{
|
259
|
field:"bschedule", name:'Backup schedule',
|
260
|
type: "dijit.form.Select",
|
261
|
help: "images/backup-schedule",
|
262
|
attrs:{store: "stores.backupSchedules", searchAttr:"schedule", sortByLabel:"false"}
|
263
|
},
|
264
|
{
|
265
|
field:"btime", name:'Last backup',
|
266
|
type: "dijit.form.TextBox",
|
267
|
attrs:{readonly:"readonly"},
|
268
|
formatter: function(item){
|
269
|
// return '<td>Last backup</td><td><input id="btime" dojoType="dijit.form.TextBox" readonly value="' + item.btime + '"></td>';
|
270
|
if(!isNaN(item.btime) && item.status != "new"){
|
271
|
var dval = home.timestampToLocaleString(item.btime);
|
272
|
return '<td>Last backup</td><td><input id="btime" dojoType="dijit.form.TextBox" readonly value="' + dval + '"></td>';
|
273
|
} else {
|
274
|
return '<td>Last backup</td><td><input id="btime" dojoType="dijit.form.TextBox" readonly value="Never"></td>';
|
275
|
}
|
276
|
}
|
277
|
},
|
278
|
{
|
279
|
field:"created", name:'Created',
|
280
|
type: "dijit.form.TextBox",
|
281
|
attrs:{readonly:"readonly"},
|
282
|
formatter: function(item){
|
283
|
if(!isNaN(item.created) && item.status != "new"){
|
284
|
var dval = home.timestampToLocaleString(item.created);
|
285
|
return '<td>Created</td><td><input id="created" dojoType="dijit.form.TextBox" readonly value="' + dval + '"></td>';
|
286
|
} else {
|
287
|
return '<td>Created</td><td><input id="created" dojoType="dijit.form.TextBox" readonly value="--"></td>';
|
288
|
}
|
289
|
}
|
290
|
},
|
291
|
{
|
292
|
field: "user",
|
293
|
name: "User",
|
294
|
type: "dijit.form.FilteringSelect",
|
295
|
attrs: {
|
296
|
store: "stores.imageids",
|
297
|
searchAttr: "id",
|
298
|
required:"true",
|
299
|
// query: "{privileges: /(a|u)/}"
|
300
|
}
|
301
|
},
|
302
|
{
|
303
|
name: "Path",
|
304
|
formatter: function(item){
|
305
|
if(user.is_admin && item.status != "new"){
|
306
|
return '<td>Path</td><td><input id="path" dojoType="dijit.form.TextBox" readonly value="' + item.path + '"></td>';
|
307
|
}
|
308
|
return "";
|
309
|
}
|
310
|
},
|
311
|
{
|
312
|
field:"installable",
|
313
|
name: "Installable",
|
314
|
type:"dijit.form.CheckBox",
|
315
|
restricted: false, // only admins
|
316
|
help: 'images/installable-images',
|
317
|
attrs:{onchange: "this.value=this.checked?'true':'false';"}
|
318
|
},
|
319
|
{
|
320
|
field:"managementlink",
|
321
|
name: "Management link",
|
322
|
type:"dijit.form.TextBox"
|
323
|
},
|
324
|
{
|
325
|
field:"upgradelink",
|
326
|
name: "Upgrade link",
|
327
|
type:"dijit.form.TextBox"
|
328
|
},
|
329
|
{
|
330
|
field:"terminallink",
|
331
|
name: "Terminal link",
|
332
|
type:"dijit.form.TextBox"
|
333
|
},
|
334
|
{
|
335
|
field:"notes",
|
336
|
name: "Notes",
|
337
|
type:"dijit.form.SimpleTextarea",
|
338
|
style: "width: 90%;"
|
339
|
}
|
340
|
],
|
341
|
|
342
|
dialogExtras : function(item){
|
343
|
return "";
|
344
|
},
|
345
|
|
346
|
restore: function(){
|
347
|
var item = images.grid.dialog.item;
|
348
|
images.store.setValue(item, 'action', 'restore');
|
349
|
// FIXME: which one is the right one? this
|
350
|
//var value = ( dijit.byId('backup').get('value') ).replace("+", "--plus--"); //Todo: for some bizarre reason "+"-signs don't survive
|
351
|
// FIXME: or this?
|
352
|
var value = ( dijit.byId('backup').get('value') ); // Update: issue fixed in stores.js
|
353
|
images.store.setValue(item, 'backup', value);
|
354
|
images.store.save();
|
355
|
images.grid.dialog.hide();
|
356
|
},
|
357
|
|
358
|
doZBackup: function() {
|
359
|
console.log("Doing ZFS backup");
|
360
|
$("#dozbackup").html('Backing up… <i class="fa fa-cog fa-spin"></i>').prop("disabled", true);
|
361
|
return $.get("/stabile/images?action=zbackup", function(){$("#dozbackup").html('Backup now').prop("disabled", false);});
|
362
|
},
|
363
|
|
364
|
getActionButtons : function(item, include_save){
|
365
|
if (user.is_readonly) return "";
|
366
|
|
367
|
var name = item.name;
|
368
|
var type = this.name;
|
369
|
|
370
|
function actionButton(args){
|
371
|
args.name = name;
|
372
|
args.type = type;
|
373
|
return grid.actionButton(args);
|
374
|
}
|
375
|
|
376
|
var id = item.uuid;
|
377
|
var status = item.status;
|
378
|
var snapshot = item.snap1;
|
379
|
|
380
|
// ['irigo'] == 'irigo' is true
|
381
|
var _user = item.user; //store.getValue(item, 'user');
|
382
|
var _type = item.type;
|
383
|
|
384
|
if (!id) {
|
385
|
console.log("Error - image has no uuid", item);
|
386
|
return "";
|
387
|
}
|
388
|
var download_button = actionButton({'action':"download", 'id':id});
|
389
|
|
390
|
var browse_button = steam2.models.Image.actionButton(id);
|
391
|
|
392
|
var clone_button = actionButton({'action':"clone", 'id':id});
|
393
|
var convert_button = actionButton({'action':"convert", 'id':id, 'title': 'convert to qcow2'});
|
394
|
var inject_button = actionButton({'action':"inject", 'id':id, 'title': 'inject drivers'});
|
395
|
var snapshot_button = actionButton({'action':"snapshot", 'id':id});
|
396
|
var revert_button = actionButton({'action':"revert", 'id':id, 'confirm':true});
|
397
|
var unsnap_button = actionButton({'action':"unsnap", 'id':id});
|
398
|
var master_button = actionButton({'action':"master", 'id':id});
|
399
|
var unmaster_button = actionButton({'action':"unmaster", 'id':id, 'title': 'unmaster'});
|
400
|
var rebase_button = actionButton({'action':"rebase", 'id':id, 'title': 'rebase'});
|
401
|
var backup_button = actionButton({'action':"backup", 'id':id});
|
402
|
var delete_button = actionButton({'action':"delete", 'id':id, 'confirm':true});
|
403
|
var save_button = include_save ? grid.saveButton(type) : "";
|
404
|
var is_master = false;
|
405
|
|
406
|
var buttons = "";
|
407
|
|
408
|
|
409
|
if(status == "new"){
|
410
|
return save_button;
|
411
|
}
|
412
|
|
413
|
/*if(status == "used" || status == "unused" || status == "active"){
|
414
|
if (_type == "qcow2" && (_user == user.username || user.is_admin )) {
|
415
|
if(snapshot && snapshot[0] != '--'){
|
416
|
buttons += unsnap_button;
|
417
|
if(status == "used" || status == "unused") buttons += revert_button;
|
418
|
} else {
|
419
|
buttons += snapshot_button;
|
420
|
}
|
421
|
}
|
422
|
}*/
|
423
|
|
424
|
if(status == "used" || status == "unused" || status == "paused") {
|
425
|
if(item.storagepool != -1){
|
426
|
buttons += browse_button;
|
427
|
}
|
428
|
|
429
|
if (_type == "qcow2" && (_user == user.username || user.is_admin )) {
|
430
|
var _masterReg = new RegExp("\\.master\\." + _type + "$");
|
431
|
is_master = _masterReg.test(item.path)?true:false;
|
432
|
|
433
|
if (!is_master && status != "paused") {
|
434
|
if(snapshot && snapshot != '--'){
|
435
|
buttons += unsnap_button;
|
436
|
buttons += revert_button;
|
437
|
} else {
|
438
|
buttons += snapshot_button;
|
439
|
}
|
440
|
}
|
441
|
if(item.master && item.master!="--" && (status=="unused" || status =="used")) { // this is a child, which may be rebased
|
442
|
if (is_master) buttons += unmaster_button;
|
443
|
// else
|
444
|
buttons += rebase_button;
|
445
|
}
|
446
|
if (item.storagepool != -1) {
|
447
|
if (is_master) {
|
448
|
is_master = true;
|
449
|
if (status=="unused") buttons += unmaster_button;
|
450
|
} else {
|
451
|
if (status=="unused") buttons += master_button;
|
452
|
}
|
453
|
}
|
454
|
}
|
455
|
if (_type != "iso") {
|
456
|
buttons += clone_button;
|
457
|
}
|
458
|
if (_type == "qcow2" && (status == "unused" || status == "used") && user.is_admin) {
|
459
|
buttons += inject_button;
|
460
|
}
|
461
|
if (_type == "img" || _type == "vmdk" || _type == "vhd") {
|
462
|
buttons += convert_button;
|
463
|
}
|
464
|
// We don't allow downloading of child images, master images may always be downloaded
|
465
|
if(( /*(!item.master || item.master=="--") && */ (status=="used" || status =="unused") && (item.storagepool != -1))
|
466
|
|| is_master) buttons += download_button;
|
467
|
}
|
468
|
|
469
|
if (
|
470
|
(_user == user.username || user.is_admin ) && item.backup!="disabled" &&
|
471
|
(status == "used" || status == "unused" || status == "paused" || (status == "active" && item.lvm == 1) )
|
472
|
) {
|
473
|
buttons += backup_button;
|
474
|
}
|
475
|
|
476
|
if((status == "unused" || status == "uploading" || status == "downloading") && (_user == user.username || user.is_admin )) {
|
477
|
buttons += delete_button;
|
478
|
}
|
479
|
|
480
|
if (buttons == "" && status != "active" && status.indexOf("backingup")==-1) {
|
481
|
return '<img height="18px" alt="busy" src="/stabile/static/img/loader.gif">';
|
482
|
}
|
483
|
|
484
|
if (user.is_admin || item.user==user.username || status == "backingup") buttons += save_button;
|
485
|
|
486
|
return buttons;
|
487
|
},
|
488
|
|
489
|
onBeforeDialog : function(item){
|
490
|
if(item.path && item.path != '--'){
|
491
|
stores.backups.url = "/stabile/images?action=listbackups&image=" +
|
492
|
escape(item.uuid);
|
493
|
stores.backups.close();
|
494
|
}
|
495
|
else{
|
496
|
// stores.backups.url = "/stabile/images?action=listbackups";
|
497
|
stores.backups.url = '';
|
498
|
stores.backups.close();
|
499
|
}
|
500
|
},
|
501
|
|
502
|
onDialogButtons: function (item) {
|
503
|
// update the human size field
|
504
|
if (dijit.byId('virtualsize')) {
|
505
|
dijit.byId('virtualsize').onChange();
|
506
|
// disable resizing of existing images
|
507
|
dijit.byId('realsize').onChange();
|
508
|
dijit.byId('backupsize').onChange();
|
509
|
var status = item.status;
|
510
|
|
511
|
var disable = function(dijitId){
|
512
|
var elm = dijit.byId(dijitId);
|
513
|
elm && elm.set('disabled', true);
|
514
|
return elm;
|
515
|
};
|
516
|
var enable = function(dijitId){
|
517
|
var elm = dijit.byId(dijitId);
|
518
|
elm && elm.set('disabled', false);
|
519
|
return elm;
|
520
|
};
|
521
|
|
522
|
if (user.is_readonly) {
|
523
|
disable('name');
|
524
|
}
|
525
|
if((status != 'new' && status != 'unused' && status != 'used') || user.is_readonly){
|
526
|
disable('virtualsize');
|
527
|
}
|
528
|
if(status == 'unused' && (user.is_admin || user.billto=='--' || (user.tktuser==user.billto && item.user==user.username)) && !user.is_readonly) {
|
529
|
enable('user');
|
530
|
} else {
|
531
|
disable('user');
|
532
|
}
|
533
|
|
534
|
if(user.is_readonly || (item.user!=user.username && !user.is_admin)) {
|
535
|
disable('storagepool');
|
536
|
} else if (status == 'unused') {
|
537
|
enable('storagepool');
|
538
|
} else if (status == 'new') {
|
539
|
dijit.byId('storagepool').set('value', 0);
|
540
|
disable('storagepool');
|
541
|
} else if (status == 'used') {
|
542
|
if (item.path.indexOf(".master." + item.type) != -1)
|
543
|
disable('storagepool');
|
544
|
else
|
545
|
enable('storagepool');
|
546
|
} else {
|
547
|
disable('storagepool');
|
548
|
}
|
549
|
|
550
|
if (item.path.indexOf(".master.qcow2")==-1 || (item.user!=user.username && !user.is_admin)) disable('installable');
|
551
|
|
552
|
if ((!user.is_admin && item.user!=user.username) || user.is_readonly) {
|
553
|
disable('name');
|
554
|
disable('notes');
|
555
|
disable('backup');
|
556
|
disable('backup');
|
557
|
disable('bschedule');
|
558
|
disable('restore_button');
|
559
|
}
|
560
|
|
561
|
if (dijit.byId('backup') && dijit.byId('backup').value=="disabled") {
|
562
|
document.getElementById('bschedulelabel').style.display = "none";
|
563
|
dijit.byId('bschedule').set('disabled', true).set('style', 'display:none');
|
564
|
}
|
565
|
|
566
|
if (status == 'new') {
|
567
|
var imgtypes = dijit.byId("type");
|
568
|
if (imgtypes) imgtypes.setStore(stores.imageTypes, imgtypes.value, {query:{type: /qcow2|vmdk|vdi|vhd|img/i}});
|
569
|
}
|
570
|
|
571
|
if (item.storagepool == -1 || status == 'active' || user.is_readonly) {
|
572
|
disable('mac');
|
573
|
}
|
574
|
|
575
|
if(item.status != "new"){
|
576
|
if (dijit.byId('type')) disable('type');
|
577
|
} else{
|
578
|
disable('backup');
|
579
|
}
|
580
|
}
|
581
|
var _masterReg = new RegExp("\.master\." + item.type + "$");
|
582
|
is_master = _masterReg.test(item.path)?true:false;
|
583
|
if (dijit.byId('master')) master = dijit.byId('master').value;
|
584
|
if (master && master!="--") {
|
585
|
// stores.images.fetch({query: {path:master+'*'}, onComplete: images.updateMasterName});
|
586
|
stores.images.fetch({query: {path:master}, onComplete: images.updateMasterName});
|
587
|
} else if (is_master) {
|
588
|
if (dijit.byId('mastername')) dijit.byId('mastername').set("value", "This image is a master image");
|
589
|
if (document.getElementById('masternamelabel')) document.getElementById('masternamelabel').innerHTML = "Master";
|
590
|
} else {
|
591
|
if (dijit.byId('mastername')) dijit.byId('mastername').set("value","--");
|
592
|
if (document.getElementById('masternamelabel')) document.getElementById('masternamelabel').innerHTML = "Master";
|
593
|
}
|
594
|
},
|
595
|
|
596
|
onPostRender: function(){
|
597
|
// images.updateSums();
|
598
|
},
|
599
|
|
600
|
rowStyler : function(row, item){
|
601
|
var _type = item.type;
|
602
|
var path = this.store.getValue(item, "path");
|
603
|
if(path.indexOf(".master." + _type) != -1){
|
604
|
row.customStyles += "font-weight:bold;";
|
605
|
} else {
|
606
|
row.customStyles = "cursor:pointer;";
|
607
|
}
|
608
|
},
|
609
|
|
610
|
// since stop propagation is not working on the help button
|
611
|
// we disable sorting.
|
612
|
canSort: function(index){
|
613
|
|
614
|
if(index === 8){ // action
|
615
|
return false;
|
616
|
}
|
617
|
if(index === 2){
|
618
|
// status! Something bug in the dojo dropdown button.
|
619
|
// it doesn't stop the event onClicks although I have specified it!
|
620
|
// Tooltip clicks then triggers a sort, and removal of the tooltip content.
|
621
|
// Therefore returning false.
|
622
|
return false;
|
623
|
}
|
624
|
return true;
|
625
|
},
|
626
|
|
627
|
// helpers
|
628
|
dialogOnSizeChangeHandler : function(caller, id){
|
629
|
var value = Number(caller.value);
|
630
|
if (!isNaN(value)) {// i.e., not '--'
|
631
|
dojo.byId(id).innerHTML = Math.round(value / (1024 * 1024 * 1024)) + ' GB';
|
632
|
}
|
633
|
},
|
634
|
|
635
|
dialogOnStoragepoolChangeHandler : function(caller){
|
636
|
var index = parseInt(caller.value);
|
637
|
if (user.node_storage_allowed) index += 1; // "On node" is first item in array, so we add one
|
638
|
var bschedule = dijit.byId("bschedule");
|
639
|
var hostpath = caller.options[index].item.hostpath;
|
640
|
var lvm = caller.options[index].item.lvm;
|
641
|
// console.log("caller", caller.options[index].item);
|
642
|
if (bschedule && (hostpath == "local" || hostpath == "node") && lvm==1)
|
643
|
bschedule.setStore(stores.backupSchedules, bschedule.value);
|
644
|
else
|
645
|
bschedule.setStore(stores.backupSchedules, bschedule.value, {query:{schedule: /manually|none/i}});
|
646
|
},
|
647
|
|
648
|
updateMasterName : function(items) {
|
649
|
if (items && items.name && dijit.byId("mastername")) {
|
650
|
dijit.byId("mastername").set("value", items.name);
|
651
|
if (dojo.byId('masternamelabel')) dojo.byId('masternamelabel').innerHTML =
|
652
|
'<a nohref="#" onClick="$.get(\'/stabile/images/' + items.uuid + '\',function(item) {home.showImageItemDialog(item)});">Master</a>'
|
653
|
// "<a href=\"#\" onClick=\"home.showImageDialog('" + items.path + "');\">Master</a>"
|
654
|
} else {
|
655
|
if (dijit.byId('mastername')) dijit.byId('mastername').set("value","--");
|
656
|
if (dojo.byId('masternamelabel')) dojo.byId('masternamelabel').innerHTML = "Master";
|
657
|
}
|
658
|
stores.images.reset('norender');
|
659
|
},
|
660
|
|
661
|
updateMissingBackups : function(filter) {
|
662
|
if (IRIGO.user.zfsavailable) $("#dozbackup").show();
|
663
|
else $("#dozbackup").hide();
|
664
|
if ($("li#imagestab.active").length == 1) {
|
665
|
var imagesfilter = {domains: '*'};
|
666
|
var carray;
|
667
|
if (!home.currentItem) {
|
668
|
} else if (home.currentItem.issystem) {
|
669
|
carray = home.currentItem.children;
|
670
|
} else {
|
671
|
imagesfilter = {domains: home.currentItem.uuid};
|
672
|
}
|
673
|
stores.images.close();
|
674
|
stores.images.fetch({query:imagesfilter, onComplete: function(result){
|
675
|
var d = Math.ceil( new Date() / 1000 );
|
676
|
var one_day=60*60*24;
|
677
|
var unique_imgs = new Array();
|
678
|
var no_backup = 0;
|
679
|
var no_24_backup = 0;
|
680
|
var master_no_backup = 0;
|
681
|
var lvm_no_schedule = 0;
|
682
|
var no_backup_list = "";
|
683
|
var no_24_backup_list = "";
|
684
|
var master_no_backup_list = "";
|
685
|
var lvm_no_schedule_list = "";
|
686
|
var backed_up = 0;
|
687
|
|
688
|
for (var i in result) {
|
689
|
if (result[i] && result[i].uuid && result[i].domains && result[i].domains!='--' && result[i].type!='iso') {
|
690
|
if (unique_imgs[result[i].uuid]) continue; // We have seen this before
|
691
|
else unique_imgs[result[i].uuid] = 1;
|
692
|
var cmatch = result[i];
|
693
|
if (cmatch) {
|
694
|
if (cmatch.lvm==1 && (!cmatch.bschedule || cmatch.bschedule=="--" || cmatch.bschedule=="manually")){
|
695
|
lvm_no_schedule++;
|
696
|
lvm_no_schedule_list += cmatch.name + "\n";
|
697
|
}
|
698
|
if (!cmatch.created || d-cmatch.created > one_day) {
|
699
|
if (!cmatch.btime || cmatch.btime=='--') { // No backup at all
|
700
|
if (cmatch.path.indexOf(".master.qcow2")!=-1) {
|
701
|
if (!cmatch.appid || cmatch.appid=='--') { // no need to back up appstore masters
|
702
|
if (cmatch.path.indexOf("/common/")!=-1) { // Only notify admins of missing common master backups
|
703
|
if (user.is_admin) {
|
704
|
master_no_backup++;
|
705
|
master_no_backup_list += cmatch.name + "\n";
|
706
|
console.log("missing master backup", cmatch);
|
707
|
}
|
708
|
} else { // Notify of personal missing master backups
|
709
|
master_no_backup++;
|
710
|
master_no_backup_list += cmatch.name + "\n";
|
711
|
}
|
712
|
}
|
713
|
} else {
|
714
|
no_backup++;
|
715
|
no_backup_list += cmatch.name + "\n";
|
716
|
no_24_backup++;
|
717
|
no_24_backup_list += cmatch.name + "\n";
|
718
|
}
|
719
|
} else if (d-cmatch.btime > one_day) { // Backup older than 24h and not a master
|
720
|
if (cmatch.path.indexOf(".master.qcow2")==-1) {
|
721
|
no_24_backup++;
|
722
|
no_24_backup_list += cmatch.name + "\n";
|
723
|
}
|
724
|
} else {
|
725
|
backed_up++;
|
726
|
}
|
727
|
} else {
|
728
|
if (cmatch.btime && cmatch.btime!='--') backed_up++;
|
729
|
}
|
730
|
}
|
731
|
}
|
732
|
}
|
733
|
var u = '<b>Missing backups:</b> ';
|
734
|
if (no_backup>0) u += "<span title=\"" + no_backup_list + "\">" + no_backup + " (none)</span>, ";
|
735
|
if (no_24_backup>0) u += "<span title=\"" + no_24_backup_list + "\">" + no_24_backup + " (none in 24h)</span>, ";
|
736
|
if (lvm_no_schedule>0) u += "<span title=\"" + lvm_no_schedule_list + "\">" + lvm_no_schedule + " (no schedule)</span>, ";
|
737
|
if (master_no_backup>0) u += "<span title=\"" + master_no_backup_list + "\">" + master_no_backup + " (masters)</span>, ";
|
738
|
u = u.slice(0,-2);
|
739
|
u += "";
|
740
|
if (no_backup+no_24_backup+lvm_no_schedule+master_no_backup>0)
|
741
|
home.missingbackups.innerHTML = u;
|
742
|
else if (backed_up>0)
|
743
|
home.missingbackups.innerHTML = '<span style="font-size:90%; color:#AAAAAA;">All your images are backed up</span>';
|
744
|
else
|
745
|
home.missingbackups.innerHTML = '<span style="font-size:90%; color:#AAAAAA;">No missing backups</span>';
|
746
|
}});
|
747
|
}
|
748
|
}
|
749
|
|
750
|
};
|
751
|
|
752
|
images.init = function(){
|
753
|
if (images._inited === true) return;
|
754
|
else images._inited = true;
|
755
|
|
756
|
images.store = stores.images;
|
757
|
images.domnode = "images-grid";
|
758
|
images.grid = grid.create(images);
|
759
|
|
760
|
// connect listeners for filtering
|
761
|
connect.connect(dijit.byId('images_search_query'), 'onChange', this, this.onSearchQueryChange);
|
762
|
connect.connect(dijit.byId('images_storagepool_filter_select'), 'onChange', this, this.onStoragePoolFilterChange);
|
763
|
connect.connect(dijit.byId('images_type_filter_select'), 'onChange', this, this.onTypeFilterChange);
|
764
|
|
765
|
connect.connect(this.grid, '_onFetchComplete', this, function(rows){
|
766
|
this.updateSums(rows);
|
767
|
if (!user.is_readonly) {
|
768
|
if (dijit.byId("imagesNewButton")) dijit.byId("imagesNewButton").set("style", "display:inline");
|
769
|
if (dijit.byId("imagesUploadButton")) dijit.byId("imagesUploadButton").set("style", "display:inline");
|
770
|
}
|
771
|
images.updateMissingBackups();
|
772
|
});
|
773
|
|
774
|
images.grid.startup();
|
775
|
|
776
|
var q = dojo.query('#imagesFileUploadHelp');
|
777
|
if(q.irigoTooltip){q.irigoTooltip();}
|
778
|
|
779
|
dojo.subscribe('upload:file_uploaded', function(){
|
780
|
images.grid.refresh();
|
781
|
stores.cdroms.close();
|
782
|
});
|
783
|
|
784
|
// refresh stores on image delete
|
785
|
dojo.subscribe('images:delete', function(task) {
|
786
|
if (typeof uploader !== 'undefined' && uploader.files) { // Remove file from uploader if we just uploaded it
|
787
|
var pathname = task.path.substring(task.path.lastIndexOf("/")+1);
|
788
|
$.each(uploader.files, function(index, value) {
|
789
|
if (value && (value.name == pathname || (value.path && value.path == task.path))) {
|
790
|
console.log("Removing upload", pathname);
|
791
|
uploader.removeFile(value.id);
|
792
|
}
|
793
|
});
|
794
|
}
|
795
|
// update stores.
|
796
|
stores.cdroms.close();
|
797
|
stores.masterimages.close();
|
798
|
stores.unusedImages.close();
|
799
|
stores.unusedImages2.close();
|
800
|
});
|
801
|
|
802
|
dojo.subscribe('images:create', function(){
|
803
|
stores.masterimages.close();
|
804
|
stores.unusedImages.close();
|
805
|
stores.unusedImages2.close();
|
806
|
});
|
807
|
|
808
|
// handle ui update while in dialog
|
809
|
dojo.subscribe("images:update", function(task){
|
810
|
console.log("images update", task);
|
811
|
|
812
|
if (task.uuid && images.grid.getRowNode(0)) images.grid.refreshRow(task); // due to bug in Dojo we refresh all if no images at all
|
813
|
else images.grid.refresh();
|
814
|
|
815
|
var item = images.grid.dialog.item;
|
816
|
|
817
|
if(!item || (item.uuid !== task.uuid && item.path !== task.uuid)){
|
818
|
return;
|
819
|
}
|
820
|
|
821
|
if (!images.grid.dialog || !images.grid.dialog.isOpen()) {
|
822
|
return;
|
823
|
} // No need to update fields if dialog not showing
|
824
|
|
825
|
if (dojo.byId('snap1') && task.snap1) {
|
826
|
if (task.snap1 == "--") {
|
827
|
item.snap1 = "--";
|
828
|
dijit.byId('snap1').set('value', "--");
|
829
|
} else {
|
830
|
var dsnap1 = (new Date(Number(task.snap1)*1000)).toLocaleString();
|
831
|
item.snap1 = dsnap1;
|
832
|
dijit.byId('snap1').set('value', dsnap1);
|
833
|
}
|
834
|
}
|
835
|
|
836
|
if (dojo.byId('path') && task.newpath) {
|
837
|
item.path = task.newpath;
|
838
|
dijit.byId('path').set('value', task.newpath);
|
839
|
}
|
840
|
if (dojo.byId('master') && task.master) {
|
841
|
item.master = task.master;
|
842
|
dijit.byId('master').set('value', task.master);
|
843
|
}
|
844
|
if (dojo.byId('backup') && task.backup) {
|
845
|
stores.backups.url = "/stabile/images?action=listbackups&image=" +
|
846
|
escape(task.backups);
|
847
|
stores.backups.close();
|
848
|
}
|
849
|
images.grid.dialog.show(item, true);
|
850
|
|
851
|
if (home.currentItem) {
|
852
|
stores.unusedImages2.close();
|
853
|
home.updateVitals(home.currentItem);
|
854
|
}
|
855
|
});
|
856
|
|
857
|
connect.connect(this.grid.dialog, 'show', this, function(image){
|
858
|
// summary: create dialog node link.
|
859
|
var self = this;
|
860
|
if(image.status != 'new'){
|
861
|
if(user.is_admin && image.mac && image.mac != '--'){
|
862
|
domConstruct.place('<a id="imageDialogNodeDialogLink" nohref="#images">Node</a>', 'imageDialogNodeDialogLink', 'replace');
|
863
|
on(dom.byId('imageDialogNodeDialogLink'), 'click', function(){
|
864
|
self.grid.dialog.hide();
|
865
|
nodes.grid.dialog.show(stores.nodes.fetchItemByIdentity({identity: image.mac }));
|
866
|
// Node.editDialogFromUuid(image.mac);
|
867
|
});
|
868
|
}
|
869
|
}
|
870
|
connect.connect(dijit.byId('backup'), 'onChange', this, this.onBackupChange);
|
871
|
});
|
872
|
|
873
|
images.onShowItem();
|
874
|
images.grid.refresh();
|
875
|
};
|
876
|
|
877
|
images.onShowItem = function() {
|
878
|
if (home.imagesOnShowItem != null && images.grid.dialog) {
|
879
|
images.grid.dialog.show(home.imagesOnShowItem);
|
880
|
home.imagesOnShowItem = null;
|
881
|
}
|
882
|
};
|
883
|
|
884
|
images.updateFilter = function(){
|
885
|
var query = this._searchQuery + " AND " + this._storagepoolQuery + " AND " + this._typeQuery;
|
886
|
console.log("filtering", query);
|
887
|
this.grid.store.query = query;
|
888
|
this.grid.filter(query, /*rerender*/true);
|
889
|
};
|
890
|
|
891
|
images.onSearchQueryChange = function(v){
|
892
|
if (v) {
|
893
|
this._searchQuery = "name:" +v + "*";
|
894
|
// this._searchQuery = "name: '*" + v + "*'" +
|
895
|
// " OR status: '" + v + "*'" +
|
896
|
// " OR type: '" + v + "*'";
|
897
|
} else {
|
898
|
this._searchQuery = "name:*";
|
899
|
// this._searchQuery = "name:*";
|
900
|
}
|
901
|
this.updateFilter();
|
902
|
};
|
903
|
|
904
|
images.onStoragePoolFilterChange = function(value){
|
905
|
switch(value){
|
906
|
case "all":
|
907
|
this._storagepoolQuery = "storagepool:all";
|
908
|
// this._storagePoolQuery = "uuid: *";
|
909
|
break;
|
910
|
case "shared_storage_pools":
|
911
|
this._storagepoolQuery = "storagepool:shared";
|
912
|
// this._storagePoolQuery = "NOT storagepool: -1";
|
913
|
break;
|
914
|
case "node_storage_pools":
|
915
|
this._storagepoolQuery = "storagepool:node";
|
916
|
break;
|
917
|
default:
|
918
|
alert('wtf? ' + value);
|
919
|
}
|
920
|
this.updateFilter();
|
921
|
};
|
922
|
|
923
|
images.onBackupChange = function(value) {
|
924
|
if (value && value != '--') $("#restore_button").show();
|
925
|
else $("#restore_button").hide();
|
926
|
};
|
927
|
|
928
|
images.onTypeFilterChange = function(value){
|
929
|
switch(value){
|
930
|
case "all":
|
931
|
this._typeQuery = "type:all";
|
932
|
// this._typeQuery = "uuid:*";
|
933
|
break;
|
934
|
case "user_images":
|
935
|
this._typeQuery = "type:user";
|
936
|
// this._typeQuery = "user:"+user.username+" AND (type:qcow2 OR type:vmdk OR type:vdi OR type:vhd OR type:img)";
|
937
|
break;
|
938
|
case "user_master_images":
|
939
|
this._typeQuery = "type:usermasters";
|
940
|
// this._typeQuery = "user:"+user.username+" AND path:*.master.*";
|
941
|
break;
|
942
|
case "user_cd_roms":
|
943
|
this._typeQuery = "type:usercdroms";
|
944
|
// this._typeQuery = "user:"+user.username+" AND type:iso";
|
945
|
break;
|
946
|
case "common_master_images":
|
947
|
this._typeQuery = "type:commonmasters";
|
948
|
// this._typeQuery = "user:common AND (type:qcow2 OR type:vmdk OR type:vdi OR type:vhd OR type:img)";
|
949
|
break;
|
950
|
case "common_cd_roms":
|
951
|
this._typeQuery = "type:commoncdroms";
|
952
|
// this._typeQuery = "user:common AND type:iso";
|
953
|
break;
|
954
|
default:
|
955
|
alert('wtf? ' + value);
|
956
|
}
|
957
|
this.updateFilter();
|
958
|
};
|
959
|
|
960
|
images.updateSums = function(rows) {
|
961
|
var totalvirtualsize = 0;
|
962
|
var totalrealsize = 0;
|
963
|
var totalbackupsize = 0;
|
964
|
|
965
|
if (!rows) {
|
966
|
for(var i = 0; i < images.grid.rowCount; i++){
|
967
|
sumit(images.grid.getItem(i));
|
968
|
}
|
969
|
} else {
|
970
|
for(var i in rows){
|
971
|
sumit(rows[i]);
|
972
|
}
|
973
|
}
|
974
|
|
975
|
function sumit(item) {
|
976
|
if (item) {
|
977
|
if (item.virtualsize && item.virtualsize!="--") totalvirtualsize += parseInt(item.virtualsize);
|
978
|
if (item.realsize && item.realsize!="--") totalrealsize += parseInt(item.realsize);
|
979
|
if (item.backupsize && item.backupsize!="--") totalbackupsize += parseInt(item.backupsize);
|
980
|
}
|
981
|
}
|
982
|
|
983
|
var stq = (user.storagequota==0)?'∞':Math.round(user.storagequota/1024);
|
984
|
var nstq = (user.storagequota==0)?'∞':Math.round(user.nodestoragequota/1024);
|
985
|
document.getElementById("storage_sum").innerHTML =
|
986
|
'<span title="Quotas: ' + stq + ' GB standard, ' + nstq + ' GB on node">Usage: ' + (Math.round(10*totalvirtualsize /1024/1024/1024)/10) + " GB" +
|
987
|
" Size: " + (Math.round(10*totalrealsize /1024/1024/1024)/10) + " GB" +
|
988
|
" Backup: " + (Math.round(10*totalbackupsize /1024/1024/1024)/10) + " GB" +
|
989
|
"</span>";
|
990
|
};
|
991
|
window.images = images;
|
992
|
return images;
|
993
|
});
|
994
|
|