[PATCH v4] [Kimchi] Storage Volume management

From: Samuel Guimarães <sguimaraes943@gmail.com> This patch adds Storage Volume management functions Wipe, Clone, Resize and Delete with multiple selection. It also includes a filter input for each Storage Pool and Gallery View for Storage Volumes. Changes from [RFC] version: v1: - HTML and CSS v2: - Delete and Wipe with multi-selection - Confirm messages with list of selected volumes when wiping or deleting volumes (requires SCSS/CSS patch sent to Wok) - Filter working - Removed "Add Volume" link from Storage Pool action button - Added "Add Volume" to Volume box action button v3: - Clone function working with multiple selection - Progress bar working for clone and create volume - Temporary volume added to the volumes when cloning - Seamless refresh on the volumes once each task is finished - Fixed issue when list wouldn't refresh when all volumes are removed from the storage pool. v4: - Prevent scroll when Drop-down in volumes list is clicked - Dropdown is not clipped from volumes list when there's only one or two items on the list - Added Media Queries for small screen resolutions Samuel Guimarães (1): Storage Volume management model/storagevolumes.py | 2 +- ui/css/kimchi.css | 369 ++++++++++++++-- ui/css/src/modules/_storage.scss | 347 ++++++++++++--- ui/js/src/kimchi.api.js | 50 +++ ui/js/src/kimchi.storage_main.js | 465 +++++++++++++++++---- ui/js/src/kimchi.storagepool_add_volume_main.js | 2 +- ui/js/src/kimchi.storagepool_resize_volume_main.js | 59 +++ ui/pages/i18n.json.tmpl | 6 + ui/pages/storagepool-resize-volume.html.tmpl | 51 +++ ui/pages/tabs/storage.html.tmpl | 155 +++---- 10 files changed, 1249 insertions(+), 257 deletions(-) create mode 100644 ui/js/src/kimchi.storagepool_resize_volume_main.js create mode 100644 ui/pages/storagepool-resize-volume.html.tmpl -- 1.9.3

