
From: Samuel Guimarães <sguimaraes943@gmail.com> This commit adds the UI for Storage Volume management action button, filter input, gallery view and gallery / list view toggleswitch button with multi-selection enabled. Signed-off-by: Samuel Guimarães <sguimaraes943@gmail.com> --- ui/css/kimchi.css | 273 +++++++++++++++---- ui/css/src/modules/_storage.scss | 295 +++++++++++++++++---- ui/js/src/kimchi.api.js | 50 ++++ ui/js/src/kimchi.storage_main.js | 256 ++++++++++++++---- ui/js/src/kimchi.storagepool_add_volume_main.js | 2 +- ui/js/src/kimchi.storagepool_resize_volume_main.js | 58 ++++ ui/pages/i18n.json.tmpl | 5 + ui/pages/storagepool-resize-volume.html.tmpl | 51 ++++ ui/pages/tabs/storage.html.tmpl | 120 ++++++--- 9 files changed, 925 insertions(+), 185 deletions(-) create mode 100644 ui/js/src/kimchi.storagepool_resize_volume_main.js create mode 100644 ui/pages/storagepool-resize-volume.html.tmpl diff --git a/ui/css/kimchi.css b/ui/css/kimchi.css index 49ea39a..0e2f420 100644 --- a/ui/css/kimchi.css +++ b/ui/css/kimchi.css @@ -2183,6 +2183,8 @@ body.wok-gallery { } #storage-root-container .wok-datagrid-body .handle { + -webkit-user-select: none; + -moz-user-select: none; user-select: none; position: relative; } @@ -2226,69 +2228,38 @@ body.wok-gallery { } #storage-root-container .volumes .volumeslist { - padding: 11px; + padding: 22px; max-height: 285px; min-height: 136px; + max-height: 505px; overflow: auto; } -#storage-root-container .volumes .volume-box { - background: #fff; - padding: 4px 20px; - margin: 11px; - display: inline-block; - width: 409px; - height: 110px; +#storage-root-container .volumes .volumeslist .row { + font-size: 0; + margin-bottom: 22px; } -#storage-root-container .volumes .volume-title { - height: 46px; - width: 100%; - border-bottom: 1px solid #ccc; - position: relative; +#storage-root-container .volumes .volumeslist .filter { + width: 514px; } -#storage-root-container .volumes .volume-name { - font-size: 15pt; - font-weight: 300; - width: 274px; - line-height: 46px; +#storage-root-container .volumes .pool-action { display: inline-block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -#storage-root-container .volumes .volume-utilization { - vertical-align: top; - text-align: right; - display: inline-block; - width: 90px; - height: 46px; - line-height: 46px; + margin-right: 20px; } #storage-root-container .volumes .volume-icon { display: inline-block; + vertical-align: middle; width: 27px; - height: 46px; - line-height: 46px; + height: 27px; + line-height: 27px; background-repeat: no-repeat; background-position: 50%; background-color: transparent; } -#storage-root-container .volumes .volume-usage { - vertical-align: top; - font-size: 15pt; - font-weight: 400; - display: inline-block; - text-align: right; - line-height: 46px; - padding-left: 0; - margin-left: 5px; -} - #storage-root-container .volumes .volume-icon.icon-high { background-image: url("/images/theme-default/high.png"); } @@ -2304,13 +2275,13 @@ body.wok-gallery { #storage-root-container .volumes .volume-progress { position: absolute; margin: 0; - width: 409px; - top: -4px; - left: -20px; + width: 407px; + top: 4px; + left: 4px; } #storage-root-container .volumes .volume-progress .progress-bar-outer { - background: transparent; + background: #ddd; height: 6px; overflow: hidden; width: 100%; @@ -2347,8 +2318,216 @@ body.wok-gallery { } #storage-root-container .volumes .pool-empty { + width: 100%; + cursor: default !important; +} + +#storage-root-container .volumes .pool-empty > span { + width: 100%; text-align: center; line-height: 136px; + vertical-align: middle !important; +} + +#storage-root-container .volumes .wok-gallery .volume-inline-progress { + display: none; +} + +#storage-root-container .volumes .wok-gallery.wok-datagrid { + background: transparent; + margin-top: -12px; +} + +#storage-root-container .volumes .wok-gallery.wok-datagrid > .wok-datagrid-header { + display: none; +} + +#storage-root-container .volumes .wok-gallery.wok-datagrid > .wok-datagrid-body { + margin-left: -3px; +} + +#storage-root-container .volumes .wok-gallery.wok-datagrid .wok-datagrid-body > .wok-datagrid-row { + position: relative; + cursor: pointer; + background: #fff !important; + border: 0 !important; + display: inline-block !important; + width: 415px !important; + margin: 12px 4px 0; +} + +#storage-root-container .volumes .wok-gallery.wok-datagrid .wok-datagrid-body > .wok-datagrid-row, #storage-root-container .volumes .wok-gallery.wok-datagrid .wok-datagrid-body > .wok-datagrid-row * { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +#storage-root-container .volumes .wok-gallery.wok-datagrid .wok-datagrid-body > .wok-datagrid-row.selected .volume-box-border { + border-color: #8cc63f; +} + +#storage-root-container .volumes .wok-gallery.wok-datagrid .wok-datagrid-body > .wok-datagrid-row.selected .volume-box-inner { + border-color: #000; +} + +#storage-root-container .volumes .wok-gallery .volume-box-outer { + border-radius: 3px; + overflow: hidden; +} + +#storage-root-container .volumes .wok-gallery .volume-box-border { + display: block; + border: 3px solid transparent; + transition: border-color .1s ease-in-out; +} + +#storage-root-container .volumes .wok-gallery .volume-box-inner { + padding: 0 16px; + width: 409px; + height: 110px; + display: flex; + flex-flow: row wrap; + justify-content: space-around; + border: 1px solid #fff; + background: #fff; + transition: border-color .1s ease-in-out; +} + +#storage-root-container .volumes .wok-gallery span.column-name, +#storage-root-container .volumes .wok-gallery span.column-used { + height: 52px; + line-height: 52px; + border-bottom: 1px solid #ccc; +} + +#storage-root-container .volumes .wok-gallery span.column-name { + width: 303px; + order: 1; +} + +#storage-root-container .volumes .wok-gallery span.column-used { + width: 72px; + order: 2; +} + +#storage-root-container .volumes .wok-gallery span.column-name label.volume-name { + width: 100%; + height: 52px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + display: block; + margin: 0; + padding-right: 6px; +} + +#storage-root-container .volumes .wok-gallery span.column-allocated, +#storage-root-container .volumes .wok-gallery span.column-capacity, +#storage-root-container .volumes .wok-gallery span.column-format, +#storage-root-container .volumes .wok-gallery span.column-type { + order: 3; + width: 72px; + margin-top: -5px; +} + +#storage-root-container .volumes .wok-gallery span.gallery-header { + font-weight: 600; + display: block; +} + +#storage-root-container .volumes .wok-list span.gallery-header { + display: none; +} + +#storage-root-container .volumes .wok-list .volume-inline-progress { + vertical-align: middle; + display: inline-block; +} + +#storage-root-container .volumes .wok-list .volume-inline-progress > span.wok-loading-icon { + margin-right: 0; +} + +#storage-root-container .volumes .wok-list .volume-progress { + width: 100%; + top: 0px; + left: 0px; +} + +#storage-root-container .volumes .wok-list .volume-progress .progress-bar-outer { + height: 3px; +} + +#storage-root-container .volumes .wok-list .volume-box-inner { + font-size: 0; +} + +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-header > span.column-name { + padding-left: 41px; +} + +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row { + position: relative; + cursor: pointer; +} + +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row.selected { + background: #ddd !important; +} + +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row > div > div > div > span { + font-family: "Open Sans", Helvetica, Arial, "Lucida Grande", sans-serif; + line-height: 2.42857; + vertical-align: top; + font-size: 12.5pt; + font-weight: 400; +} + +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row > div > div > div > span.column-name { + padding-left: 15px; +} + +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-header > span, +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row > div > div > div > span { + display: inline-block; + vertical-align: middle; +} + +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-header span.column-format, +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row > div > div > div span.column-format { + width: 173px; +} + +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-header span.column-used, +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-header span.column-type, +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-header span.column-capacity, +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-header span.column-allocated, +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row > div > div > div span.column-used, +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row > div > div > div span.column-type, +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row > div > div > div span.column-capacity, +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row > div > div > div span.column-allocated { + width: 165px; + text-transform: capitalize; +} + +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-header > span.column-name, +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row > div > div > div > span.column-name { + width: 444px; +} + +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-header > span.column-name label, +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row > div > div > div > span.column-name label { + padding: 0 5px 0 0; + margin-right: 0; + margin-bottom: 0; + font-size: inherit; + font-weight: inherit; + width: 100%; + display: block; + height: auto; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } #storage-root-container .wok-datagrid > .wok-datagrid-body > .wok-datagrid-row[data-stat="inactive"] { diff --git a/ui/css/src/modules/_storage.scss b/ui/css/src/modules/_storage.scss index 4a9f9e1..5b7f44a 100644 --- a/ui/css/src/modules/_storage.scss +++ b/ui/css/src/modules/_storage.scss @@ -25,6 +25,8 @@ position: relative; } .handle { + -webkit-user-select: none; + -moz-user-select: none; user-select: none; position: relative; } @@ -58,66 +60,44 @@ width: 100%; background: $navbar-default-toggle-hover-bg; display: none; + > .footer { z-index: 100; } + .volumeslist { - padding: 11px; + padding: 22px; max-height: 285px; min-height: 136px; + max-height: 505px; overflow: auto; } - .volume-box { - background: $navbar-inverse-toggle-icon-bar-bg; - padding: 4px 20px; - margin: 11px; - display: inline-block; - width: 409px; - height: 110px; - } - .volume-title { - height: 46px; - width: 100%; - border-bottom: 1px solid $input-border; - position: relative; + + .volumeslist .row { + font-size: 0; + margin-bottom: 22px; } - .volume-name { - font-size: 15pt; - font-weight: 300; - width: 274px; - line-height: 46px; - display: inline-block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + + .volumeslist .filter { + width: 514px; } - .volume-utilization { - vertical-align: top; - text-align: right; + + .pool-action { display: inline-block; - width: 90px; - height: 46px; - line-height: 46px; + margin-right: 20px; } + .volume-icon { display: inline-block; + vertical-align: middle; width: 27px; - height: 46px; - line-height: 46px; + height: 27px; + line-height: 27px; background-repeat: no-repeat; background-position: 50%; background-color: transparent; } - .volume-usage { - vertical-align: top; - font-size: 15pt; - font-weight: 400; - display: inline-block; - text-align: right; - line-height: 46px; - padding-left: 0; - margin-left: 5px; - } + .volume-icon.icon-high { background-image: url('#{$wok-icon-path}/high.png'); } @@ -127,14 +107,16 @@ .volume-icon.icon-low { background-image: url('#{$wok-icon-path}/low.png'); } + .volume-progress { position: absolute; margin: 0; - width: 409px; - top: -4px; - left: -20px; + width: 407px; + top: 4px; + left: 4px; + .progress-bar-outer { - background: transparent; + background: $table-bg-hover; height: 6px; overflow: hidden; width: 100%; @@ -145,6 +127,7 @@ width: 100%; } } + .volume-data { margin: 0; padding: 0; @@ -164,10 +147,230 @@ } } } - .pool-empty { + .pool-empty{ + width: 100%; + cursor: default !important; + > span { + width: 100%; text-align: center; line-height: 136px; + vertical-align: middle !important; + } } + + .wok-gallery { + + .volume-inline-progress { + display: none; + } + + &.wok-datagrid { + background: transparent; + margin-top: -12px; + } + + &.wok-datagrid > .wok-datagrid-header { + display: none; + } + + &.wok-datagrid > .wok-datagrid-body { + margin-left: -3px; + } + + &.wok-datagrid .wok-datagrid-body > .wok-datagrid-row { + position: relative; + cursor: pointer; + background: $body-bg !important; + border: 0 !important; + display: inline-block !important; + width: 415px !important; + margin: 12px 4px 0; + &, * { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + } + } + + &.wok-datagrid .wok-datagrid-body > .wok-datagrid-row.selected .volume-box-border { + border-color: $guests-color; + } + + &.wok-datagrid .wok-datagrid-body > .wok-datagrid-row.selected .volume-box-inner { + border-color: $gray-base; + } + + .volume-box-outer { + border-radius: 3px; + overflow: hidden; + } + + .volume-box-border { + display: block; + border: 3px solid transparent; + transition: border-color .1s ease-in-out + } + + .volume-box-inner { + padding: 0 16px; + width: 409px; + height: 110px; + display: flex; + flex-flow: row wrap; + justify-content: space-around; + border: 1px solid #fff; + background: #fff; + transition: border-color .1s ease-in-out; + } + + span.column-name, + span.column-used { + height: 52px; + line-height: 52px; + border-bottom: 1px solid $input-border; + } + + span.column-name { + width: 303px; + order: 1; + } + + span.column-used { + width: 72px; + order: 2; + } + + span.column-name label.volume-name { + width: 100%; + height: 52px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + display: block; + margin: 0; + padding-right: 6px; + } + + span.column-allocated, + span.column-capacity, + span.column-format, + span.column-type { + order: 3; + width: 72px; + margin-top: -5px; + } + + span.gallery-header { + font-weight: 600; + display: block; + } + + } + + .wok-list { + + span.gallery-header { + display: none; + } + + .volume-inline-progress { + vertical-align: middle; + display: inline-block; + } + + .volume-inline-progress > span.wok-loading-icon { + margin-right: 0; + } + + .volume-progress { + width: 100%; + top: 0px; + left: 0px; + + .progress-bar-outer { + height: 3px; + } + } + + .volume-box-inner { + font-size: 0; + } + + &.wok-datagrid > .wok-datagrid-header { + + > span.column-name { + padding-left: 41px; + } + + } + + &.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row { + position: relative; + cursor: pointer; + } + + &.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row.selected { + background: $table-bg-hover !important; + } + + &.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row > div > div > div { + + > span { + font-family: $font-family-sans-serif; + line-height: (1 + $line-height-base); + vertical-align: top; + font-size: 12.5pt; + font-weight: 400; + } + + > span.column-name { + padding-left: 15px; + } + + } + + &.wok-datagrid > .wok-datagrid-header, + &.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row > div > div > div { + + >span { + display: inline-block; + vertical-align: middle; + } + + span.column-format { + width: 173px; + } + + span.column-used, + span.column-type, + span.column-capacity, + span.column-allocated { + width: 165px; + text-transform: capitalize; + } + + > span.column-name { + width: 444px; + + label { + padding: 0 5px 0 0; + margin-right: 0; + margin-bottom: 0; + font-size: inherit; + font-weight: inherit; + width: 100%; + display: block; + height: auto; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + } + + } + + } .wok-datagrid > .wok-datagrid-body > .wok-datagrid-row[data-stat="inactive"] { color: $gray-light !important; diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js index 83d1cc7..321bcb0 100644 --- a/ui/js/src/kimchi.api.js +++ b/ui/js/src/kimchi.api.js @@ -725,6 +725,56 @@ var kimchi = { }); }, + cloneStoragePoolVolume: function(poolName, volumeName, suc, err) { + var url = 'plugins/kimchi/storagepools/' + encodeURIComponent(poolName) + '/storagevolumes/' + encodeURIComponent(volumeName) + '/clone'; + wok.requestJSON({ + url : url, + type : 'POST', + data : JSON.stringify(data), + contentType : 'application/json', + dataType : 'json', + success : suc, + error : err + }); + }, + + resizeStoragePoolVolume: function(poolName, volumeName, data, suc, err) { + var url = 'plugins/kimchi/storagepools/' + encodeURIComponent(poolName) + '/storagevolumes/' + encodeURIComponent(volumeName) + '/resize'; + wok.requestJSON({ + url : url, + type : 'POST', + data : JSON.stringify(data), + contentType : 'application/json', + dataType : 'json', + success : suc, + error : err + }); + }, + + wipeStoragePoolVolume: function(poolName, volumeName, suc, err) { + var url = 'plugins/kimchi/storagepools/' + encodeURIComponent(poolName) + '/storagevolumes/' + encodeURIComponent(volumeName) + '/wipe'; + wok.requestJSON({ + url : url, + type : 'POST', + contentType : 'application/json', + dataType : 'json', + success : suc, + error : err + }); + }, + + deleteStoragePoolVolume: function(poolName, volumeName, suc, err) { + var url = 'plugins/kimchi/storagepools/' + encodeURIComponent(poolName) + '/storagevolumes/' + encodeURIComponent(volumeName); + wok.requestJSON({ + url : url, + type : 'DELETE', + contentType : 'application/json', + dataType : 'json', + success : suc, + error : err + }); + }, + getHostVgs: function(suc, err) { var url = 'plugins/kimchi/host/vgs/'; wok.requestJSON({ diff --git a/ui/js/src/kimchi.storage_main.js b/ui/js/src/kimchi.storage_main.js index 5312388..cf0e2e1 100644 --- a/ui/js/src/kimchi.storage_main.js +++ b/ui/js/src/kimchi.storage_main.js @@ -65,6 +65,166 @@ kimchi.doListStoragePools = function() { kimchi.storageBindClick = function() { + $('.volumes').on('click','.toggle-gallery',function(e){ + e.preventDefault(); + e.stopPropagation(); + var button = $(this); + var volumeBlock = $(this).parent().parent().parent(); + var text = $('span.text', button).text(); + $(".wok-list, .wok-gallery",volumeBlock).toggleClass("wok-list wok-gallery"); + $('span.text', button).text(text == i18n['KCHTMPL6005M'] ? i18n['KCHTMPL6004M'] : i18n['KCHTMPL6005M']); + }); + + if(wok.tabMode['storage'] === 'admin') { + + $('.volumes').on('click','.volume-delete',function(e){ + e.preventDefault(); + e.stopPropagation(); + var button = $(this); + kimchi.selectedSP = $(this).data('name'); + var volumeBlock = $(this).data('name'); + var volumes = $('[data-name="'+kimchi.selectedSP+'"] input:checkbox:checked').map(function(){ + return this.value; + }).get(); + kimchi.selectedVolumes = volumes.slice(); + // console.log('selected volumes'); + // console.log(kimchi.selectedVolumes); + // console.log('selected volumes clones'); + // console.log(volumes); + var formatedVolumes = ''; + if(kimchi.selectedVolumes.length && !button.parent().is('disabled')){ + for (i = 0; i < kimchi.selectedVolumes.length; i++) { + formatedVolumes += "<li>" + kimchi.selectedVolumes[i] + "</li>"; + } + var confirmMessage = i18n['KCHPOOL6010M'].replace('%1','<ul>'+formatedVolumes+'</ul>'+i18n['KCHPOOL6009M']); + var settings = { + title : i18n['KCHAPI6001M'], + content : confirmMessage, + confirm : i18n['KCHAPI6002M'], + cancel : i18n['KCHAPI6003M'] + }; + wok.confirm(settings, function() { + $.each(kimchi.selectedVolumes, function(i,j) { + console.log(i + ": " + j); + volumes = jQuery.grep(volumes, function(value) { + return value != j; + }); + kimchi.deleteStoragePoolVolume(kimchi.selectedSP,j,function(){ + wok.message.success(i18n['KCHPOOL6017M'].replace('%1','<strong>'+j+'</strong>')); + },function(err){ + wok.message.error(err.responseJSON.reason); + }); + if(volumes.length === 0){ + wok.topic('kimchi/storageVolumeDeleted').publish(); + kimchi.selectedVolumes = ''; + volumes = ''; + } + }); + }); + }else { + return false; + } + }); + + $('.volumes').on('click','.volume-wipe',function(e){ + e.preventDefault(); + e.stopPropagation(); + var button = $(this); + kimchi.selectedSP = $(this).data('name'); + var volumeBlock = $(this).data('name'); + var volumes = $('[data-name="'+kimchi.selectedSP+'"] input:checkbox:checked').map(function(){ + return this.value; + }).get(); + kimchi.selectedVolumes = volumes.slice(); + var formatedVolumes = ''; + if(kimchi.selectedVolumes.length && !button.parent().is('disabled')){ + for (i = 0; i < kimchi.selectedVolumes.length; i++) { + formatedVolumes += "<li>" + kimchi.selectedVolumes[i] + "</li>"; + } + var confirmMessage = i18n['KCHPOOL6018M'].replace('%1','<ul>'+formatedVolumes+'</ul>'+i18n['KCHPOOL6009M']); + var settings = { + title : i18n['KCHPOOL6019M'], + content : confirmMessage, + confirm : i18n['KCHAPI6002M'], + cancel : i18n['KCHAPI6003M'] + }; + wok.confirm(settings, function() { + $.each(kimchi.selectedVolumes, function(i,j) { + volumes = jQuery.grep(volumes, function(value) { + return value != j; + }); + kimchi.wipeStoragePoolVolume(kimchi.selectedSP,j,function(){ + wok.message.success(i18n['KCHPOOL6017M'].replace('%1','<strong>'+j+'</strong>')); + },function(err){ + wok.message.error(err.responseJSON.reason); + }); + if(volumes.length === 0){ + wok.topic('kimchi/storageVolumeWiped').publish(); + kimchi.selectedVolumes = ''; + volumes = ''; + } + }); + }); + }else { + return false; + } + }); + + $('.volumes').on('click','.volume-resize',function(e){ + e.preventDefault(); + e.stopPropagation(); + var button = $(this); + kimchi.selectedSP = $(this).data('name'); + var volumes = $('[data-name="'+kimchi.selectedSP+'"] input:checkbox:checked').map(function(){ + return this.value; + }).get(); + kimchi.selectedVolumes = volumes.slice(0,1); + if(kimchi.selectedVolumes.length && !button.parent().is('disabled')){ + wok.window.open('plugins/kimchi/storagepool-resize-volume.html'); + }else { + return false; + } + }); + + $('.volumes').on('click','.wok-datagrid-row',function(e){ + if (!$(e.target).is("input[type='checkbox']") && !$(e.target).is("label")) { + var volumeBlock = $(this); + var checkbox = volumeBlock.find('[name="selected-volume[]"]'); + checkbox.trigger('click'); + } + }); + + $('.volumes').on('change','[name="selected-volume[]"]',function(e){ + var checkbox = $(this); + var volumeBlock = $(this).closest('.wok-datagrid-row'); + var volumesBlock = $(this).closest('.volumeslist'); + var disabled = []; + if($('[name="selected-volume[]"]:checked',volumesBlock).length > 1) { + disabled = ['volume-clone','volume-wipe','volume-delete']; + $('.volume-resize',volumesBlock).parent().addClass('disabled'); + for (i = 0; i < disabled.length; i++) { + $('.'+disabled[i],volumesBlock).parent().removeClass('disabled'); + } + }else if($('[name="selected-volume[]"]:checked',volumesBlock).length === 1){ + disabled = ['volume-resize','volume-clone','volume-wipe','volume-delete']; + for (i = 0; i < disabled.length; i++) { + $('.'+disabled[i],volumesBlock).parent().removeClass('disabled'); + } + }else { + disabled = ['volume-resize','volume-clone','volume-wipe','volume-delete']; + for (i = 0; i < disabled.length; i++) { + $('.'+disabled[i],volumesBlock).parent().addClass('disabled'); + } + } + if(checkbox.is(":checked")){ + volumeBlock.addClass('selected'); + }else { + volumeBlock.removeClass('selected'); + } + }); + + } + $('.inactive').each(function(index) { if ('active' === $(this).data('state')) { $(this).hide(); @@ -229,7 +389,8 @@ kimchi._generateVolumeHTML = function(volume) { if(volume['type'] === 'kimchi-iso') { return ''; } - var volumeHtml = $('#volumeTmpl').html(); + var volumeHtml = $('#volumeTmpl2').html(); + volume.checkbox = volume.name.replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi,'-'), volume.capacityLevel = Math.round(volume.allocation / volume.capacity * 100) || 0; if (volume.capacityLevel > 100) { volume.capacityIcon = 'icon-high'; @@ -273,11 +434,12 @@ kimchi.doListVolumes = function(poolObj) { return result; }; - var volumeDiv = $('#volume' + poolName); - $(volumeDiv).empty(); + var volumeDiv = $('#volume-' + poolName); + var volumeDatatable = $('.wok-datagrid > .wok-datagrid-body',volumeDiv); + $(volumeDatatable).empty(); var slide = $('.volumes', poolObj); var handleArrow = $('.arrow-down', poolObj); - + console.log('arrow down'); kimchi.listStorageVolumes(poolName, function(result) { var listHtml = ''; var ongoingVolumes = []; @@ -310,9 +472,14 @@ kimchi.doListVolumes = function(poolObj) { }); if (listHtml.length > 0) { - volumeDiv.html(listHtml); + $('.filter',volumeDiv).prop('disabled',false); + $('.toggle-gallery',volumeDiv).prop('disabled',false); + $(volumeDatatable).html(listHtml); + } else { - volumeDiv.html("<div class='pool-empty'>" + i18n['KCHPOOL6002M'] + "</div>"); + $('.filter',volumeDiv).prop('disabled',true); + $('.toggle-gallery',volumeDiv).prop('disabled',true); + $(volumeDatatable).html("<div class='pool-empty wok-datagrid-row'><span class='volume-empty'>" + i18n['KCHPOOL6002M'] + "</span></div>"); } $.each(ongoingVolumesMap, function(volumeName, task) { @@ -322,6 +489,18 @@ kimchi.doListVolumes = function(poolObj) { poolObj.removeClass('in'); kimchi.changeArrow(handleArrow); slide.slideDown('slow'); + + volumeDivId = volumeDiv.attr('id'); + + volumeOptions = { + valueNames: ['volume-name-filter', 'volume-format-filter', 'volume-type-filter'] + }; + volumeFilterList = new List(volumeDivId, volumeOptions); + + volumeFilterList.sort('volume-name-filter', { + order: "asc" + }); + }, function(err) { wok.message.error(err.responseJSON.reason); }, false); @@ -329,53 +508,6 @@ kimchi.doListVolumes = function(poolObj) { kimchi.initLogicalPoolExtend = function() { - // $("#logicalPoolExtend").dialog({ - // autoOpen : false, - // modal : true, - // width : 600, - // resizable : false, - // closeText: "X", - // open : function(){ - // $('#loading-info', '#logicalPoolExtend').removeClass('hidden'); - // $(".ui-dialog-titlebar-close", $("#logicalPoolExtend").parent()).removeAttr("title"); - // kimchi.listHostPartitions(function(data) { - // $('#loading-info', '#logicalPoolExtend').addClass('hidden'); - // if (data.length > 0) { - // for(var i=0;i<data.length;i++){ - // if (data[i].type === 'part' || data[i].type === 'disk') { - // $('.host-partition', '#logicalPoolExtend').append(wok.substitute($('#logicalPoolExtendTmpl').html(), data[i])); - // } - // } - // } else { - // $('.host-partition').html(i18n['KCHPOOL6011M']); - // $('.host-partition').addClass('text-help'); - // } - // }, function(err) { - // $('#loading-info', '#logicalPoolExtend').addClass('hidden'); - // $('.host-partition').html(i18n['KCHPOOL6013M'] + '<br/>(' + err.responseJSON.reason + ')'); - // $('.host-partition').addClass('text-help'); - // }); - // }, - // beforeClose : function() { $('.host-partition', '#logicalPoolExtend').empty(); }, - // buttons : [{ - // class: "ui-button-primary", - // text: i18n.KCHAPI6007M, - // click: function(){ - // var devicePaths = []; - // $("input[type='checkbox']:checked", "#logicalPoolExtend").each(function(){ - // devicePaths.push($(this).prop('value')); - // }) - // kimchi.updateStoragePool($("#logicalPoolExtend").dialog("option", "poolName"),{disks: devicePaths},function(data){ - // var item = $("#"+$("#logicalPoolExtend").dialog("option", "poolName")); - // $(".usage", $(".storage-name", item)).text((Math.round(data.allocated/data.capacity*100)||0)+"%"); - // $(".storage-text", $(".storage-capacity", item)).text(wok.changetoProperUnit(data.capacity,1)); - // $(".storage-text", $(".storage-allocate", item)).text(wok.changetoProperUnit(data.allocated,1)); - // }); - // $(this).dialog("close"); - // } - // }] - // }); - $('#logicalPoolExtend').on('hidden.bs.modal', function () { $('.host-partition', '#logicalPoolExtend').empty(); }); @@ -442,6 +574,24 @@ kimchi.storage_main = function() { kimchi.doListVolumes(poolNode); }); + wok.topic('kimchi/storageVolumeDeleted').subscribe(function() { + pool = kimchi.selectedSP; + var poolNode = $('.storage-li[data-name="' + pool + '"]'); + kimchi.doListVolumes(poolNode); + }); + + wok.topic('kimchi/storageVolumeWiped').subscribe(function() { + pool = kimchi.selectedSP; + var poolNode = $('.storage-li[data-name="' + pool + '"]'); + kimchi.doListVolumes(poolNode); + }); + + wok.topic('kimchi/storageVolumeResized').subscribe(function() { + pool = kimchi.selectedSP; + var poolNode = $('.storage-li[data-name="' + pool + '"]'); + kimchi.doListVolumes(poolNode); + }); + wok.topic('kimchi/volumeTransferProgress').subscribe(function(result) { var extractProgressData = function(data) { var sizeArray = /(\d+)\/(\d+)/g.exec(data) || [0, 0, 0]; diff --git a/ui/js/src/kimchi.storagepool_add_volume_main.js b/ui/js/src/kimchi.storagepool_add_volume_main.js index c398369..e167a20 100644 --- a/ui/js/src/kimchi.storagepool_add_volume_main.js +++ b/ui/js/src/kimchi.storagepool_add_volume_main.js @@ -180,6 +180,6 @@ kimchi.sp_add_volume_main = function() { uploadFile(); } event.preventDefault(); - $('#modalWindow').modal.close(); + wok.window.close(); }); }; diff --git a/ui/js/src/kimchi.storagepool_resize_volume_main.js b/ui/js/src/kimchi.storagepool_resize_volume_main.js new file mode 100644 index 0000000..0b350bd --- /dev/null +++ b/ui/js/src/kimchi.storagepool_resize_volume_main.js @@ -0,0 +1,58 @@ +/* + * Project Kimchi + * + * Copyright IBM Corp, 2016 + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +kimchi.sp_resize_volume_main = function() { + + var addButton = $('#sp-resize-volume-button'); + var size = $('#volume-size'); + var form = $('form#form-sp-resize-volume'); + + $(addButton).prop('disabled',true); + $(size).on('keyup', function(){ + if($(this).val().length !==0) { + addButton.prop('disabled', false); + } else{ + addButton.prop('disabled',true); + } + }); + $(addButton).on('click',function(e){ + e.preventDefault(); + e.stopPropagation(); + $(form).submit(); + }); + $(form).on('submit',function(e){ + e.preventDefault(); + e.stopPropagation(); + var newsize = parseInt($(size).val()); + var data = {}; + data = { + size: newsize + }; + kimchi.resizeStoragePoolVolume(kimchi.selectedSP, kimchi.selectedVolumes[0], data, function() { + $(size).prop('disabled', true); + $(addButton).prop('disabled',true); + wok.topic('kimchi/storageVolumeResized').publish(); + wok.window.close(); + }, function(err) { + wok.message.error(err.responseJSON.reason, '#alert-modal-container'); + $(size).prop('disabled', false); + $(addButton).prop('disabled',false); + $(size).focus(); + }); + }); +} diff --git a/ui/pages/i18n.json.tmpl b/ui/pages/i18n.json.tmpl index e152989..2b786a5 100644 --- a/ui/pages/i18n.json.tmpl +++ b/ui/pages/i18n.json.tmpl @@ -98,6 +98,8 @@ "KCHPOOL6006M": "$_("Loading iSCSI targets...")", "KCHPOOL6007M": "$_("No iSCSI found. Please input one.")", "KCHPOOL6008M": "$_("Failed to load iSCSI targets.")", + "KCHPOOL6009M": "$_("Would you like to continue?")", + "KCHPOOL6010M": "$_("This will permanently delete the following storage volumes: %1")", "KCHPOOL6005E": "$_("Invalid NFS mount path.")", "KCHPOOL6006E": "$_("No logical device selected.")", @@ -108,6 +110,9 @@ "KCHPOOL6014M": "$_("In progress...")", "KCHPOOL6015M": "$_("Failed!")", "KCHPOOL6016M": "$_("No LVM found in the system.")", + "KCHPOOL6017M": "$_("Volume %1 was successfully removed.")", + "KCHPOOL6018M": "$_("This will permanently wipe the following storage volumes: %1")", + "KCHPOOL6019M": "$_("Wipe Confirmation")", "KCHVMSTOR0001E": "$_("CDROM path needs to be a valid local/remote path and cannot be blank.")", "KCHVMSTOR0002E": "$_("Disk pool or volume cannot be blank.")" diff --git a/ui/pages/storagepool-resize-volume.html.tmpl b/ui/pages/storagepool-resize-volume.html.tmpl new file mode 100644 index 0000000..fd9d798 --- /dev/null +++ b/ui/pages/storagepool-resize-volume.html.tmpl @@ -0,0 +1,51 @@ +#* + * Project Kimchi + * + * Copyright IBM Corp, 2016 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *# +#unicode UTF-8 +#import gettext +#from wok.cachebust import href +#silent t = gettext.translation($lang.domain, $lang.localedir, languages=$lang.lang, fallback=True) +#silent _ = t.gettext +#silent _t = t.gettext +<html> +<body> +<div id="sp-add-volume-window" class="window modal-content"> + <form id="form-sp-resize-volume"> + <div class="modal-header"> + <h4 class="modal-title">$_("Resize Volume")</h4> + </div> + <div class="modal-body"> + <span id="alert-modal-container"></span> + <div class="form-group"> + <label for="volume-size">$_("Size")</label> + <input type="number" id="volume-size" class="form-control" name="size" /> + <p class="help-block"> + <i class="fa fa-info-circle"></i> $_("The total space which can be used to store data. The unit is bytes.") + </p> + </div> + </div> + <div class="modal-footer"> + <button type="submit" id="sp-resize-volume-button" class="btn btn-default" disabled="disabled">$_("Ok")</button> + <button type="button" class="btn btn-default" data-dismiss="modal">$_("Cancel")</button> + </div> + </form> +</div> +<script type="text/javascript"> + kimchi.sp_resize_volume_main(); +</script> +</body> +</html> diff --git a/ui/pages/tabs/storage.html.tmpl b/ui/pages/tabs/storage.html.tmpl index fa51e48..46f81c2 100644 --- a/ui/pages/tabs/storage.html.tmpl +++ b/ui/pages/tabs/storage.html.tmpl @@ -102,50 +102,66 @@ </div> </div> <script id="storageTmpl" type="html/text"> - <div id="{name}" class="storage-li in" data-name="{name}" data-stat="{state}"> - <span class='column-state' val="{state}"> - <span class='storage-state {state}'> - <i class="fa fa-power-off"></i> - </span> - </span><!-- - --><span class='column-name' title="{name}" val="{name}">{name}</span><!-- - --><span class='column-type' val="{type}">{type}</span><!-- - --><span class='column-location' val="{path}">{path}</span><!-- - --><span class='column-usage {state}' val="{usage}" ><span class='usage-icon {icon}'>{usage}</span>%</span><!-- - --><span class='column-allocated' val="{allocated}">{allocated}</span><!-- - --><span class='column-capacity' val="{capacity}">{capacity}</span><!-- - --><span class="column-disks {state}"> - <div class="handle arrow-down"></div> - </span><!-- - --><span class="column-action storage-button" style="display:none"> - <span class="pull-right"> +<div id="{name}" class="storage-li in" data-name="{name}" data-stat="{state}"> + <span class='column-state' val="{state}"><span class='storage-state {state}'><i class="fa fa-power-off"></i></span></span><!-- + --><span class='column-name' title="{name}" val="{name}">{name}</span><!-- + --><span class='column-type' val="{type}">{type}</span><!-- + --><span class='column-location' val="{path}">{path}</span><!-- + --><span class='column-usage {state}' val="{usage}" ><span class='usage-icon {icon}'>{usage}</span>%</span><!-- + --><span class='column-allocated' val="{allocated}">{allocated}</span><!-- + --><span class='column-capacity' val="{capacity}">{capacity}</span><!-- + --><span class="column-disks {state}"><div class="handle arrow-down"></div></span><!-- + --><span class="column-action storage-button" style="display:none"> + <span class="pull-right"> <div class="dropdown menu-flat storage-action" data-state="{state}" data-type="{type}" data-name="{name}"> <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="false" aria-haspopup="true"><span class="edit-alt"></span>$_("Actions")<span class="caret"></span></button> <ul class="dropdown-menu actionsheet"> - <li role="presentation"> - <a href="#" class="pool-deactivate" data-inuse="{in_use}" data-stat="{state}" data-name="{name}" data-persistent="{persistent}" href="#"><i class="fa fa-minus-circle"></i>$_("Deactivate")</a> - </li> - <li role="presentation"> - <a href="#" class="pool-activate" data-stat="{state}" data-name="{name}"><i class="fa fa-power-off"></i>$_("Activate")</a> - </li> - <li role="presentation"> - <a href="#" class="pool-add-volume" data-stat="{state}" data-name="{name}" data-type="{type}"><i class="fa fa-plus-circle"></i>$_("Add Volume")</a> - </li> - <li role="presentation" class="{enableExt}"> - <a href="#" class="pool-extend" data-stat="{state}" data-name="{name}" data-toggle="modal" data-target="#logicalPoolExtend"><i class="fa fa-external-link-square"></i>$_("Extend")</a> - </li> - <li role="presentation" class="critical"> - <a href="#" class="pool-delete" data-inuse="{in_use}" data-stat="{state}" data-name="{name}"><i class="fa fa-ban"></i>$_("Undefine")</a> - </li> + <li><a href="#" class="pool-deactivate" data-inuse="{in_use}" data-stat="{state}" data-name="{name}" data-persistent="{persistent}"><i class="fa fa-minus-circle"></i>$_("Deactivate")</a></li> + <li><a href="#" class="pool-activate" data-stat="{state}" data-name="{name}"><i class="fa fa-power-off"></i>$_("Activate")</a></li> + <li class="{enableExt}"><a href="#" class="pool-extend" data-stat="{state}" data-name="{name}" data-toggle="modal" data-target="#logicalPoolExtend"><i class="fa fa-external-link-square"></i>$_("Extend")</a></li> + <li class="critical"><a href="#" class="pool-delete" data-inuse="{in_use}" data-stat="{state}" data-name="{name}"><i class="fa fa-ban"></i>$_("Undefine")</a></li> </ul> </div> - </span> </span> - <div class="volumes"> - <div id="volume{name}" class="volumeslist" data-name="{name}" ></div> - <div class="clear"></div> - </div> - </div> + </span> + <div class="volumes"> + <div id="volume-{name}" class="volumeslist" data-name="{name}"> + <div class="row"> + <div class="pull-left"> + <div class="dropdown menu-flat pool-action"> + <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="false" aria-haspopup="true"><span class="edit-alt"></span>$_("Actions")<span class="caret"></span></button> + <ul class="dropdown-menu actionsheet"> + <li><a href="#" class="pool-add-volume" data-stat="{state}" data-name="{name}" data-type="{type}"><i class="fa fa-plus-circle"></i> $_("Add Volume")</a></li> + <li class="disabled"><a href="#" class="volume-resize" data-name="{name}"><i class="fa fa-external-link-square"></i> $_("Resize")</a></li> + <li class="disabled"><a href="#" class="volume-clone" data-name="{name}"><i class="fa fa-copy"></i> $_("Clone")</a></li> + <li class="disabled"><a href="#" class="volume-wipe" data-name="{name}"><i class="fa fa-eraser"></i> $_("Wipe")</a></li> + <li class="disabled critical"><a href="#" class="volume-delete" data-name="{name}"><i class="fa fa-minus-circle"></i> $_("Delete")</a></li> + </ul> + </div> + <button type="button" class="btn btn-default toggle-gallery"><span class="text">$_('View Gallery')</span> <i class="fa fa-angle-right"></i><i class="fa fa-angle-right"></i><i class="fa fa-angle-right"></i></button> + </div> + <div class="pull-right"> + <label><span class="sr-only">$_('Filter:')</span> + <input type="text" class="filter form-control search" placeholder="$_('Filter')"> + </label> + </div> + </div> + <div class="wok-datagrid wok-list"> + <div class="wok-datagrid-header"> + <span class="column-name">$_('Name')</span><!-- + --><span class="column-format">$_('Format')</span><!-- + --><span class="column-type">$_('Type')</span><!-- + --><span class="column-used">$_('Used')</span><!-- + --><span class="column-allocated">$_('Allocated')</span><!-- + --><span class="column-capacity">$_('Capacity')</span> + </div> + <ul class="wok-datagrid-body list" id="volume-{name}-table"> + </ul> + </div> + </div> + <div class="clear"></div> + </div> +</div> </script> <script id="volumeTmpl" type="html/text"> <div class="volume-box" data-volume-name="{name}"> @@ -187,6 +203,34 @@ </ul> </div> </script> +<script id="volumeTmpl2" type="html/text"> +<li class="wok-datagrid-row"> + <div class="volume-progress hidden"> + <div class="progress-bar-outer"> + <div class="progress-bar-inner"></div> + </div> + <div class="progress-label"> + <span class="progress-status"></span> + <span class="progress-transferred"></span> + </div> + </div> + <div class="volume-box-outer"> + <div class="volume-box-border"> + <div class="volume-box-inner" data-volume-name="{name}"> + <span class="column-name" title="{name}"> + <input type="checkbox" class="wok-checkbox" name="selected-volume[]" id="{checkbox}" value="{name}"> + <label class="volume-name volume-name-filter" for="{checkbox}"><span class="volume-inline-progress hidden"><span class="wok-loading-icon"></span></span> {name}</label><!-- + --></span><!-- + --><span class="column-format volume-format-filter"><span class="gallery-header">$_('Format')</span>{format}</span><!-- + --><span class="column-type volume-type-filter"><span class="gallery-header">$_('Type')</span>{type}</span><!-- + --><span class="column-used"><span role="presentation" class="volume-icon {capacityIcon}"></span> {capacityLevel}%</span><!-- + --><span class="column-allocated"><span class="gallery-header">$_('Allocation')</span>{allocation}</span><!-- + --><span class="column-capacity"><span class="gallery-header">$_('Capacity')</span>{capacity}</span> + </div> + </div> + </div> +</li> +</script> <script id="logicalPoolExtendTmpl" type="html/text"> <div> <input type="checkbox" class="wok-checkbox" id="{name}" value="{path}" name="devices"> -- 1.9.3