
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