From: Samuel Guimarães <sguimaraes943@gmail.com> This commit adds Storage Volume management functions Wipe, Clone, Resize and Delete with multiple selection. It also includes a filter input for each Storage Pool and Gallery View for Storage Volumes. Signed-off-by: Samuel Guimarães <sguimaraes943@gmail.com> --- model/storagevolumes.py | 2 +- ui/css/kimchi.css | 369 ++++++++++++++-- ui/css/src/modules/_storage.scss | 347 ++++++++++++--- ui/js/src/kimchi.api.js | 50 +++ ui/js/src/kimchi.storage_main.js | 465 +++++++++++++++++---- ui/js/src/kimchi.storagepool_add_volume_main.js | 2 +- ui/js/src/kimchi.storagepool_resize_volume_main.js | 59 +++ ui/pages/i18n.json.tmpl | 6 + ui/pages/storagepool-resize-volume.html.tmpl | 51 +++ ui/pages/tabs/storage.html.tmpl | 155 +++---- 10 files changed, 1249 insertions(+), 257 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/model/storagevolumes.py b/model/storagevolumes.py index da42e85..0b34c5b 100644 --- a/model/storagevolumes.py +++ b/model/storagevolumes.py @@ -435,7 +435,7 @@ class StorageVolumeModel(object): 'name': name, 'new_pool': new_pool, 'new_name': new_name} - taskid = add_task(u'/plugins/kimchi/storagepools/%s/storagevolumes/%s' + taskid = add_task(u'/plugins/kimchi/storagepools/%s/storagevolumes/%s/clone' % (pool, new_name), self._clone_task, self.objstore, params) return self.task.lookup(taskid) diff --git a/ui/css/kimchi.css b/ui/css/kimchi.css index 49ea39a..4798f64 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; } @@ -2221,74 +2223,66 @@ body.wok-gallery { display: none; } +#storage-root-container .volumes input[type=checkbox][disabled].wok-checkbox + label:before { + content: ''; +} + +#storage-root-container .volumes .toggle-gallery { + left: 202px; +} + +@media (min-width: 1200px) { + #storage-root-container .volumes .toggle-gallery { + left: 282px; + } +} + #storage-root-container .volumes > .footer { z-index: 100; } #storage-root-container .volumes .volumeslist { - padding: 11px; + padding: 22px; max-height: 285px; min-height: 136px; - overflow: auto; + max-height: 505px; + overflow-x: hidden; + overflow-y: 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: 344px; + height: 38px; + margin-top: 1px; } -#storage-root-container .volumes .volume-name { - font-size: 15pt; - font-weight: 300; - width: 274px; - line-height: 46px; - display: inline-block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; +@media (min-width: 992px) { + #storage-root-container .volumes .volumeslist .filter { + width: 514px; + } } -#storage-root-container .volumes .volume-utilization { - vertical-align: top; - text-align: right; +#storage-root-container .volumes .pool-action { 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 +2298,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 +2341,291 @@ 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.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-gallery span.column-progress { + display: none; +} + +#storage-root-container .volumes .wok-gallery .tooltip-iner { + font-size: 11pt; + padding: 6px 12px; +} + +#storage-root-container .volumes .wok-list .tooltip { + display: none !important; +} + +#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; + margin-right: 7px; +} + +#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: 57px; +} + +@media (min-width: 992px) { + #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: 95px; + } +} + +@media (min-width: 1200px) { + #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 { + left: 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-header > span.column-progress, +#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, +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row > div > div > div > span.column-progress { + width: 79px; + text-transform: capitalize; +} + +@media (min-width: 992px) { + #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-header > span.column-progress, + #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, + #storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row > div > div > div > span.column-progress { + width: 95px; + } +} + +@media (min-width: 1200px) { + #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-header > span.column-progress, + #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, + #storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row > div > div > div > span.column-progress { + left: 165px; + } +} + +#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: 202px; +} + +@media (min-width: 992px) { + #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: 333px; + } +} + +@media (min-width: 1200px) { + #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 .volumes .wok-list.wok-datagrid > .wok-datagrid-header > span.column-progress, +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row > div > div > div > span.column-progress { + white-space: nowrap; } #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..733b172 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,61 @@ width: 100%; background: $navbar-default-toggle-hover-bg; display: none; + + input[type=checkbox][disabled].wok-checkbox + label:before { + content: ''; + } + + .toggle-gallery { + left: 202px; + @media (min-width: $screen-lg) { + left: 282px; + } + } + > .footer { z-index: 100; } + .volumeslist { - padding: 11px; + padding: 22px; max-height: 285px; min-height: 136px; - overflow: auto; - } - .volume-box { - background: $navbar-inverse-toggle-icon-bar-bg; - padding: 4px 20px; - margin: 11px; - display: inline-block; - width: 409px; - height: 110px; + max-height: 505px; + overflow-x: hidden; + overflow-y: auto; } - .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: 344px; + height: 38px; + margin-top: 1px; + @media (min-width: $screen-md) { + 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 +124,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 +144,7 @@ width: 100%; } } + .volume-data { margin: 0; padding: 0; @@ -164,10 +164,263 @@ } } } - .pool-empty { + .pool-empty{ + width: 100%; + cursor: default !important; + > span { + width: 100%; text-align: center; line-height: 136px; + vertical-align: middle !important; + } + } + + .wok-gallery { + + &.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; + } + + span.column-progress { + display: none; + } + + .tooltip-iner { + font-size: 11pt; + padding: 6px 12px; + } + + } + + .wok-list { + + .tooltip { + display: none !important; + } + + span.gallery-header { + display: none; + } + + .volume-inline-progress { + vertical-align: middle; + display: inline-block; + margin-right: 7px; + } + + .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: 57px; + @media (min-width: $screen-md) { + width: 95px; + } + @media (min-width: $screen-lg) { + left: 173px; + } + } + + > span.column-used, + > span.column-type, + > span.column-capacity, + > span.column-allocated, + > span.column-progress { + width: 79px; + text-transform: capitalize; + @media (min-width: $screen-md) { + width: 95px; + } + @media (min-width: $screen-lg) { + left: 165px; + } + } + + > span.column-name { + width: 202px; + @media (min-width: $screen-md) { + width: 333px; + } + @media (min-width: $screen-lg) { + 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; + } + } + + > span.column-progress { + white-space: nowrap; + } + } + + } + + } .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 1ef3649..ce93d7d 100644 --- a/ui/js/src/kimchi.api.js +++ b/ui/js/src/kimchi.api.js @@ -760,6 +760,56 @@ var kimchi = { }); }, + cloneStoragePoolVolume: function(poolName, volumeName, data, 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..27e6c94 100644 --- a/ui/js/src/kimchi.storage_main.js +++ b/ui/js/src/kimchi.storage_main.js @@ -65,6 +65,200 @@ 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); + $('.dropdown.pool-action.open .dropdown-toggle').dropdown('toggle'); + 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['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) { + 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>')); + wok.topic('kimchi/storageVolumeDeleted').publish(); + },function(err){ + wok.message.error(err.responseJSON.reason); + }); + if(volumes.length === 0){ + kimchi.selectedVolumes = ''; + wok.topic('kimchi/storageVolumeDeleted').publish(); + } + }); + }); + }else { + return false; + } + }); + + $('.volumes').on('click','.volume-wipe',function(e){ + e.preventDefault(); + e.stopPropagation(); + var button = $(this); + $('.dropdown.pool-action.open .dropdown-toggle').dropdown('toggle'); + 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>')); + wok.topic('kimchi/storageVolumeWiped').publish(); + },function(err){ + wok.message.error(err.responseJSON.reason); + }); + if(volumes.length === 0){ + kimchi.selectedVolumes = ''; + wok.topic('kimchi/storageVolumeWiped').publish(); + } + }); + }); + }else { + return false; + } + }); + + $('.volumes').on('click','.volume-resize',function(e){ + e.preventDefault(); + e.stopPropagation(); + var button = $(this); + $('.dropdown.pool-action.open .dropdown-toggle').dropdown('toggle'); + 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','.volume-clone',function(e){ + e.preventDefault(); + e.stopPropagation(); + var button = $(this); + $('.dropdown.pool-action.open .dropdown-toggle').dropdown('toggle'); + 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.volumesToClone = volumes.slice(); + var formatedVolumes = ''; + if(kimchi.volumesToClone.length && !button.parent().is('disabled')){ + $.each(kimchi.volumesToClone, function(i,j) { + volumes = jQuery.grep(volumes, function(value) { + return value != j; + }); + var data = {}; + data = { + pool: kimchi.selectedSP + } + kimchi.cloneStoragePoolVolume(kimchi.selectedSP,j,data,function(){ + wok.topic('kimchi/storageVolumeCloned').publish(); + },function(err){ + wok.message.error(err.responseJSON.reason); + }); + if(volumes.length === 0){ + kimchi.volumesToClone = ''; + wok.topic('kimchi/storageVolumeCloned').publish(); + } + }); + }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(); @@ -103,10 +297,10 @@ kimchi.storageBindClick = function() { $(this).data('type') !== 'iscsi' && $(this).data('type') !== 'scsi'; if(canAddVolume) { - $(this).show(); + $(this).parent().show(); } else { - $(this).hide(); + $(this).parent().hide(); } }); @@ -229,7 +423,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'; @@ -251,77 +446,144 @@ kimchi.doListVolumes = function(poolObj) { var getOngoingVolumes = function() { var result = {}; + var clone = 'status=running&target_uri=' + encodeURIComponent('^/plugins/kimchi/storagepools/.+/storagevolumes/.+/clone'); var filter = 'status=running&target_uri=' + encodeURIComponent('^/plugins/kimchi/storagepools/' + poolName + '/*'); kimchi.getTasksByFilter(filter, function(tasks) { for(var i = 0; i < tasks.length; i++) { - var volumeName = tasks[i].target_uri.split('/').pop(); - result[volumeName] = tasks[i]; + if(tasks[i].message !== 'cloning volume') { + var volumeName = tasks[i].target_uri.split('/').pop(); + result[volumeName] = tasks[i]; - if(kimchi.trackingTasks.indexOf(tasks[i].id) >= 0) { - continue; + if(kimchi.trackingTasks.indexOf(tasks[i].id) >= 0) { + continue; + } + + kimchi.trackTask(tasks[i].id, function(result) { + wok.topic('kimchi/volumeTransferFinished').publish(result); + }, function(result) { + wok.topic('kimchi/volumeTransferError').publish(result); + }, function(result) { + wok.topic('kimchi/volumeTransferProgress').publish(result); + }); } + } + }, null, true); + kimchi.getTasksByFilter(clone, function(tasks) { + for(var i = 0; i < tasks.length; i++) { + if(tasks[i].message === 'cloning volume') { + var volumeName = tasks[i].target_uri.split('/')[6]; + result[volumeName] = tasks[i]; - kimchi.trackTask(tasks[i].id, function(result) { - wok.topic('kimchi/volumeTransferFinished').publish(result); - }, function(result) { - wok.topic('kimchi/volumeTransferError').publish(result); - }, function(result) { - wok.topic('kimchi/volumeTransferProgress').publish(result); - }); + if(kimchi.trackingTasks.indexOf(tasks[i].id) >= 0) { + continue; + } + + kimchi.trackTask(tasks[i].id, function(result) { + wok.topic('kimchi/volumeCloneFinished').publish(result); + }, function(result) { + wok.topic('kimchi/volumeCloneError').publish(result); + }, function(result) { + wok.topic('kimchi/volumeCloneProgress').publish(result); + }); + } } }, null, true); return result; }; - var volumeDiv = $('#volume' + poolName); - $(volumeDiv).empty(); + var volumeDiv = $('#volume-' + poolName); + var volumeDatatable = $('.wok-datagrid > .wok-datagrid-body',volumeDiv); var slide = $('.volumes', poolObj); var handleArrow = $('.arrow-down', poolObj); - kimchi.listStorageVolumes(poolName, function(result) { var listHtml = ''; var ongoingVolumes = []; var ongoingVolumesMap = getOngoingVolumes(); $.each(ongoingVolumesMap, function(volumeName, task) { ongoingVolumes.push(volumeName); - var volume = { - poolName: poolName, - used_by: [], - capacity: 0, - name: volumeName, - format: '', - bootable: null, - os_distro: '', - allocation: 0, - os_version: '', - path: '', - type: 'file', - capacityLevel: 0, - capacityIcon: '' - }; - listHtml += kimchi._generateVolumeHTML(volume); + var volume = { + poolName: poolName, + used_by: [], + capacity: 0, + name: volumeName, + format: '', + bootable: null, + os_distro: '', + allocation: 0, + os_version: '', + path: '', + type: 'file', + capacityLevel: 0, + capacityIcon: '' + }; + listHtml += kimchi._generateVolumeHTML(volume); }); $.each(result, function(index, value) { if (ongoingVolumes.indexOf(value.name) === -1) { + $(volumeDatatable).empty(); value.poolname = poolName; listHtml += kimchi._generateVolumeHTML(value); } }); if (listHtml.length > 0) { - volumeDiv.html(listHtml); + $(volumeDatatable).empty(); + $('.filter',volumeDiv).prop('disabled',false); + $('.toggle-gallery',volumeDiv).prop('disabled',false); + $(volumeDatatable).html(listHtml); + } else { - volumeDiv.html("<div class='pool-empty'>" + i18n['KCHPOOL6002M'] + "</div>"); + $(volumeDatatable).empty(); + $('.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) { wok.topic('kimchi/volumeTransferProgress').publish(task); }); + var checkbox = volumeDiv.find('[name="selected-volume[]"]'); + checkbox.trigger('change'); + checkbox.prop('checked',false); poolObj.removeClass('in'); kimchi.changeArrow(handleArrow); slide.slideDown('slow'); + + $(window).resize(function() { + $('.pool-action.open', volumeDiv).removeClass('open'); + }); + + $('.pool-action', volumeDiv).on('show.bs.dropdown', function () { + $(volumeDiv).scrollTop(0); + $(this).css('position','absolute'); + $('.toggle-gallery',volumeDiv).css({ + 'position':'absolute', + 'margin-top': '1px' + }); + $(volumeDiv).bind('mousewheel DOMMouseScroll', function(e) { + e.preventDefault(); + }); + }) + + $('.pool-action', volumeDiv).on('hide.bs.dropdown', function () { + $(volumeDiv).unbind('mousewheel DOMMouseScroll'); + $(this).removeAttr( 'style' ); + $('.toggle-gallery',volumeDiv).removeAttr( 'style' ); + }) + + 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 +591,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 +657,29 @@ 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/storageVolumeCloned').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]; @@ -467,22 +705,32 @@ kimchi.storage_main = function() { var progress = extractProgressData(result['message']); var size = progress['size']; var percent = progress['percent']; - - volumeBox = $('#volume' + poolName + ' [data-volume-name="' + volumeName + '"]'); - $('.progress-bar-inner', volumeBox).css({ - width: percent + '%' - }); - $('.progress-transferred', volumeBox).text(size); + volumeBox = $('#volume-' + poolName + ' [data-volume-name="' + volumeName + '"]').closest('.wok-datagrid-row'); $('.volume-progress', volumeBox).removeClass('hidden'); + $('.column-progress', volumeBox).removeClass('hidden'); + $('.column-progress', '.wok-datagrid-header').removeClass('hidden'); + $('.volume-inline-progress', volumeBox).removeClass('hidden'); + $('.column-format > .format-text', volumeBox).text('--'); + $('.progress-bar', volumeBox).attr('aria-valuenow',percent+'%').css('width',percent+'%'); + $('input[type="checkbox"]',volumeBox).prop('disabled',true); + $(volumeBox).addClass('in-progress') + $('.volume-box-inner', volumeBox).attr({'data-toggle':'tooltip','data-original-title': i18n['KCHPOOL6014M'] + ' ' +size}); + $('.tooltip-inner', volumeBox).text(i18n['KCHPOOL6014M']+' '+size); + $('.progress-transferred', volumeBox).text(size); $('.progress-status', volumeBox).text(i18n['KCHPOOL6014M']); + $('[data-toggle="tooltip"]',volumeBox).tooltip(); }); wok.topic('kimchi/volumeTransferFinished').subscribe(function(result) { var uriElements = result.target_uri.split('/'); var poolName = uriElements[4]; var volumeName = uriElements.pop(); - var volumeBox = $('#volume' + poolName + ' [data-volume-name="' + volumeName + '"]'); + volumeBox = $('#volume-' + poolName + ' [data-volume-name="' + volumeName + '"]').closest('.wok-datagrid-row'); + $(volumeBox).removeClass('in-progress') $('.volume-progress', volumeBox).addClass('hidden'); + $('.column-progress', volumeBox).addClass('hidden'); + $('.column-progress', '.wok-datagrid-header').addClass('hidden'); + $('.volume-inline-progress', volumeBox).addClass('hidden'); kimchi.getStoragePoolVolume(poolName, volumeName, function(volume) { var html = kimchi._generateVolumeHTML(volume); $(volumeBox).replaceWith(html); @@ -505,9 +753,46 @@ kimchi.storage_main = function() { var uriElements = result.target_uri.split('/'); var poolName = uriElements[4]; var volumeName = uriElements.pop(); - volumeBox = $('#volume' + poolName + ' [data-volume-name="' + volumeName + '"]'); + volumeBox = $('#volume-' + poolName + ' [data-volume-name="' + volumeName + '"]').closest('.wok-datagrid-row'); $('.progress-status', volumeBox).text(i18n['KCHPOOL6015M']); }); + + wok.topic('kimchi/volumeCloneFinished').subscribe(function(result) { + var uriElements = result.target_uri.split('/'); + var poolName = uriElements[4]; + var poolNode = $('.storage-li[data-name="' + poolName + '"]'); + kimchi.doListVolumes(poolNode); + }); + + wok.topic('kimchi/volumeCloneProgress').subscribe(function(result) { + var uriElements = result.target_uri.split('/'); + var poolName = uriElements[4]; + var volumeName = uriElements[6]; + volumeBox = $('#volume-' + poolName + ' [data-volume-name="' + volumeName + '"]').closest('.wok-datagrid-row'); + $('.column-progress', volumeBox).removeClass('hidden'); + $('.column-progress', '.wok-datagrid-header').removeClass('hidden'); + $('.volume-inline-progress', volumeBox).removeClass('hidden'); + $('.column-format > .format-text', volumeBox).text('--'); + $('input[type="checkbox"]',volumeBox).prop('disabled',true); + $(volumeBox).addClass('in-progress') + $('.volume-box-inner', volumeBox).attr({'data-toggle':'tooltip','data-original-title': i18n['KCHPOOL6014M'] }); + $('.tooltip-inner', volumeBox).text(i18n['KCHPOOL6014M']); + $('.progress-status', volumeBox).text(i18n['KCHPOOL6014M']); + $('[data-toggle="tooltip"]',volumeBox).tooltip(); + }); + + wok.topic('kimchi/volumeCloneError').subscribe(function(result) { + // Error message from Async Task status + if (result['message']) { + var errText = result['message']; + } + // Error message from standard kimchi exception + else { + var errText = result['responseJSON']['reason']; + } + result && wok.message.error(errText); + }); + }; kimchi.changeArrow = function(obj) { 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..791cc1d --- /dev/null +++ b/ui/js/src/kimchi.storagepool_resize_volume_main.js @@ -0,0 +1,59 @@ +/* + * 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 bytes = newsize * 1048576; + var data = {}; + data = { + size: bytes + }; + 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..a1f43d3 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,10 @@ "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")", + "KCHPOOL6020M": "$_("Volume %1 is cloning.")", "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..7a8c7d4 --- /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 megabytes.") + </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..cf1eac7 100644 --- a/ui/pages/tabs/storage.html.tmpl +++ b/ui/pages/tabs/storage.html.tmpl @@ -102,90 +102,101 @@ </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> -</script> -<script id="volumeTmpl" type="html/text"> - <div class="volume-box" data-volume-name="{name}"> - <div class="volume-title"> - <div class="volume-name" title="{name}">{name}</div> - <div class="volume-utilization"> - <span class="volume-icon {capacityIcon}"></span> - <span class="volume-usage">{capacityLevel}%</span> + </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="volume-progress hidden"> - <div class="progress-bar-outer"> - <div class="progress-bar-inner"></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><!-- + --><span class="column-progress hidden">$_('Progress')</span> </div> - <div class="progress-label"> - <span class="progress-status"></span> - <span class="progress-transferred"></span> + <ul class="wok-datagrid-body list" id="volume-{name}-table"> + </ul> + </div> + <div class="wok-mask hidden"> + <div class="wok-mask-loader-container"> + <div class="wok-mask-loading"> + <div class="wok-mask-loading-icon"></div> + <div class="wok-mask-loading-text">$_("Loading...")</div> + </div> </div> </div> </div> - <div class="volume-setting"> + <div class="clear"></div> + </div> +</div> +</script> +<script id="volumeTmpl2" type="html/text"> +<li class="wok-datagrid-row"> + <div class="volume-progress hidden"> + <div class="progress-bar-outer progress"> + <div class="progress-bar-inner progress-bar" role="progressbar" aria-valuenow="0%" aria-valuemin="0%" aria-valuemax="100%" ></div> + </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><span class="format-text">{format}</span></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><!-- + --><span class="column-progress hidden"><span class="progress-status"></span> <span class="progress-transferred"></span> + </span> </div> - <ul class="volume-data"> - <li> - <span class="value" title="{format}">{format}</span> - <span class="key">$_("Format")</span> - </li> - <li> - <span class="value" title="{type}">{type}</span> - <span class="key">$_("Type")</span> - </li> - <li> - <span class="value" title="{allocation}">{allocation}</span> - <span class="key">$_("Allocation")</span> - </li> - <li> - <span class="value" title="{capacity}">{capacity}</span> - <span class="key">$_("Capacity")</span> - </li> - </ul> - </div> + </div> + </div> +</li> </script> <script id="logicalPoolExtendTmpl" type="html/text"> <div> -- 1.9.3

