ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From a.@apache.org
Subject [04/28] ignite git commit: IGNITE-843 Implemented Web Console.
Date Wed, 18 May 2016 09:21:07 GMT
http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/public/stylesheets/style.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/public/stylesheets/style.scss b/modules/web-console/src/main/js/public/stylesheets/style.scss
new file mode 100644
index 0000000..589028c
--- /dev/null
+++ b/modules/web-console/src/main/js/public/stylesheets/style.scss
@@ -0,0 +1,2128 @@
+/*
+ * 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.
+ */
+
+@import "font-awesome-custom";
+@import "bootstrap-custom";
+
+@import "./variables";
+
+@import "./../../app/directives/information/information.scss";
+
+@font-face {
+    font-family: 'Roboto Slab';
+    font-style: normal;
+    font-weight: 400;
+    src: local('Roboto Slab Regular'), local('RobotoSlab-Regular'), url(//fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37ZiwlidHJgAgmTjOEEzwu1L8.ttf) format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto Slab';
+    font-style: normal;
+    font-weight: 700;
+    src: local('Roboto Slab Bold'), local('RobotoSlab-Bold'), url(//fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJTdGNerWpg2Hn6A-BxWgZ_I.ttf) format('truetype');
+}
+
+hr {
+    margin: 20px 0;
+}
+
+.theme-line a.active {
+    font-weight: bold;
+    font-size: 1.1em;
+}
+
+.theme-line a:focus {
+    text-decoration: underline;
+    outline: none;
+}
+
+.navbar-default .navbar-brand, .navbar-default .navbar-brand:hover {
+    position: absolute;
+    left: 0;
+    text-align: center;
+}
+
+.navbar-brand {
+    padding: 5px 0;
+    margin: 10px 0;
+}
+
+.modal.center .modal-dialog {
+    position: fixed;
+    top: 50%;
+    left: 50%;
+    -webkit-transform: translateX(-50%) translateY(-50%);
+    transform: translateX(-50%) translateY(-50%);
+}
+
+.border-left {
+    box-shadow: 1px 0 0 0 $gray-lighter inset;
+}
+
+.border-right {
+    box-shadow: 1px 0 0 0 $gray-lighter;
+}
+
+.theme-line header {
+    background-color: $ignite-background-color;
+}
+
+.theme-line .docs-header h1 {
+    color: $ignite-header-color;
+    margin-top: 0;
+    font-size: 22px;
+}
+
+.theme-line .footer {
+    text-align: center;
+}
+
+.table.table-vertical-middle tbody > tr > td {
+  vertical-align: middle;
+}
+
+ul.navbar-nav, .sidebar-nav {
+    li.active > a {
+        color: $link-color;
+    }
+
+    li.active > a:not(.dropdown-toggle) {
+        cursor: default;
+        pointer-events: none;
+    }
+}
+
+.theme-line .sidebar-nav {
+    padding-bottom: 30px;
+
+    ul {
+        padding: 0;
+        list-style: none;
+        margin: 3px 0 0;
+
+        li {
+            line-height: $input-height;
+
+            a {
+                font-size: 18px;
+                color: $ignite-header-color;
+                position: relative;
+                white-space: nowrap;
+                overflow: hidden;
+                -o-text-overflow: ellipsis;
+                text-overflow: ellipsis;
+
+                span.fa-stack {
+                    margin-right: 5px;
+                    font-size: 12px;
+                    height: 26px;
+                }
+            }
+
+            a:hover { color: $link-hover-color; }
+
+            a.active {
+                color: $link-color;
+            }
+        }
+    }
+}
+
+.theme-line .sidebar-nav ul li a:hover {
+    text-decoration: none;
+}
+
+.theme-line .select {
+    li a.active {
+        color: $dropdown-link-active-color;
+    }
+
+    li a:hover {
+        color: $dropdown-link-hover-color;
+    }
+}
+
+.theme-line .select,
+.theme-line .typeahead {
+    .active {
+        font-size: 1em;
+        background-color: $gray-lighter;
+    }
+}
+
+.theme-line button.form-control.placeholder {
+    color: $input-color-placeholder;
+}
+
+.theme-line .summary-pojo-list > ul.dropdown-menu {
+    width: 100%;
+    max-width: none;
+}
+
+.tooltip {
+  word-wrap: break-word;
+}
+
+.theme-line ul.dropdown-menu {
+    min-width: 120px;
+    max-width: 280px;
+    max-height: 20em;
+    overflow: auto;
+    overflow-x: hidden;
+    outline-style: none;
+
+    li > a {
+        display: block;
+
+        //cursor: default;
+        padding: 3px 10px;
+
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+
+        i {
+            float: right;
+            color: $brand-primary;
+            background-color: transparent;
+            line-height: $line-height-base;
+            margin-left: 5px;
+            margin-right: 0;
+        }
+    }
+
+    li.divider {
+        margin: 3px 0;
+    }
+}
+
+.theme-line .border-left .sidebar-nav {
+    padding-left: 15px;
+}
+
+.theme-line .suggest {
+    padding: 5px;
+    display: inline-block;
+    font-size: 12px;
+}
+
+.theme-line header {
+    border-bottom: 8px solid $ignite-border-bottom-color;
+
+    p {
+        color: $ignite-header-color;
+    }
+}
+
+.header .nav.navbar-nav.pull-right > li > a {
+    padding-right: 0;
+}
+
+.header .title {
+    margin: 20px 0 5px 0;
+    padding: 0 15px;
+
+    font-size: 1.4em;
+}
+
+.header .nav.navbar-nav .not-link {
+    padding: 15px;
+    display: inline-block;
+}
+
+.nav > li {
+    > a {
+        color: $navbar-default-link-color
+    }
+    > a:hover {
+        color: $link-hover-color
+    }
+    > a.active {
+        color: $link-color
+    }
+}
+
+.theme-line header .navbar-nav a {
+    line-height: 25px;
+    font-size: 18px;
+}
+
+.theme-line .section-right {
+    padding-left: 30px;
+}
+
+.body-overlap .main-content {
+    margin-top: 30px;
+}
+
+.body-box .main-content,
+.body-overlap .main-content {
+    padding: 30px;
+    box-shadow: 0 0 0 1px $ignite-border-color;
+    background-color: $ignite-background-color;
+}
+
+body {
+    font-weight: 400;
+}
+
+h1, h2, h3, h4, h5, h6 {
+    font-weight: 700;
+    margin-bottom: 10px;
+}
+
+.container-footer {
+    margin-top: 20px;
+    margin-bottom: 20px;
+
+    p {
+        font-size: 12px;
+        margin-bottom: 0;
+    }
+}
+
+/* Modal */
+.modal {
+    display: block;
+    overflow: hidden;
+}
+
+.modal .close {
+    position: absolute;
+    top: 10px;
+    right: 10px;
+    float: none;
+}
+
+.modal-header {
+    border-top-left-radius: 6px;
+    border-top-right-radius: 6px;
+}
+
+// Close icon
+.modal-header .close {
+    margin-right: -2px;
+}
+
+.modal .modal-dialog {
+    width: 650px;
+}
+
+.modal .modal-content {
+    background-color: $gray-lighter;
+
+    .input-tip {
+        padding-top: 1px;
+    }
+}
+
+.modal .modal-content .modal-header {
+    background-color: $ignite-background-color;
+    text-align: center;
+    color: $ignite-header-color;
+    padding: 15px 25px 15px 15px;
+}
+
+.modal .modal-content .modal-header h4 {
+    font-size: 22px;
+}
+
+.modal .modal-content .modal-footer {
+    margin-top: 0;
+}
+
+.modal-footer {
+    label {
+        float: left;
+        margin: 0;
+    }
+
+    .btn:last-child {
+        margin-right: 0;
+    }
+
+    .checkbox {
+        margin: 0;
+    }
+}
+
+.login-header {
+    margin-top: 0;
+    margin-bottom: 20px;
+    font-size: 2em;
+}
+
+.login-footer {
+    @extend .modal-footer;
+
+    padding-left: 0;
+    padding-right: 0;
+
+    .btn {
+        margin-right: 0;
+    }
+}
+
+.modal-body {
+    margin-left: 20px;
+    margin-right: 20px;
+}
+
+.modal-body-with-scroll {
+    max-height: 420px;
+    overflow-y: auto;
+    margin: 0;
+}
+
+.greedy {
+    min-height: 100%;
+    height: #{"calc(100vh - 270px)"};
+}
+
+.signin-greedy {
+    height: #{"calc(100vh - 300px)"};
+}
+
+@media (min-width: 768px) {
+    .navbar-nav > li > a {
+        padding: 0 15px;
+    }
+}
+
+.details-row {
+    padding: 0 5px;
+}
+
+.details-row, .settings-row {
+    display: block;
+    margin: 10px 0;
+
+    [class*="col-"] {
+        display: inline-block;
+        vertical-align: middle;
+        float: none;
+    }
+
+    input[type="checkbox"] {
+        line-height: 20px;
+        margin-right: 5px;
+    }
+
+    .checkbox label {
+        line-height: 20px !important;
+        vertical-align: middle;
+    }
+}
+
+.group-section {
+    margin-top: 20px;
+}
+
+.details-row:first-child {
+    margin-top: 0;
+
+    .group-section {
+        margin-top: 10px;
+    }
+}
+
+.details-row:last-child {
+    margin-bottom: 0;
+}
+
+.settings-row:first-child {
+    margin-top: 0;
+
+    .group-section {
+        margin-top: 0;
+    }
+}
+
+.settings-row:last-child {
+    margin-bottom: 0;
+}
+
+button, .btn {
+    margin-right: 5px;
+}
+
+i.btn {
+    margin-right: 0;
+}
+
+.btn {
+    padding: 3px 6px;
+
+    :focus {
+        //outline: none;
+        //border: 1px solid $btn-default-border;
+    }
+}
+
+.btn-group.pull-right {
+    margin-right: 0;
+}
+
+.btn-group {
+    margin-right: 5px;
+
+    > button, a.btn {
+        margin-right: 0;
+    }
+
+    button.btn + .btn {
+        margin-left: 0;
+    }
+
+    > .btn + .dropdown-toggle {
+        margin-right: 0;
+        padding: 3px 6px;
+        border-left-width: 0;
+    }
+}
+
+h1,
+h2,
+h3 {
+    user-select: none;
+    font-weight: normal;
+    /* Makes the vertical size of the text the same for all fonts. */
+    line-height: 1;
+}
+
+h3 {
+    font-size: 1.2em;
+    margin-top: 0;
+    margin-bottom: 1.5em;
+}
+
+.base-control {
+    text-align: left;
+    padding: 3px 3px;
+    height: $input-height;
+}
+
+.sql-name-input {
+    @extend .form-control;
+
+    width: auto;
+}
+
+.form-control {
+    @extend .base-control;
+
+    display: inline-block;
+
+    button {
+        text-align: left;
+    }
+}
+
+button.form-control {
+    display: block;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+
+.theme-line .notebook-header {
+    border-color: $gray-lighter;
+
+    h1 {
+        padding: 0;
+        margin: 0;
+
+        height: 40px;
+
+        label {
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+            margin-top: 5px;
+        }
+
+        .btn-group {
+            margin-top: -5px;
+            margin-left: 5px;
+        }
+
+        > i.btn {
+            float: right;
+            line-height: 30px;
+        }
+
+        input {
+            font-size: 22px;
+            height: 35px;
+        }
+
+        a.dropdown-toggle {
+            font-size: $font-size-base;
+            margin-right: 5px;
+        }
+    }
+}
+
+.theme-line .sql-notebooks {
+    li.custom > a {
+        color: $brand-info;
+        font-weight: bold;
+    }
+
+    li.custom > a:hover {
+        color: darken($brand-info, 15%);
+    }
+}
+
+.theme-line .paragraphs {
+    .panel-group .panel + .panel {
+        margin-top: 30px;
+    }
+
+    .btn-group {
+        margin-right: 0;
+    }
+
+    .sql-editor {
+        padding: 5px 0;
+
+        .ace_cursor {
+            opacity: 1;
+        }
+
+        .ace_hidden-cursors {
+            opacity: 1;
+        }
+
+        .ace_gutter-cell, .ace_folding-enabled > .ace_gutter-cell {
+            padding-right: 5px;
+        }
+    }
+
+    .sql-controls {
+        margin: 10px 0;
+        padding: 0 10px;
+    }
+
+    .sql-table-total {
+        padding: 0 10px;
+
+        label, b {
+            display: inline-block;
+
+            padding-top: 5px;
+
+            height: 27px;
+        }
+
+        margin-bottom: 10px;
+    }
+
+    .sql-table {
+        height: 400px;
+    }
+
+    table thead {
+        background-color: white;
+    }
+
+    .wrong-caches-filter {
+        text-align: center;
+        color: $ignite-placeholder-color;
+        height: 65px;
+        line-height: 65px;
+    }
+
+    .empty-caches {
+        text-align: center;
+        color: $ignite-placeholder-color;
+        height: 55px;
+        line-height: 55px;
+    }
+
+    .sql-error-result {
+        padding: 10px 0;
+
+        text-align: center;
+        color: $brand-primary;
+
+        border-top: 1px solid $ignite-border-color;
+    }
+
+    .sql-empty-result {
+        margin-top: 10px;
+        margin-bottom: 10px;
+        text-align: center;
+        color: $ignite-placeholder-color;
+    }
+
+    .sql-next {
+        float: right;
+
+        .disabled {
+            cursor: default;
+            text-decoration: none;
+        }
+
+        a {
+            margin-right: 5px;
+            margin-bottom: 5px;
+        }
+
+        i {
+            margin-top: 3px;
+            margin-right: 10px;
+        }
+    }
+}
+
+.theme-line .panel-heading {
+    padding: 5px 10px;
+    margin: 0;
+    cursor: pointer;
+    font-size: $font-size-large;
+    line-height: 24px;
+
+    label {
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        max-width: calc(100% - 85px);
+        cursor: pointer;
+    }
+
+    .btn-group {
+        vertical-align:top;
+        margin-left: 10px;
+
+        i { line-height: 18px; }
+    }
+
+    > i {
+        vertical-align: top;
+        line-height: 26px;
+        height: 26px;
+    }
+
+    .fa {
+        line-height: 26px;
+    }
+
+    .fa-floppy-o {
+        float: right;
+    }
+
+    .fa-chevron-circle-right, .fa-chevron-circle-down {
+        font-size: $font-size-base;
+        color: inherit;
+        float: left;
+    }
+
+    .fa-undo {
+        padding: 1px 6px;
+
+        font-size: 16px;
+    }
+
+    .fa-undo:hover {
+        padding: 0 5px;
+
+        border-radius: 5px;
+        border: thin dotted $ignite-darck-border-color;
+    }
+}
+
+.theme-line .panel-heading:hover {
+    text-decoration: underline;
+}
+
+.theme-line .panel-body {
+    padding: 20px;
+}
+
+.theme-line .main-content a.customize {
+    margin-left: 5px;
+}
+
+.theme-line .panel-collapse {
+    margin: 0;
+}
+
+.theme-line table.links {
+    table-layout: fixed;
+    border-collapse: collapse;
+
+    width: 100%;
+
+    label.placeholder {
+        text-align: center;
+        color: $ignite-placeholder-color;
+        width: 100%;
+    }
+
+    input[type="text"] {
+        font-weight: normal;
+    }
+
+    input[type="radio"] {
+        margin-left: 1px;
+        margin-right: 5px;
+    }
+
+    tbody {
+        border-left: 10px solid transparent;
+    }
+
+    tbody td:first-child {
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+    }
+
+    tfoot > tr > td {
+        padding: 0;
+
+        .pagination {
+            margin: 10px 0;
+
+            > .active > a {
+                border-color: $table-border-color;
+                background-color: $gray-lighter;
+            }
+        }
+    }
+}
+
+.theme-line table.links-edit {
+    @extend table.links;
+
+    margin-top: 0;
+    margin-bottom: 5px;
+
+    label {
+        line-height: $input-height;
+    }
+
+    td {
+        padding-left: 0;
+    }
+}
+
+.theme-line table.links-edit-sub {
+    @extend table.links-edit;
+
+    margin-top: 0;
+    margin-bottom: 0;
+}
+
+.theme-line table.links-edit-details {
+    @extend table.links;
+
+    margin-bottom: 10px;
+
+    label {
+        line-height: $input-height;
+        color: $ignite-header-color;
+    }
+
+    td {
+        padding: 0;
+
+        .input-tip {
+            padding: 0;
+        }
+    }
+}
+
+.theme-line table.admin {
+    tr:hover {
+        cursor: default;
+    }
+
+    thead {
+        .pagination {
+            margin: 0;
+        }
+    }
+
+    thead > tr th.header {
+        padding: 0 0 10px;
+
+        div {
+            padding: 0;
+        }
+
+        input[type="text"] {
+            font-weight: normal;
+        }
+    }
+
+    margin-bottom: 10px;
+
+    label {
+        line-height: $input-height;
+        color: $ignite-header-color;
+    }
+
+    thead > tr th, td {
+        padding: 10px 10px;
+
+        .input-tip {
+            padding: 0;
+        }
+    }
+
+    tfoot > tr > td {
+        padding: 0;
+    }
+
+    .pagination {
+        margin: 10px 0;
+        font-weight: normal;
+
+        > .active > a {
+            border-color: $table-border-color;
+            background-color: $gray-lighter;
+        }
+    }
+}
+
+.admin-summary {
+    padding-bottom: 10px;
+}
+
+.import-domain-model-wizard-page {
+    margin: 15px;
+}
+
+.scrollable-y {
+    overflow-x: hidden;
+    overflow-y: auto;
+}
+
+.theme-line table.metadata {
+    margin-bottom: 10px;
+
+    tr:hover {
+        cursor: default;
+    }
+
+    thead > tr {
+        label {
+            font-weight: bold;
+        }
+
+        input[type="checkbox"] {
+            cursor: pointer;
+        }
+    }
+
+    thead > tr th.header {
+        padding: 0 0 10px;
+
+        .pull-right {
+            padding: 0;
+        }
+
+        input[type="checkbox"] {
+            cursor: pointer;
+        }
+
+        input[type="text"] {
+            font-weight: normal;
+        }
+    }
+
+    > thead > tr > th {
+        padding: 5px 0 5px 5px !important;
+    }
+
+    tbody > tr > td {
+        padding: 0;
+    }
+}
+
+.td-ellipsis {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+}
+
+.table-modal-striped {
+    width: 100%;
+
+    > tbody > tr {
+        border-bottom: 2px solid $ignite-border-color;
+
+        input[type="checkbox"] {
+            cursor: pointer;
+        }
+    }
+
+    > tbody > tr > td {
+        padding: 5px 0 5px 5px !important;
+    }
+}
+
+.theme-line table.sql-results {
+    margin: 0;
+
+    td {
+        padding: 3px 6px;
+    }
+
+    > thead > tr > td {
+        padding: 3px 0;
+    }
+
+    thead > tr > th {
+        padding: 3px 6px;
+
+        line-height: $input-height;
+    }
+
+    tfoot > tr > td {
+        padding: 0;
+
+        .pagination {
+            margin: 10px 0 0 0;
+
+            > .active > a {
+                border-color: $table-border-color;
+                background-color: $gray-lighter;
+            }
+        }
+    }
+}
+
+.affix {
+    z-index: 910;
+    background-color: white;
+
+    hr {
+        margin: 0;
+    }
+}
+
+.affix.padding-top-dflt {
+    hr {
+        margin-top: 10px;
+    }
+}
+
+.affix + .bs-affix-fix {
+    height: 78px;
+}
+
+.panel-details {
+    margin-top: 5px;
+    padding: 10px 5px;
+
+    border-radius: 5px;
+    border: thin dotted $ignite-border-color;
+}
+
+.group {
+    border-radius: 5px;
+    border: thin dotted $ignite-border-color;
+
+    text-align: left;
+
+    hr {
+        margin: 7px 0;
+    }
+}
+
+.group-legend {
+    margin: -10px 5px 0 10px;
+    overflow: visible;
+    position: relative;
+
+    label {
+        padding: 0 5px;
+        background: white;
+    }
+}
+
+.group-legend-btn {
+    background: white;
+    float: right;
+    line-height: 20px;
+    padding: 0 5px 0 5px;
+}
+
+.group-content {
+    margin: 10px;
+
+    table {
+        width: 100%;
+    }
+}
+
+.group-content-empty {
+    color: $input-color-placeholder;
+
+    padding: 10px 0;
+    position: relative;
+
+    text-align: center;
+}
+
+.content-not-available {
+    min-height: 28px;
+
+    margin-right: 20px;
+
+    border-radius: 5px;
+    border: thin dotted $ignite-border-color;
+
+    padding: 0;
+
+    color: $input-color-placeholder;
+    display: table;
+    width: 100%;
+    height: 26px;
+
+    label {
+        display: table-cell;
+        text-align: center;
+        vertical-align: middle;
+    }
+}
+
+.tooltip > .tooltip-inner {
+    text-align: left;
+    border: solid 1px #ccc;
+}
+
+.popover-footer {
+    margin: 0; // reset heading margin
+    padding: 8px 14px;
+    font-size: $font-size-base;
+    color: $input-color-placeholder;
+    background-color: $popover-title-bg;
+    border-top: 1px solid darken($popover-title-bg, 5%);
+    border-radius: 0 0 ($border-radius-large - 1) ($border-radius-large - 1);
+}
+
+.popover-content {
+    padding: 5px;
+}
+
+.popover:focus {
+    outline: none;
+    border: 1px solid $btn-default-border;
+}
+
+.theme-line .popover.settings {
+    .close {
+        position: absolute;
+        top: 5px;
+        right: 5px;
+    }
+}
+
+.theme-line .popover.cache-metadata {
+    @extend .popover.settings;
+
+    z-index: 1030;
+    min-width: 305px;
+    max-width: 450px;
+
+    .popover-title {
+        color: black;
+
+        line-height: 27px;
+
+        padding: 3px 5px 3px 10px;
+
+        white-space: nowrap;
+        overflow: hidden;
+        -o-text-overflow: ellipsis;
+        text-overflow: ellipsis;
+
+        .close {
+            float: right;
+            top: 0;
+            right: 0;
+            position: relative;
+            margin-left: 10px;
+            line-height: 27px;
+        }
+    }
+
+    > .popover-content {
+        overflow: auto;
+
+        white-space: nowrap;
+
+        min-height: 400px;
+        max-height: 400px;
+
+        .content-empty {
+            display: block;
+            text-align: center;
+            line-height: 380px;
+
+            color: $input-color-placeholder;
+        }
+    }
+
+    .clickable { cursor: pointer; }
+}
+
+.theme-line .popover.summary-project-structure {
+    @extend .popover.settings;
+
+    z-index: 1030;
+    min-width: 305px;
+
+    .popover-title {
+        color: black;
+
+        line-height: 27px;
+
+        padding: 3px 5px 3px 10px;
+
+        white-space: nowrap;
+        overflow: hidden;
+        -o-text-overflow: ellipsis;
+        text-overflow: ellipsis;
+
+        .close {
+            float: right;
+            top: 0;
+            right: 0;
+            position: relative;
+            margin-left: 10px;
+            line-height: 27px;
+        }
+    }
+
+    > .popover-content {
+        overflow: auto;
+
+        white-space: nowrap;
+
+        min-height: 300px;
+        max-height: 300px;
+    }
+}
+
+.theme-line .popover.validation-error {
+    max-width: 400px;
+    color: $brand-primary;
+    background: white;
+    border: 1px solid $brand-primary;
+
+    &.right > .arrow {
+        border-right-color: $brand-primary;
+    }
+
+    .close {
+        vertical-align: middle;
+    }
+}
+
+label {
+    font-weight: normal;
+    margin-bottom: 0;
+}
+
+.form-horizontal .checkbox {
+    padding-top: 0;
+    min-height: 0;
+}
+
+.input-tip {
+    display: block;
+    overflow: hidden;
+    position: relative;
+}
+
+.labelHeader {
+    font-weight: bold;
+    text-transform: capitalize;
+}
+
+.labelField {
+    float: left;
+    margin-right: 5px;
+}
+
+.labelFormField {
+    float: left;
+    line-height: $input-height;
+}
+
+.labelLogin {
+    margin-right: 10px;
+}
+
+.form-horizontal .form-group {
+    margin: 0;
+}
+
+.form-horizontal .has-feedback .form-control-feedback {
+    right: 0;
+}
+
+.tipField {
+    float: right;
+    line-height: $input-height;
+    margin-left: 5px;
+}
+
+.tipLabel {
+    font-size: $font-size-base;
+    margin-left: 5px;
+}
+
+.fieldSep {
+    float: right;
+    line-height: $input-height;
+    margin: 0 5px;
+}
+
+.fieldButton {
+    float: right;
+    margin-left: 5px;
+    margin-right: 0;
+}
+
+.fa {
+    cursor: pointer;
+}
+
+.fa-cursor-default {
+    cursor: default !important;
+}
+
+.fa-remove {
+    color: $brand-primary;
+}
+
+.fa-chevron-circle-down {
+    color: $brand-primary;
+    margin-right: 5px;
+}
+
+.fa-chevron-circle-right {
+    color: $brand-primary;
+    margin-right: 5px;
+}
+
+.fa-question-circle {
+    cursor: default;
+}
+
+label.required:after {
+    color: $brand-primary;
+    content: ' *';
+    display: inline;
+}
+
+.blank {
+    visibility: hidden;
+}
+
+.alert {
+    outline: 0;
+    padding: 10px;
+    position: fixed;
+    z-index: 1050;
+    margin: 20px;
+
+    &.top-right {
+        top: 60px;
+        right: 0;
+
+        .close {
+            padding-left: 10px;
+        }
+    }
+
+    .alert-icon {
+        padding-right: 10px;
+        font-size: 16px;
+    }
+
+    .alert-title {
+        color: $text-color;
+    }
+
+    .close {
+        margin-right: 0;
+        line-height: 19px;
+    }
+}
+
+.summary-tabs {
+    margin-top: 0.65em;
+}
+
+.summary-tab {
+    img {
+        margin-right: 5px;
+        height: 16px;
+        width: 16px;
+        float: left;
+    }
+}
+
+input[type="number"]::-webkit-outer-spin-button,
+input[type="number"]::-webkit-inner-spin-button {
+    -webkit-appearance: none;
+    margin: 0;
+}
+
+input[type="number"] {
+    -moz-appearance: textfield;
+}
+
+input.ng-dirty.ng-invalid, button.ng-dirty.ng-invalid {
+    border-color: $ignite-invalid-color;
+
+    :focus {
+        border-color: $ignite-invalid-color;
+    }
+}
+
+.form-control-feedback {
+    display: inline-block;
+    color: $brand-primary;
+    line-height: $input-height;
+    pointer-events: initial;
+}
+
+.theme-line .nav-tabs > li > a {
+    padding: 5px 5px;
+    color: $ignite-header-color;
+}
+
+.viewedUser {
+    text-align: center;
+    background-color: $brand-warning;
+}
+
+a {
+    cursor: pointer;
+}
+
+.st-sort-ascent:after {
+    content: '\25B2';
+}
+
+.st-sort-descent:after {
+    content: '\25BC';
+}
+
+th[st-sort] {
+    cursor: pointer;
+}
+
+.panel {
+    margin-bottom: 0;
+}
+
+.panel-group {
+    margin-bottom: 0;
+}
+
+.panel-group .panel + .panel {
+    margin-top: 20px;
+}
+
+.section {
+    margin-top: 20px;
+}
+
+.section-top {
+    width: 100%;
+    margin-top: 10px;
+    margin-bottom: 20px;
+}
+
+.advanced-options {
+    @extend .section;
+    margin-bottom: 20px;
+
+    i {
+        font-size: 16px;
+    }
+}
+
+.modal-advanced-options {
+    @extend .advanced-options;
+    margin-top: 10px;
+    margin-bottom: 10px;
+}
+
+.margin-left-dflt {
+    margin-left: 10px;
+}
+
+.margin-top-dflt {
+    margin-top: 10px;
+}
+
+.margin-top-dflt-2x {
+    margin-top: 20px;
+}
+
+.margin-bottom-dflt {
+    margin-bottom: 10px;
+}
+
+.margin-dflt {
+    margin-top: 10px;
+    margin-bottom: 10px;
+}
+
+.padding-top-dflt {
+    padding-top: 10px;
+}
+
+.padding-left-dflt {
+    padding-left: 10px;
+}
+
+.padding-bottom-dflt {
+    padding-bottom: 10px;
+}
+
+.padding-dflt {
+    padding-top: 10px;
+    padding-bottom: 10px;
+}
+
+.agent-download {
+    padding: 10px 10px 10px 20px;
+}
+
+.ace_content {
+    padding-left: 5px;
+}
+
+.ace_hidden-cursors {
+    opacity: 0;
+}
+
+.ace_cursor {
+    opacity: 0;
+}
+
+.ace_editor {
+    margin: 10px 5px 10px 0;
+
+    .ace_gutter {
+        background: transparent !important;
+        border: 1px $ignite-border-color;
+        border-right-style: solid;
+    }
+
+    .ace_gutter-cell, .ace_folding-enabled > .ace_gutter-cell {
+        padding-left: 0.65em;
+    }
+}
+
+.preview-highlight-1 {
+    position: absolute;
+    background-color: #f7faff;
+    z-index: 20;
+}
+
+.preview-highlight-2 {
+    position: absolute;
+    background-color: #f0f6ff;
+    z-index: 21;
+}
+
+.preview-highlight-3 {
+    position: absolute;
+    background-color: #e8f2ff;
+    z-index: 22;
+}
+
+.preview-highlight-4 {
+    position: absolute;
+    background-color: #e1eeff;
+    z-index: 23;
+}
+
+.preview-highlight-5 {
+    position: absolute;
+    background-color: #DAEAFF;
+    z-index: 24;
+}
+
+.preview-highlight-6 {
+    position: absolute;
+    background-color: #D2E5FF;
+    z-index: 25;
+}
+
+.preview-highlight-7 {
+    position: absolute;
+    background-color: #CBE1FF;
+    z-index: 26;
+}
+
+.preview-highlight-8 {
+    position: absolute;
+    background-color: #C3DDFF;
+    z-index: 27;
+}
+
+.preview-highlight-9 {
+    position: absolute;
+    background-color: #BCD9FF;
+    z-index: 28;
+}
+
+.preview-highlight-10 {
+    position: absolute;
+    background-color: #B5D5FF;
+    z-index: 29;
+}
+
+.preview-panel {
+    min-height: 28px;
+
+    margin-left: 20px;
+
+    border-radius: 5px;
+    border: thin dotted $ignite-border-color;
+
+    padding: 0;
+}
+
+.preview-legend {
+    top: -10px;
+    right: 20px;
+    position: absolute;
+    z-index: 900;
+
+    a {
+        background-color: white;
+        margin-left: 5px;
+        font-size: 0.9em;
+    }
+
+    .inactive {
+        color: $input-color-placeholder;
+    }
+}
+
+.preview-content-empty {
+    color: $input-color-placeholder;
+    display: table;
+    width: 100%;
+    height: 26px;
+
+    label {
+        display: table-cell;
+        text-align: center;
+        vertical-align: middle;
+    }
+}
+
+.chart-settings-link {
+    padding-left: 10px;
+    line-height: $input-height;
+
+    label, button {
+        margin-left: 5px;
+        margin-right: 0;
+    }
+
+    button.select-manual-caret {
+        padding-right: 3px;
+
+        .caret { margin-left: 3px; }
+    }
+
+    a, i {
+        font-size: $font-size-base;
+        color: $link-color !important;
+        margin-right: 5px;
+    }
+
+    div {
+        margin-left: 20px;
+        display: inline-block;
+    }
+}
+
+.chart-settings {
+    margin: 10px 5px 5px 5px !important;
+}
+
+.chart-settings-columns-list {
+    border: 1px solid $ignite-border-color;
+    list-style: none;
+    margin-bottom: 10px;
+    min-height: 30px;
+    max-height: 200px;
+    padding: 5px;
+
+    overflow: auto;
+
+    & > li {
+        float: left;
+    }
+
+    li:nth-child(even) {
+        margin-right: 0;
+    }
+
+    .fa-close {
+        margin-left: 10px;
+    }
+}
+
+.btn-chart-column {
+    border-radius: 3px;
+    font-size: 12px;
+    margin: 3px 3px;
+    padding: 1px 5px;
+    line-height: 1.5;
+    cursor: default;
+}
+
+.btn-chart-column-movable {
+    @extend .btn-chart-column;
+    cursor: move;
+}
+
+.btn-chart-column-agg-fx {
+    border: 0;
+    margin: 0 0 0 10px;
+}
+
+.dw-loading {
+    min-height: 100px;
+}
+
+.dw-loading > .dw-loading-body > .dw-loading-text {
+    left: -50%;
+}
+
+.dw-loading.dw-loading-overlay {
+    z-index: 1030;
+}
+
+.modal {
+    .dw-loading.dw-loading-overlay {
+        z-index: 9999;
+    }
+
+    .dw-loading-body {
+        left: 10%;
+    }
+}
+
+.panel-tip-container {
+    display: inline-block;
+}
+
+button.dropdown-toggle {
+    margin-right: 5px;
+}
+
+button.select-toggle {
+    position: relative;
+    padding-right: 15px;
+}
+
+button.select-toggle::after {
+    content: "";
+    border-top: 0.3em solid;
+    border-right: 0.3em solid transparent;
+    border-left: 0.3em solid transparent;
+    position: absolute;
+    right: 5px;
+    top: 50%;
+    vertical-align: middle;
+}
+
+// Prevent scroll bars from being hidden for OS X.
+::-webkit-scrollbar {
+    -webkit-appearance: none;
+}
+
+::-webkit-scrollbar:vertical {
+    width: 10px;
+}
+
+::-webkit-scrollbar:horizontal {
+    height: 10px;
+}
+
+::-webkit-scrollbar-thumb {
+    border-radius: 8px;
+    border: 2px solid white; /* should match background, can't be transparent */
+    background-color: rgba(0, 0, 0, .5);
+}
+
+::-webkit-scrollbar-track {
+    background-color: white;
+    border-radius: 8px;
+}
+
+treecontrol.tree-classic {
+    > ul > li {
+        padding: 0;
+    }
+
+    li {
+        padding-left: 15px;
+    }
+
+    li.tree-expanded i.tree-branch-head.fa, li.tree-collapsed i.tree-branch-head.fa, li.tree-leaf i.tree-branch-head.fa, .tree-label i.fa {
+        background: none no-repeat;
+        padding: 1px 5px 1px 1px;
+    }
+
+    li.tree-leaf i.tree-leaf-head {
+        background: none no-repeat !important;
+        padding: 0 !important;
+    }
+
+    li .tree-selected {
+        background-color: white;
+        font-weight: normal;
+    }
+
+    span {
+        margin-right: 10px;
+    }
+}
+
+.docs-content {
+    .affix {
+        border-bottom: 1px solid $gray-lighter;
+    }
+
+    min-height: 100px;
+}
+
+.carousel-caption {
+    position: relative;
+    left: auto;
+    right: auto;
+
+    margin-top: 10px;
+
+    h3 {
+        margin-bottom: 10px;
+    }
+}
+
+.carousel-control {
+    font-size: 20px;
+    z-index: 16;
+
+    // Toggles
+    .fa-chevron-left,.fa-chevron-right {
+        position: absolute;
+        bottom: 28px;
+        margin-top: -10px;
+        z-index: 16;
+        display: inline-block;
+        margin-left: -10px;
+    }
+
+    .fa-chevron-left {
+        left: 90%;
+        margin-left: -10px;
+    }
+
+    .fa-chevron-right {
+        right: 90%;
+        margin-right: -10px;
+    }
+}
+
+.carousel-control.left {
+    background-image: none;
+}
+
+.carousel-control.right {
+    background-image: none;
+}
+
+.getting-started-puzzle {
+    margin-left: 20px;
+}
+
+.getting-started {
+    margin: 15px 15px 300px;
+}
+
+.getting-started-demo {
+    color: $brand-info;
+}
+
+.home-panel {
+    border-radius: 5px;
+    border: thin dotted $panel-default-border;
+    background-color: $panel-default-heading-bg;
+
+    margin-top: 20px;
+    padding: 10px;
+}
+
+.home {
+    min-height: 880px;
+    padding: 20px;
+
+    @media(min-width: 992px) {
+        min-height: 450px;
+    }
+}
+
+.additional-filter {
+    input[type="checkbox"] {
+        position: absolute;
+        margin-top: 8px;
+    }
+
+    a {
+        font-weight: normal;
+        padding-left: 20px;
+        float: none;
+    }
+}
+
+.grid {
+    .ui-grid-header-cell .ui-grid-cell-contents {
+        text-align: center;
+
+        > span:not(.ui-grid-header-cell-label) {
+            position: absolute;
+            right: -3px;
+        }
+    }
+
+    .ui-grid-cell .ui-grid-cell-contents {
+        text-align: center;
+
+        > i.fa {
+            cursor: default;
+        }
+    }
+
+    .ui-grid-column-menu-button {
+        right: -3px;
+    }
+
+    .ui-grid-menu-button {
+        margin-top: -1px;
+    }
+
+    .ui-grid-column-menu-button-last-col {
+        margin-right: 0
+    }
+
+    .no-rows {
+        .center-container {
+            background: white;
+
+            .centered > div {
+                display: inline-block;
+                padding: 10px;
+
+                opacity: 1;
+
+                background-color: #f5f5f5;
+                border-radius: 6px;
+                border: 1px solid $ignite-darck-border-color;
+            }
+        }
+    }
+}
+
+.cell-right .ui-grid-cell-contents {
+    text-align: right !important;
+}
+
+.cell-left .ui-grid-cell-contents {
+    text-align: left !important;
+}
+
+.grid.ui-grid {
+    border-left-width: 0;
+    border-right-width: 0;
+    border-bottom-width: 0;
+}
+
+.summary-tabs {
+    .nav-tabs > li:first-child,
+    .nav-tabs > li:first-child.active {
+        & > a,
+        & > a:focus,
+        & > a:hover {
+            border-left: none;
+            border-top-left-radius: 0;
+        }
+    }
+}
+
+.ribbon-wrapper {
+    width: 150px;
+    height: 150px;
+    position: absolute;
+    overflow: hidden;
+    top: 0;
+    z-index: 1001;
+    pointer-events: none;
+}
+
+.ribbon-wrapper.right {
+    right: 0;
+}
+
+.ribbon {
+    position: absolute;
+    top: 42px;
+    width: 200px;
+    padding: 1px 0;
+    color: $btn-primary-color;
+    background: $btn-primary-border;
+
+    -moz-box-shadow: 0 0 10px rgba(0,0,0,0.5);
+    -webkit-box-shadow: 0 0 10px rgba(0,0,0,0.5);
+    box-shadow: 0 0 10px rgba(0,0,0,0.5);
+
+    right: -42px;
+    -moz-transform: rotate(45deg);
+    -webkit-transform: rotate(45deg);
+    -o-transform: rotate(45deg);
+    -ms-transform: rotate(45deg);
+    transform: rotate(45deg);
+
+    > label {
+        display: block;
+        padding: 1px 0;
+        height: 24px;
+        line-height: 18px;
+
+        text-align: center;
+        text-decoration: none;
+        font-family: 'Roboto Slab', sans-serif;
+        font-size: 20px;
+        font-weight: 500;
+
+        border: 1px solid rgba(255,255,255,0.3);
+
+        -moz-text-shadow: 0 0 10px rgba(0,0,0,0.31);
+        -webkit-text-shadow: 0 0 10px rgba(0,0,0,0.31);
+        text-shadow: 0 0 10px rgba(0,0,0,0.31);
+    }
+}
+
+html,body,.splash-screen {
+    width: 100%;
+    height: 100%;
+}
+
+.splash {
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    top: 0;
+    opacity: 1;
+    background-color: white;
+    z-index: 99999;
+
+    .splash-wrapper {
+        display: inline-block;
+        vertical-align: middle;
+        position: relative;
+        width: 100%;
+    }
+
+    .splash-wellcome {
+        font-size: 18px;
+        margin: 20px 0;
+        text-align: center;
+    }
+}
+
+.splash:before {
+  content: '';
+  display: inline-block;
+  height: 100%;
+  vertical-align: middle;
+}
+
+.spinner {
+    margin: 0 auto;
+    width: 100px;
+    text-align: center;
+
+    > div {
+        width: 18px;
+        height: 18px;
+        margin: 0 5px;
+        border-radius: 100%;
+        display: inline-block;
+        -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
+        animation: sk-bouncedelay 1.4s infinite ease-in-out both;
+        background-color: $brand-primary;
+    }
+
+    .bounce1 {
+        -webkit-animation-delay: -0.32s;
+        animation-delay: -0.32s;
+    }
+
+    .bounce2 {
+        -webkit-animation-delay: -0.16s;
+        animation-delay: -0.16s;
+    }
+}
+
+@-webkit-keyframes sk-bouncedelay {
+    0%, 80%, 100% {
+        -webkit-transform: scale(0)
+    }
+    40% {
+        -webkit-transform: scale(1.0)
+    }
+}
+
+@keyframes sk-bouncedelay {
+    0%, 80%, 100% {
+        -webkit-transform: scale(0);
+        transform: scale(0);
+    }
+    40% {
+        -webkit-transform: scale(1.0);
+        transform: scale(1.0);
+    }
+}
+
+[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
+    display: none !important;
+}
+
+.nvd3 .nv-axis .nv-axisMaxMin text {
+    font-weight: normal; /* Here the text can be modified*/
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/public/stylesheets/variables.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/public/stylesheets/variables.scss b/modules/web-console/src/main/js/public/stylesheets/variables.scss
new file mode 100644
index 0000000..8500eac
--- /dev/null
+++ b/modules/web-console/src/main/js/public/stylesheets/variables.scss
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+@import "bootstrap-variables";
+
+$logo-path: "/images/logo.png";
+$input-height: 28px;
+$ignite-placeholder-color: #999999;
+$ignite-border-color: #ddd;
+$ignite-darck-border-color: #aaa;
+$ignite-border-bottom-color: $brand-primary;
+$ignite-background-color: #fff;
+$ignite-header-color: #555;
+$ignite-invalid-color: $brand-primary;

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/serve.js b/modules/web-console/src/main/js/serve.js
new file mode 100644
index 0000000..891855c
--- /dev/null
+++ b/modules/web-console/src/main/js/serve.js
@@ -0,0 +1,116 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+const http = require('http'),
+    https = require('https'),
+    path = require('path');
+
+/**
+ * Event listener for HTTP server "error" event.
+ */
+const _onError = (port, 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.
+ */
+const _onListening = (addr) => {
+    var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
+
+    console.log('Start listening on ' + bind);
+};
+
+const igniteModules = (process.env.IGNITE_MODULES && path.relative(__dirname, process.env.IGNITE_MODULES)) || './ignite_modules';
+
+const fireUp = require('fire-up').newInjector({
+    basePath: __dirname,
+    modules: [
+        './serve/**/*.js',
+        `${igniteModules}/**/*.js`
+    ]
+});
+
+Promise.all([fireUp('settings'), fireUp('app'), fireUp('agent-manager'), fireUp('browser-manager')])
+    .then((values) => {
+        const settings = values[0];
+        const app = values[1];
+        const agentMgr = values[2];
+        const browserMgr = values[3];
+
+        // Start rest server.
+        const server = settings.server.SSLOptions
+            ? https.createServer(settings.server.SSLOptions) : http.createServer();
+
+        server.listen(settings.server.port);
+        server.on('error', _onError.bind(null, settings.server.port));
+        server.on('listening', _onListening.bind(null, server.address()));
+
+        app.listen(server);
+        browserMgr.attach(server);
+
+        // Start legacy agent server for reject connection with message.
+        if (settings.agent.legacyPort) {
+            const agentLegacySrv = settings.agent.SSLOptions
+                ? https.createServer(settings.agent.SSLOptions) : http.createServer();
+
+            agentLegacySrv.listen(settings.agent.legacyPort);
+            agentLegacySrv.on('error', _onError.bind(null, settings.agent.legacyPort));
+            agentLegacySrv.on('listening', _onListening.bind(null, agentLegacySrv.address()));
+
+            agentMgr.attachLegacy(agentLegacySrv);
+        }
+
+        // Start agent server.
+        const agentServer = settings.agent.SSLOptions
+            ? https.createServer(settings.agent.SSLOptions) : http.createServer();
+
+        agentServer.listen(settings.agent.port);
+        agentServer.on('error', _onError.bind(null, settings.agent.port));
+        agentServer.on('listening', _onListening.bind(null, agentServer.address()));
+
+        agentMgr.attach(agentServer);
+
+        // Used for automated test.
+        if (process.send)
+            process.send('running');
+    }).catch((err) => {
+        console.error(err);
+
+        process.exit(1);
+    });

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/agent.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/serve/agent.js b/modules/web-console/src/main/js/serve/agent.js
new file mode 100644
index 0000000..78dd66f
--- /dev/null
+++ b/modules/web-console/src/main/js/serve/agent.js
@@ -0,0 +1,601 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+// Fire me up!
+
+/**
+ * Module interaction with agents.
+ */
+module.exports = {
+    implements: 'agent-manager',
+    inject: ['require(lodash)', 'require(ws)', 'require(fs)', 'require(path)', 'require(jszip)', 'require(socket.io)', 'settings', 'mongo']
+};
+
+/**
+ * @param _
+ * @param fs
+ * @param ws
+ * @param path
+ * @param JSZip
+ * @param socketio
+ * @param settings
+ * @param mongo
+ * @returns {AgentManager}
+ */
+module.exports.factory = function(_, ws, fs, path, JSZip, socketio, settings, mongo) {
+    /**
+     *
+     */
+    class Command {
+        /**
+         * @param {Boolean} demo Is need run command on demo node.
+         * @param {String} name Command name.
+         */
+        constructor(demo, name) {
+            this._demo = demo;
+
+            /**
+             * Command name.
+             * @type {String}
+             */
+            this._name = name;
+
+            /**
+             * Command parameters.
+             * @type {Array.<String>}
+             */
+            this._params = [];
+        }
+
+        /**
+         * Add parameter to command.
+         * @param {string} key Parameter key.
+         * @param {Object} value Parameter value.
+         * @returns {Command}
+         */
+        addParam(key, value) {
+            this._params.push({key, value});
+
+            return this;
+        }
+    }
+
+    /**
+     * Connected agent descriptor.
+     */
+    class Agent {
+        /**
+         * @param {socketIo.Socket} socket - Agent socket for interaction.
+         */
+        constructor(socket) {
+            /**
+             * Agent socket for interaction.
+             *
+             * @type {socketIo.Socket}
+             * @private
+             */
+            this._socket = socket;
+        }
+
+        /**
+         * Send message to agent.
+         *
+         * @this {Agent}
+         * @param {String} event Command name.
+         * @param {Object} data Command params.
+         * @param {Function} [callback] on finish
+         */
+        _emit(event, data, callback) {
+            if (!this._socket.connected) {
+                if (callback)
+                    callback('org.apache.ignite.agent.AgentException: Connection is closed');
+
+                return;
+            }
+
+            this._socket.emit(event, data, callback);
+        }
+
+        /**
+         * Send message to agent.
+         *
+         * @param {String} event - Event name.
+         * @param {Object?} data - Transmitted data.
+         * @returns {Promise}
+         */
+        executeAgent(event, data) {
+            return new Promise((resolve, reject) =>
+                this._emit(event, data, (error, res) => {
+                    if (error)
+                        return reject(error);
+
+                    resolve(res);
+                })
+            );
+        }
+
+        /**
+         * Execute rest request on node.
+         *
+         * @param {Command} cmd - REST command.
+         * @return {Promise}
+         */
+        executeRest(cmd) {
+            const params = {cmd: cmd._name};
+
+            for (const param of cmd._params)
+                params[param.key] = param.value;
+
+            return new Promise((resolve, reject) => {
+                this._emit('node:rest', {uri: 'ignite', params, demo: cmd._demo, method: 'GET'}, (error, res) => {
+                    if (error)
+                        return reject(new Error(error));
+
+                    error = res.error;
+
+                    const code = res.code;
+
+                    if (code === 401)
+                        return reject(new Error('Agent is failed to authenticate in grid. Please check agent\'s login and password or node port.'));
+
+                    if (code !== 200)
+                        return reject(new Error(error || 'Failed connect to node and execute REST command.'));
+
+                    try {
+                        const msg = JSON.parse(res.data);
+
+                        if (msg.successStatus === 0)
+                            return resolve(msg.response);
+
+                        if (msg.successStatus === 2)
+                            return reject(new Error('Agent is failed to authenticate in grid. Please check agent\'s login and password or node port.'));
+
+                        reject(new Error(msg.error));
+                    }
+                    catch (e) {
+                        return reject(e);
+                    }
+                });
+            });
+        }
+
+        /**
+         * @param {String} driverPath
+         * @param {String} driverClass
+         * @param {String} url
+         * @param {Object} info
+         * @returns {Promise} Promise on list of tables (see org.apache.ignite.schema.parser.DbTable java class)
+         */
+        metadataSchemas(driverPath, driverClass, url, info) {
+            return this.executeAgent('schemaImport:schemas', {driverPath, driverClass, url, info});
+        }
+
+        /**
+         * @param {String} driverPath
+         * @param {String} driverClass
+         * @param {String} url
+         * @param {Object} info
+         * @param {Array} schemas
+         * @param {Boolean} tablesOnly
+         * @returns {Promise} Promise on list of tables (see org.apache.ignite.schema.parser.DbTable java class)
+         */
+        metadataTables(driverPath, driverClass, url, info, schemas, tablesOnly) {
+            return this.executeAgent('schemaImport:metadata', {driverPath, driverClass, url, info, schemas, tablesOnly});
+        }
+
+        /**
+         * @returns {Promise} Promise on list of jars from driver folder.
+         */
+        availableDrivers() {
+            return this.executeAgent('schemaImport:drivers');
+        }
+
+        /**
+         *
+         * @param {Boolean} demo Is need run command on demo node.
+         * @param {Boolean} attr Get attributes, if this parameter has value true. Default value: true.
+         * @param {Boolean} mtr Get metrics, if this parameter has value true. Default value: false.
+         * @returns {Promise}
+         */
+        topology(demo, attr, mtr) {
+            const cmd = new Command(demo, 'top')
+                .addParam('attr', attr !== false)
+                .addParam('mtr', !!mtr);
+
+            return this.executeRest(cmd);
+        }
+
+        /**
+         *
+         * @param {Boolean} demo Is need run command on demo node.
+         * @param {String} cacheName Cache name.
+         * @param {String} query Query.
+         * @param {int} pageSize Page size.
+         * @returns {Promise}
+         */
+        fieldsQuery(demo, cacheName, query, pageSize) {
+            const cmd = new Command(demo, 'qryfldexe')
+                .addParam('cacheName', cacheName)
+                .addParam('qry', query)
+                .addParam('pageSize', pageSize);
+
+            return this.executeRest(cmd);
+        }
+
+        /**
+         *
+         * @param {Boolean} demo Is need run command on demo node.
+         * @param {String} cacheName Cache name.
+         * @param {int} pageSize Page size.
+         * @returns {Promise}
+         */
+        scan(demo, cacheName, pageSize) {
+            const cmd = new Command(demo, 'qryscanexe')
+                .addParam('cacheName', cacheName)
+                .addParam('pageSize', pageSize);
+
+            return this.executeRest(cmd);
+        }
+
+        /**
+         * @param {Boolean} demo Is need run command on demo node.
+         * @param {int} queryId Query Id.
+         * @param {int} pageSize Page size.
+         * @returns {Promise}
+         */
+        queryFetch(demo, queryId, pageSize) {
+            const cmd = new Command(demo, 'qryfetch')
+                .addParam('qryId', queryId)
+                .addParam('pageSize', pageSize);
+
+            return this.executeRest(cmd);
+        }
+
+        /**
+         * @param {Boolean} demo Is need run command on demo node.
+         * @param {int} queryId Query Id.
+         * @returns {Promise}
+         */
+        queryClose(demo, queryId) {
+            const cmd = new Command(demo, 'qrycls')
+                .addParam('qryId', queryId);
+
+            return this.executeRest(cmd);
+        }
+
+        /**
+         * @param {Boolean} demo Is need run command on demo node.
+         * @param {String} cacheName Cache name.
+         * @returns {Promise}
+         */
+        metadata(demo, cacheName) {
+            const cmd = new Command(demo, 'metadata')
+                .addParam('cacheName', cacheName);
+
+            return this.executeRest(cmd);
+        }
+
+        /**
+         * @param {Boolean} demo Is need run command on demo node.
+         * @param {String} evtOrderKey Event order key, unique for tab instance.
+         * @param {String} evtThrottleCntrKey Event throttle counter key, unique for tab instance.
+         * @returns {Promise}
+         */
+        collect(demo, evtOrderKey, evtThrottleCntrKey) {
+            const cmd = new Command(demo, 'exe')
+                .addParam('name', 'org.apache.ignite.internal.visor.compute.VisorGatewayTask')
+                .addParam('p1', '')
+                .addParam('p2', 'org.apache.ignite.internal.visor.node.VisorNodeDataCollectorTask')
+                .addParam('p3', 'org.apache.ignite.internal.visor.node.VisorNodeDataCollectorTaskArg')
+                .addParam('p4', true)
+                .addParam('p5', 'CONSOLE_' + evtOrderKey)
+                .addParam('p6', evtThrottleCntrKey)
+                .addParam('p7', 10)
+                .addParam('p8', false);
+
+            return this.executeRest(cmd);
+        }
+
+        /**
+         * @param {Boolean} demo Is need run command on demo node.
+         * @param {String} nid Node id.
+         * @param {String} cacheName Cache name.
+         * @returns {Promise}
+         */
+        cacheClear(demo, nid, cacheName) {
+            const cmd = new Command(demo, 'exe')
+                .addParam('name', 'org.apache.ignite.internal.visor.compute.VisorGatewayTask')
+                .addParam('p1', nid)
+                .addParam('p2', 'org.apache.ignite.internal.visor.cache.VisorCacheClearTask')
+                .addParam('p3', 'java.lang.String')
+                .addParam('p4', cacheName);
+
+            return this.executeRest(cmd);
+        }
+
+        /**
+         * @param {Boolean} demo Is need run command on demo node.
+         * @param {String} nid Node id.
+         * @param {String} cacheName Cache name.
+         * @returns {Promise}
+         */
+        cacheStop(demo, nid, cacheName) {
+            const cmd = new Command(demo, 'exe')
+                .addParam('name', 'org.apache.ignite.internal.visor.compute.VisorGatewayTask')
+                .addParam('p1', nid)
+                .addParam('p2', 'org.apache.ignite.internal.visor.cache.VisorCacheStopTask')
+                .addParam('p3', 'java.lang.String')
+                .addParam('p4', cacheName);
+
+            return this.executeRest(cmd);
+        }
+
+        /**
+         * @param {Boolean} demo Is need run command on demo node.
+         * @param {String} nid Node id.
+         * @returns {Promise}
+         */
+        ping(demo, nid) {
+            const cmd = new Command(demo, 'exe')
+                .addParam('name', 'org.apache.ignite.internal.visor.compute.VisorGatewayTask')
+                .addParam('p1', 'null')
+                .addParam('p2', 'org.apache.ignite.internal.visor.node.VisorNodePingTask')
+                .addParam('p3', 'java.util.UUID')
+                .addParam('p4', nid);
+
+            return this.executeRest(cmd);
+        }
+    }
+
+    /**
+     * Connected agents manager.
+     */
+    class AgentManager {
+        /**
+         * @constructor
+         */
+        constructor() {
+            /**
+             * Connected agents by user id.
+             * @type {Object.<ObjectId, Array.<Agent>>}
+             */
+            this._agents = {};
+
+            /**
+             * Connected browsers by user id.
+             * @type {Object.<ObjectId, Array.<Socket>>}
+             */
+            this._browsers = {};
+
+            const agentArchives = fs.readdirSync(settings.agent.dists)
+                .filter((file) => path.extname(file) === '.zip');
+
+            /**
+             * Supported agents distribution.
+             * @type {Object.<String, String>}
+             */
+            this.supportedAgents = {};
+
+            const jarFilter = (file) => path.extname(file) === '.jar';
+
+            for (const archive of agentArchives) {
+                const filePath = path.join(settings.agent.dists, archive);
+
+                const zip = new JSZip(fs.readFileSync(filePath));
+
+                const jarPath = _.find(_.keys(zip.files), jarFilter);
+
+                const jar = new JSZip(zip.files[jarPath].asNodeBuffer());
+
+                const manifest = jar.files['META-INF/MANIFEST.MF']
+                    .asText()
+                    .trim()
+                    .split(/\s*\n+\s*/)
+                    .map((line, r) => {
+                        r = line.split(/\s*:\s*/);
+
+                        this[r[0]] = r[1];
+
+                        return this;
+                    }, {})[0];
+
+                const ver = manifest['Implementation-Version'];
+
+                if (ver) {
+                    this.supportedAgents[ver] = {
+                        fileName: archive,
+                        filePath,
+                        buildTime: manifest['Build-Time']
+                    };
+                }
+            }
+
+            const latest = _.head(Object.keys(this.supportedAgents).sort((a, b) => {
+                const aParts = a.split('.');
+                const bParts = b.split('.');
+
+                for (let i = 0; i < aParts.length; ++i) {
+                    if (bParts.length === i)
+                        return 1;
+
+                    if (aParts[i] === aParts[i])
+                        continue;
+
+                    return aParts[i] > bParts[i] ? 1 : -1;
+                }
+            }));
+
+            // Latest version of agent distribution.
+            if (latest)
+                this.supportedAgents.latest = this.supportedAgents[latest];
+        }
+
+        attachLegacy(server) {
+            const wsSrv = new ws.Server({server});
+
+            wsSrv.on('connection', (_wsClient) => {
+                _wsClient.send(JSON.stringify({
+                    method: 'authResult',
+                    args: ['You are using an older version of the agent. Please reload agent archive']
+                }));
+            });
+        }
+
+        /**
+         * @param {http.Server|https.Server} srv Server instance that we want to attach agent handler.
+         */
+        attach(srv) {
+            if (this._server)
+                throw 'Agent server already started!';
+
+            this._server = srv;
+
+            /**
+             * @type {socketIo.Server}
+             */
+            this._socket = socketio(this._server);
+
+            this._socket.on('connection', (socket) => {
+                socket.on('agent:auth', (data, cb) => {
+                    if (!_.isEmpty(this.supportedAgents)) {
+                        const ver = data.ver;
+                        const bt = data.bt;
+
+                        if (_.isEmpty(ver) || _.isEmpty(bt) || _.isEmpty(this.supportedAgents[ver]) ||
+                            this.supportedAgents[ver].buildTime > bt)
+                            return cb('You are using an older version of the agent. Please reload agent archive');
+                    }
+
+                    mongo.Account.findOne({token: data.token}, (err, account) => {
+                        // TODO IGNITE-1379 send error to web master.
+                        if (err)
+                            cb('Failed to authorize user');
+                        else if (!account)
+                            cb('Invalid token, user not found');
+                        else {
+                            const agent = new Agent(socket);
+
+                            socket.on('disconnect', () => {
+                                this._removeAgent(account._id, agent);
+                            });
+
+                            this._addAgent(account._id, agent);
+
+                            cb();
+                        }
+                    });
+                });
+            });
+        }
+
+        /**
+         * @param {ObjectId} userId
+         * @param {Socket} user
+         * @returns {int} connected agent count.
+         */
+        addAgentListener(userId, user) {
+            let users = this._browsers[userId];
+
+            if (!users)
+                this._browsers[userId] = users = [];
+
+            users.push(user);
+
+            const agents = this._agents[userId];
+
+            return agents ? agents.length : 0;
+        }
+
+        /**
+         * @param {ObjectId} userId
+         * @param {Socket} user
+         * @returns {int} connected agent count.
+         */
+        removeAgentListener(userId, user) {
+            const users = this._browsers[userId];
+
+            _.remove(users, (_user) => _user === user);
+        }
+
+        /**
+         * @param {ObjectId} userId
+         * @returns {Promise.<Agent>}
+         */
+        findAgent(userId) {
+            if (!this._server)
+                return Promise.reject(new Error('Agent server not started yet!'));
+
+            const agents = this._agents[userId];
+
+            if (!agents || agents.length === 0)
+                return Promise.reject(new Error('Failed to connect to agent'));
+
+            return Promise.resolve(agents[0]);
+        }
+
+        /**
+         * Close connections for all user agents.
+         * @param {ObjectId} userId
+         */
+        close(userId) {
+            if (!this._server)
+                return;
+
+            const agents = this._agents[userId];
+
+            this._agents[userId] = [];
+
+            for (const agent of agents)
+                agent._emit('agent:close', 'Security token was changed for user');
+        }
+
+        /**
+         * @param userId
+         * @param {Agent} agent
+         */
+        _removeAgent(userId, agent) {
+            const agents = this._agents[userId];
+
+            _.remove(agents, (_agent) => _agent === agent);
+
+            const users = this._browsers[userId];
+
+            _.forEach(users, (user) => user.emit('agent:count', {count: agents.length}));
+        }
+
+        /**
+         * @param {ObjectId} userId
+         * @param {Agent} agent
+         */
+        _addAgent(userId, agent) {
+            let agents = this._agents[userId];
+
+            if (!agents)
+                this._agents[userId] = agents = [];
+
+            agents.push(agent);
+
+            const users = this._browsers[userId];
+
+            _.forEach(users, (user) => user.emit('agent:count', {count: agents.length}));
+        }
+    }
+
+    return new AgentManager();
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/agent_dists/README.txt
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/serve/agent_dists/README.txt b/modules/web-console/src/main/js/serve/agent_dists/README.txt
new file mode 100644
index 0000000..d51bdf9
--- /dev/null
+++ b/modules/web-console/src/main/js/serve/agent_dists/README.txt
@@ -0,0 +1,7 @@
+Ignite Web Console
+======================================
+
+This is default folder for agent distributives.
+
+Also, you could specify custom folder in `serve/config/settings.json`
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/app.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/serve/app.js b/modules/web-console/src/main/js/serve/app.js
new file mode 100644
index 0000000..5d6b2cf
--- /dev/null
+++ b/modules/web-console/src/main/js/serve/app.js
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+// Fire me up!
+
+module.exports = {
+    implements: 'app',
+    inject: ['require(express)', 'configure', 'routes']
+};
+
+module.exports.factory = function(Express, configure, routes) {
+    return {
+        /**
+         * @param {Server} srv
+         */
+        listen: (srv) => {
+            const app = new Express();
+
+            configure.express(app);
+
+            routes.register(app);
+
+            srv.addListener('request', app);
+        }
+    };
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/browser.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/serve/browser.js b/modules/web-console/src/main/js/serve/browser.js
new file mode 100644
index 0000000..837450d
--- /dev/null
+++ b/modules/web-console/src/main/js/serve/browser.js
@@ -0,0 +1,304 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+// Fire me up!
+
+/**
+ * Module interaction with browsers.
+ */
+module.exports = {
+    implements: 'browser-manager',
+    inject: ['require(lodash)', 'require(socket.io)', 'agent-manager', 'configure']
+};
+
+module.exports.factory = (_, socketio, agentMgr, configure) => {
+    const _errorToJson = (err) => {
+        return {
+            message: err.message || err,
+            code: err.code || 1
+        };
+    };
+
+    return {
+        attach: (server) => {
+            const io = socketio(server);
+
+            configure.socketio(io);
+
+            io.sockets.on('connection', (socket) => {
+                const user = socket.client.request.user;
+
+                const demo = socket.client.request._query.IgniteDemoMode === 'true';
+
+                // Return available drivers to browser.
+                socket.on('schemaImport:drivers', (cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => agent.availableDrivers())
+                        .then((drivers) => cb(null, drivers))
+                        .catch((err) => cb(_errorToJson(err)));
+                });
+
+                // Return schemas from database to browser.
+                socket.on('schemaImport:schemas', (preset, cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => {
+                            const jdbcInfo = {user: preset.user, password: preset.password};
+
+                            return agent.metadataSchemas(preset.jdbcDriverJar, preset.jdbcDriverClass, preset.jdbcUrl, jdbcInfo);
+                        })
+                        .then((schemas) => cb(null, schemas))
+                        .catch((err) => cb(_errorToJson(err)));
+                });
+
+                // Return tables from database to browser.
+                socket.on('schemaImport:tables', (preset, cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => {
+                            const jdbcInfo = {user: preset.user, password: preset.password};
+
+                            return agent.metadataTables(preset.jdbcDriverJar, preset.jdbcDriverClass, preset.jdbcUrl, jdbcInfo,
+                                preset.schemas, preset.tablesOnly);
+                        })
+                        .then((tables) => cb(null, tables))
+                        .catch((err) => cb(_errorToJson(err)));
+                });
+
+                // Return topology command result from grid to browser.
+                socket.on('node:topology', (attr, mtr, cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => agent.topology(demo, attr, mtr))
+                        .then((clusters) => cb(null, clusters))
+                        .catch((err) => cb(_errorToJson(err)));
+                });
+
+                // Close query on node.
+                socket.on('node:query:close', (queryId, cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => agent.queryClose(demo, queryId))
+                        .then(() => cb())
+                        .catch((err) => cb(_errorToJson(err)));
+                });
+
+                // Execute query on node and return first page to browser.
+                socket.on('node:query', (cacheName, pageSize, query, cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => {
+                            if (query === null)
+                                return agent.scan(demo, cacheName, pageSize);
+
+                            return agent.fieldsQuery(demo, cacheName, query, pageSize);
+                        })
+                        .then((res) => cb(null, res))
+                        .catch((err) => cb(_errorToJson(err)));
+                });
+
+                // Fetch next page for query and return result to browser.
+                socket.on('node:query:fetch', (queryId, pageSize, cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => agent.queryFetch(demo, queryId, pageSize))
+                        .then((res) => cb(null, res))
+                        .catch((err) => cb(_errorToJson(err)));
+                });
+
+                // Execute query on node and return full result to browser.
+                socket.on('node:query:getAll', (cacheName, query, cb) => {
+                    // Set page size for query.
+                    const pageSize = 1024;
+
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => {
+                            const firstPage = query === null ? agent.scan(demo, cacheName, pageSize)
+                                : agent.fieldsQuery(demo, cacheName, query, pageSize);
+
+                            const fetchResult = (acc) => {
+                                if (acc.last)
+                                    return acc;
+
+                                return agent.queryFetch(demo, acc.queryId, pageSize)
+                                    .then((res) => {
+                                        acc.rows = acc.rows.concat(res.rows);
+
+                                        acc.last = res.last;
+
+                                        return fetchResult(acc);
+                                    });
+                            };
+
+                            return firstPage
+                                .then(fetchResult);
+                        })
+                        .then((res) => cb(null, res))
+                        .catch((err) => cb(_errorToJson(err)));
+                });
+
+                // Return cache metadata from all nodes in grid.
+                socket.on('node:cache:metadata', (cacheName, cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => agent.metadata(demo, cacheName))
+                        .then((caches) => {
+                            let types = [];
+
+                            const _compact = (className) => {
+                                return className.replace('java.lang.', '').replace('java.util.', '').replace('java.sql.', '');
+                            };
+
+                            const _typeMapper = (meta, typeName) => {
+                                let fields = meta.fields[typeName];
+
+                                let columns = [];
+
+                                for (const fieldName in fields) {
+                                    if (fields.hasOwnProperty(fieldName)) {
+                                        const fieldClass = _compact(fields[fieldName]);
+
+                                        columns.push({
+                                            type: 'field',
+                                            name: fieldName,
+                                            clazz: fieldClass,
+                                            system: fieldName === '_KEY' || fieldName === '_VAL',
+                                            cacheName: meta.cacheName,
+                                            typeName
+                                        });
+                                    }
+                                }
+
+                                const indexes = [];
+
+                                for (const index of meta.indexes[typeName]) {
+                                    fields = [];
+
+                                    for (const field of index.fields) {
+                                        fields.push({
+                                            type: 'index-field',
+                                            name: field,
+                                            order: index.descendings.indexOf(field) < 0,
+                                            unique: index.unique,
+                                            cacheName: meta.cacheName,
+                                            typeName
+                                        });
+                                    }
+
+                                    if (fields.length > 0) {
+                                        indexes.push({
+                                            type: 'index',
+                                            name: index.name,
+                                            children: fields,
+                                            cacheName: meta.cacheName,
+                                            typeName
+                                        });
+                                    }
+                                }
+
+                                columns = _.sortBy(columns, 'name');
+
+                                if (!_.isEmpty(indexes)) {
+                                    columns = columns.concat({
+                                        type: 'indexes',
+                                        name: 'Indexes',
+                                        cacheName: meta.cacheName,
+                                        typeName,
+                                        children: indexes
+                                    });
+                                }
+
+                                return {
+                                    type: 'type',
+                                    cacheName: meta.cacheName || '',
+                                    typeName,
+                                    children: columns
+                                };
+                            };
+
+                            for (const meta of caches) {
+                                const cacheTypes = meta.types.map(_typeMapper.bind(null, meta));
+
+                                if (!_.isEmpty(cacheTypes))
+                                    types = types.concat(cacheTypes);
+                            }
+
+                            return cb(null, types);
+                        })
+                        .catch((err) => cb(_errorToJson(err)));
+                });
+
+                // Fetch next page for query and return result to browser.
+                socket.on('node:visor:collect', (evtOrderKey, evtThrottleCntrKey, cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => agent.collect(demo, evtOrderKey, evtThrottleCntrKey))
+                        .then((data) => {
+                            if (data.finished)
+                                return cb(null, data.result);
+
+                            cb(_errorToJson(data.error));
+                        })
+                        .catch((err) => cb(_errorToJson(err)));
+                });
+
+                // Clear specified cache on specified node and return result to browser.
+                socket.on('node:cache:clear', (nid, cacheName, cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => agent.cacheClear(demo, nid, cacheName))
+                        .then((data) => {
+                            if (data.finished)
+                                return cb(null, data.result);
+
+                            cb(_errorToJson(data.error));
+                        })
+                        .catch((err) => cb(_errorToJson(err)));
+                });
+
+                // Stop specified cache on specified node and return result to browser.
+                socket.on('node:cache:stop', (nids, cacheName, cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => agent.cacheStop(demo, nids, cacheName))
+                        .then((data) => {
+                            if (data.finished)
+                                return cb(null, data.result);
+
+                            cb(_errorToJson(data.error));
+                        })
+                        .catch((err) => cb(_errorToJson(err)));
+                });
+
+
+                // Ping node and return result to browser.
+                socket.on('node:ping', (nid, cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => agent.ping(demo, nid))
+                        .then((data) => {
+                            if (data.finished)
+                                return cb(null, data.result);
+
+                            cb(_errorToJson(data.error));
+                        })
+                        .catch((err) => cb(_errorToJson(err)));
+                });
+
+                const count = agentMgr.addAgentListener(user._id, socket);
+
+                socket.emit('agent:count', {count});
+            });
+
+            // Handle browser disconnect event.
+            io.sockets.on('disconnect', (socket) =>
+                agentMgr.removeAgentListener(socket.client.request.user._id, socket)
+            );
+        }
+    };
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/config/settings.json.sample
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/serve/config/settings.json.sample b/modules/web-console/src/main/js/serve/config/settings.json.sample
new file mode 100644
index 0000000..94dd9f7
--- /dev/null
+++ b/modules/web-console/src/main/js/serve/config/settings.json.sample
@@ -0,0 +1,26 @@
+{
+    "server": {
+        "port": 3000,
+        "ssl": false,
+        "key": "serve/keys/test.key",
+        "cert": "serve/keys/test.crt",
+        "keyPassphrase": "password"
+    },
+    "mongoDB": {
+        "url": "mongodb://localhost/console"
+    },
+    "agent-server": {
+        "port": 3001,
+        "ssl": false,
+        "key": "serve/keys/test.key",
+        "cert": "serve/keys/test.crt",
+        "keyPassphrase": "password"
+    },
+    "smtp": {
+        "service": "",
+        "username": "Apache Ignite Web Console",
+        "sign": "Kind regards,<br>Apache Ignite Team",
+        "email": "",
+        "password": ""
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/configure.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/serve/configure.js b/modules/web-console/src/main/js/serve/configure.js
new file mode 100644
index 0000000..71f7c8a
--- /dev/null
+++ b/modules/web-console/src/main/js/serve/configure.js
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+// Fire me up!
+
+/**
+ * Module for configuration express and websocket server.
+ */
+module.exports = {
+    implements: 'configure',
+    inject: ['require(morgan)', 'require(cookie-parser)', 'require(body-parser)',
+        'require(express-session)', 'require(connect-mongo)', 'require(passport)', 'require(passport.socketio)', 'settings', 'mongo']
+};
+
+module.exports.factory = function(logger, cookieParser, bodyParser, session, connectMongo, passport, passportSocketIo, settings, mongo) {
+    const _sessionStore = new (connectMongo(session))({mongooseConnection: mongo.connection});
+
+    return {
+        express: (app) => {
+            app.use(logger('dev', {
+                skip: (req, res) => res.statusCode < 400
+            }));
+
+            app.use(cookieParser(settings.sessionSecret));
+
+            app.use(bodyParser.json({limit: '50mb'}));
+            app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
+
+            app.use(session({
+                secret: settings.sessionSecret,
+                resave: false,
+                saveUninitialized: true,
+                cookie: {
+                    expires: new Date(Date.now() + settings.cookieTTL),
+                    maxAge: settings.cookieTTL
+                },
+                store: _sessionStore
+            }));
+
+            app.use(passport.initialize());
+            app.use(passport.session());
+
+            passport.serializeUser(mongo.Account.serializeUser());
+            passport.deserializeUser(mongo.Account.deserializeUser());
+
+            passport.use(mongo.Account.createStrategy());
+        },
+        socketio: (io) => {
+            const _onAuthorizeSuccess = (data, accept) => {
+                accept(null, true);
+            };
+
+            const _onAuthorizeFail = (data, message, error, accept) => {
+                accept(null, false);
+            };
+
+            io.use(passportSocketIo.authorize({
+                cookieParser,
+                key: 'connect.sid', // the name of the cookie where express/connect stores its session_id
+                secret: settings.sessionSecret, // the session_secret to parse the cookie
+                store: _sessionStore, // we NEED to use a sessionstore. no memorystore please
+                success: _onAuthorizeSuccess, // *optional* callback on success - read more below
+                fail: _onAuthorizeFail // *optional* callback on fail/error - read more below
+            }));
+        }
+    };
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/keys/test.crt
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/serve/keys/test.crt b/modules/web-console/src/main/js/serve/keys/test.crt
new file mode 100644
index 0000000..50c6d5c
--- /dev/null
+++ b/modules/web-console/src/main/js/serve/keys/test.crt
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE-----
+MIIB6zCCAVQCCQDcAphbU6UcLjANBgkqhkiG9w0BAQsFADA6MRIwEAYDVQQDDAls
+b2NhbGhvc3QxJDAiBgkqhkiG9w0BCQEWFXNldmRva2ltb3ZAYXBhY2hlLm9yZzAe
+Fw0xNTA3MTQxMzAyNTNaFw0xODA2MjMxMzAyNTNaMDoxEjAQBgNVBAMMCWxvY2Fs
+aG9zdDEkMCIGCSqGSIb3DQEJARYVc2V2ZG9raW1vdkBhcGFjaGUub3JnMIGfMA0G
+CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDP/zpJrdHqCj6lPpeFF6LQtzKef6UiyBBo
+rbuOtCCgW8KMJJciluBWk2126qLt9smBN4jBpSNU3pq0r9gBMUTd/LSe7aY4D5ED
+Pjp7XsypNVKeHaHbFi7KhfHy0LYxsWiNPmmHJv4dtYOp+pGK25rkXNfyJxxjgxN6
+wo34+MnZIQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAFk9XEjcdyihws+fVmdGGUFo
+bVxI9YGH6agiNbU3WNF4B4VRzcPPW8z2mEo7eF9kgYmq/YzH4T8tgi/qkL/u8eZV
+Wmi9bg6RThLN6/hj3wVoOFKykbDQ05FFdhIJXN5UOjPmxYM97EKqg6J0W2HAb8SG
++UekPnmAo/2HTKsLykH8
+-----END CERTIFICATE-----

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/keys/test.key
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/serve/keys/test.key b/modules/web-console/src/main/js/serve/keys/test.key
new file mode 100644
index 0000000..1b395c0
--- /dev/null
+++ b/modules/web-console/src/main/js/serve/keys/test.key
@@ -0,0 +1,18 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,6798185330CE2EE2
+
+sOwkmD8rvjx11l09V26dJhLhl+SyPIhyeZ3TqHXrYCATKoXlzidT+uPu1jVYtrwr
+nBLA6TrIDYRrBNlEsqGZ0cSvWTIczzVW1xZKHEJo5q2vUT/W8u/Q1QQtS3P3GeKF
+dEzx496rpZqwwVw59GNbuIwyYoVvQf3iEXzfhplGmLPELYIplDFOLgNuXQyXSGx6
+rwKsCxXMLsDyrA6DCz0Odf08p2HvWk/s5Ne3DFcQlqRNtIrBVGD2O0/Fp8ZZ2I4E
+Yn2OIIWJff3HanOjLOWKdN8YAn5UleNmlEUdIHeS5qaQ68mabOxLkSef9qglV+sd
+FHTtUq0cG6t6nhxZBziexha6v1yl/xABAHHhNPOfak+HthWxRD4N9f1yFYAeTmkn
+4kwBWoSUe12XRf2pGNqhEUKN/KhDmWk85wI55i/Cu2XmNoiBFlS9BXrRYU8uVCJw
+KlxjKTDWl1opCyvxTDxJnMkt44ZT445LRePKVueGIIKSUIXNQypOE+C1I0CL0N2W
+Ts3m9nthquvLeMx92k7b8yW69BER5uac3SIlGCOJObQXsHgyk8wYiyd/zLKfjctG
+PXieaW81UKjp+GqWpvWPz3VqnKwoyUWeVOOTviurli6kYOrHuySTMqMb6hxJctw9
+grAQTT0UPiAKWcM7InLzZnRjco+v9QLLEokjVngXPba16K/CItFY16xuGlaFLW7Y
+XTc67AkL8b76HBZelMjmCsqjvSoULhuMFwTOvUMm/mSM8rMoi9asrJRLQHRMWCST
+/6RENPLzPlOMnNLBujpBbn8V3/aYzEZsHMI+6S3d27WYlTJIqpabSA==
+-----END RSA PRIVATE KEY-----


Mime
View raw message