http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/nodejs/views/includes/controls.jade ---------------------------------------------------------------------- diff --git a/modules/web-control-center/nodejs/views/includes/controls.jade b/modules/web-control-center/nodejs/views/includes/controls.jade deleted file mode 100644 index 4a618fa..0000000 --- a/modules/web-control-center/nodejs/views/includes/controls.jade +++ /dev/null @@ -1,322 +0,0 @@ -//- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You 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. - -mixin block-callout(titleWorkflow, contentWorkflow, whatsNextWorkflow, whatsNextContent) - .block-callout-parent.block-callout-border.margin-bottom-dflt - .block-callout - i.fa.fa-check-square - label #{titleWorkflow} - p(ng-bind-html=contentWorkflow) - .block-callout - i.fa.fa-check-square - label #{whatsNextWorkflow} - p(ng-bind-html=whatsNextContent) - - -mixin tipField(lines) - i.tipField.fa.fa-question-circle(ng-if=lines bs-tooltip='joinTip(#{lines})' type='button') - i.tipField.fa.fa-question-circle.blank(ng-if='!#{lines}') - -mixin tipLabel(lines) - i.tipLabel.fa.fa-question-circle(ng-if=lines bs-tooltip='joinTip(#{lines})' type='button') - i.tipLabel.fa.fa-question-circle.blank(ng-if='!#{lines}') - -mixin ico-exclamation(mdl, err, msg) - i.fa.fa-exclamation-triangle.form-control-feedback(ng-show='inputForm["#{mdl}"].$error.#{err}' bs-tooltip data-title='#{msg}' type='button') - -mixin btn-save(show, click) - i.tipField.fa.fa-floppy-o(ng-show=show ng-click=click) - -mixin btn-add(click) - i.tipField.fa.fa-plus(ng-click=click) - -mixin btn-remove(click) - i.tipField.fa.fa-remove(ng-click=click) - -mixin btn-up(show, click) - i.tipField.fa.fa-arrow-up(ng-show=show ng-click=click) - -mixin btn-down(show, click) - i.tipField.fa.fa-arrow-down(ng-show=show ng-click=click) - -mixin table-pair-edit(keyModel, valModel, keyPlaceholder, valPlaceholder) - .col-sm-6(style='float: right') - input.form-control(type='text' ng-model=valModel placeholder=valPlaceholder) - label.fieldSep / - .input-tip - input.form-control(type='text' ng-model=keyModel placeholder=keyPlaceholder) - -mixin table-pair(header, tblMdl, keyFld, valFld, keyPlaceholder, valPlaceholder) - .col-sm-6 - label.table-header #{header}: - +tipLabel('field.tip') - button.btn.btn-primary.fieldButton(ng-click='tableNewItem(field)') Add - table.links-edit.col-sm-12(st-table=tblMdl) - tbody - tr.col-sm-12(ng-repeat='item in #{tblMdl}') - td.col-sm-6 - div(ng-show='!tableEditing(field, $index)') - a.labelFormField(ng-click='curPair = tableStartEdit(backupItem, field, $index); curKey = curPair.#{keyFld}; curValue = curPair.#{valFld}') {{$index + 1}}) {{item.#{keyFld}}} / {{item.#{valFld}}} - +btn-remove('tableRemove(backupItem, field, $index)') - div(ng-show='tableEditing(field, $index)') - label.labelField {{$index + 1}}) - +btn-save('tablePairSaveVisible(curKey, curValue)', 'tablePairSave(tablePairValid, backupItem, field, curKey, curValue, $index)') - +table-pair-edit('curKey', 'curValue', keyPlaceholder, valPlaceholder) - tr.col-sm-12(ng-show='tableNewItemActive(field)') - td.col-sm-6 - +btn-save('tablePairSaveVisible(newKey, newValue)', 'tablePairSave(tablePairValid, backupItem, field, newKey, newValue, -1)') - +table-pair-edit('newKey', 'newValue', keyPlaceholder, valPlaceholder) - -mixin details-row - - var lblDetailClasses = ['col-sm-4', 'details-label'] - - - var detailMdl = 'getModel(backupItem, detail)[detail.model]'; - - var detailCommon = {'ng-model': detailMdl, 'ng-required': 'detail.required'}; - - - var customValidators = {'ng-attr-ipaddress': '{{detail.ipaddress}}'} - - div(ng-switch='detail.type') - div(ng-switch-when='label') - label {{detail.label}} - div.checkbox(ng-switch-when='check') - label - input(type='checkbox')&attributes(detailCommon) - |{{detail.label}} - +tipLabel('detail.tip') - div(ng-switch-when='text') - label(class=lblDetailClasses ng-class='{required: detail.required}') {{detail.label}}: - .col-sm-8 - +tipField('detail.tip') - .input-tip - input.form-control(type='text' placeholder='{{detail.placeholder}}')&attributes(detailCommon) - div(ng-switch-when='number' ) - label(class=lblDetailClasses ng-class='{required: detail.required}') {{detail.label}}: - .col-sm-8 - +tipField('detail.tip') - .input-tip - input.form-control(name='{{detail.model}}' type='number' placeholder='{{detail.placeholder}}' min='{{detail.min ? detail.min : 0}}' max='{{detail.max ? detail.max : Number.MAX_VALUE}}')&attributes(detailCommon) - +ico-exclamation('{{detail.model}}', 'min', 'Value is less than allowable minimum.') - +ico-exclamation('{{detail.model}}', 'max', 'Value is more than allowable maximum.') - +ico-exclamation('{{detail.model}}', 'number', 'Invalid value. Only numbers allowed.') - div(ng-switch-when='dropdown') - label(class=lblDetailClasses ng-class='{required: detail.required}') {{detail.label}}: - .col-sm-8 - +tipField('detail.tip') - .input-tip - button.form-control(bs-select data-placeholder='{{detail.placeholder}}' bs-options='item.value as item.label for item in {{detail.items}}')&attributes(detailCommon) - div(ng-switch-when='dropdown-multiple') - label(class=lblDetailClasses ng-class='{required: detail.required}') {{detail.label}}: - .col-sm-8 - button.form-control(bs-select data-multiple='1' data-placeholder='{{detail.placeholder}}' bs-options='item.value as item.label for item in {{detail.items}}')&attributes(detailCommon) - +tipField('detail.tip') - div(ng-switch-when='table-simple')&attributes(detailCommon) - div(ng-if='detail.label') - label.table-header {{detail.label}}: - +tipLabel('detail.tableTip') - table.col-sm-12.links-edit-details(st-table='#{detailMdl}') - tbody - tr(ng-repeat='item in #{detailMdl} track by $index') - td - div(ng-show='!tableEditing(detail, $index)') - a.labelFormField(ng-click='curValue = tableStartEdit(backupItem, detail, $index)') {{$index + 1}}) {{item}} - +btn-remove('tableRemove(backupItem, detail, $index)') - +btn-down('detail.reordering && tableSimpleDownVisible(backupItem, detail, $index)', 'tableSimpleDown(backupItem, detail, $index)') - +btn-up('detail.reordering && $index > 0', 'tableSimpleUp(backupItem, detail, $index)') - div(ng-show='tableEditing(detail, $index)') - label.labelField {{$index + 1}}) - +btn-save('tableSimpleSaveVisible(curValue)', 'tableSimpleSave(tableSimpleValid, backupItem, detail, curValue, $index)') - .input-tip.form-group.has-feedback - input.form-control(name='{{detail.model}}.edit' type='text' ng-model='curValue' placeholder='{{detail.placeholder}}')&attributes(customValidators) - +ico-exclamation('{{detail.model}}.edit', 'ipaddress', 'Invalid address, see help for format description.') - button.btn.btn-primary.fieldButton(ng-disabled='!newValue' ng-click='tableSimpleSave(tableSimpleValid, backupItem, detail, newValue, -1)') Add - +tipField('detail.tip') - .input-tip.form-group.has-feedback - input.form-control(name='{{detail.model}}' type='text' ng-model='newValue' ng-focus='tableNewItem(detail)' placeholder='{{detail.placeholder}}')&attributes(customValidators) - +ico-exclamation('{{detail.model}}', 'ipaddress', 'Invalid address, see help for format description.') - -mixin table-db-field-edit(dbName, dbType, javaName, javaType) - div(style='width: 22%; float: right') - button.form-control(ng-model=javaType bs-select data-placeholder='Java type' bs-options='item.value as item.label for item in {{javaTypes}}') - label.fieldSep / - div(style='width: 20%; float: right') - input.form-control(type='text' ng-model=javaName placeholder='Java name') - label.fieldSep / - div(style='width: 22%; float: right') - button.form-control(ng-model=dbType bs-select data-placeholder='JDBC type' bs-options='item.value as item.label for item in {{jdbcTypes}}') - label.fieldSep / - .input-tip - input.form-control(type='text' ng-model=dbName placeholder='DB name') - -mixin table-group-item-edit(fieldName, className, direction) - div(style='width: 15%; float: right') - button.form-control(ng-model=direction bs-select data-placeholder='Sort' bs-options='item.value as item.label for item in {{sortDirections}}') - label.fieldSep / - div(style='width: 38%; float: right') - input.form-control(type='text' ng-model=className placeholder='Class name') - label.fieldSep / - .input-tip - input.form-control(type='text' ng-model=fieldName placeholder='Field name') - -mixin form-row - +form-row-custom(['col-sm-2'], ['col-sm-4']) - -mixin form-row-custom(lblClasses, fieldClasses) - - var fieldMdl = 'getModel(backupItem, field)[field.model]'; - - var fieldCommon = {'ng-model': fieldMdl, 'ng-required': 'field.required || required(field)'}; - - var fieldRequiredClass = '{true: "required"}[field.required || required(field)]' - - var fieldHide = '{{field.hide}}' - - div(ng-switch='field.type') - div.col-sm-6(ng-switch-when='label') - label {{field.label}} - div.checkbox.col-sm-6(ng-switch-when='check' ng-hide=fieldHide) - label - input(type='checkbox')&attributes(fieldCommon) - | {{field.label}} - +tipLabel('field.tip') - div(ng-switch-when='text' ng-hide=fieldHide) - label(class=lblClasses ng-class=fieldRequiredClass) {{field.label}}: - div(class=fieldClasses) - +tipField('field.tip') - .input-tip - input.form-control(type='text' placeholder='{{field.placeholder}}')&attributes(fieldCommon) - div(ng-switch-when='password' ng-hide=fieldHide) - label(class=lblClasses ng-class=fieldRequiredClass) {{field.label}}: - div(class=fieldClasses) - +tipField('field.tip') - .input-tip - input.form-control(type='password' placeholder='{{field.placeholder}}')&attributes(fieldCommon) - div(ng-switch-when='number' ng-hide=fieldHide) - label(class=lblClasses ng-class=fieldRequiredClass) {{field.label}}: - div(class=fieldClasses) - +tipField('field.tip') - .input-tip - input.form-control(name='{{field.model}}' type='number' placeholder='{{field.placeholder}}' min='{{field.min ? field.min : 0}}' max='{{field.max ? field.max : Number.MAX_VALUE}}')&attributes(fieldCommon) - +ico-exclamation('{{field.model}}', 'min', 'Value is less than allowable minimum.') - +ico-exclamation('{{field.model}}', 'max', 'Value is more than allowable maximum.') - +ico-exclamation('{{field.model}}', 'number', 'Invalid value. Only numbers allowed.') - div(ng-switch-when='dropdown' ng-hide=fieldHide) - label(class=lblClasses ng-class=fieldRequiredClass) {{field.label}}: - div(class=fieldClasses) - +tipField('field.tip') - .input-tip - button.form-control(bs-select data-placeholder='{{field.placeholder}}' bs-options='item.value as item.label for item in {{field.items}}')&attributes(fieldCommon) - div(ng-switch-when='dropdown-multiple' ng-hide=fieldHide) - label(class=lblClasses ng-class=fieldRequiredClass) {{field.label}}: - div(class=fieldClasses) - +tipField('field.tip') - .input-tip - button.form-control(bs-select ng-disabled='{{field.items}}.length == 0' data-multiple='1' data-placeholder='{{field.placeholder}}' bs-options='item.value as item.label for item in {{field.items}}')&attributes(fieldCommon) - a.customize(ng-show='field.addLink' ng-href='{{field.addLink.ref}}') {{field.addLink.label}} - div(ng-switch-when='dropdown-details' ng-hide=fieldHide) - - var expanded = 'field.details[' + fieldMdl + '].expanded' - - label(class=lblClasses ng-class=fieldRequiredClass) {{field.label}}: - div(class=fieldClasses) - +tipField('field.tip') - .input-tip - button.form-control(bs-select data-placeholder='{{field.placeholder}}' bs-options='item.value as item.label for item in {{field.items}}')&attributes(fieldCommon) - a.customize(ng-show='#{fieldMdl} && field.details[#{fieldMdl}].fields' ng-click='#{expanded} = !#{expanded}') {{#{expanded} ? "Hide settings" : "Show settings"}} - .col-sm-6.panel-details(ng-show='#{expanded} && #{fieldMdl}') - .details-row(ng-repeat='detail in field.details[#{fieldMdl}].fields') - +details-row - div(ng-switch-when='table-simple' ng-hide=fieldHide)&attributes(fieldCommon) - .col-sm-6 - label.table-header {{field.label}}: - +tipLabel('field.tableTip') - button.btn.btn-primary.fieldButton(ng-click='tableNewItem(field)') Add - table.links-edit.col-sm-12(st-table='#{fieldMdl}') - tbody - tr.col-sm-12(ng-repeat='item in #{fieldMdl} track by $index') - td.col-sm-6 - div(ng-show='!tableEditing(field, $index)') - a.labelFormField(ng-click='curValue = tableStartEdit(backupItem, field, $index)') {{$index + 1}}) {{item | compact}} - +btn-remove('tableRemove(backupItem, field, $index)') - +btn-down('field.reordering && tableSimpleDownVisible(backupItem, field, $index)', 'tableSimpleDown(backupItem, field, $index)') - +btn-up('field.reordering && $index > 0', 'tableSimpleUp(backupItem, field, $index)') - div(ng-show='tableEditing(field, $index)') - label.labelField {{$index + 1}}) - +btn-save('tableSimpleSaveVisible(curValue)', 'tableSimpleSave(tableSimpleValid, backupItem, field, curValue, $index)') - .input-tip - input.form-control(type='text' ng-model='curValue' placeholder='{{field.placeholder}}') - tr.col-sm-12(ng-show='tableNewItemActive(field)') - td.col-sm-6 - +btn-save('tableSimpleSaveVisible(newValue)', 'tableSimpleSave(tableSimpleValid, backupItem, field, newValue, -1)') - .input-tip - input.form-control(type='text' ng-model='newValue' placeholder='{{field.placeholder}}') - div(ng-switch-when='indexedTypes') - +table-pair('Index key-value type pairs', fieldMdl, 'keyClass', 'valueClass', 'Key class full name', 'Value class full name') - div(ng-switch-when='queryFields' ng-hide=fieldHide) - +table-pair('{{field.label}}', fieldMdl, 'name', 'className', 'Field name', 'Field class full name') - div(ng-switch-when='dbFields' ng-hide=fieldHide) - .col-sm-6 - label.table-header {{field.label}}: - +tipLabel('field.tip') - button.btn.btn-primary.fieldButton(ng-click='tableNewItem(field)') Add - table.links-edit.col-sm-12(st-table=fieldMdl) - tbody - tr.col-sm-12(ng-repeat='item in #{fieldMdl}') - td.col-sm-6 - div(ng-show='!tableEditing(field, $index)') - a.labelFormField(ng-click='curField = tableStartEdit(backupItem, field, $index); curDbName = curField.dbName; curDbType = curField.dbType; curJavaName = curField.javaName; curJavaType = curField.javaType') {{$index + 1}}) {{item.dbName}} / {{item.dbType}} / {{item.javaName}} / {{item.javaType}} - +btn-remove('tableRemove(backupItem, field, $index)') - div(ng-if='tableEditing(field, $index)') - label.labelField {{$index + 1}}) - +btn-save('tableDbFieldSaveVisible(curDbName, curDbType, curJavaName, curJavaType)', 'tableDbFieldSave(field, curDbName, curDbType, curJavaName, curJavaType, $index)') - +table-db-field-edit('curDbName', 'curDbType', 'curJavaName', 'curJavaType') - tr(ng-show='tableNewItemActive(field)') - td.col-sm-6 - +btn-save('tableDbFieldSaveVisible(newDbName, newDbType, newJavaName, newJavaType)', 'tableDbFieldSave(field, newDbName, newDbType, newJavaName, newJavaType, -1)') - +table-db-field-edit('newDbName', 'newDbType', 'newJavaName', 'newJavaType') - div(ng-switch-when='queryGroups' ng-hide=fieldHide) - .col-sm-6 - label.table-header {{field.label}}: - +tipLabel('field.tip') - button.btn.btn-primary.fieldButton(ng-click='tableNewItem(field)') Add - table.links-edit.col-sm-12(st-table=fieldMdl) - tbody - tr.col-sm-12(ng-repeat='group in #{fieldMdl}') - td.col-sm-6 - div - .col-sm-12(ng-show='!tableEditing(field, $index)') - a.labelFormField(ng-click='curGroup = tableStartEdit(backupItem, field, $index); curGroupName = curGroup.name; curFields = curGroup.fields') {{$index + 1}}) {{group.name}} - +btn-remove('tableRemove(backupItem, field, $index)') - +btn-add('tableGroupNewItem($index); newDirection = "ASC"') - div(ng-show='tableEditing(field, $index)') - label.labelField {{$index + 1}}) - +btn-save('tableGroupSaveVisible(curGroupName)', 'tableGroupSave(curGroupName, $index)') - .input-tip - input.form-control(type='text' ng-model='curGroupName' placeholder='Index name') - div - table.links-edit.col-sm-12(st-table='group.fields' ng-init='groupIndex = $index') - tr(ng-repeat='groupItem in group.fields') - td - div(ng-show='!tableGroupItemEditing(groupIndex, $index)') - a.labelFormField(ng-click='curGroupItem = tableGroupItemStartEdit(groupIndex, $index); curFieldName = curGroupItem.name; curClassName = curGroupItem.className; curDirection = curGroupItem.direction') {{$index + 1}}) {{groupItem.name}} / {{groupItem.className}} / {{groupItem.direction}} - +btn-remove('tableRemoveGroupItem(group, $index)') - div(ng-show='tableGroupItemEditing(groupIndex, $index)') - label.labelField {{$index + 1}}) - +btn-save('tableGroupItemSaveVisible(curFieldName, curClassName)', 'tableGroupItemSave(curFieldName, curClassName, curDirection, groupIndex, $index)') - +table-group-item-edit('curFieldName', 'curClassName', 'curDirection') - tr.col-sm-12(style='padding-left: 18px' ng-show='tableGroupNewItemActive(groupIndex)') - td - +btn-save('tableGroupItemSaveVisible(newFieldName, newClassName)', 'tableGroupItemSave(newFieldName, newClassName, newDirection, groupIndex, -1)') - +table-group-item-edit('newFieldName', 'newClassName', 'newDirection') - tr.col-sm-12(ng-show='tableNewItemActive(field)') - td.col-sm-6 - +btn-save('tableGroupSaveVisible(newGroupName)', 'tableGroupSave(newGroupName, -1)') - .input-tip - input.form-control(type='text' ng-model='newGroupName' placeholder='Group name') \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/nodejs/views/includes/footer.jade ---------------------------------------------------------------------- diff --git a/modules/web-control-center/nodejs/views/includes/footer.jade b/modules/web-control-center/nodejs/views/includes/footer.jade deleted file mode 100644 index d8ff5d7..0000000 --- a/modules/web-control-center/nodejs/views/includes/footer.jade +++ /dev/null @@ -1,22 +0,0 @@ -//- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You 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. - -.container.container-footer - footer - center - p Apache Ignite Control Center, version 1.0.0 - p © 2015 The Apache Software Foundation. - p Apache, Apache Ignite, the Apache feather and the Apache Ignite logo are trademarks of The Apache Software Foundation. \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/nodejs/views/includes/header.jade ---------------------------------------------------------------------- diff --git a/modules/web-control-center/nodejs/views/includes/header.jade b/modules/web-control-center/nodejs/views/includes/header.jade deleted file mode 100644 index ab2d31e..0000000 --- a/modules/web-control-center/nodejs/views/includes/header.jade +++ /dev/null @@ -1,39 +0,0 @@ -//- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You 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. -mixin header-item(active, ref, txt) - li - a(ng-class='{active: isActive("#{active}")}' href='#{ref}') #{txt} - -header.header(id='header') - .viewedUser(ng-show='becomeUsed') Currently assuming " - strong {{user.username}} - | ", - a(href='/admin/become') revert to your identity. - .container - h1.navbar-brand - a(href='/') Apache Ignite Web Configurator - .navbar-collapse.collapse(ng-controller='auth') - ul.nav.navbar-nav(ng-controller='activeLink' ng-show='user') - +header-item('/configuration', '/configuration/clusters', 'Configuration') - //+header-item('/monitoring', '/monitoring', 'Monitoring') - //+header-item('/sql', '/sql', 'SQL') - //+header-item('/deploy', '/deploy', 'Deploy') - ul.nav.navbar-nav.pull-right - li(ng-if='user') - a.dropdown-toggle(data-toggle='dropdown' bs-dropdown='userDropdown' data-placement='bottom-right') {{user.username}} - span.caret - li.nav-login(ng-if='!user') - a(ng-click='login()' href='#') Log In \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/nodejs/views/index.jade ---------------------------------------------------------------------- diff --git a/modules/web-control-center/nodejs/views/index.jade b/modules/web-control-center/nodejs/views/index.jade deleted file mode 100644 index 999c4f8..0000000 --- a/modules/web-control-center/nodejs/views/index.jade +++ /dev/null @@ -1,30 +0,0 @@ -//- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You 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. - -extends templates/layout - -block container - .row - .docs-content - div - p - | Apache Ignite<sup>tm</sup> In-Memory Data Fabric is a high-performance, - | integrated and distributed in-memory platform for computing and transacting on large-scale data - | sets in real-time, orders of magnitude faster than possible with traditional disk-based or flash technologies. - .block-image.block-display-image - img(ng-src='https://www.filepicker.io/api/file/lydEeGB6Rs9hwbpcQxiw' alt='Apache Ignite stack') - .text-center(ng-controller='auth') - button.btn.btn-primary(ng-click='login()' href='#') Configure Now http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/nodejs/views/login.jade ---------------------------------------------------------------------- diff --git a/modules/web-control-center/nodejs/views/login.jade b/modules/web-control-center/nodejs/views/login.jade deleted file mode 100644 index 5bb39dd..0000000 --- a/modules/web-control-center/nodejs/views/login.jade +++ /dev/null @@ -1,55 +0,0 @@ -//- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You 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. - -mixin lbl(txt) - label.col-sm-3.required #{txt} - -.modal.center(role='dialog') - .modal-dialog - .modal-content - .modal-header.header - div(id='errors-container') - button.close(type='button', ng-click='$hide()', aria-hidden='true') × - h1.navbar-brand - a(href='/') Apache Ignite Web Configurator - h4.modal-title(style='padding-right: 55px') Authentication - p(style='padding-right: 55px') Log in or register in order to collaborate - form.form-horizontal(name='loginForm') - .modal-body.row - .col-sm-9.login.col-sm-offset-1 - .details-row(ng-show='action == "register"') - +lbl('Full Name:') - .col-sm-9 - input.form-control(type='text', ng-model='user_info.username', placeholder='John Smith', focus-me='action=="register"', ng-required='action=="register"') - .details-row - +lbl('Email:') - .col-sm-9 - input.form-control(type='email', ng-model='user_info.email', placeholder='y...@domain.com', focus-me='action=="login"', required) - .details-row - +lbl('Password:') - .col-sm-9 - input.form-control(type='password', ng-model='user_info.password', placeholder='Password', required, ng-keyup='action == "login" && $event.keyCode == 13 ? auth(action, user_info) : null') - .details-row(ng-show='action == "register"') - +lbl('Confirm:') - .col-sm-9.input-tip.has-feedback - input.form-control(type='password', ng-model='user_info.confirm', match="user_info.password" placeholder='Confirm password', required, ng-keyup='$event.keyCode == 13 ? auth(action, user_info) : null') - - .modal-footer - a.show-signup.ng-hide(ng-show='action != "login"', ng-click='action = "login";') log in - a.show-signup(ng-show="action != 'register'", ng-click='action = "register";') sign up - | or - button.btn.btn-primary(ng-show='action == "login"' ng-click='auth(action, user_info)') Log In - button.btn.btn-primary(ng-show='action == "register"' ng-disabled='loginForm.$invalid || user_info.password != user_info.confirm' ng-click='auth(action, user_info)') Sign Up http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/nodejs/views/settings/admin.jade ---------------------------------------------------------------------- diff --git a/modules/web-control-center/nodejs/views/settings/admin.jade b/modules/web-control-center/nodejs/views/settings/admin.jade deleted file mode 100644 index 4d50631..0000000 --- a/modules/web-control-center/nodejs/views/settings/admin.jade +++ /dev/null @@ -1,58 +0,0 @@ -//- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You 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. - -extends ../templates/layout - -append scripts - script(src='/admin-controller.js') - -block container - .row(ng-controller='adminController') - .docs-content - .docs-header - h1 List of registered users - hr - .docs-body - table.table.table-striped.admin(st-table='displayedUsers' st-safe-src='users') - thead - tr - th.header(colspan='5') - .col-sm-2.pull-right - input.form-control(type='text' st-search='' placeholder='Filter users...') - tr - th(st-sort='username') User name - th(st-sort='email') Email - th.col-sm-2(st-sort='lastLogin') Last login - th(width='1%' st-sort='admin') Admin - th(width='1%') Actions - tbody - tr(ng-repeat='row in displayedUsers') - td {{row.username}} - td - a(ng-href='mailto:{{row.email}}') {{row.email}} - td - span {{row.lastLogin | date:'medium'}} - td(style='text-align: center;') - input(type='checkbox' ng-disabled='row.adminChanging || row._id == user._id' - ng-model='row.admin' ng-change='toggleAdmin(row)') - td(style='text-align: center;') - a(ng-click='removeUser(row)' ng-show='row._id != user._id' bs-tooltip data-title='Remove user') - i.fa.fa-remove - a(style='margin-left: 5px' ng-href='admin/become?viewedUserId={{row._id}}' ng-show='row._id != user._id' bs-tooltip data-title='Become this user') - i.fa.fa-eye - tfoot - tr - td(colspan='5' class="text-right") - div(st-pagination st-items-by-page='15' st-displayed-pages='5') - http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/nodejs/views/settings/profile.jade ---------------------------------------------------------------------- diff --git a/modules/web-control-center/nodejs/views/settings/profile.jade b/modules/web-control-center/nodejs/views/settings/profile.jade deleted file mode 100644 index dbc6dea..0000000 --- a/modules/web-control-center/nodejs/views/settings/profile.jade +++ /dev/null @@ -1,58 +0,0 @@ -//- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You 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. - -extends ../templates/layout - -mixin lbl(txt) - label.col-sm-2.required.labelFormField #{txt} - -append scripts - script(src='/profile-controller.js') - -block container - .row(ng-controller='profileController') - .docs-content - .docs-header - h1 User profile - hr - .docs-body - form.form-horizontal(name='profileForm' novalidate) - .col-sm-10(style='padding: 0') - .details-row - +lbl('User name:') - .col-sm-4 - input.form-control(type='text' ng-model='profileUser.username' placeholder='Input name' required) - .details-row - +lbl('Email:') - .col-sm-4 - input.form-control(type='email' ng-model='profileUser.email' placeholder='y...@domain.com' required) - .details-row - .checkbox - label - input(type="checkbox" ng-model='profileUser.changePassword') - | Change password - div(ng-show='profileUser.changePassword') - .details-row - +lbl('New password:') - .col-sm-4 - input.form-control(type='password', ng-model='profileUser.newPassword' placeholder='New password' ng-required='profileUser.changePassword') - .details-row - +lbl('Confirm:') - .col-sm-4 - input.form-control(type='password', ng-model='profileUser.confirmPassword' match='profileUser.newPassword' placeholder='Confirm new password' ng-required='profileUser.changePassword') - .col-sm-12.details-row - button.btn.btn-primary(ng-disabled='profileForm.$invalid' ng-click='saveUser()') Save - http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/nodejs/views/templates/confirm.jade ---------------------------------------------------------------------- diff --git a/modules/web-control-center/nodejs/views/templates/confirm.jade b/modules/web-control-center/nodejs/views/templates/confirm.jade deleted file mode 100644 index bdaf9bf..0000000 --- a/modules/web-control-center/nodejs/views/templates/confirm.jade +++ /dev/null @@ -1,27 +0,0 @@ -//- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You 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. - -.modal(tabindex='-1' role='dialog') - .modal-dialog - .modal-content - .modal-header - button.close(type="button" ng-click="$hide()") × - h4.modal-title Confirmation - .modal-body(ng-show='content') - p(ng-bind-html='content' style='text-align: center;') - .modal-footer - button.btn.btn-default(type="button" ng-click="$hide()") Cancel - button.btn.btn-primary(type="button" ng-click="ok()") Confirm \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/nodejs/views/templates/copy.jade ---------------------------------------------------------------------- diff --git a/modules/web-control-center/nodejs/views/templates/copy.jade b/modules/web-control-center/nodejs/views/templates/copy.jade deleted file mode 100644 index 22cc64c..0000000 --- a/modules/web-control-center/nodejs/views/templates/copy.jade +++ /dev/null @@ -1,31 +0,0 @@ -//- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You 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. - -.modal(tabindex='-1' role='dialog') - .modal-dialog - .modal-content - .modal-header - button.close(type="button" ng-click="$hide()") × - h4.modal-title Copy - form.form-horizontal(name='inputForm' novalidate) - .modal-body.row - .col-sm-9.login.col-sm-offset-1 - label.required.labelFormField() New name: - .col-sm-9 - input.form-control(type="text" ng-model='newName' required) - .modal-footer - button.btn.btn-default(type="button" ng-click="$hide()") Cancel - button.btn.btn-primary(type="button" ng-disabled='inputForm.$invalid' ng-click="ok(newName)") Confirm \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/nodejs/views/templates/layout.jade ---------------------------------------------------------------------- diff --git a/modules/web-control-center/nodejs/views/templates/layout.jade b/modules/web-control-center/nodejs/views/templates/layout.jade deleted file mode 100644 index a4191ae..0000000 --- a/modules/web-control-center/nodejs/views/templates/layout.jade +++ /dev/null @@ -1,61 +0,0 @@ -//- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You 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. - -doctype html -html(ng-app='ignite-web-control-center', ng-init='user = #{JSON.stringify(user)}; becomeUsed = #{becomeUsed}') - head - title= title - - block css - // Bootstrap - link(rel='stylesheet', href='//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/css/bootstrap.css') - - // Font Awesome Icons - link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.css') - - // Font - link(rel='stylesheet', href='//fonts.googleapis.com/css?family=Roboto+Slab:700:serif|Roboto+Slab:400:serif') - - link(rel='stylesheet', href='/stylesheets/style.css') - - block scripts - script(src='//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.js') - - script(src='//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.0/lodash.min.js') - - script(src='//ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular.js') - script(src='//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.2/angular-sanitize.js') - script(src='//cdnjs.cloudflare.com/ajax/libs/angular-strap/2.3.0/angular-strap.js') - script(src='//cdnjs.cloudflare.com/ajax/libs/angular-strap/2.3.0/angular-strap.tpl.min.js') - - script(src='//cdnjs.cloudflare.com/ajax/libs/angular-smart-table/2.0.3/smart-table.js') - - script(src='//cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/ace.js') - script(src='//angular-ui.github.io/ui-ace/dist/ui-ace.min.js') - - script(src='/common-module.js') - script(src='/data-structures.js') - - body.theme-line.body-overlap - .wrapper - include ../includes/header - - block main-container - .container.body-container - .main-content - block container - - include ../includes/footer http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/nodejs/views/templates/select.jade ---------------------------------------------------------------------- diff --git a/modules/web-control-center/nodejs/views/templates/select.jade b/modules/web-control-center/nodejs/views/templates/select.jade deleted file mode 100644 index 10c1946..0000000 --- a/modules/web-control-center/nodejs/views/templates/select.jade +++ /dev/null @@ -1,26 +0,0 @@ -//- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You 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. - -ul.select.dropdown-menu(tabindex='-1', ng-show='$isVisible()', role='select') - li(role='presentation', ng-repeat='match in $matches') - hr(ng-if='match.value == undefined' style='margin: 5px 0') - a(style='cursor: default; padding: 3px 6px;', role='menuitem', tabindex='-1', ng-class='{active: $isActive($index)}' ng-click='$select($index, $event)') - i(class='{{$iconCheckmark}}', ng-if='$isActive($index)' ng-class='{active: $isActive($index)}' style='color: #ec1c24; margin-left: 15px; line-height: 20px; float: right;background-color: transparent;') - span(ng-bind='match.label') - li(ng-if='$showAllNoneButtons || ($isMultiple && $matches.length > 5)') - hr(style='margin: 5px 0') - a(ng-click='$selectAll()') {{$allText}} - a(ng-click='$selectNone()') {{$noneText}} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/src/main/js/.gitignore ---------------------------------------------------------------------- diff --git a/modules/web-control-center/src/main/js/.gitignore b/modules/web-control-center/src/main/js/.gitignore new file mode 100644 index 0000000..65f2596 --- /dev/null +++ b/modules/web-control-center/src/main/js/.gitignore @@ -0,0 +1,4 @@ +node_modules +*.idea +*.log +*.css \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/src/main/js/DEVNOTES.txt ---------------------------------------------------------------------- diff --git a/modules/web-control-center/src/main/js/DEVNOTES.txt b/modules/web-control-center/src/main/js/DEVNOTES.txt new file mode 100644 index 0000000..aa56011 --- /dev/null +++ b/modules/web-control-center/src/main/js/DEVNOTES.txt @@ -0,0 +1,21 @@ +Ignite Web Control Center Instructions +====================================== + +How to deploy: + +1. Install locally NodeJS using installer from site https://nodejs.org for your OS. +2. Install locally MongoDB folow instructions from site http://docs.mongodb.org/manual/installation +3. Checkout ignite-843 branch. +4. Change directory '$IGNITE_HOME/modules/web-control-center/nodejs'. +5. Run "npm install" in terminal for download all dependencies. + +Steps 1 - 5 should be executed once. + +How to run: + +1. Run MongoDB. + 1.1 In terminal change dir to $MONGO_ISNTALL_DIR/server/3.0/bin. + 1.2 Run "mongod". +2. In new terminal change directory '$IGNITE_HOME/modules/web-control-center/nodejs'. +3. Start application by executing "npm start". +4. In browser open: http://localhost:3000 \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/src/main/js/app.js ---------------------------------------------------------------------- diff --git a/modules/web-control-center/src/main/js/app.js b/modules/web-control-center/src/main/js/app.js new file mode 100644 index 0000000..8c347db --- /dev/null +++ b/modules/web-control-center/src/main/js/app.js @@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +var flash = require('connect-flash'); +var express = require('express'); +var path = require('path'); +var favicon = require('serve-favicon'); +var logger = require('morgan'); +var cookieParser = require('cookie-parser'); +var bodyParser = require('body-parser'); +var session = require('express-session'); +var mongoStore = require('connect-mongo')(session); + +var publicRoutes = require('./routes/public'); +var clustersRouter = require('./routes/clusters'); +var cachesRouter = require('./routes/caches'); +var metadataRouter = require('./routes/metadata'); +var summary = require('./routes/summary'); +var adminRouter = require('./routes/admin'); +var profileRouter = require('./routes/profile'); +var sqlRouter = require('./routes/sql'); + +var passport = require('passport'); + +var db = require('./db'); + +var app = express(); + +// Views engine setup. +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'jade'); + +// Site favicon. +app.use(favicon(__dirname + '/public/favicon.ico')); + +app.use(logger('dev')); + +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({extended: false})); + +app.use(require('less-middleware')(path.join(__dirname, 'public'), { + render: { + compress: false + } +})); + +app.use(express.static(path.join(__dirname, 'public'))); +app.use(express.static(path.join(__dirname, 'controllers'))); +app.use(express.static(path.join(__dirname, 'helpers'))); + +app.use(cookieParser('keyboard cat')); + +app.use(session({ + secret: 'keyboard cat', + resave: false, + saveUninitialized: true, + store: new mongoStore({ + mongooseConnection: db.mongoose.connection + }) +})); + +app.use(flash()); + +app.use(passport.initialize()); +app.use(passport.session()); + +passport.serializeUser(db.Account.serializeUser()); +passport.deserializeUser(db.Account.deserializeUser()); + +passport.use(db.Account.createStrategy()); + +var mustAuthenticated = function (req, res, next) { + req.isAuthenticated() ? next() : res.redirect('/'); +}; + +var adminOnly = function(req, res, next) { + req.isAuthenticated() && req.user.admin ? next() : res.sendStatus(403); +}; + +app.all('/configuration/*', mustAuthenticated); + +app.all('*', function(req, res, next) { + var becomeUsed = req.session.viewedUser && req.user.admin; + + res.locals.user = becomeUsed ? req.session.viewedUser : req.user; + res.locals.becomeUsed = becomeUsed; + + req.currentUserId = function() { + if (!req.user) + return null; + + if (req.session.viewedUser && req.user.admin) + return req.session.viewedUser._id; + + return req.user._id; + }; + + next(); +}); + +app.use('/', publicRoutes); +app.use('/admin', mustAuthenticated, adminOnly, adminRouter); +app.use('/profile', mustAuthenticated, profileRouter); + +app.use('/configuration/clusters', clustersRouter); +app.use('/configuration/caches', cachesRouter); +app.use('/configuration/metadata', metadataRouter); +app.use('/configuration/summary', summary); +app.use('/sql', sqlRouter); + +// Catch 404 and forward to error handler. +app.use(function (req, res, next) { + var err = new Error('Not Found: ' + req.originalUrl); + err.status = 404; + next(err); +}); + +// Error handlers. + +// Development error handler: will print stacktrace. +if (app.get('env') === 'development') { + app.use(function (err, req, res) { + res.status(err.status || 500); + res.render('error', { + message: err.message, + error: err + }); + }); +} + +// Production error handler: no stacktraces leaked to user. +app.use(function (err, req, res) { + res.status(err.status || 500); + res.render('error', { + message: err.message, + error: {} + }); +}); + +module.exports = app; http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/src/main/js/bin/www ---------------------------------------------------------------------- diff --git a/modules/web-control-center/src/main/js/bin/www b/modules/web-control-center/src/main/js/bin/www new file mode 100644 index 0000000..4cf0583 --- /dev/null +++ b/modules/web-control-center/src/main/js/bin/www @@ -0,0 +1,85 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ +var app = require('../app'); +var config = require('../helpers/configuration-loader.js'); +var debug = require('debug')('ignite-web-control-center:server'); +var http = require('http'); + +/** + * Get port from environment and store in Express. + */ +var port = normalizePort(process.env.PORT || config.get('express:port')); +app.set('port', port); + +/** + * Create HTTP server. + */ +var server = http.createServer(app); + +/** + * Listen on provided port, on all network interfaces. + */ +server.listen(port); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string, or false. + */ +function normalizePort(val) { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ +function onListening() { + var addr = server.address(); + var bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + + debug('Listening on ' + bind); +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/src/main/js/config/default.json ---------------------------------------------------------------------- diff --git a/modules/web-control-center/src/main/js/config/default.json b/modules/web-control-center/src/main/js/config/default.json new file mode 100644 index 0000000..72dbd4e --- /dev/null +++ b/modules/web-control-center/src/main/js/config/default.json @@ -0,0 +1,8 @@ +{ + "express": { + "port": 3000 + }, + "mongoDB": { + "url": "mongodb://localhost/web-control-center" + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/src/main/js/controllers/admin-controller.js ---------------------------------------------------------------------- diff --git a/modules/web-control-center/src/main/js/controllers/admin-controller.js b/modules/web-control-center/src/main/js/controllers/admin-controller.js new file mode 100644 index 0000000..09490fe --- /dev/null +++ b/modules/web-control-center/src/main/js/controllers/admin-controller.js @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +controlCenterModule.controller('adminController', ['$scope', '$http', '$common', '$confirm', function ($scope, $http, $common, $confirm) { + $scope.users = null; + + function reload() { + $http.post('admin/list') + .success(function (data) { + $scope.users = data; + }) + .error(function (errMsg) { + $common.showError($common.errorMessage(errMsg)); + }); + } + + reload(); + + $scope.removeUser = function (user) { + $confirm.show('Are you sure you want to remove user: "' + user.username + '"?').then(function () { + $http.post('admin/remove', {userId: user._id}).success( + function () { + var i = _.findIndex($scope.users, function (u) { + return u._id == user._id; + }); + + if (i >= 0) + $scope.users.splice(i, 1); + + $common.showInfo('User has been removed: "' + user.username + '"'); + }).error(function (errMsg) { + $common.showError('Failed to remove user: "' + $common.errorMessage(errMsg) + '"'); + }); + }); + }; + + $scope.toggleAdmin = function (user) { + if (user.adminChanging) + return; + + user.adminChanging = true; + + $http.post('admin/save', {userId: user._id, adminFlag: user.admin}).success( + function () { + $common.showInfo('Admin right was successfully toggled for user: "' + user.username + '"'); + + user.adminChanging = false; + }).error(function (errMsg) { + $common.showError('Failed to toggle admin right for user: "' + $common.errorMessage(errMsg) + '"'); + + user.adminChanging = false; + }); + } +}]); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/src/main/js/controllers/cache-viewer-controller.js ---------------------------------------------------------------------- diff --git a/modules/web-control-center/src/main/js/controllers/cache-viewer-controller.js b/modules/web-control-center/src/main/js/controllers/cache-viewer-controller.js new file mode 100644 index 0000000..6e0c130 --- /dev/null +++ b/modules/web-control-center/src/main/js/controllers/cache-viewer-controller.js @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +var demoResults = [ + { + id: 256, + s: 'com.foo.User@3213', + fields: { + id: 256, + firstName: 'Ivan', + lastName: 'Ivanov', + old: 23 + } + }, + + { + id: 384, + s: 'com.foo.User@23214', + fields: { + id: 384, + firstName: 'Sergey', + lastName: 'Petrov', + old: 28 + } + }, + + { + id: 923, + s: 'com.foo.User@93494', + fields: { + id: 923, + firstName: 'Andrey', + lastName: 'Sidorov', + old: 28 + } + } +]; + +var demoCaches = ['Users', 'Organizations', 'Cities']; + +controlCenterModule.controller('cacheViewerController', ['$scope', '$http', '$common', function ($scope, $http, $common) { + $scope.results = demoResults; + + $scope.caches = demoCaches; + + $scope.defCache = $scope.caches.length > 0 ? $scope.caches[0] : null; + + var sqlEditor = ace.edit('querySql'); + + sqlEditor.setOptions({ + highlightActiveLine: false, + showPrintMargin: false, + showGutter: true, + theme: "ace/theme/chrome", + mode: "ace/mode/sql", + fontSize: 14 + }); + + sqlEditor.setValue("select u.id from User u where u.name like 'aaaa';"); + + sqlEditor.selection.clearSelection() + +}]); http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/src/main/js/controllers/caches-controller.js ---------------------------------------------------------------------- diff --git a/modules/web-control-center/src/main/js/controllers/caches-controller.js b/modules/web-control-center/src/main/js/controllers/caches-controller.js new file mode 100644 index 0000000..0c23e3b --- /dev/null +++ b/modules/web-control-center/src/main/js/controllers/caches-controller.js @@ -0,0 +1,333 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +controlCenterModule.controller('cachesController', ['$scope', '$http', '$common', '$confirm', '$copy', '$table', function ($scope, $http, $common, $confirm, $copy, $table) { + $scope.joinTip = $common.joinTip; + $scope.getModel = $common.getModel; + + $scope.tableNewItem = $table.tableNewItem; + $scope.tableNewItemActive = $table.tableNewItemActive; + $scope.tableEditing = $table.tableEditing; + $scope.tableStartEdit = $table.tableStartEdit; + $scope.tableRemove = $table.tableRemove; + + $scope.tableSimpleSave = $table.tableSimpleSave; + $scope.tableSimpleSaveVisible = $table.tableSimpleSaveVisible; + $scope.tableSimpleUp = $table.tableSimpleUp; + $scope.tableSimpleDown = $table.tableSimpleDown; + $scope.tableSimpleDownVisible = $table.tableSimpleDownVisible; + + $scope.tablePairSave = $table.tablePairSave; + $scope.tablePairSaveVisible = $table.tablePairSaveVisible; + + $scope.atomicities = [ + {value: 'ATOMIC', label: 'ATOMIC'}, + {value: 'TRANSACTIONAL', label: 'TRANSACTIONAL'} + ]; + + $scope.modes = [ + {value: 'PARTITIONED', label: 'PARTITIONED'}, + {value: 'REPLICATED', label: 'REPLICATED'}, + {value: 'LOCAL', label: 'LOCAL'} + ]; + + $scope.atomicWriteOrderModes = [ + {value: 'CLOCK', label: 'CLOCK'}, + {value: 'PRIMARY', label: 'PRIMARY'} + ]; + + $scope.memoryModes = [ + {value: 'ONHEAP_TIERED', label: 'ONHEAP_TIERED'}, + {value: 'OFFHEAP_TIERED', label: 'OFFHEAP_TIERED'}, + {value: 'OFFHEAP_VALUES', label: 'OFFHEAP_VALUES'} + ]; + + $scope.evictionPolicies = [ + {value: 'LRU', label: 'LRU'}, + {value: 'RND', label: 'Random'}, + {value: 'FIFO', label: 'FIFO'}, + {value: 'SORTED', label: 'Sorted'}, + {value: undefined, label: 'Not set'} + ]; + + $scope.rebalanceModes = [ + {value: 'SYNC', label: 'SYNC'}, + {value: 'ASYNC', label: 'ASYNC'}, + {value: 'NONE', label: 'NONE'} + ]; + + $scope.cacheStoreFactories = [ + {value: 'CacheJdbcPojoStoreFactory', label: 'JDBC POJO store factory'}, + {value: 'CacheJdbcBlobStoreFactory', label: 'JDBC BLOB store factory'}, + {value: 'CacheHibernateBlobStoreFactory', label: 'Hibernate BLOB store factory'}, + {value: undefined, label: 'Not set'} + ]; + + $scope.cacheStoreJdbcDialects = [ + {value: 'Oracle', label: 'Oracle'}, + {value: 'DB2', label: 'IBM DB2'}, + {value: 'SQLServer', label: 'Microsoft SQL Server'}, + {value: 'MySQL', label: 'My SQL'}, + {value: 'PostgreSQL', label: 'Postgre SQL'}, + {value: 'H2', label: 'H2 database'} + ]; + + $scope.general = []; + $scope.advanced = []; + + $http.get('/models/caches.json') + .success(function (data) { + $scope.screenTip = data.screenTip; + $scope.general = data.general; + $scope.advanced = data.advanced; + }) + .error(function (errMsg) { + $common.showError(errMsg); + }); + + $scope.caches = []; + + $scope.required = function (field) { + var model = $common.isDefined(field.path) ? field.path + '.' + field.model : field.model; + + var backupItem = $scope.backupItem; + + var memoryMode = backupItem.memoryMode; + + var onHeapTired = memoryMode == 'ONHEAP_TIERED'; + var offHeapTired = memoryMode == 'OFFHEAP_TIERED'; + + var offHeapMaxMemory = backupItem.offHeapMaxMemory; + + if (model == 'offHeapMaxMemory' && offHeapTired) + return true; + + if (model == 'evictionPolicy.kind' && onHeapTired) + return backupItem.swapEnabled || ($common.isDefined(offHeapMaxMemory) && offHeapMaxMemory >= 0); + + return false; + }; + + $scope.tableSimpleValid = function (item, field, fx, index) { + var model = item[field.model]; + + if ($common.isDefined(model)) { + var idx = _.indexOf(model, fx); + + // Found itself. + if (index >= 0 && index == idx) + return true; + + // Found duplicate. + if (idx >= 0) { + $common.showError('SQL function such class name already exists!'); + + return false; + } + } + + return true; + }; + + $scope.tablePairValid = function (item, field, keyCls, valCls, index) { + var model = item[field.model]; + + if ($common.isDefined(model)) { + var idx = _.findIndex(model, function (pair) { + return pair.keyClass == keyCls + }); + + // Found itself. + if (index >= 0 && index == idx) + return true; + + // Found duplicate. + if (idx >= 0) { + $common.showError('Indexed type with such key class already exists!'); + + return false; + } + } + + return true; + }; + + // When landing on the page, get caches and show them. + $http.post('caches/list') + .success(function (data) { + $scope.spaces = data.spaces; + $scope.caches = data.caches; + + var restoredItem = angular.fromJson(sessionStorage.cacheBackupItem); + + if (restoredItem) { + if (restoredItem._id) { + var idx = _.findIndex($scope.caches, function (cache) { + return cache._id == restoredItem._id; + }); + + if (idx >= 0) { + $scope.selectedItem = $scope.caches[idx]; + $scope.backupItem = restoredItem; + } + else + sessionStorage.removeItem('cacheBackupItem'); + } + else + $scope.backupItem = restoredItem; + } + else if ($scope.caches.length > 0) + $scope.selectItem($scope.caches[0]); + + $scope.$watch('backupItem', function (val) { + if (val) + sessionStorage.cacheBackupItem = angular.toJson(val); + }, true); + }) + .error(function (errMsg) { + $common.showError(errMsg); + }); + + $scope.selectItem = function (item) { + $table.tableReset(); + + $scope.selectedItem = item; + $scope.backupItem = angular.copy(item); + }; + + // Add new cache. + $scope.createItem = function () { + $table.tableReset(); + + $scope.backupItem = {mode: 'PARTITIONED', atomicityMode: 'ATOMIC', readFromBackup: true, copyOnRead: true}; + $scope.backupItem.space = $scope.spaces[0]._id; + }; + + // Check cache logical consistency. + function validate(item) { + var cacheStoreFactorySelected = item.cacheStoreFactory && item.cacheStoreFactory.kind; + + if (cacheStoreFactorySelected && !(item.readThrough || item.writeThrough)) { + $common.showError('Store is configured but read/write through are not enabled!'); + + return false; + } + + if ((item.readThrough || item.writeThrough) && !cacheStoreFactorySelected) { + $common.showError('Read / write through are enabled but store is not configured!'); + + return false; + } + + if (item.writeBehindEnabled && !cacheStoreFactorySelected) { + $common.showError('Write behind enabled but store is not configured!'); + + return false; + } + + return true; + } + + // Save cache into database. + function save(item) { + $http.post('caches/save', item) + .success(function (_id) { + var idx = _.findIndex($scope.caches, function (cache) { + return cache._id == _id; + }); + + if (idx >= 0) + angular.extend($scope.caches[idx], item); + else { + item._id = _id; + + $scope.caches.push(item); + } + + $scope.selectItem(item); + + $common.showInfo('Cache "' + item.name + '" saved.'); + }) + .error(function (errMsg) { + $common.showError(errMsg); + }); + } + + // Save cache. + $scope.saveItem = function () { + $table.tableReset(); + + var item = $scope.backupItem; + + if (validate(item)) + save(item); + }; + + // Save cache with new name. + $scope.saveItemAs = function () { + $table.tableReset(); + + if (validate($scope.backupItem)) + $copy.show($scope.backupItem.name).then(function (newName) { + var item = angular.copy($scope.backupItem); + + item._id = undefined; + item.name = newName; + + save(item); + }); + }; + + // Remove cache from db. + $scope.removeItem = function () { + $table.tableReset(); + + var selectedItem = $scope.selectedItem; + + $confirm.show('Are you sure you want to remove cache: "' + selectedItem.name + '"?').then( + function () { + var _id = selectedItem._id; + + $http.post('caches/remove', {_id: _id}) + .success(function () { + $common.showInfo('Cache has been removed: ' + selectedItem.name); + + var caches = $scope.caches; + + var idx = _.findIndex(caches, function (cache) { + return cache._id == _id; + }); + + if (idx >= 0) { + caches.splice(idx, 1); + + if (caches.length > 0) + $scope.selectItem(caches[0]); + else { + $scope.selectedItem = undefined; + $scope.backupItem = undefined; + } + } + }) + .error(function (errMsg) { + $common.showError(errMsg); + }); + } + ); + }; + }] +); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a335724/modules/web-control-center/src/main/js/controllers/clusters-controller.js ---------------------------------------------------------------------- diff --git a/modules/web-control-center/src/main/js/controllers/clusters-controller.js b/modules/web-control-center/src/main/js/controllers/clusters-controller.js new file mode 100644 index 0000000..1ec78a1 --- /dev/null +++ b/modules/web-control-center/src/main/js/controllers/clusters-controller.js @@ -0,0 +1,309 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +controlCenterModule.controller('clustersController', ['$scope', '$http', '$common', '$confirm', '$copy', '$table', function ($scope, $http, $common, $confirm, $copy, $table) { + $scope.joinTip = $common.joinTip; + $scope.getModel = $common.getModel; + + $scope.tableNewItem = $table.tableNewItem; + $scope.tableNewItemActive = $table.tableNewItemActive; + $scope.tableEditing = $table.tableEditing; + $scope.tableStartEdit = $table.tableStartEdit; + $scope.tableRemove = $table.tableRemove; + + $scope.tableSimpleSave = $table.tableSimpleSave; + $scope.tableSimpleSaveVisible = $table.tableSimpleSaveVisible; + $scope.tableSimpleUp = $table.tableSimpleUp; + $scope.tableSimpleDown = $table.tableSimpleDown; + $scope.tableSimpleDownVisible = $table.tableSimpleDownVisible; + + $scope.templates = [ + { + value: {discovery: {kind: 'Multicast', Vm: {addresses: ['127.0.0.1:47500..47510']}, Multicast: {}}}, + label: 'multicast' + }, + {value: {discovery: {kind: 'Vm', Vm: {addresses: ['127.0.0.1:47500..47510']}}}, label: 'local'} + ]; + + $scope.discoveries = [ + {value: 'Vm', label: 'static IPs'}, + {value: 'Multicast', label: 'multicast'}, + {value: 'S3', label: 'AWS S3'}, + {value: 'Cloud', label: 'apache jclouds'}, + {value: 'GoogleStorage', label: 'google cloud storage'}, + {value: 'Jdbc', label: 'JDBC'}, + {value: 'SharedFs', label: 'shared filesystem'} + ]; + + $scope.swapSpaceSpis = [ + {value: 'FileSwapSpaceSpi', label: 'File-based swap'}, + {value: undefined, label: 'Not set'} + ]; + + $scope.events = []; + + for (var eventGroupName in eventGroups) { + if (eventGroups.hasOwnProperty(eventGroupName)) { + $scope.events.push({value: eventGroupName, label: eventGroupName}); + } + } + + $scope.cacheModes = [ + {value: 'LOCAL', label: 'LOCAL'}, + {value: 'REPLICATED', label: 'REPLICATED'}, + {value: 'PARTITIONED', label: 'PARTITIONED'} + ]; + + $scope.deploymentModes = [ + {value: 'PRIVATE', label: 'PRIVATE'}, + {value: 'ISOLATED', label: 'ISOLATED'}, + {value: 'SHARED', label: 'SHARED'}, + {value: 'CONTINUOUS', label: 'CONTINUOUS'} + ]; + + $scope.transactionConcurrency = [ + {value: 'OPTIMISTIC', label: 'OPTIMISTIC'}, + {value: 'PESSIMISTIC', label: 'PESSIMISTIC'} + ]; + + $scope.transactionIsolation = [ + {value: 'READ_COMMITTED', label: 'READ_COMMITTED'}, + {value: 'REPEATABLE_READ', label: 'REPEATABLE_READ'}, + {value: 'SERIALIZABLE', label: 'SERIALIZABLE'} + ]; + + $scope.segmentationPolicy = [ + {value: 'RESTART_JVM', label: 'RESTART_JVM'}, + {value: 'STOP', label: 'STOP'}, + {value: 'NOOP', label: 'NOOP'} + ]; + + $scope.marshallers = [ + {value: 'OptimizedMarshaller', label: 'OptimizedMarshaller'}, + {value: 'JdkMarshaller', label: 'JdkMarshaller'} + ]; + + $scope.tableSimpleValid = function (item, field, val, index) { + var model = $common.getModel(item, field)[field.model]; + + if ($common.isDefined(model)) { + var idx = _.indexOf(model, val); + + // Found itself. + if (index >= 0 && index == idx) + return true; + + // Found duplicate. + if (idx >= 0) { + var msg = 'Such IP address already exists!'; + + if (field.model == 'regions') + msg = 'Such region already exists!'; + if (field.model == 'zones') + msg = 'Such zone already exists!'; + + $common.showError(msg); + + return false; + } + } + + return true; + }; + + $scope.clusters = []; + + $http.get('/models/clusters.json') + .success(function (data) { + $scope.screenTip = data.screenTip; + $scope.templateTip = data.templateTip; + + $scope.general = data.general; + $scope.advanced = data.advanced; + }) + .error(function (errMsg) { + $common.showError(errMsg); + }); + + // When landing on the page, get clusters and show them. + $http.post('clusters/list') + .success(function (data) { + $scope.caches = data.caches; + $scope.spaces = data.spaces; + $scope.clusters = data.clusters; + + var restoredItem = angular.fromJson(sessionStorage.clusterBackupItem); + + if (restoredItem) { + if (restoredItem._id) { + var idx = _.findIndex($scope.clusters, function (cluster) { + return cluster._id == restoredItem._id; + }); + + if (idx >= 0) { + $scope.selectedItem = $scope.clusters[idx]; + $scope.backupItem = restoredItem; + } + else + sessionStorage.removeItem('clusterBackupItem'); + } + else + $scope.backupItem = restoredItem; + } + else if ($scope.clusters.length > 0) + $scope.selectItem($scope.clusters[0]); + + $scope.$watch('backupItem', function (val) { + if (val) + sessionStorage.clusterBackupItem = angular.toJson(val); + }, true); + }) + .error(function (errMsg) { + $common.showError(errMsg); + }); + + $scope.selectItem = function (item) { + $table.tableReset(); + + $scope.selectedItem = item; + $scope.backupItem = angular.copy(item); + }; + + // Add new cluster. + $scope.createItem = function () { + $table.tableReset(); + + $scope.backupItem = angular.copy($scope.create.template); + $scope.backupItem.space = $scope.spaces[0]._id; + }; + + $scope.indexOfCache = function (cacheId) { + return _.findIndex($scope.caches, function (cache) { + return cache.value == cacheId; + }); + }; + + // Check cluster logical consistency. + function validate(item) { + if (!item.swapSpaceSpi || !item.swapSpaceSpi.kind && item.caches) { + for (var i = 0; i < item.caches.length; i++) { + var idx = $scope.indexOfCache(item.caches[i]); + + if (idx >= 0) { + var cache = $scope.caches[idx]; + + if (cache.swapEnabled) { + $common.showError('Swap space SPI is not configured, but cache "' + cache.label + '" configured to use swap!'); + + return false; + } + } + } + } + + return true; + } + + // Save cluster in database. + function save(item) { + $http.post('clusters/save', item) + .success(function (_id) { + var idx = _.findIndex($scope.clusters, function (cluster) { + return cluster._id == _id; + }); + + if (idx >= 0) + angular.extend($scope.clusters[idx], item); + else { + item._id = _id; + + $scope.clusters.push(item); + } + + $scope.selectItem(item); + + $common.showInfo('Cluster "' + item.name + '" saved.'); + }) + .error(function (errMsg) { + $common.showError(errMsg); + }); + } + + // Save cluster. + $scope.saveItem = function () { + $table.tableReset(); + + var item = $scope.backupItem; + + if (validate(item)) + save(item); + }; + + // Save cluster with new name. + $scope.saveItemAs = function () { + $table.tableReset(); + + if (validate($scope.backupItem)) + $copy.show($scope.backupItem.name).then(function (newName) { + var item = angular.copy($scope.backupItem); + + item._id = undefined; + item.name = newName; + + save(item); + }); + }; + + // Remove cluster from db. + $scope.removeItem = function () { + $table.tableReset(); + + var selectedItem = $scope.selectedItem; + + $confirm.show('Are you sure you want to remove cluster: "' + selectedItem.name + '"?').then( + function () { + var _id = selectedItem._id; + + $http.post('clusters/remove', {_id: _id}) + .success(function () { + $common.showInfo('Cluster has been removed: ' + selectedItem.name); + + var clusters = $scope.clusters; + + var idx = _.findIndex(clusters, function (cluster) { + return cluster._id == _id; + }); + + if (idx >= 0) { + clusters.splice(idx, 1); + + if (clusters.length > 0) + $scope.selectItem(clusters[0]); + else { + $scope.selectedItem = undefined; + $scope.backupItem = undefined; + } + } + }) + .error(function (errMsg) { + $common.showError(errMsg); + }); + } + ); + }; + }] +); \ No newline at end of file