Hi Samuel, Some comments: 1. When I switch to the "Gallery view" I can see all volumes selected and after one second, they are deselected 2. When you select multiple volumes to perform an action, there is no feedback to user that an operation will be done on those selected volumes. I can select others, deselect the ones I have selected before and so. We should block the volume selected, add a loading icon, for example, and a mark check when the operation is completed. 3. Usually we don't use success messages on Wok and its plugins. Any reason to add them now? 4. The message are duplicated. I can see 2 messages when trying to delete multiple items "Volume selected were deleted" + "Volume X was successfully deleted". 5. The same messages for delete operation are shown when I wipe volumes. 6. The wipe operation will change the allocation value and so it must be updated in the volume box. 7. The scroll bar should only scroll the volumes. It is hard to get back all time to have the Actions menu available. I think that is all from my side. Regards, Aline Manera On 05/23/2016 12:46 PM, sguimaraes943@gmail.com wrote:
From: Samuel Guimarães <sguimaraes943@gmail.com>
This patch adds Storage Volume management functions Wipe, Clone, Resize and Delete with multiple selection. It also includes a filter input for each Storage Pool and Gallery View for Storage Volumes.
Changes from [RFC] version:
v1: - HTML and CSS
v2: - Delete and Wipe with multi-selection - Confirm messages with list of selected volumes when wiping or deleting volumes (requires SCSS/CSS patch sent to Wok) - Filter working - Removed "Add Volume" link from Storage Pool action button - Added "Add Volume" to Volume box action button
v3: - Clone function working with multiple selection - Progress bar working for clone and create volume - Temporary volume added to the volumes when cloning - Seamless refresh on the volumes once each task is finished - Fixed issue when list wouldn't refresh when all volumes are removed from the storage pool.
v4: - Prevent scroll when Drop-down in volumes list is clicked - Dropdown is not clipped from volumes list when there's only one or two items on the list - Added Media Queries for small screen resolutions
Samuel Guimarães (1): Storage Volume management
model/storagevolumes.py | 2 +- ui/css/kimchi.css | 369 ++++++++++++++-- ui/css/src/modules/_storage.scss | 347 ++++++++++++--- ui/js/src/kimchi.api.js | 50 +++ ui/js/src/kimchi.storage_main.js | 465 +++++++++++++++++---- ui/js/src/kimchi.storagepool_add_volume_main.js | 2 +- ui/js/src/kimchi.storagepool_resize_volume_main.js | 59 +++ ui/pages/i18n.json.tmpl | 6 + ui/pages/storagepool-resize-volume.html.tmpl | 51 +++ ui/pages/tabs/storage.html.tmpl | 155 +++---- 10 files changed, 1249 insertions(+), 257 deletions(-) create mode 100644 ui/js/src/kimchi.storagepool_resize_volume_main.js create mode 100644 ui/pages/storagepool-resize-volume.html.tmpl

