[PATCH v5] [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. 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 v5: - Fixed black border in Firefox and IE when switching from List View to Gallery View - Allocation and % Used in the parent table are now updated when wiping/resizing/deleting/cloning volumes - Removed success messages / duplicated messages - Changed scrollbar position in Volumes area - When applying multiple actions, the checkbox is hidden/disabled and a spinner is shown while the the queue of selected items is being processed Samuel Guimarães (1): Storage Volume management model/storagevolumes.py | 2 +- ui/css/kimchi.css | 396 +++++++++++++++-- ui/css/src/modules/_storage.scss | 375 ++++++++++++++-- ui/js/src/kimchi.api.js | 50 +++ ui/js/src/kimchi.storage_main.js | 488 +++++++++++++++++---- 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 | 4 + ui/pages/storagepool-resize-volume.html.tmpl | 51 +++ ui/pages/tabs/storage.html.tmpl | 155 ++++--- 10 files changed, 1323 insertions(+), 259 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 | 396 +++++++++++++++-- ui/css/src/modules/_storage.scss | 375 ++++++++++++++-- ui/js/src/kimchi.api.js | 50 +++ ui/js/src/kimchi.storage_main.js | 488 +++++++++++++++++---- 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 | 4 + ui/pages/storagepool-resize-volume.html.tmpl | 51 +++ ui/pages/tabs/storage.html.tmpl | 155 ++++--- 10 files changed, 1323 insertions(+), 259 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..b54724b 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,64 @@ 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; - max-height: 285px; + padding: 22px; min-height: 136px; - overflow: auto; + max-height: 505px; + overflow: hidden; } -#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 +2296,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 +2339,318 @@ 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; + overflow-y: auto; + overflow-x: hidden; + max-height: 400px; +} + +#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; + 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-width: 3px; + border-style: solid; + border-color: transparent; + border-color: rgba(255, 255, 255, 0); + 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-width: 1px; + border-style: solid; + border-color: #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 { + overflow-y: auto; + overflow-x: hidden; + max-height: 350px; +} + +#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 .volume-box-border { + border-color: rgba(255, 255, 255, 0); + border-width: 0px; + border-style: solid; +} + +#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row .volume-box-inner { + border-color: rgba(255, 255, 255, 0); + border-width: 0px; + border-style: solid; +} + +#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..4666b1b 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,59 @@ 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; - max-height: 285px; + padding: 22px; 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: hidden; } - .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 +122,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 +142,7 @@ width: 100%; } } + .volume-data { margin: 0; padding: 0; @@ -164,10 +162,291 @@ } } } - .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; + overflow-y: auto; + overflow-x: hidden; + max-height: 400px; + } + + &.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; + border-color: rgba($guests-color,1); + } + + &.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-width: 3px; + border-style: solid; + border-color: transparent; + border-color: rgba(255,255,255,0); + 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-width: 1px; + border-style: solid; + border-color: #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 { + overflow-y: auto; + overflow-x: hidden; + max-height: 350px; + } + + &.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row { + position: relative; + cursor: pointer; + + .volume-box-border { + border-color: rgba(255,255,255,0); + border-width: 0px; + border-style: solid; + } + + .volume-box-inner { + border-color: rgba(255,255,255,0); + border-width: 0px; + border-style: solid; + } + + } + + &.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..8d53a63 100644 --- a/ui/js/src/kimchi.storage_main.js +++ b/ui/js/src/kimchi.storage_main.js @@ -65,6 +65,202 @@ 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() { + $('[data-name="'+kimchi.selectedSP+'"] input:checkbox:checked').prop('disabled',true); + $.each(kimchi.selectedVolumes, function(i,j) { + $('[data-volume-name="'+j+'"] .volume-inline-progress').removeClass('hidden'); + volumes = jQuery.grep(volumes, function(value) { + return value != j; + }); + kimchi.deleteStoragePoolVolume(kimchi.selectedSP,j,function(){ + 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['KCHPOOL6017M'].replace('%1','<ul>'+formatedVolumes+'</ul>'+i18n['KCHPOOL6009M']); + var settings = { + title : i18n['KCHPOOL6018M'], + content : confirmMessage, + confirm : i18n['KCHAPI6002M'], + cancel : i18n['KCHAPI6003M'] + }; + wok.confirm(settings, function() { + $('[data-name="'+kimchi.selectedSP+'"] input:checkbox:checked').prop('disabled',true); + $.each(kimchi.selectedVolumes, function(i,j) { + $('[data-volume-name="'+j+'"] .volume-inline-progress').removeClass('hidden'); + volumes = jQuery.grep(volumes, function(value) { + return value != j; + }); + kimchi.wipeStoragePoolVolume(kimchi.selectedSP,j,function(){ + 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 +299,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 +425,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'; @@ -246,82 +443,170 @@ kimchi._generateVolumeHTML = function(volume) { return wok.substitute(volumeHtml, volume); }; +kimchi.doUpdateStoragePool = function(poolObj){ + var poolName = poolObj.data('name'); + kimchi.getStoragePool(poolName, function(result) { + result.usage = Math.round(result.allocated / result.capacity * 100) || 0; + if (result.usage <= 100 && result.usage >= 85) { + result.icon = 'icon-high'; + }else if (result.usage <= 85 && result.usage >= 75 ) { + result.icon = 'icon-med'; + } else { + result.icon = 'icon-low'; + } + result.allocated = wok.changetoProperUnit(result.allocated,1); + $('> .column-usage > .usage-icon',poolObj).attr('class', 'usage-icon').addClass(result.icon).text(result.usage); + $('> .column-allocated',poolObj).attr('val',result.allocated).text(result.allocated); + },function(){ + return false; + }); +}; + kimchi.doListVolumes = function(poolObj) { var poolName = poolObj.data('name'); 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' ); + }); + + kimchi.doUpdateStoragePool(poolObj); + + 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 +614,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 +680,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 +728,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 +776,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..4efc1ec 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,8 @@ "KCHPOOL6014M": "$_("In progress...")", "KCHPOOL6015M": "$_("Failed!")", "KCHPOOL6016M": "$_("No LVM found in the system.")", + "KCHPOOL6017M": "$_("This will permanently wipe the following storage volumes: %1")", + "KCHPOOL6018M": "$_("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..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, Just one comment. The progress bar for a new volume is being displayed along the area width as you can see in the image below Is that intentional? I'd suggest to move the progress bar with the "In Progress" label or we can also remove it IMO as we already have the "In Progress" label. Maybe only add the total size to the label like "In Progress... 264.0MB/10GB' Also the unit should be MB, GB, KB, etc as it is for the volume size. MiB, GiB and KiB are for transfer rate unit On 05/24/2016 04:51 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.
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
v5: - Fixed black border in Firefox and IE when switching from List View to Gallery View - Allocation and % Used in the parent table are now updated when wiping/resizing/deleting/cloning volumes - Removed success messages / duplicated messages - Changed scrollbar position in Volumes area - When applying multiple actions, the checkbox is hidden/disabled and a spinner is shown while the the queue of selected items is being processed
Samuel Guimarães (1): Storage Volume management
model/storagevolumes.py | 2 +- ui/css/kimchi.css | 396 +++++++++++++++-- ui/css/src/modules/_storage.scss | 375 ++++++++++++++-- ui/js/src/kimchi.api.js | 50 +++ ui/js/src/kimchi.storage_main.js | 488 +++++++++++++++++---- 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 | 4 + ui/pages/storagepool-resize-volume.html.tmpl | 51 +++ ui/pages/tabs/storage.html.tmpl | 155 ++++--- 10 files changed, 1323 insertions(+), 259 deletions(-) create mode 100644 ui/js/src/kimchi.storagepool_resize_volume_main.js create mode 100644 ui/pages/storagepool-resize-volume.html.tmpl
participants (2)
-
Aline Manera
-
sguimaraes943@gmail.com