-----Original Message----- From: kimchi-devel-bounces@ovirt.org [mailto:kimchi-devel-bounces@ovirt.org] On Behalf Of Aline Manera Sent: segunda-feira, 23 de maio de 2016 17:41 To: sguimaraes943@gmail.com; Kimchi Devel <kimchi-devel@ovirt.org> Subject: Re: [Kimchi-devel] [PATCH v4] [Kimchi] Storage Volume management Hi Samuel, Some comments: 1. When I switch to the "Gallery view" I can see all volumes selected and after one second, they are deselected Hi. I tried to reproduce this here but it is working fine. Tested with Firefox and Chrome and watched for DOM changes and after a minute the checkboxes were still selected. Even If I run an asynchronous task in another Storage Pool it won't change the volumes in another Storage Pool. 2. When you select multiple volumes to perform an action, there is no feedback to user that an operation will be done on those selected volumes. I can select others, deselect the ones I have selected before and so. We should block the volume selected, add a loading icon, for example, and a mark check when the operation is completed. I think my machine is running these tasks too fast so that's why I didn't foresaw this as a requirement. I'll disable the checkboxes and add the spinner icon. 3. Usually we don't use success messages on Wok and its plugins. Any reason to add them now? I've added some to System Services and OVS Bridges there was no feedback to the user pointing a change in the tables or that an action was being performed but with multiple selection sometimes it may render multiple messages in the area so I'll remove them. 4. The message are duplicated. I can see 2 messages when trying to delete multiple items "Volume selected were deleted" + "Volume X was successfully deleted". See above. 5. The same messages for delete operation are shown when I wipe volumes. See above. 6. The wipe operation will change the allocation value and so it must be updated in the volume box. You mean the Storage Pool line in the parent table? Once the wipe process is completed the line is updated with 0.0B in the allocated column, however the parent %Used and Allocated columns are not refreshed. Are these two the only fields that should be updated? Should it be done for Clone and Delete as well? 7. The scroll bar should only scroll the volumes. It is hard to get back all time to have the Actions menu available. I'm ok with this for the list view appending a scrollbar on the "table" element but with the Gallery view I think it looks odd. See attached screenshots. I think that is all from my side. Regards, Aline Manera On 05/23/2016 12:46 PM, sguimaraes943@gmail.com wrote:
From: Samuel Guimarães <sguimaraes943@gmail.com>
This patch adds Storage Volume management functions Wipe, Clone, Resize and Delete with multiple selection. It also includes a filter input for each Storage Pool and Gallery View for Storage Volumes.
Changes from [RFC] version:
v1: - HTML and CSS
v2: - Delete and Wipe with multi-selection - Confirm messages with list of selected volumes when wiping or deleting volumes (requires SCSS/CSS patch sent to Wok) - Filter working - Removed "Add Volume" link from Storage Pool action button - Added "Add Volume" to Volume box action button
v3: - Clone function working with multiple selection - Progress bar working for clone and create volume - Temporary volume added to the volumes when cloning - Seamless refresh on the volumes once each task is finished - Fixed issue when list wouldn't refresh when all volumes are removed from the storage pool.
v4: - Prevent scroll when Drop-down in volumes list is clicked - Dropdown is not clipped from volumes list when there's only one or two items on the list - Added Media Queries for small screen resolutions
Samuel Guimarães (1): Storage Volume management
model/storagevolumes.py | 2 +- ui/css/kimchi.css | 369 ++++++++++++++-- ui/css/src/modules/_storage.scss | 347 ++++++++++++--- ui/js/src/kimchi.api.js | 50 +++ ui/js/src/kimchi.storage_main.js | 465 +++++++++++++++++---- ui/js/src/kimchi.storagepool_add_volume_main.js | 2 +- ui/js/src/kimchi.storagepool_resize_volume_main.js | 59 +++ ui/pages/i18n.json.tmpl | 6 + ui/pages/storagepool-resize-volume.html.tmpl | 51 +++ ui/pages/tabs/storage.html.tmpl | 155 +++---- 10 files changed, 1249 insertions(+), 257 deletions(-) create mode 100644 ui/js/src/kimchi.storagepool_resize_volume_main.js create mode 100644 ui/pages/storagepool-resize-volume.html.tmpl
_______________________________________________ Kimchi-devel mailing list Kimchi-devel@ovirt.org http://lists.ovirt.org/mailman/listinfo/kimchi-devel

On 05/24/2016 10:27 AM, Samuel Henrique De Oliveira Guimaraes wrote:
-----Original Message----- From: kimchi-devel-bounces@ovirt.org [mailto:kimchi-devel-bounces@ovirt.org] On Behalf Of Aline Manera Sent: segunda-feira, 23 de maio de 2016 17:41 To: sguimaraes943@gmail.com; Kimchi Devel <kimchi-devel@ovirt.org> Subject: Re: [Kimchi-devel] [PATCH v4] [Kimchi] Storage Volume management
Hi Samuel,
Some comments:
1. When I switch to the "Gallery view" I can see all volumes selected and after one second, they are deselected
Hi. I tried to reproduce this here but it is working fine. Tested with Firefox and Chrome and watched for DOM changes and after a minute the checkboxes were still selected. Even If I run an asynchronous task in another Storage Pool it won't change the volumes in another Storage Pool.
I am saying, when you are in the storage volumes list view without any storage volume selected and switch to the gallery view, all the storage volumes appear selected for a second and then the page is refreshed to deselect all them.
2. When you select multiple volumes to perform an action, there is no feedback to user that an operation will be done on those selected volumes. I can select others, deselect the ones I have selected before and so. We should block the volume selected, add a loading icon, for example, and a mark check when the operation is completed.
I think my machine is running these tasks too fast so that's why I didn't foresaw this as a requirement. I'll disable the checkboxes and add the spinner icon.
3. Usually we don't use success messages on Wok and its plugins. Any reason to add them now?
I've added some to System Services and OVS Bridges there was no feedback to the user pointing a change in the tables or that an action was being performed but with multiple selection sometimes it may render multiple messages in the area so I'll remove them.
4. The message are duplicated. I can see 2 messages when trying to delete multiple items "Volume selected were deleted" + "Volume X was successfully deleted".
See above.
5. The same messages for delete operation are shown when I wipe volumes.
See above.
6. The wipe operation will change the allocation value and so it must be updated in the volume box.
You mean the Storage Pool line in the parent table? Once the wipe process is completed the line is updated with 0.0B in the allocated column, however the parent %Used and Allocated columns are not refreshed. Are these two the only fields that should be updated? Should it be done for Clone and Delete as well?
I was talking about the storage volume settings. When I do wipe multiple volumes, only one of them get the settings updated. About what you said, I agree we should update the storage pool settings as new volumes or deleting volumes may change the storage pool allocation. I think only %used and allocated must be updated.
7. The scroll bar should only scroll the volumes. It is hard to get back all time to have the Actions menu available.
I'm ok with this for the list view appending a scrollbar on the "table" element but with the Gallery view I think it looks odd. See attached screenshots.
IMO It is OK for me on both views (list and gallery). Do you have any other idea to have the same behavior on gallery view which does not look odd as you said?
I think that is all from my side.
Regards, Aline Manera
On 05/23/2016 12:46 PM, sguimaraes943@gmail.com wrote:
From: Samuel Guimarães <sguimaraes943@gmail.com>
This patch adds Storage Volume management functions Wipe, Clone, Resize and Delete with multiple selection. It also includes a filter input for each Storage Pool and Gallery View for Storage Volumes.
Changes from [RFC] version:
v1: - HTML and CSS
v2: - Delete and Wipe with multi-selection - Confirm messages with list of selected volumes when wiping or deleting volumes (requires SCSS/CSS patch sent to Wok) - Filter working - Removed "Add Volume" link from Storage Pool action button - Added "Add Volume" to Volume box action button
v3: - Clone function working with multiple selection - Progress bar working for clone and create volume - Temporary volume added to the volumes when cloning - Seamless refresh on the volumes once each task is finished - Fixed issue when list wouldn't refresh when all volumes are removed from the storage pool.
v4: - Prevent scroll when Drop-down in volumes list is clicked - Dropdown is not clipped from volumes list when there's only one or two items on the list - Added Media Queries for small screen resolutions
Samuel Guimarães (1): Storage Volume management
model/storagevolumes.py | 2 +- ui/css/kimchi.css | 369 ++++++++++++++-- ui/css/src/modules/_storage.scss | 347 ++++++++++++--- ui/js/src/kimchi.api.js | 50 +++ ui/js/src/kimchi.storage_main.js | 465 +++++++++++++++++---- ui/js/src/kimchi.storagepool_add_volume_main.js | 2 +- ui/js/src/kimchi.storagepool_resize_volume_main.js | 59 +++ ui/pages/i18n.json.tmpl | 6 + ui/pages/storagepool-resize-volume.html.tmpl | 51 +++ ui/pages/tabs/storage.html.tmpl | 155 +++---- 10 files changed, 1249 insertions(+), 257 deletions(-) create mode 100644 ui/js/src/kimchi.storagepool_resize_volume_main.js create mode 100644 ui/pages/storagepool-resize-volume.html.tmpl
_______________________________________________ Kimchi-devel mailing list Kimchi-devel@ovirt.org http://lists.ovirt.org/mailman/listinfo/kimchi-devel
participants (3)
-
Aline Manera
-
Samuel Henrique De Oliveira Guimaraes
-
sguimaraes943@gmail.com