aboutsummaryrefslogtreecommitdiffstats
path: root/ui/imports/ui
diff options
context:
space:
mode:
authorKoren Lev <korenlev@gmail.com>2017-07-27 16:42:15 +0300
committerKoren Lev <korenlev@gmail.com>2017-07-27 16:42:15 +0300
commitb88c78e3cf2bef22aa2f1c4d0bf305e303bc15f0 (patch)
treeffa30a6e1511d72562d8772b8700cda52b2752a1 /ui/imports/ui
parentb70483739d1f6f4f0d31987ed2e4d1e30d71d579 (diff)
adding calipso ui
Change-Id: Ifa6f63daebb07f45580f747341960e898fdb00c4 Signed-off-by: Koren Lev <korenlev@gmail.com>
Diffstat (limited to 'ui/imports/ui')
-rw-r--r--ui/imports/ui/actions/environment-panel.actions.js225
-rw-r--r--ui/imports/ui/actions/graph-tooltip-window.actions.js30
-rw-r--r--ui/imports/ui/actions/main-app.actions.js21
-rw-r--r--ui/imports/ui/actions/navigation.js84
-rw-r--r--ui/imports/ui/actions/search-interested-parties.js93
-rw-r--r--ui/imports/ui/actions/tree-node.actions.js144
-rw-r--r--ui/imports/ui/actions/vedge-info-window.actions.js41
-rw-r--r--ui/imports/ui/components/accordion-nav-menu/accordion-nav-menu.html43
-rw-r--r--ui/imports/ui/components/accordion-nav-menu/accordion-nav-menu.js155
-rw-r--r--ui/imports/ui/components/accordion-nav-menu/accordion-nav-menu.styl11
-rw-r--r--ui/imports/ui/components/accordion-wiki-menu/accordion-wiki-menu.html42
-rw-r--r--ui/imports/ui/components/accordion-wiki-menu/accordion-wiki-menu.js147
-rw-r--r--ui/imports/ui/components/accordionTreeNode/accordion-tree-node.styl5
-rw-r--r--ui/imports/ui/components/accordionTreeNode/accordionTreeNode.html54
-rw-r--r--ui/imports/ui/components/accordionTreeNode/accordionTreeNode.js284
-rw-r--r--ui/imports/ui/components/accordionTreeNodeChildren/accordionTreeNodeChildren.html19
-rw-r--r--ui/imports/ui/components/accordionTreeNodeChildren/accordionTreeNodeChildren.js125
-rw-r--r--ui/imports/ui/components/aggregate-dashboard/aggregate-dashboard.html35
-rw-r--r--ui/imports/ui/components/aggregate-dashboard/aggregate-dashboard.js212
-rw-r--r--ui/imports/ui/components/aggregate-dashboard/aggregate-dashboard.styl10
-rw-r--r--ui/imports/ui/components/alarm-icons/alarm-icons.html78
-rw-r--r--ui/imports/ui/components/alarm-icons/alarm-icons.js53
-rw-r--r--ui/imports/ui/components/alarm-icons/alarm-icons.styl1
-rw-r--r--ui/imports/ui/components/auto-search-result-line/auto-search-result-line.html16
-rw-r--r--ui/imports/ui/components/auto-search-result-line/auto-search-result-line.js51
-rw-r--r--ui/imports/ui/components/auto-search-result-line/auto-search-result-line.styl23
-rw-r--r--ui/imports/ui/components/breadcrumb/breadcrumb.html17
-rw-r--r--ui/imports/ui/components/breadcrumb/breadcrumb.js83
-rw-r--r--ui/imports/ui/components/breadcrumb/breadcrumb.styl3
-rw-r--r--ui/imports/ui/components/breadcrumbNode/breadcrumbNode.html15
-rw-r--r--ui/imports/ui/components/breadcrumbNode/breadcrumbNode.js43
-rw-r--r--ui/imports/ui/components/breadcrumbNode/breadcrumbNode.styl4
-rw-r--r--ui/imports/ui/components/clique-constraint/clique-constraint.html96
-rw-r--r--ui/imports/ui/components/clique-constraint/clique-constraint.js329
-rw-r--r--ui/imports/ui/components/clique-constraint/clique-constraint.styl35
-rw-r--r--ui/imports/ui/components/clique-constraints-list/clique-constraints-list.html52
-rw-r--r--ui/imports/ui/components/clique-constraints-list/clique-constraints-list.js77
-rw-r--r--ui/imports/ui/components/clique-constraints-list/clique-constraints-list.styl22
-rw-r--r--ui/imports/ui/components/clique-type/clique-type.html100
-rw-r--r--ui/imports/ui/components/clique-type/clique-type.js371
-rw-r--r--ui/imports/ui/components/clique-type/clique-type.styl54
-rw-r--r--ui/imports/ui/components/clique-types-list/clique-types-list.html56
-rw-r--r--ui/imports/ui/components/clique-types-list/clique-types-list.js82
-rw-r--r--ui/imports/ui/components/clique-types-list/clique-types-list.styl22
-rw-r--r--ui/imports/ui/components/d3graph/d3graph.html13
-rw-r--r--ui/imports/ui/components/d3graph/d3graph.js126
-rw-r--r--ui/imports/ui/components/d3graph/d3graph.styl12
-rw-r--r--ui/imports/ui/components/dashboard/dashboard.html157
-rw-r--r--ui/imports/ui/components/dashboard/dashboard.js222
-rw-r--r--ui/imports/ui/components/dashboard/dashboard.styl0
-rw-r--r--ui/imports/ui/components/data-cubic/data-cubic.html23
-rw-r--r--ui/imports/ui/components/data-cubic/data-cubic.js71
-rw-r--r--ui/imports/ui/components/data-cubic/data-cubic.styl27
-rw-r--r--ui/imports/ui/components/detailed-node-info-box/detailed-node-info-box.html53
-rw-r--r--ui/imports/ui/components/detailed-node-info-box/detailed-node-info-box.js57
-rw-r--r--ui/imports/ui/components/detailed-node-info-box/detailed-node-info-box.styl33
-rw-r--r--ui/imports/ui/components/env-aci-info/env-aci-info.html88
-rw-r--r--ui/imports/ui/components/env-aci-info/env-aci-info.js61
-rw-r--r--ui/imports/ui/components/env-amqp-credentials-info/env-amqp-credentials-info.html108
-rw-r--r--ui/imports/ui/components/env-amqp-credentials-info/env-amqp-credentials-info.js52
-rw-r--r--ui/imports/ui/components/env-delete-modal/env-delete-modal.html48
-rw-r--r--ui/imports/ui/components/env-delete-modal/env-delete-modal.js55
-rw-r--r--ui/imports/ui/components/env-delete-modal/env-delete-modal.styl2
-rw-r--r--ui/imports/ui/components/env-form/env-form.html37
-rw-r--r--ui/imports/ui/components/env-form/env-form.js94
-rw-r--r--ui/imports/ui/components/env-form/env-form.styl0
-rw-r--r--ui/imports/ui/components/env-main-info/env-main-info.html193
-rw-r--r--ui/imports/ui/components/env-main-info/env-main-info.js123
-rw-r--r--ui/imports/ui/components/env-master-host-credentials-info/env-master-host-credentials-info.html109
-rw-r--r--ui/imports/ui/components/env-master-host-credentials-info/env-master-host-credentials-info.js52
-rw-r--r--ui/imports/ui/components/env-monitoring-info/env-monitoring-info.html283
-rw-r--r--ui/imports/ui/components/env-monitoring-info/env-monitoring-info.js76
-rw-r--r--ui/imports/ui/components/env-nfv-info/env-nfv-info.html128
-rw-r--r--ui/imports/ui/components/env-nfv-info/env-nfv-info.js63
-rw-r--r--ui/imports/ui/components/env-open-stack-db-credentials-info/env-open-stack-db-credentials-info.html109
-rw-r--r--ui/imports/ui/components/env-open-stack-db-credentials-info/env-open-stack-db-credentials-info.js52
-rw-r--r--ui/imports/ui/components/env-os-api-endpoint-info/env-os-api-endpoint-info.html125
-rw-r--r--ui/imports/ui/components/env-os-api-endpoint-info/env-os-api-endpoint-info.js52
-rw-r--r--ui/imports/ui/components/environment-dashboard/environment-dashboard.html59
-rw-r--r--ui/imports/ui/components/environment-dashboard/environment-dashboard.js380
-rw-r--r--ui/imports/ui/components/environment-dashboard/environment-dashboard.styl12
-rw-r--r--ui/imports/ui/components/environment-wizard/environment-wizard.html83
-rw-r--r--ui/imports/ui/components/environment-wizard/environment-wizard.js452
-rw-r--r--ui/imports/ui/components/environment-wizard/environment-wizard.styl27
-rw-r--r--ui/imports/ui/components/environment/environment.html63
-rw-r--r--ui/imports/ui/components/environment/environment.js570
-rw-r--r--ui/imports/ui/components/environment/environment.styl61
-rw-r--r--ui/imports/ui/components/flow-graph/flow-graph.html17
-rw-r--r--ui/imports/ui/components/flow-graph/flow-graph.js383
-rw-r--r--ui/imports/ui/components/flow-graph/flow-graph.styl18
-rw-r--r--ui/imports/ui/components/general-folder-node-dashboard/general-folder-node-dashboard.html24
-rw-r--r--ui/imports/ui/components/general-folder-node-dashboard/general-folder-node-dashboard.js112
-rw-r--r--ui/imports/ui/components/general-folder-node-dashboard/general-folder-node-dashboard.styl12
-rw-r--r--ui/imports/ui/components/general-node-dashboard/general-node-dashboard.html17
-rw-r--r--ui/imports/ui/components/general-node-dashboard/general-node-dashboard.js84
-rw-r--r--ui/imports/ui/components/general-node-dashboard/general-node-dashboard.styl6
-rw-r--r--ui/imports/ui/components/general-node-info-box/general-node-info-box.html37
-rw-r--r--ui/imports/ui/components/general-node-info-box/general-node-info-box.js63
-rw-r--r--ui/imports/ui/components/general-node-info-box/general-node-info-box.styl33
-rw-r--r--ui/imports/ui/components/get-started/get-started.html412
-rw-r--r--ui/imports/ui/components/get-started/get-started.js32
-rw-r--r--ui/imports/ui/components/graph-tooltip-window/graph-tooltip-window.html17
-rw-r--r--ui/imports/ui/components/graph-tooltip-window/graph-tooltip-window.js57
-rw-r--r--ui/imports/ui/components/graph-tooltip-window/graph-tooltip-window.styl25
-rw-r--r--ui/imports/ui/components/host-dashboard/host-dashboard.html29
-rw-r--r--ui/imports/ui/components/host-dashboard/host-dashboard.js197
-rw-r--r--ui/imports/ui/components/host-dashboard/host-dashboard.styl6
-rw-r--r--ui/imports/ui/components/icon/icon.html18
-rw-r--r--ui/imports/ui/components/icon/icon.js48
-rw-r--r--ui/imports/ui/components/icon/icon.styl2
-rw-r--r--ui/imports/ui/components/index.styl57
-rw-r--r--ui/imports/ui/components/input-model/input-model.html21
-rw-r--r--ui/imports/ui/components/input-model/input-model.js116
-rw-r--r--ui/imports/ui/components/inventory-properties-display/inventory-properties-display.html13
-rw-r--r--ui/imports/ui/components/inventory-properties-display/inventory-properties-display.js90
-rw-r--r--ui/imports/ui/components/inventory-properties-display/inventory-properties-display.styl2
-rw-r--r--ui/imports/ui/components/landing/landing.html201
-rw-r--r--ui/imports/ui/components/landing/landing.js35
-rw-r--r--ui/imports/ui/components/landing/landing.styl80
-rw-r--r--ui/imports/ui/components/link-type/link-type.html88
-rw-r--r--ui/imports/ui/components/link-type/link-type.js328
-rw-r--r--ui/imports/ui/components/link-type/link-type.styl34
-rw-r--r--ui/imports/ui/components/link-types-list/link-types-list.html56
-rw-r--r--ui/imports/ui/components/link-types-list/link-types-list.js87
-rw-r--r--ui/imports/ui/components/link-types-list/link-types-list.styl23
-rw-r--r--ui/imports/ui/components/list-info-box/list-info-box.html60
-rw-r--r--ui/imports/ui/components/list-info-box/list-info-box.js111
-rw-r--r--ui/imports/ui/components/list-info-box/list-info-box.styl4
-rw-r--r--ui/imports/ui/components/loading/loading.html12
-rw-r--r--ui/imports/ui/components/loading/loading.js30
-rw-r--r--ui/imports/ui/components/loading/loading.styl0
-rw-r--r--ui/imports/ui/components/main/main.html15
-rw-r--r--ui/imports/ui/components/main/main.js98
-rw-r--r--ui/imports/ui/components/main/main.styl0
-rw-r--r--ui/imports/ui/components/message/message.html168
-rw-r--r--ui/imports/ui/components/message/message.js257
-rw-r--r--ui/imports/ui/components/message/message.styl41
-rw-r--r--ui/imports/ui/components/messages-info-box/messages-info-box.html27
-rw-r--r--ui/imports/ui/components/messages-info-box/messages-info-box.js66
-rw-r--r--ui/imports/ui/components/messages-info-box/messages-info-box.styl2
-rw-r--r--ui/imports/ui/components/messages-list/messages-list.html103
-rw-r--r--ui/imports/ui/components/messages-list/messages-list.js291
-rw-r--r--ui/imports/ui/components/messages-list/messages-list.styl37
-rw-r--r--ui/imports/ui/components/messages-modal/messages-modal.html78
-rw-r--r--ui/imports/ui/components/messages-modal/messages-modal.js285
-rw-r--r--ui/imports/ui/components/messages-modal/messages-modal.styl18
-rw-r--r--ui/imports/ui/components/mt-input/mt-input.html7
-rw-r--r--ui/imports/ui/components/mt-input/mt-input.js114
-rw-r--r--ui/imports/ui/components/mt-input/mt-input.styl2
-rw-r--r--ui/imports/ui/components/mt-radios/mt-radios.html22
-rw-r--r--ui/imports/ui/components/mt-radios/mt-radios.js70
-rw-r--r--ui/imports/ui/components/mt-radios/mt-radios.styl2
-rw-r--r--ui/imports/ui/components/mt-select/mt-select.html13
-rw-r--r--ui/imports/ui/components/mt-select/mt-select.js99
-rw-r--r--ui/imports/ui/components/mt-select/mt-select.styl2
-rw-r--r--ui/imports/ui/components/network-graph-manager/network-graph-manager.html5
-rw-r--r--ui/imports/ui/components/network-graph-manager/network-graph-manager.js282
-rw-r--r--ui/imports/ui/components/network-graph-manager/network-graph-manager.styl2
-rw-r--r--ui/imports/ui/components/network-graph/network-graph.html7
-rw-r--r--ui/imports/ui/components/network-graph/network-graph.js697
-rw-r--r--ui/imports/ui/components/network-graph/network-graph.styl20
-rw-r--r--ui/imports/ui/components/network-info-box/network-info-box.html38
-rw-r--r--ui/imports/ui/components/network-info-box/network-info-box.js69
-rw-r--r--ui/imports/ui/components/network-info-box/network-info-box.styl2
-rw-r--r--ui/imports/ui/components/new-scanning/new-scanning.html53
-rw-r--r--ui/imports/ui/components/new-scanning/new-scanning.js73
-rw-r--r--ui/imports/ui/components/new-scanning/new-scanning.styl7
-rw-r--r--ui/imports/ui/components/pager/pager.html42
-rw-r--r--ui/imports/ui/components/pager/pager.js123
-rw-r--r--ui/imports/ui/components/pager/pager.styl4
-rw-r--r--ui/imports/ui/components/project-dashboard/project-dashboard.html38
-rw-r--r--ui/imports/ui/components/project-dashboard/project-dashboard.js149
-rw-r--r--ui/imports/ui/components/project-dashboard/project-dashboard.styl14
-rw-r--r--ui/imports/ui/components/region-dashboard/region-dashboard.html38
-rw-r--r--ui/imports/ui/components/region-dashboard/region-dashboard.js220
-rw-r--r--ui/imports/ui/components/region-dashboard/region-dashboard.styl14
-rw-r--r--ui/imports/ui/components/scanning-request/scanning-request.html74
-rw-r--r--ui/imports/ui/components/scanning-request/scanning-request.js371
-rw-r--r--ui/imports/ui/components/scanning-request/scanning-request.styl7
-rw-r--r--ui/imports/ui/components/scans-list/scans-list.html88
-rw-r--r--ui/imports/ui/components/scans-list/scans-list.js224
-rw-r--r--ui/imports/ui/components/scans-list/scans-list.styl33
-rw-r--r--ui/imports/ui/components/scheduled-scan/scheduled-scan.html116
-rw-r--r--ui/imports/ui/components/scheduled-scan/scheduled-scan.js511
-rw-r--r--ui/imports/ui/components/scheduled-scan/scheduled-scan.styl34
-rw-r--r--ui/imports/ui/components/scheduled-scans-list/scheduled-scans-list.html66
-rw-r--r--ui/imports/ui/components/scheduled-scans-list/scheduled-scans-list.js160
-rw-r--r--ui/imports/ui/components/scheduled-scans-list/scheduled-scans-list.styl33
-rw-r--r--ui/imports/ui/components/search-auto-complete-list/search-auto-complete-list.html22
-rw-r--r--ui/imports/ui/components/search-auto-complete-list/search-auto-complete-list.js167
-rw-r--r--ui/imports/ui/components/search-auto-complete-list/search-auto-complete.styl28
-rw-r--r--ui/imports/ui/components/select-model/select-model.html23
-rw-r--r--ui/imports/ui/components/select-model/select-model.js79
-rw-r--r--ui/imports/ui/components/selectable-ordered-input/selectable-ordered-input.html29
-rw-r--r--ui/imports/ui/components/selectable-ordered-input/selectable-ordered-input.js243
-rw-r--r--ui/imports/ui/components/selectable-ordered-input/selectable-ordered-input.styl30
-rw-r--r--ui/imports/ui/components/time-selection-widget/time-selection-widget.html33
-rw-r--r--ui/imports/ui/components/time-selection-widget/time-selection-widget.js45
-rw-r--r--ui/imports/ui/components/time-selection-widget/time-selection-widget.styl5
-rw-r--r--ui/imports/ui/components/top-navbar-menu/top-navbar-menu.html59
-rw-r--r--ui/imports/ui/components/top-navbar-menu/top-navbar-menu.js129
-rw-r--r--ui/imports/ui/components/top-navbar-menu/top-navbar-menu.styl23
-rw-r--r--ui/imports/ui/components/tree-node/tree-node.html58
-rw-r--r--ui/imports/ui/components/tree-node/tree-node.js419
-rw-r--r--ui/imports/ui/components/tree-node/tree-node.styl53
-rw-r--r--ui/imports/ui/components/user-list/user-list.html53
-rw-r--r--ui/imports/ui/components/user-list/user-list.js74
-rw-r--r--ui/imports/ui/components/user-list/user-list.styl22
-rw-r--r--ui/imports/ui/components/user/user.html111
-rw-r--r--ui/imports/ui/components/user/user.js366
-rw-r--r--ui/imports/ui/components/user/user.styl34
-rw-r--r--ui/imports/ui/components/vedge-info-window/vedge-info-window.html114
-rw-r--r--ui/imports/ui/components/vedge-info-window/vedge-info-window.js380
-rw-r--r--ui/imports/ui/components/vedge-info-window/vedge-info-window.styl43
-rw-r--r--ui/imports/ui/components/zone-dashboard/zone-dashboard.html35
-rw-r--r--ui/imports/ui/components/zone-dashboard/zone-dashboard.js214
-rw-r--r--ui/imports/ui/components/zone-dashboard/zone-dashboard.styl10
-rw-r--r--ui/imports/ui/index.styl1
-rw-r--r--ui/imports/ui/lib/environment-tree-node-behavior.js46
-rw-r--r--ui/imports/ui/lib/input-model.js31
-rw-r--r--ui/imports/ui/lib/inventory-tree-node-behavior.js66
-rw-r--r--ui/imports/ui/lib/select-model.js31
-rw-r--r--ui/imports/ui/reducers/environment-panel.reducer.js194
-rw-r--r--ui/imports/ui/reducers/graph-tooltip-window.reducer.js48
-rw-r--r--ui/imports/ui/reducers/i18n.reducer.js180
-rw-r--r--ui/imports/ui/reducers/index.js33
-rw-r--r--ui/imports/ui/reducers/main-app.reducer.js28
-rw-r--r--ui/imports/ui/reducers/navigation.js99
-rw-r--r--ui/imports/ui/reducers/search-interested-parties.js76
-rw-r--r--ui/imports/ui/reducers/tree-node.reducer.js232
-rw-r--r--ui/imports/ui/reducers/vedge-info-window.reducer.js50
-rw-r--r--ui/imports/ui/store/index.js11
-rw-r--r--ui/imports/ui/store/store.js25
233 files changed, 20454 insertions, 0 deletions
diff --git a/ui/imports/ui/actions/environment-panel.actions.js b/ui/imports/ui/actions/environment-panel.actions.js
new file mode 100644
index 0000000..fb7350c
--- /dev/null
+++ b/ui/imports/ui/actions/environment-panel.actions.js
@@ -0,0 +1,225 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+//import * as R from 'ramda';
+
+export const SET_ENV_NAME = 'SET_ENV_NAME';
+export const UPDATE_ENV_TREE_NODE = 'UPDATE_ENV_TREE_NODE';
+export const ADD_UPDATE_CHILDREN_ENV_TREE_NODE = 'ADD_UPDATE_CHILDREN_ENV_TREE_NODE';
+export const RESET_ENV_TREE_NODE_CHILDREN = 'RESET_ENV_TREE_NODE_CHILDREN';
+export const START_OPEN_ENV_TREE_NODE = 'START_OPEN_ENV_TREE_NODE';
+export const END_OPEN_ENV_TREE_NODE = 'END_OPEN_ENV_TREE_NODE';
+export const START_CLOSE_ENV_TREE_NODE = 'START_CLOSE_ENV_TREE_NODE';
+export const END_CLOSE_ENV_TREE_NODE = 'END_CLOSE_ENV_TREE_NODE';
+export const SET_ENV_CHILD_DETECTED_TREE_NODE = 'SET_ENV_CHILD_DETECTED_TREE_NODE';
+export const SET_ENV_SELECTED_NODE = 'SET_ENV_SELECTED_NODE';
+export const SET_ENV_ENV_ID = 'SET_ENV_ENV_ID';
+export const SET_ENV_SELECTED_NODE_INFO = 'SET_ENV_SELECTED_NODE_INFO';
+export const SET_ENV_AS_LOADED = 'SET_ENV_AS_LOADED';
+export const SET_ENV_AS_NOT_LOADED = 'SET_ENV_AS_NOT_LOADED';
+export const SET_ENV_SELECTED_NODE_AS_ENV = 'SET_ENV_SELECTED_NODE_AS_ENV';
+export const SET_SHOW_DASHBOARD = 'SET_SHOW_DASHBOARD';
+export const SET_SHOW_GRAPH = 'SET_SHOW_GRAPH';
+export const TOGGLE_ENV_SHOW = 'TOGGLE_ENV_SHOW';
+export const SET_ENV_POSITION_REPORT_IS_NEEDED_AS_ON = 'SET_ENV_POSITION_REPORT_IS_NEEDED_AS_ON';
+export const REPORT_ENV_NODE_POSITION_RETRIEVED = 'REPORT_ENV_NODE_POSITION_RETRIEVED';
+export const SET_ENV_SCROLL_TO_NODE_IS_NEEDED_AS_ON = 'SET_ENV_SCROLL_TO_NODE_IS_NEEDED_AS_ON';
+export const REPORT_ENV_SCROLL_TO_NODE_PERFORMED = 'REPORT_ENV_SCROLL_TO_NODE_PERFORMED';
+export const RESET_ENV_NEED_CHILD_DETECTION = 'RESET_ENV_NEED_CHILD_DETECTION';
+
+export function setEnvName(envName) {
+ return {
+ type: SET_ENV_NAME,
+ payload: {
+ envName: envName
+ }
+ };
+}
+
+export function updateEnvTreeNode(nodeInfo) {
+ return {
+ type: UPDATE_ENV_TREE_NODE,
+ payload: {
+ nodeInfo: nodeInfo
+ }
+ };
+}
+
+export function addUpdateChildrenEnvTreeNode(nodePath, childrenInfo) {
+ return {
+ type: ADD_UPDATE_CHILDREN_ENV_TREE_NODE,
+ payload: {
+ nodePath: nodePath,
+ childrenInfo: childrenInfo
+ },
+ };
+}
+
+export function resetEnvTreeNodeChildren(nodePath) {
+ return {
+ type: RESET_ENV_TREE_NODE_CHILDREN,
+ payload: {
+ nodePath: nodePath,
+ }
+ };
+}
+
+export function startOpenEnvTreeNode(nodePath) {
+ return {
+ type: START_OPEN_ENV_TREE_NODE,
+ payload: {
+ nodePath: nodePath,
+ }
+ };
+}
+
+export function endOpenEnvTreeNode(nodePath) {
+ return {
+ type: END_OPEN_ENV_TREE_NODE,
+ payload: {
+ nodePath: nodePath,
+ }
+ };
+}
+
+export function startCloseEnvTreeNode(nodePath) {
+ return {
+ type: START_CLOSE_ENV_TREE_NODE,
+ payload: {
+ nodePath: nodePath,
+ }
+ };
+}
+
+export function endCloseEnvTreeNode(nodePath) {
+ return {
+ type: END_CLOSE_ENV_TREE_NODE,
+ payload: {
+ nodePath: nodePath,
+ }
+ };
+}
+
+export function setEnvChildDetectedTreeNode(nodePath) {
+ return {
+ type: SET_ENV_CHILD_DETECTED_TREE_NODE,
+ payload: {
+ nodePath: nodePath
+ }
+ };
+}
+
+export function setEnvSelectedNode(nodeId, nodeType) {
+ return {
+ type: SET_ENV_SELECTED_NODE,
+ payload: {
+ nodeId: nodeId,
+ nodeType: nodeType
+ }
+ };
+}
+
+export function setEnvSelectedNodeAsEnv() {
+ return {
+ type: SET_ENV_SELECTED_NODE_AS_ENV,
+ };
+}
+
+export function setEnvEnvId(_id) {
+ return {
+ type: SET_ENV_ENV_ID,
+ payload: {
+ _id: _id
+ }
+ };
+}
+
+export function setEnvSelectedNodeInfo(nodeInfo) {
+ return {
+ type: SET_ENV_SELECTED_NODE_INFO,
+ payload: {
+ nodeInfo: nodeInfo
+ }
+ };
+}
+
+export function setEnvAsLoaded() {
+ return {
+ type: SET_ENV_AS_LOADED,
+ };
+}
+
+export function setEnvAsNotLoaded() {
+ return {
+ type: SET_ENV_AS_NOT_LOADED
+ };
+}
+
+export function setShowDashboard() {
+ return {
+ type: SET_SHOW_DASHBOARD
+ };
+}
+
+export function setShowGraph() {
+ return {
+ type: SET_SHOW_GRAPH
+ };
+}
+
+export function toggleEnvShow() {
+ return {
+ type: TOGGLE_ENV_SHOW
+ };
+}
+
+export function setEnvPositionReportIsNeededAsOn(nodePath) {
+ return {
+ type: SET_ENV_POSITION_REPORT_IS_NEEDED_AS_ON,
+ payload: {
+ nodePath: nodePath
+ }
+ };
+}
+
+export function reportEnvNodePositionRetrieved(nodePath, rect) {
+ return {
+ type: REPORT_ENV_NODE_POSITION_RETRIEVED,
+ payload: {
+ nodePath: nodePath,
+ rect: rect
+ }
+ };
+}
+
+export function setEnvScrollToNodeIsNeededAsOn(nodePath) {
+ return {
+ type: SET_ENV_SCROLL_TO_NODE_IS_NEEDED_AS_ON,
+ payload: {
+ nodePath: nodePath
+ }
+ };
+}
+
+export function reportEnvScrollToNodePerformed(nodePath) {
+ return {
+ type: REPORT_ENV_SCROLL_TO_NODE_PERFORMED,
+ payload: {
+ nodePath: nodePath
+ }
+ };
+}
+
+export function resetEnvNeedChildDetection(nodePath) {
+ return {
+ type: RESET_ENV_NEED_CHILD_DETECTION,
+ payload: {
+ nodePath: nodePath
+ }
+ };
+}
diff --git a/ui/imports/ui/actions/graph-tooltip-window.actions.js b/ui/imports/ui/actions/graph-tooltip-window.actions.js
new file mode 100644
index 0000000..08c48b6
--- /dev/null
+++ b/ui/imports/ui/actions/graph-tooltip-window.actions.js
@@ -0,0 +1,30 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+//import * as R from 'ramda';
+
+export const ACTIVATE_GRAPH_TOOLTIP_WINDOW = 'ACTIVATE_GRAPH_TOOLTIP_WINDOW';
+export const CLOSE_GRAPH_TOOLTIP_WINDOW = 'CLOSE_GRAPH_TOOLTIP_WINDOW';
+
+export function activateGraphTooltipWindow(label, attributes, left, top) {
+ return {
+ type: ACTIVATE_GRAPH_TOOLTIP_WINDOW,
+ payload: {
+ label: label,
+ attributes: attributes,
+ left: left,
+ top: top
+ }
+ };
+}
+
+export function closeGraphTooltipWindow() {
+ return {
+ type: CLOSE_GRAPH_TOOLTIP_WINDOW
+ };
+}
diff --git a/ui/imports/ui/actions/main-app.actions.js b/ui/imports/ui/actions/main-app.actions.js
new file mode 100644
index 0000000..1daafa1
--- /dev/null
+++ b/ui/imports/ui/actions/main-app.actions.js
@@ -0,0 +1,21 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+//import * as R from 'ramda';
+
+export const SET_MAIN_APP_SELECTED_ENVIRONMENT = 'SET_MAIN_APP_SELECTED_ENVIRONMENT';
+
+export function setMainAppSelectedEnvironment(_id, name) {
+ return {
+ type: SET_MAIN_APP_SELECTED_ENVIRONMENT,
+ payload: {
+ _id: _id,
+ name: name
+ }
+ };
+}
diff --git a/ui/imports/ui/actions/navigation.js b/ui/imports/ui/actions/navigation.js
new file mode 100644
index 0000000..06b1501
--- /dev/null
+++ b/ui/imports/ui/actions/navigation.js
@@ -0,0 +1,84 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import * as R from 'ramda';
+
+const SET_CURRENT_NODE = 'SET_CURRENT_NODE';
+const SET_CURRENT_NODE_FROM_TREE_CONTROL = 'SET_CURRENT_NODE_FROM_TREE_CONTROL';
+
+function setCurrentNode(item) {
+ let nodeChain = convertToNodeChain(item.id_path, item.name_path);
+ R.last(nodeChain).item = item;
+
+ return {
+ type: SET_CURRENT_NODE,
+ payload: {
+ nodeChain: nodeChain
+ }
+ };
+}
+
+function setCurrentNodeFromTreeControl (item) {
+ let nodeChain = convertToNodeChain(item.id_path, item.name_path);
+ R.last(nodeChain).item = item;
+
+ return {
+ type: SET_CURRENT_NODE_FROM_TREE_CONTROL,
+ payload: {
+ nodeChain: nodeChain
+ }
+ };
+}
+
+function convertToNodeChain(idPath, namePath) {
+ let convert = R.pipe(R.split(), R.slice(1, Infinity));
+ let paths = convert('/', idPath);
+ let names = convert('/', namePath);
+ let nodesData = R.zip(paths, names);
+ let nodeChain = R.map((nodeData) => {
+ return {
+ id: nodeData[0],
+ name: nodeData[1]
+ };
+ }, nodesData);
+
+ let parent = null;
+
+ for (let i = 0; i < nodeChain.length; i++) {
+ let node = nodeChain[i];
+ node.parent = parent;
+ node.fullIdPath = calcFullIdPath(node);
+ node.fullNamePath = calcFullNamePath(node);
+ parent = node;
+ }
+
+ return nodeChain;
+}
+
+function calcFullIdPath (node) {
+ if (R.isNil(node)) { return null; }
+ if (R.isNil(node.parent)) { return '/' + node.id; }
+
+ let parentFullPath = calcFullIdPath(node.parent);
+ return parentFullPath + '/' + node.id;
+}
+
+function calcFullNamePath (node) {
+ if (R.isNil(node)) { return null; }
+ if (R.isNil(node.parent)) { return '/' + node.name; }
+
+ let parentFullPath = calcFullNamePath(node.parent);
+ return parentFullPath + '/' + node.name;
+}
+
+export {
+ SET_CURRENT_NODE,
+ SET_CURRENT_NODE_FROM_TREE_CONTROL,
+ setCurrentNode,
+ setCurrentNodeFromTreeControl
+};
diff --git a/ui/imports/ui/actions/search-interested-parties.js b/ui/imports/ui/actions/search-interested-parties.js
new file mode 100644
index 0000000..1eb9b78
--- /dev/null
+++ b/ui/imports/ui/actions/search-interested-parties.js
@@ -0,0 +1,93 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+//import * as R from 'ramda';
+
+const ADD_SEARCH_INTERESTED_PARTY = 'ADD_SEARCH_INTERESTED_PARTY';
+const REMOVE_SEARCH_INTERESTED_PARTY = 'REMOVE_SEARCH_INTERESTED_PARTY';
+const SET_SEARCH_TERM = 'SET_SEARCH_TERM';
+const SET_SEARCH_AUTO_COMPLETE_TERM = 'SET_SEARCH_AUTO_COMPLETE_TERM';
+const RESET_SEARCH_AUTO_COMPLETE_FUTURE = 'RESET_SEARCH_AUTO_COMPLETE_FUTURE';
+const SET_SEARCH_AUTO_COMPLETE_FUTURE = 'SET_SEARCH_AUTO_COMPLETE_FUTURE';
+
+const AUTO_COMPLETE_DELAY = 300; // miliseconds.
+
+function addSearchInterestedParty(listener) {
+ return {
+ type: ADD_SEARCH_INTERESTED_PARTY,
+ payload: {
+ listener: listener
+ }
+ };
+}
+
+function removeSearchInterestedParty(listener) {
+ return {
+ type: REMOVE_SEARCH_INTERESTED_PARTY,
+ payload: {
+ listener: listener
+ }
+ };
+}
+
+function setSearchTerm(searchTerm) {
+ return {
+ type: SET_SEARCH_TERM,
+ payload: {
+ searchTerm: searchTerm
+ }
+ };
+}
+
+function setSearchAutoCompleteTerm(searchTerm) {
+ return {
+ type: SET_SEARCH_AUTO_COMPLETE_TERM,
+ payload: {
+ searchTerm: searchTerm
+ }
+ };
+}
+
+function resetSearchAutoCompleteFuture() {
+ return {
+ type: RESET_SEARCH_AUTO_COMPLETE_FUTURE,
+ };
+}
+
+function setSearchAutoCompleteFuture(futureId) {
+ return {
+ type: SET_SEARCH_AUTO_COMPLETE_FUTURE,
+ payload: {
+ futureId: futureId
+ }
+ };
+}
+
+function notifySearchAutoCompleteTermChanged(searchTerm) {
+ return (dispatch) => {
+ let autoCompleteFutureId = setTimeout(() => {
+ dispatch(resetSearchAutoCompleteFuture());
+ dispatch(setSearchAutoCompleteTerm(searchTerm));
+ }, AUTO_COMPLETE_DELAY);
+ dispatch(setSearchAutoCompleteFuture(autoCompleteFutureId));
+ };
+}
+
+export {
+ ADD_SEARCH_INTERESTED_PARTY,
+ REMOVE_SEARCH_INTERESTED_PARTY,
+ SET_SEARCH_TERM,
+ SET_SEARCH_AUTO_COMPLETE_TERM,
+ RESET_SEARCH_AUTO_COMPLETE_FUTURE,
+ SET_SEARCH_AUTO_COMPLETE_FUTURE,
+ addSearchInterestedParty,
+ removeSearchInterestedParty,
+ setSearchTerm,
+ setSearchAutoCompleteTerm,
+ notifySearchAutoCompleteTermChanged
+};
diff --git a/ui/imports/ui/actions/tree-node.actions.js b/ui/imports/ui/actions/tree-node.actions.js
new file mode 100644
index 0000000..0fad8c9
--- /dev/null
+++ b/ui/imports/ui/actions/tree-node.actions.js
@@ -0,0 +1,144 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+//import * as R from 'ramda';
+
+export const UPDATE_TREE_NODE_INFO = 'UPDATE_TREE_NODE_INFO';
+export const ADD_UPDATE_CHILDREN_TREE_NODE = 'ADD_UPDATE_CHILDREN_TREE_NODE';
+export const RESET_TREE_NODE_CHILDREN = 'RESET_TREE_NODE_CHILDREN';
+export const START_OPEN_TREE_NODE = 'START_OPEN_TREE_NODE';
+export const END_OPEN_TREE_NODE = 'END_OPEN_TREE_NODE';
+export const START_CLOSE_TREE_NODE = 'START_CLOSE_TREE_NODE';
+export const END_CLOSE_TREE_NODE = 'END_CLOSE_TREE_NODE';
+export const SET_CHILD_DETECTED_TREE_NODE = 'SET_CHILD_DETECTED_TREE_NODE';
+export const SET_POSITION_REPORT_IS_NEEDED_AS_ON = 'SET_POSITION_REPORT_IS_NEEDED_AS_ON';
+export const REPORT_NODE_POSITION_RETRIEVED = 'REPORT_NODE_POSITION_RETRIEVED';
+export const SET_SCROLL_TO_NODE_IS_NEEDED_AS_ON = 'SET_SCROLL_TO_NODE_IS_NEEDED_AS_ON';
+export const REPORT_SCROLL_TO_NODE_PERFORMED = 'REPORT_SCROLL_TO_NODE_PERFORMED';
+export const RESET_NEED_CHILD_DETECTION = 'RESET_NEED_CHILD_DETECTION';
+
+export function updateTreeNodeInfo(nodeInfo, level) {
+ return {
+ type: UPDATE_TREE_NODE_INFO,
+ payload: {
+ nodeInfo: nodeInfo,
+ level: level
+ }
+ };
+}
+
+export function addUpdateChildrenTreeNode(nodePath, childrenInfo, level) {
+ return {
+ type: ADD_UPDATE_CHILDREN_TREE_NODE,
+ payload: {
+ nodePath: nodePath,
+ childrenInfo: childrenInfo,
+ level: level
+ },
+ };
+}
+
+export function resetTreeNodeChildren(nodePath) {
+ return {
+ type: RESET_TREE_NODE_CHILDREN,
+ payload: {
+ nodePath: nodePath,
+ }
+ };
+}
+
+export function startOpenTreeNode(nodePath) {
+ return {
+ type: START_OPEN_TREE_NODE,
+ payload: {
+ nodePath: nodePath,
+ }
+ };
+}
+
+export function endOpenTreeNode(nodePath) {
+ return {
+ type: END_OPEN_TREE_NODE,
+ payload: {
+ nodePath: nodePath,
+ }
+ };
+}
+
+export function startCloseTreeNode(nodePath) {
+ return {
+ type: START_CLOSE_TREE_NODE,
+ payload: {
+ nodePath: nodePath,
+ }
+ };
+}
+
+export function endCloseTreeNode(nodePath) {
+ return {
+ type: END_CLOSE_TREE_NODE,
+ payload: {
+ nodePath: nodePath,
+ }
+ };
+}
+
+export function setChildDetectedTreeNode(nodePath) {
+ return {
+ type: SET_CHILD_DETECTED_TREE_NODE,
+ payload: {
+ nodePath: nodePath
+ }
+ };
+}
+
+export function setPositionReportIsNeededAsOn(nodePath) {
+ return {
+ type: SET_POSITION_REPORT_IS_NEEDED_AS_ON,
+ payload: {
+ nodePath: nodePath
+ }
+ };
+}
+
+export function reportNodePositionRetrieved(nodePath, rect) {
+ return {
+ type: REPORT_NODE_POSITION_RETRIEVED,
+ payload: {
+ nodePath: nodePath,
+ rect: rect
+ }
+ };
+}
+
+export function setScrollToNodeIsNeededAsOn(nodePath) {
+ return {
+ type: SET_SCROLL_TO_NODE_IS_NEEDED_AS_ON,
+ payload: {
+ nodePath: nodePath
+ }
+ };
+}
+
+export function reportScrollToNodePerformed(nodePath) {
+ return {
+ type: REPORT_SCROLL_TO_NODE_PERFORMED,
+ payload: {
+ nodePath: nodePath
+ }
+ };
+}
+
+export function resetNeedChildDetection(nodePath) {
+ return {
+ type: RESET_NEED_CHILD_DETECTION,
+ payload: {
+ nodePath: nodePath
+ }
+ };
+}
diff --git a/ui/imports/ui/actions/vedge-info-window.actions.js b/ui/imports/ui/actions/vedge-info-window.actions.js
new file mode 100644
index 0000000..0431648
--- /dev/null
+++ b/ui/imports/ui/actions/vedge-info-window.actions.js
@@ -0,0 +1,41 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+//import * as R from 'ramda';
+
+export const ACTIVATE_VEDGE_INFO_WINDOW = 'ACTIVATE_VEDGE_INFO_WINDOW';
+export const CLOSE_VEDGE_INFO_WINDOW = 'CLOSE_VEDGE_INFO_WINDOW';
+
+export function activateVedgeInfoWindow(node, left, top) {
+ // todo: remove. this is for debug
+ /*
+ node = {
+ _id: '0',
+ id: 'devstack-vpp1-VPP',
+ id_path: '',
+ name: 'devstack-vpp1-VPP',
+ name_path: '',
+ environment: 'Devstack-VPP'
+ };
+ */
+
+ return {
+ type: ACTIVATE_VEDGE_INFO_WINDOW,
+ payload: {
+ node: node,
+ left: left,
+ top: top
+ }
+ };
+}
+
+export function closeVedgeInfoWindow() {
+ return {
+ type: CLOSE_VEDGE_INFO_WINDOW
+ };
+}
diff --git a/ui/imports/ui/components/accordion-nav-menu/accordion-nav-menu.html b/ui/imports/ui/components/accordion-nav-menu/accordion-nav-menu.html
new file mode 100644
index 0000000..d4cf5de
--- /dev/null
+++ b/ui/imports/ui/components/accordion-nav-menu/accordion-nav-menu.html
@@ -0,0 +1,43 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="accordionNavMenu">
+ <nav class="os-accordion-nav-menu">
+ <div id="left-nav-menu" class="left-nav-menu">
+ <div class="menu-header">
+ <p>
+ {{ envName }}
+ </p>
+ <a href="#"><i class="material-icons">menu</i></a>
+ </div>
+ <ul class="sm-menu-items-list">
+ <li>
+ <a class="sm-toggle-graph-button toggleGraph" href="#">
+ <span><i class="material-icons">share</i>Toggle Graph</span>
+ </a>
+ </li>
+ <li>
+ <a class="sm-btn-dashboard">
+ <span><i class="material-icons">home</i>Dashboard</span>
+ </a>
+ </li>
+
+ {{#if mainNode }}
+ <div class="sm-inventory-tree">
+ {{>TreeNode (argsTreeNode mainNode) }}
+ </div>
+ {{/if }}
+
+ <li><a href="#"><i class="fa fa-envelope"> </i> Contact</a></li>
+ </ul>
+ <div class="menu-footer"> Cisco Systems inc. All rights reserved.</div>
+ </div>
+ </nav>
+</template>
diff --git a/ui/imports/ui/components/accordion-nav-menu/accordion-nav-menu.js b/ui/imports/ui/components/accordion-nav-menu/accordion-nav-menu.js
new file mode 100644
index 0000000..b3bafa2
--- /dev/null
+++ b/ui/imports/ui/components/accordion-nav-menu/accordion-nav-menu.js
@@ -0,0 +1,155 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ Template Component: accordionNavMenu
+ */
+
+/* eslint indent: "off" */
+
+import * as R from 'ramda';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+//import { Tracker } from 'meteor/tracker';
+//import { Session } from 'meteor/session';
+//import { InventoryTreeNodeBehavior } from '/imports/ui/lib/inventory-tree-node-behavior';
+import { EnvironmentTreeNodeBehavior } from '/imports/ui/lib/environment-tree-node-behavior';
+//import { Inventory } from '/imports/api/inventories/inventories';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+
+import '/imports/ui/components/tree-node/tree-node';
+import '/imports/ui/components/accordionTreeNode/accordionTreeNode';
+import '/imports/ui/components/d3graph/d3graph';
+
+import { store } from '/imports/ui/store/store';
+import {
+ resetEnvTreeNodeChildren,
+ addUpdateEnvTreeNode,
+ addUpdateChildrenEnvTreeNode,
+ startOpenEnvTreeNode,
+ startCloseEnvTreeNode,
+ endCloseEnvTreeNode,
+ setEnvChildDetectedTreeNode,
+} from '/imports/ui/actions/environment-panel.actions';
+
+import './accordion-nav-menu.html';
+
+Template.accordionNavMenu.onCreated(function () {
+ let instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault ({});
+
+ createAttachedFns(instance);
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+
+ new SimpleSchema({
+ envName: { type: String },
+ mainNode: { type: Object, blackbox: true },
+ onOpeningDone: { type: Function },
+ onNodeSelected: { type: Function },
+ onToggleGraphReq: { type: Function },
+ onResetSelectedNodeReq: { type: Function },
+ onPositionRetrieved: { type: Function },
+ onScrollToNodePerformed: { type: Function },
+ onOpenLinkReq: { type: Function },
+ onResetNeedChildDetection: { type: Function },
+ }).validate(data);
+ });
+
+});
+
+
+Template.accordionNavMenu.rendered = function () {
+};
+
+Template.accordionNavMenu.onDestroyed(function () {
+});
+
+/*
+ * Events
+ */
+
+Template.accordionNavMenu.events({
+ 'click .sm-btn-dashboard': function (_event, _instance) {
+ let data = Template.currentData();
+ data.onResetSelectedNodeReq();
+ },
+
+ 'click .sm-toggle-graph-button': function (_event, _instance) {
+ let data = Template.currentData();
+ data.onToggleGraphReq();
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.accordionNavMenu.helpers({
+ argsTreeNode: function (node) {
+ let instance = Template.instance();
+ let data = Template.currentData();
+
+ return {
+ behavior: EnvironmentTreeNodeBehavior,
+ showDetailsLine: false,
+ openState: node.openState,
+ node: node.nodeInfo,
+ children: node.children,
+ childDetected: node.childDetected,
+ needChildDetection: node.needChildDetection,
+ linkDetected: node.linkDetected,
+ level: node.level,
+ positionNeeded: node.positionNeeded,
+ scrollToNodeIsNeeded: node.scrollToNodeIsNeeded,
+ onResetChildren: instance._fns.onResetChildren,
+ onChildRead: instance._fns.onChildRead,
+ onChildrenRead: instance._fns.onChildrenRead,
+ onStartOpenReq: instance._fns.onStartOpenReq,
+ onStartCloseReq: instance._fns.onStartCloseReq,
+ onClosingDone: instance._fns.onClosingDone,
+ onChildDetected: instance._fns.onChildDetected,
+ onOpeningDone: data.onOpeningDone,
+ onNodeSelected: data.onNodeSelected,
+ onPositionRetrieved: data.onPositionRetrieved,
+ onScrollToNodePerformed: data.onScrollToNodePerformed,
+ onOpenLinkReq: data.onOpenLinkReq,
+ onResetNeedChildDetection: data.onResetNeedChildDetection,
+ };
+ }
+}); // end: helpers
+
+function createAttachedFns(instance) {
+
+ instance._fns = {
+ onResetChildren: function (nodePath) {
+ store.dispatch(resetEnvTreeNodeChildren(R.tail(nodePath)));
+ },
+ onChildRead: function (nodePath, childNode) {
+ store.dispatch(addUpdateEnvTreeNode(R.tail(nodePath), childNode));
+ },
+ onChildrenRead: function (nodePath, childrenInfo) {
+ store.dispatch(addUpdateChildrenEnvTreeNode(R.tail(nodePath), childrenInfo));
+ },
+ onStartOpenReq: (nodePath) => {
+ store.dispatch(startOpenEnvTreeNode(R.tail(nodePath)));
+ },
+ onStartCloseReq: (nodePath) => {
+ store.dispatch(startCloseEnvTreeNode(R.tail(nodePath)));
+ },
+ onClosingDone: (nodePath) => {
+ store.dispatch(endCloseEnvTreeNode(R.tail(nodePath)));
+ },
+ onChildDetected: (nodePath) => {
+ store.dispatch(setEnvChildDetectedTreeNode(R.tail(nodePath)));
+ },
+ };
+}
diff --git a/ui/imports/ui/components/accordion-nav-menu/accordion-nav-menu.styl b/ui/imports/ui/components/accordion-nav-menu/accordion-nav-menu.styl
new file mode 100644
index 0000000..b3c9ef1
--- /dev/null
+++ b/ui/imports/ui/components/accordion-nav-menu/accordion-nav-menu.styl
@@ -0,0 +1,11 @@
+.os-accordion-nav-menu
+ .sm-btn-dashboard
+ cursor: pointer;
+
+ .sm-inventory-tree
+ float: left;
+ width: 100%;
+
+ display: flex;
+ flex-flow: column;
+ align-items: stretch;
diff --git a/ui/imports/ui/components/accordion-wiki-menu/accordion-wiki-menu.html b/ui/imports/ui/components/accordion-wiki-menu/accordion-wiki-menu.html
new file mode 100644
index 0000000..921c8cd
--- /dev/null
+++ b/ui/imports/ui/components/accordion-wiki-menu/accordion-wiki-menu.html
@@ -0,0 +1,42 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="accordionWikiMenu">
+
+ <div class="left-nav-content-wiki">
+ <nav>
+ <div id="left-nav-menu" class="left-nav-menu">
+ <div class="menu-header"> Let's get started! </div>
+ <ul>
+ <li><a href="#wikiFirstStep"><i class="material-icons">class</i> First step</a></li>
+ <li><a href="#wikiAddNewEnv">
+ <i class="material-icons">class</i> Add new environment</a></li>
+ <li><a href="#wikiAccessSwitchEnv">
+ <i class="material-icons">class</i> Access and Switch environment</a></li>
+ <li><a href="#wikiMainDashboard">
+ <i class="material-icons">class</i> Main Dashboard</a></li>
+ <li><a href="#wikiMainMessages"><i class="material-icons">class</i> Main messages</a></li>
+ <li><a href="#wikiWorkWithEnvs">
+ <i class="material-icons">class</i> Work with environments</a></li>
+ <li><a href="#wikiScanningEnv">
+ <i class="material-icons">class</i> Scanning an environment</a></li>
+ <li><a href="#wikiDeletingEnv">
+ <i class="material-icons">class</i> Deleting an environment</a></li>
+ <li><a href="#wikiEditingEnv">
+ <i class="material-icons">class</i> Editing an environment</a></li>
+ <li><a href="#wikiCalipsoSetting"><i class="material-icons">class</i> Calipso setting</a></li>
+ <li><a href="#wikiBrowsingEnv"><i class="material-icons">class</i> Browsing your cloud environment</a></li>
+ </ul>
+ <div class="menu-footer"> Cisco Systems inc. All rights reserved.</div>
+ </div>
+ </nav>
+ </div>
+
+</template>
diff --git a/ui/imports/ui/components/accordion-wiki-menu/accordion-wiki-menu.js b/ui/imports/ui/components/accordion-wiki-menu/accordion-wiki-menu.js
new file mode 100644
index 0000000..68f38d1
--- /dev/null
+++ b/ui/imports/ui/components/accordion-wiki-menu/accordion-wiki-menu.js
@@ -0,0 +1,147 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: accordionWikiMenu
+ */
+
+import { Template } from 'meteor/templating';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+
+//import { store } from '/imports/ui/store/store';
+//import { setCurrentNode } from '/imports/ui/actions/navigation';
+
+import './accordion-wiki-menu.html';
+
+Template.accordionWikiMenu.rendered = function () {
+
+ // init wow lib
+ new WOW().init();
+
+ // smooth scrolling function
+ $(function() {
+ $('a[href*="#"]:not([href="#"])').click(function() {
+ if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') && location.hostname == this.hostname) {
+ var target = $(this.hash);
+ target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
+ if (target.length) {
+ $('html, body').animate({
+ scrollTop: target.offset().top - 90
+ }, 1000);
+ return false;
+ }
+ }
+ });
+ });
+
+ /* accordion menu plugin*/
+ (function($, window, _document, _undefined) {
+ var pluginName = 'accordion';
+ var defaults = {
+ speed: 200,
+ showDelay: 0,
+ hideDelay: 0,
+ singleOpen: true,
+ clickEffect: true,
+ indicator: 'submenu-indicator-minus',
+ subMenu: 'submenu',
+ event: 'click touchstart' // click, touchstart
+ };
+
+ function Plugin(element, options) {
+ this.element = element;
+ this.settings = $.extend({}, defaults, options);
+ this._defaults = defaults;
+ this._name = pluginName;
+ this.init();
+ }
+ $.extend(Plugin.prototype, {
+ init: function() {
+ this.openSubmenu();
+ this.submenuIndicators();
+ if (defaults.clickEffect) {
+ this.addClickEffect();
+ }
+ },
+ openSubmenu: function() {
+ $(this.element).children('ul').find('li').bind(defaults.event, function(e) {
+ e.stopPropagation();
+ e.preventDefault();
+ var $subMenus = $(this).children('.' + defaults.subMenu);
+ var $allSubMenus = $(this).find('.' + defaults.subMenu);
+ if ($subMenus.length > 0) {
+ if ($subMenus.css('display') == 'none') {
+ $subMenus.slideDown(defaults.speed).siblings('a').addClass(defaults.indicator);
+ if (defaults.singleOpen) {
+ $(this).siblings().find('.' + defaults.subMenu).slideUp(defaults.speed)
+ .end().find('a').removeClass(defaults.indicator);
+ }
+ return false;
+ } else {
+ $(this).find('.' + defaults.subMenu).delay(defaults.hideDelay).slideUp(defaults.speed);
+ }
+ if ($allSubMenus.siblings('a').hasClass(defaults.indicator)) {
+ $allSubMenus.siblings('a').removeClass(defaults.indicator);
+ }
+ }
+ window.location.href = $(this).children('a').attr('href');
+ });
+ },
+ submenuIndicators: function() {
+ if ($(this.element).find('.' + defaults.subMenu).length > 0) {
+ $(this.element).find('.' + defaults.subMenu).siblings('a').append('<span class="submenu-indicator">+</span>');
+ }
+ },
+ addClickEffect: function() {
+ var ink, d, x, y;
+ $(this.element).find('a').bind('click touchstart', function(e) {
+ $('.ink').remove();
+ if ($(this).children('.ink').length === 0) {
+ $(this).prepend('<span class="ink"></span>');
+ }
+ ink = $(this).find('.ink');
+ ink.removeClass('animate-ink');
+ if (!ink.height() && !ink.width()) {
+ d = Math.max($(this).outerWidth(), $(this).outerHeight());
+ ink.css({
+ height: d,
+ width: d
+ });
+ }
+ x = e.pageX - $(this).offset().left - ink.width() / 2;
+ y = e.pageY - $(this).offset().top - ink.height() / 2;
+ ink.css({
+ top: y + 'px',
+ left: x + 'px'
+ }).addClass('animate-ink');
+ });
+ }
+ });
+ $.fn[pluginName] = function(options) {
+ this.each(function() {
+ if (!$.data(this, 'plugin_' + pluginName)) {
+ $.data(this, 'plugin_' + pluginName, new Plugin(this, options));
+ }
+ });
+ return this;
+ };
+ })(jQuery, window, document);
+
+ jQuery(document).ready(function($) {
+ $('#left-nav-menu').accordion();
+ $('.colors a').click(function() {
+ if ($(this).attr('class') != 'default') {
+ $('#left-nav-menu').removeClass();
+ $('#left-nav-menu').addClass('menu').addClass($(this).attr('class'));
+ } else {
+ $('#left-nav-menu').removeClass();
+ $('#left-nav-menu').addClass('menu');
+ }
+ });
+ });
+};
diff --git a/ui/imports/ui/components/accordionTreeNode/accordion-tree-node.styl b/ui/imports/ui/components/accordionTreeNode/accordion-tree-node.styl
new file mode 100644
index 0000000..b41d1f9
--- /dev/null
+++ b/ui/imports/ui/components/accordionTreeNode/accordion-tree-node.styl
@@ -0,0 +1,5 @@
+.os-accordion-tree-node
+ .js-item-link
+ display: flex;
+ justify-content: space-between;
+
diff --git a/ui/imports/ui/components/accordionTreeNode/accordionTreeNode.html b/ui/imports/ui/components/accordionTreeNode/accordionTreeNode.html
new file mode 100644
index 0000000..cb1b10e
--- /dev/null
+++ b/ui/imports/ui/components/accordionTreeNode/accordionTreeNode.html
@@ -0,0 +1,54 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="accordionTreeNode" >
+ <li class="os-accordion-tree-node {{#if node.clique }}genGraphClick{{/if}}"
+ id="{{ node.id }}"
+ title="{{ node.name }}"
+ type="{{ node.type }}"
+ clique="{{ node.clique }}"
+ objId="{{ node._id._str }}"
+ >
+ <a class="js-item-link" href="#">
+ <div class="sm-node-content-part">
+ {{#if hasChildren }}
+ <i class="material-icons">class</i>
+ {{else }}
+ <i class="material-icons">description</i>
+ {{/if }}
+
+ {{ node.object_name }}
+ </div>
+
+ {{#if showNow }}
+
+ <div class="sm-open-close-indicator">
+ {{#if hasChildren }}
+
+ {{#if isOpen }}
+ <i class="fa fa-minus" aria-hidden="true"></i>
+ {{else }}
+ <i class="fa fa-plus" aria-hidden="true"></i>
+ {{/if }}
+
+ {{/if }}
+ </div>
+
+ {{/if }}
+ </a>
+
+ {{#if isNotClose }}
+ {{> accordionTreeNodeChildren (createChildrenArgs node selectedNode) }}
+ {{/if }}
+
+ </li>
+ {{ reactOnShowOpen(showOpen) }}
+ {{ reactOnNewData(node) }}
+</template>
diff --git a/ui/imports/ui/components/accordionTreeNode/accordionTreeNode.js b/ui/imports/ui/components/accordionTreeNode/accordionTreeNode.js
new file mode 100644
index 0000000..837c6a1
--- /dev/null
+++ b/ui/imports/ui/components/accordionTreeNode/accordionTreeNode.js
@@ -0,0 +1,284 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: accordionTreeNode
+ */
+
+/* eslint no-undef: off */
+
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+
+import { Inventory } from '/imports/api/inventories/inventories';
+//import { store } from '/client/imports/store';
+//import { setCurrentNode } from '/client/imports/actions/navigation';
+
+//import { d3Graph } from '/imports/lib/d3-graph';
+
+import '/imports/ui/components/accordionTreeNodeChildren/accordionTreeNodeChildren';
+import './accordionTreeNode.html';
+
+var subMenuClass = 'submenu';
+var switchingSpeed = 200;
+
+Template.accordionTreeNode.onCreated(function () {
+ var instance = this;
+ this.state = new ReactiveDict();
+ this.state.setDefault({
+ openState: 'close',
+ needChildrenClosing: false,
+ openedChildId: null,
+ showNow: false,
+ startAsClickedState: 'not_done',
+ data: null,
+ });
+
+ instance.autorun(function () {
+ //var tempData = instance.state.get('data');
+
+ let data = Template.currentData();
+ let node = data.node;
+ instance.subscribe('inventory.first-child',
+ node.id, node.type, node.name, node.environment);
+ });
+
+});
+
+Template.accordionTreeNode.rendered = function () {
+ var instance = this;
+
+ setTimeout(function () {
+ instance.state.set('showNow', true);
+ }, 50);
+
+ instance.autorun(function () {
+ var openState = instance.state.get('openState');
+ switch (openState) {
+ case 'opening':
+ // Blaze arcitecture bug: in render the children are not it rendered.
+ // There for we need to wait until children are rendered to do the animation.
+ instance.state.set('openState', 'open');
+ activateNodeAction(instance);
+ setTimeout(function () {
+ animateOpening(instance.$(instance.firstNode));
+ }, 65);
+ break;
+
+ case 'closing':
+
+ animateClosing(instance.$(instance.firstNode));
+ setTimeout(function () {
+ instance.state.set('openState', 'close');
+ //instance.data.onClose(instance.data.node.id);
+ }, 200);
+ break;
+
+ case 'none':
+ break;
+
+ default:
+ break;
+ }
+ });
+
+};
+
+Template.accordionTreeNode.helpers({
+ reactOnShowOpen: function (showOpen) {
+ let instance = Template.instance();
+ let openState = instance.state.get('openState');
+ let nextOpenState = null;
+
+ if (showOpen === false) {
+ if (openState === 'open' ||
+ openState === 'opening') {
+ nextOpenState = 'closing';
+ }
+ } else if (showOpen === true) {
+ if (openState === 'close' ||
+ openState === 'closing') {
+ nextOpenState = 'opening';
+ }
+ }
+
+ if (nextOpenState) {
+ setTimeout(function () {
+ instance.state.set('openState', nextOpenState);
+ }, 10);
+ }
+ },
+
+ reactOnNewData: function (node) {
+ let instance = Template.instance();
+ instance.state.set('data', { node: node });
+ },
+
+ isNot: function (condition) {
+ return ! condition;
+ },
+
+ isNotClose: function () {
+ var instance = Template.instance();
+ var openState = instance.state.get('openState');
+ return (openState !== 'close');
+ },
+
+ hasClique: function(){
+ var controller = Iron.controller();
+ var envName = controller.state.get('envName');
+
+ if(Inventory.find({
+ parent_id: this.node.id,
+ parent_type: this.node.type,
+ environment: envName,
+ clique:true,
+ show_in_tree:true
+ }).count() > 0){
+
+ return 'true';
+ }
+ else{
+ return 'false';
+ }
+
+ },
+
+ hasChildren: function(){
+ return hasChildren(this);
+ },
+
+ isOpen: function () {
+ var instance = Template.instance();
+ return instance.state.get('openState') === 'open';
+ },
+
+ isOpenOrOpening: function () {
+ var instance = Template.instance();
+ var openState = instance.state.get('openState');
+ return (openState === 'open' || openState === 'opening');
+ },
+
+ createChildrenArgs: function(
+ parentNode,
+ selectedNode
+ ) {
+
+ let instance = Template.instance();
+ return {
+ node: parentNode,
+ selectedNode: selectedNode,
+ onClick(childNode) {
+ instance.data.onClick(childNode);
+ },
+ };
+ },
+
+ isNeedChildrenClosing: function () {
+ var instance = Template.instance();
+ return instance.state.get('needChildrenClosing');
+ },
+
+ closeWhenNeeded: function() {
+ var instance = Template.instance();
+ var openState = instance.state.get('openState');
+
+ if (! singleOpenOption) { return; }
+ if (! instance.data.openedFamilyId) { return; }
+ if (openState !== 'open') { return; }
+ if (instance.data.node.id === instance.data.openedFamilyId) { return; }
+
+ instance.state.set('openState', 'closing');
+ },
+
+ showNow: function () {
+ var instance = Template.instance();
+ return instance.state.get('showNow');
+ },
+});
+
+Template.accordionTreeNode.events({
+ 'click': function(event, instance){
+ event.stopPropagation();
+ event.preventDefault();
+
+ instance.data.onClick(instance.data.node);
+
+ /*
+ * todo : remove code
+ store.dispatch(setCurrentNode(
+ instance.data.node.id_path,
+ instance.data.node.name_path));
+
+ var openState = instance.state.get('openState');
+ var nextState = openState;
+
+ if (hasChildren(instance.data)) {
+ switch (openState) {
+ case 'open':
+ nextState = 'closing';
+ break;
+
+ case 'opening':
+ break;
+
+ case 'close':
+ nextState = 'opening';
+ break;
+
+ case 'closing':
+ break;
+ }
+
+ instance.state.set('openState', nextState);
+
+ }
+
+
+ */
+ },
+});
+
+function activateNodeAction (_instance) {
+
+}
+
+function hasChildren(instance) {
+ var counterName = 'inventory.first-child!counter!id=' + instance.node.id;
+ return Counts.get(counterName) > 0;
+
+ /*
+ var controller = Iron.controller();
+ var envName = controller.state.get('envName');
+
+ return hasChildrenQuery(instance.node, envName);
+ */
+}
+
+/*
+function hasChildrenQuery(node, envName) {
+ return Inventory.find({
+ parent_id: node.id,
+ parent_type: node.type,
+ environment: envName,
+ show_in_tree: true
+ }, {
+ limit: 1
+ }).count() > 0;
+}
+*/
+
+function animateOpening($element) {
+ $subMenu = $element.children('.' + subMenuClass);
+ $subMenu.slideDown(switchingSpeed);
+}
+
+function animateClosing($element) {
+ $subMenu = $element.children('.' + subMenuClass);
+ $subMenu.slideUp(switchingSpeed);
+}
diff --git a/ui/imports/ui/components/accordionTreeNodeChildren/accordionTreeNodeChildren.html b/ui/imports/ui/components/accordionTreeNodeChildren/accordionTreeNodeChildren.html
new file mode 100644
index 0000000..786ecb1
--- /dev/null
+++ b/ui/imports/ui/components/accordionTreeNodeChildren/accordionTreeNodeChildren.html
@@ -0,0 +1,19 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="accordionTreeNodeChildren" >
+<ul class="submenu">
+ {{#each childItem in children }}
+ {{> accordionTreeNode (createTreeNodeArgs childItem selectedNode) }}
+ {{/each }}
+</ul>
+{{ reactOnNewData node }}
+</template>
+
diff --git a/ui/imports/ui/components/accordionTreeNodeChildren/accordionTreeNodeChildren.js b/ui/imports/ui/components/accordionTreeNodeChildren/accordionTreeNodeChildren.js
new file mode 100644
index 0000000..a74059c
--- /dev/null
+++ b/ui/imports/ui/components/accordionTreeNodeChildren/accordionTreeNodeChildren.js
@@ -0,0 +1,125 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: accordionTreeNodeChildren
+ */
+
+/* eslint no-undef: off */
+
+import * as R from 'ramda';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { Inventory } from '/imports/api/inventories/inventories';
+
+import './accordionTreeNodeChildren.html';
+
+Template.accordionTreeNodeChildren.onCreated(function () {
+ var instance = this;
+ this.state = new ReactiveDict();
+ this.state.setDefault({
+ data: null,
+ siblingId: null
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+ let node = data.node;
+ instance.subscribe('inventory.children',
+ node.id, node.type, node.name, node.environment);
+
+ if (R.equals('host_ref', node.type)) {
+ instance.subscribe('inventory?name&env&type',
+ node.name, node.environment, 'host');
+
+ Inventory.find({
+ name: node.name,
+ environment: node.environment,
+ type: 'host'
+ }).forEach((sibling) => {
+ instance.state.set('siblingId', sibling.id);
+ });
+ }
+ });
+
+});
+
+Template.accordionTreeNodeChildren.helpers({
+ reactOnNewData: function (node) {
+ let instance = Template.instance();
+ instance.state.set('data', { node: node });
+ },
+
+ children: function () {
+ let instance = Template.instance();
+ let siblingId = instance.state.get('siblingId');
+
+ return getChildrenQuery(instance.data.node, siblingId);
+ },
+
+ createTreeNodeArgs: function(
+ node,
+ selectedNode
+ ) {
+
+ var instance = Template.instance();
+
+ let firstChild = null;
+ let restOfChildren = null;
+ let showOpen = false;
+
+ if ((! R.isNil(selectedNode)) &&
+ selectedNode.length > 0
+ ) {
+ firstChild = selectedNode[0];
+ restOfChildren = selectedNode.length > 1 ?
+ R.slice(1, Infinity, selectedNode) : null;
+ showOpen = firstChild.id === node.id ? true : false;
+ }
+
+ return {
+ node: node,
+ showOpen: showOpen,
+ selectedNode: restOfChildren,
+ onClick: instance.data.onClick
+ };
+ },
+
+
+});
+
+Template.accordionTreeNodeChildren.events({
+});
+
+function getChildrenQuery(node, siblingId) {
+ let query =
+ {
+ $or: [
+ {
+ parent_id: node.id,
+ parent_type: node.type,
+ environment: node.environment,
+ show_in_tree: true
+ }
+ ]
+ };
+
+
+ if (R.equals('host_ref', node.type)) {
+ query = R.merge(query, {
+ $or: R.append({
+ parent_id: siblingId,
+ show_in_tree: true
+ }, query.$or)
+ });
+ }
+
+ console.log('getChildrenQuery', R.toString(query));
+
+ return Inventory.find(query);
+}
diff --git a/ui/imports/ui/components/aggregate-dashboard/aggregate-dashboard.html b/ui/imports/ui/components/aggregate-dashboard/aggregate-dashboard.html
new file mode 100644
index 0000000..b1769ca
--- /dev/null
+++ b/ui/imports/ui/components/aggregate-dashboard/aggregate-dashboard.html
@@ -0,0 +1,35 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="AggregateDashboard">
+ <div class="os-aggregate-dashboard flex-box justify-content-between">
+ <div class="flex-box-3 main-layout-no-nav">
+
+ <div class="flex">
+ <div class="flex-box-1 cards white title">
+ <h4>Aggregate name: {{ aggregate.name }}</h4>
+ </div>
+ </div>
+
+ <div class="sm-info-boxes">
+ {{#each infoBox in infoBoxes }}
+ {{> DataCubic (argsInfoBox infoBox) }}
+ {{/each }}
+ </div>
+
+ <div class="sm-list-info-boxes">
+ {{#each listInfoBox in listInfoBoxes }}
+ {{> ListInfoBox (argsListInfoBox listInfoBox) }}
+ {{/each }}
+ </div>
+
+ </div>
+ </div>
+</template>
diff --git a/ui/imports/ui/components/aggregate-dashboard/aggregate-dashboard.js b/ui/imports/ui/components/aggregate-dashboard/aggregate-dashboard.js
new file mode 100644
index 0000000..5e7278d
--- /dev/null
+++ b/ui/imports/ui/components/aggregate-dashboard/aggregate-dashboard.js
@@ -0,0 +1,212 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: AggregateDashboard
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { Inventory } from '/imports/api/inventories/inventories';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { regexEscape } from '/imports/lib/regex-utils';
+import * as R from 'ramda';
+import { store } from '/imports/ui/store/store';
+import { Icon } from '/imports/lib/icon';
+
+//import '/imports/ui/components/accordionNavMenu/accordionNavMenu';
+import '/imports/ui/components/data-cubic/data-cubic';
+import '/imports/ui/components/list-info-box/list-info-box';
+
+import './aggregate-dashboard.html';
+
+let infoBoxes = [{
+ header: ['components', 'aggregateDashboard', 'infoBoxes', 'instances', 'header'],
+ dataSource: 'instancesCount',
+ icon: { type: 'fa', name: 'desktop' },
+ theme: 'dark'
+}, {
+ header: ['components', 'aggregateDashboard', 'infoBoxes', 'vServices', 'header'],
+ dataSource: 'vServicesCount',
+ icon: { type: 'fa', name: 'object-group' },
+ theme: 'dark'
+}, {
+ header: ['components', 'aggregateDashboard', 'infoBoxes', 'hosts', 'header'],
+ dataSource: 'hostsCount',
+ icon: { type: 'fa', name: 'server' },
+ theme: 'dark'
+}, {
+ header: ['components', 'aggregateDashboard', 'infoBoxes', 'vConnectors', 'header'],
+ dataSource: 'vConnectorsCount',
+ icon: { type: 'fa', name: 'compress' },
+ theme: 'dark'
+}, {
+ header: ['components', 'aggregateDashboard', 'infoBoxes', 'vEdges', 'header'],
+ dataSource: 'vEdgesCount',
+ icon: { type: 'fa', name: 'external-link' },
+ theme: 'dark'
+}];
+
+let listInfoBoxes = [{
+ header: ['components', 'aggregateDashboard', 'listInfoBoxes', 'hosts', 'header'],
+ listName: 'hosts',
+ listItemFormat: { label: 'name', value: 'id_path' },
+ icon: { type: 'material', name: 'developer_board' },
+}];
+
+/*
+ * Lifecycles
+ */
+
+Template.AggregateDashboard.onCreated(function() {
+ var instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ _id: null,
+ id_path: null,
+ instancesCount: 0,
+ vServicesCount: 0,
+ hostsCount: 0,
+ vConnectors: 0,
+ vEdges: 0,
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+
+ new SimpleSchema({
+ _id: { type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } } },
+ onNodeSelected: { type: Function },
+ }).validate(data);
+
+ instance.state.set('_id', data._id);
+ });
+
+ instance.autorun(function () {
+ let _id = instance.state.get('_id');
+
+ instance.subscribe('inventory?_id', _id);
+ Inventory.find({ _id: _id }).forEach((aggr) => {
+ instance.state.set('id_path', aggr.id_path);
+
+ instance.subscribe('inventory?id_path', aggr.id_path);
+ instance.subscribe('inventory?id_path_start&type', aggr.id_path, 'instance');
+ instance.subscribe('inventory?id_path_start&type', aggr.id_path, 'vservice');
+ instance.subscribe('inventory?id_path_start&type', aggr.id_path, 'host');
+ instance.subscribe('inventory?id_path_start&type', aggr.id_path, 'vconnector');
+ instance.subscribe('inventory?id_path_start&type', aggr.id_path, 'vedge');
+
+ let idPathExp = new RegExp(`^${regexEscape(aggr.id_path)}`);
+
+ instance.state.set('instancesCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'instance'
+ }).count());
+
+ instance.state.set('vServicesCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'vservice'
+ }).count());
+
+ instance.state.set('hostsCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'host'
+ }).count());
+
+ instance.state.set('vConnectorsCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'vconnector'
+ }).count());
+
+ instance.state.set('vEdgesCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'vedge'
+ }).count());
+ });
+ });
+});
+
+/*
+Template.AggregateDashboard.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.AggregateDashboard.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.AggregateDashboard.helpers({
+ aggregate: function () {
+ let instance = Template.instance();
+ let aggregate_id_path = instance.state.get('id_path');
+
+ return Inventory.findOne({ id_path: aggregate_id_path });
+ },
+
+ infoBoxes: function () {
+ return infoBoxes;
+ },
+
+ listInfoBoxes: function () {
+ return listInfoBoxes;
+ },
+
+ argsInfoBox: function (infoBox) {
+ let instance = Template.instance();
+
+ return {
+ header: R.path(infoBox.header, store.getState().api.i18n),
+ dataInfo: instance.state.get(infoBox.dataSource).toString(),
+ icon: new Icon(infoBox.icon),
+ theme: infoBox.theme
+ };
+ },
+
+ argsListInfoBox: function (listInfoBox) {
+ let instance = Template.instance();
+ let data = Template.currentData();
+ let aggregate_id_path = instance.state.get('id_path');
+
+ return {
+ header: R.path(listInfoBox.header, store.getState().api.i18n),
+ list: getList(listInfoBox.listName, aggregate_id_path),
+ //dataInfo: instance.state.get(infoBox.dataSource).toString(),
+ icon: new Icon(listInfoBox.icon),
+ //theme: infoBox.theme
+ listItemFormat: listInfoBox.listItemFormat,
+ onItemSelected: function (itemKey) {
+ data.onNodeSelected(new Mongo.ObjectID(itemKey));
+ }
+ };
+ }
+});
+
+
+function getList(listName, parentIdPath) {
+ let idPathExp = new RegExp(`^${regexEscape(parentIdPath)}`);
+
+ switch (listName) {
+ case 'hosts':
+ return Inventory.find({
+ id_path: idPathExp,
+ type: 'host'
+ });
+
+ default:
+ throw 'unknowned list type';
+ }
+}
diff --git a/ui/imports/ui/components/aggregate-dashboard/aggregate-dashboard.styl b/ui/imports/ui/components/aggregate-dashboard/aggregate-dashboard.styl
new file mode 100644
index 0000000..9764778
--- /dev/null
+++ b/ui/imports/ui/components/aggregate-dashboard/aggregate-dashboard.styl
@@ -0,0 +1,10 @@
+.os-aggregate-dashboard
+ .sm-info-boxes
+ display: flex
+ flex-flow: row wrap;
+ justify-content: space-around
+
+ .sm-list-info-boxes
+ display: flex;
+ flex-flow: row wrap
+ justify-content: space-around
diff --git a/ui/imports/ui/components/alarm-icons/alarm-icons.html b/ui/imports/ui/components/alarm-icons/alarm-icons.html
new file mode 100644
index 0000000..d584990
--- /dev/null
+++ b/ui/imports/ui/components/alarm-icons/alarm-icons.html
@@ -0,0 +1,78 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="alarmIcons">
+ <div class="os-alarm-icons">
+
+ <div class="alarm-icons">
+
+ <div class="dropdown">
+ <div class="material-icons mdl-badge mdl-badge--overlap dropdown-toggle"
+ data-badge="{{countOf 'messages/count?level=info'}}"
+ type="button"
+ id="dropdownMenu1"
+ data-toggle="modal"
+ data-target="#messagesModalGlobal"
+ data-message-level="info"
+ >notifications</div>
+ </div>
+
+ <div class="dropdown">
+ <div class="material-icons mdl-badge mdl-badge--overlap dropdown-toggle"
+ data-badge="{{countOf 'messages/count?level=warning'}}"
+ type="button"
+ id="dropdownMenu1"
+ data-toggle="modal"
+ data-target="#messagesModalGlobal"
+ data-message-level="warning"
+ >warning</div>
+ </div>
+
+ <div class="dropdown">
+ <div class="material-icons mdl-badge mdl-badge--overlap dropdown-toggle"
+ data-badge="{{countOf 'messages/count?level=error'}}"
+ type="button"
+ id="dropdownMenu1"
+ data-toggle="modal"
+ data-target="#messagesModalGlobal"
+ data-message-level="error"
+ >error</div>
+ </div>
+
+ <div class="dropdown">
+ <div class="material-icons dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">settings</div>
+ <ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenu1">
+ <li class="dropdown-header"
+ ><a href="{{pathFor route='scheduled-scans-list' query=''}}" >Scheduled Scans</a></li>
+ <li class="dropdown-header"
+ ><a href="{{pathFor route='scans-list' query=''}}" >Scans</a></li>
+ <li class="dropdown-header"
+ ><a href="{{pathFor route='link-types-list' query=''}}" >Link Types</a></li>
+ <li class="dropdown-header"
+ ><a href="{{pathFor route='clique-types-list' query=''}}" >Clique Types</a></li>
+ <li class="dropdown-header"
+ ><a href="{{pathFor route='clique-constraints-list' query=''}}" >Clique Constraints</a></li>
+
+ <li class="dropdown-header">
+ <a href="{{pathFor route='messages-list' query=''}}" >Messages</a>
+ </li>
+
+ {{#if isAdmin }}
+ <li class="dropdown-header">
+ <a href="{{pathFor route='user-list' query=''}}">Users</a>
+ </li>
+ {{/if }}
+ </ul>
+ </div>
+
+ </div>
+
+ </div>
+</template>
diff --git a/ui/imports/ui/components/alarm-icons/alarm-icons.js b/ui/imports/ui/components/alarm-icons/alarm-icons.js
new file mode 100644
index 0000000..5c7af31
--- /dev/null
+++ b/ui/imports/ui/components/alarm-icons/alarm-icons.js
@@ -0,0 +1,53 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: alarmIcons
+ */
+
+import '/imports/ui/components/breadcrumb/breadcrumb';
+import { Messages } from '/imports/api/messages/messages';
+import { Roles } from 'meteor/alanning:roles';
+
+import './alarm-icons.html';
+
+/*
+ * Lifecycle
+ */
+
+Template.alarmIcons.onCreated(function () {
+ let instance = this;
+
+ instance.autorun(function () {
+ instance.subscribe('messages/count?level', 'info');
+ instance.subscribe('messages/count?level', 'warning');
+ instance.subscribe('messages/count?level', 'error');
+ });
+});
+
+/*
+ * Helpers
+ */
+
+Template.alarmIcons.helpers({
+ isAdmin: function () {
+ return Roles.userIsInRole(Meteor.userId(), 'manage-users', Roles.GLOBAL_GROUP);
+ },
+
+ infosCount: function(){
+ return Messages.find({level:'info'}).count();
+ },
+
+ warningsCount: function(){
+ return Messages.find({level:'warning'}).count();
+ },
+
+ errorsCount: function(){
+ return Messages.find({level:'error'}).count();
+ },
+});
diff --git a/ui/imports/ui/components/alarm-icons/alarm-icons.styl b/ui/imports/ui/components/alarm-icons/alarm-icons.styl
new file mode 100644
index 0000000..20bf947
--- /dev/null
+++ b/ui/imports/ui/components/alarm-icons/alarm-icons.styl
@@ -0,0 +1 @@
+// alarm icon styles
diff --git a/ui/imports/ui/components/auto-search-result-line/auto-search-result-line.html b/ui/imports/ui/components/auto-search-result-line/auto-search-result-line.html
new file mode 100644
index 0000000..247e1ea
--- /dev/null
+++ b/ui/imports/ui/components/auto-search-result-line/auto-search-result-line.html
@@ -0,0 +1,16 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="AutoSearchResultLine">
+ <li class="os-auto-search-result-line">
+ <span class="sm-header-subline"><span class="sm-object-name">{{ objectName }}</span> - <span class="sm-object-type">{{ objectType }}</span></span>
+ <span class="sm-detail-subline">{{ namePath }}</span>
+ </li>
+</template>
diff --git a/ui/imports/ui/components/auto-search-result-line/auto-search-result-line.js b/ui/imports/ui/components/auto-search-result-line/auto-search-result-line.js
new file mode 100644
index 0000000..23272de
--- /dev/null
+++ b/ui/imports/ui/components/auto-search-result-line/auto-search-result-line.js
@@ -0,0 +1,51 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: AutoSearchResultLine
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+
+import './auto-search-result-line.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.AutoSearchResultLine.onCreated(function() {
+});
+
+/*
+Template.AutoSearchResultLine.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.AutoSearchResultLine.events({
+ 'click': function(event, instance) {
+ event.stopPropagation();
+ event.preventDefault();
+
+ instance.data.onClick(instance.data.namePath);
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.AutoSearchResultLine.helpers({
+});
+
+
diff --git a/ui/imports/ui/components/auto-search-result-line/auto-search-result-line.styl b/ui/imports/ui/components/auto-search-result-line/auto-search-result-line.styl
new file mode 100644
index 0000000..df6caa5
--- /dev/null
+++ b/ui/imports/ui/components/auto-search-result-line/auto-search-result-line.styl
@@ -0,0 +1,23 @@
+.os-auto-search-result-line
+ cursor: pointer;
+ line-height: 1.1;
+ font-size: 1.1em;
+ padding: 5px 15px;
+ margin: 0 auto;
+ color: brand-blue
+ border-bottom: 1px solid #e8e8e8;
+
+ &:hover
+ background-color: #f2f2f2;
+
+ .sm-header-subline
+ display: block;
+ margin-bottom: 5px;
+
+ .sm-detail-subline
+ display: block;
+ white-space: nowrap;
+ color: spark-grey
+
+.os-auto-search-result-line:last-child
+ border-bottom: none;
diff --git a/ui/imports/ui/components/breadcrumb/breadcrumb.html b/ui/imports/ui/components/breadcrumb/breadcrumb.html
new file mode 100644
index 0000000..0967b7d
--- /dev/null
+++ b/ui/imports/ui/components/breadcrumb/breadcrumb.html
@@ -0,0 +1,17 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="breadcrumb" >
+ <ol class="os-breadcrumb breadcrumb">
+ {{#each node in nodesList }}
+ {{> breadcrumbNode (argsNode node) }}
+ {{/each }}
+ </ol>
+</template>
diff --git a/ui/imports/ui/components/breadcrumb/breadcrumb.js b/ui/imports/ui/components/breadcrumb/breadcrumb.js
new file mode 100644
index 0000000..642797f
--- /dev/null
+++ b/ui/imports/ui/components/breadcrumb/breadcrumb.js
@@ -0,0 +1,83 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: breadcrumb
+ */
+
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import * as R from 'ramda';
+//import { Inventory } from '/imports/api/inventories/inventories';
+
+import '../breadcrumbNode/breadcrumbNode';
+import './breadcrumb.html';
+
+Template.breadcrumb.onCreated(function () {
+ let instance = this;
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ nodeId: null,
+ nodesList: [],
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+ new SimpleSchema({
+ nodeId: { type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } } },
+ onNodeSelected: { type: Function },
+ }).validate(data);
+
+ instance.state.set('nodeId', data.nodeId);
+ });
+
+ instance.autorun(function () {
+ let nodeId = instance.state.get('nodeId');
+
+ if (R.isNil(nodeId)) {
+ return;
+ }
+
+ Meteor.apply('expandNodePath', [ nodeId ], { wait: false }, function (err, res) {
+ if (err) {
+ console.error(err);
+ return;
+ }
+
+ if (R.isNil(res)) {
+ instance.state.set('nodesList', []);
+ return;
+ }
+
+ instance.state.set('nodesList', res);
+ });
+ });
+});
+
+Template.breadcrumb.onDestroyed(function () {
+});
+
+Template.breadcrumb.helpers({
+ nodesList: function () {
+ let instance = Template.instance();
+ return instance.state.get('nodesList');
+ },
+
+ argsNode: function (node) {
+ //let instance = Template.instance();
+ let data = Template.currentData();
+
+ return {
+ node: node,
+ onClick: function () {
+ data.onNodeSelected(node);
+ }
+ };
+ },
+}); // end: helpers
diff --git a/ui/imports/ui/components/breadcrumb/breadcrumb.styl b/ui/imports/ui/components/breadcrumb/breadcrumb.styl
new file mode 100644
index 0000000..f0417de
--- /dev/null
+++ b/ui/imports/ui/components/breadcrumb/breadcrumb.styl
@@ -0,0 +1,3 @@
+.os-breadcrumb
+ background-color: brand-blue;
+ margin-bottom: 0px;
diff --git a/ui/imports/ui/components/breadcrumbNode/breadcrumbNode.html b/ui/imports/ui/components/breadcrumbNode/breadcrumbNode.html
new file mode 100644
index 0000000..041d2fa
--- /dev/null
+++ b/ui/imports/ui/components/breadcrumbNode/breadcrumbNode.html
@@ -0,0 +1,15 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="breadcrumbNode">
+<li class="os-breadcrumb-node">
+ <a>{{ node.object_name }}</a>
+</li>
+</template>
diff --git a/ui/imports/ui/components/breadcrumbNode/breadcrumbNode.js b/ui/imports/ui/components/breadcrumbNode/breadcrumbNode.js
new file mode 100644
index 0000000..801df43
--- /dev/null
+++ b/ui/imports/ui/components/breadcrumbNode/breadcrumbNode.js
@@ -0,0 +1,43 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: breadcrumbNode
+ */
+
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+
+import './breadcrumbNode.html';
+
+Template.breadcrumbNode.onCreated(function () {
+ let instance = this;
+ instance.state = new ReactiveDict();
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+ new SimpleSchema({
+ node: { type: Object, blackbox: true },
+ onClick: { type: Function },
+ }).validate(data);
+ });
+
+});
+
+Template.breadcrumbNode.helpers({
+});
+
+Template.breadcrumbNode.events({
+ 'click': function(event, instance) {
+ event.stopPropagation();
+ event.preventDefault();
+
+ instance.data.onClick();
+ }
+});
diff --git a/ui/imports/ui/components/breadcrumbNode/breadcrumbNode.styl b/ui/imports/ui/components/breadcrumbNode/breadcrumbNode.styl
new file mode 100644
index 0000000..e2915d8
--- /dev/null
+++ b/ui/imports/ui/components/breadcrumbNode/breadcrumbNode.styl
@@ -0,0 +1,4 @@
+.os-breadcrumb-node
+ a
+ color: white;
+ cursor pointer
diff --git a/ui/imports/ui/components/clique-constraint/clique-constraint.html b/ui/imports/ui/components/clique-constraint/clique-constraint.html
new file mode 100644
index 0000000..0583872
--- /dev/null
+++ b/ui/imports/ui/components/clique-constraint/clique-constraint.html
@@ -0,0 +1,96 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="CliqueConstraint">
+ <div class="os-clique-constraint cards white">
+ {{#if notificationsExists}}
+ <div class="sm-notification-panel alert alert-danger">
+ {{#each note in notifications }}
+ <div>{{ note }}</div>
+ {{/each }}
+ </div>
+ {{/if}}
+
+ <h3>{{ getState 'pageHeader' }}</h3>
+
+ <div class="sm-form-container">
+ <form role="form" class="sm-item-form form-horizontal">
+
+ <div class="sm-field-group-id cl-field-group">
+ <label class="cl-field-label">Id</label>
+ <input name="id"
+ disabled="disabled"
+ value="{{ getModelField '_id' }}"
+ class="sm-input-id cl-input" type="text" placeholder="Id" />
+ <div class="cl-field-id">Id</div>
+ </div>
+
+ <!--div class="sm-field-group-env cl-field-group">
+ <label class="cl-field-label">Environment</label>
+ <select name="env" class="sm-input-env cl-input"
+ {{ getAttrDisabled }} >
+ {{#each env in envsList }}
+ <option value="{{ env.name }}"
+ {{ getAttrSelected env.name (getModelField 'environment') }}
+ >{{ env.name }}</option>
+ {{/each }}
+ </select>
+ <div class="cl-field-desc">Environment</div>
+ </div-->
+
+ <div class="sm-field-group-focal-point-type cl-field-group">
+ <label class="cl-field-label">Focal Point Type</label>
+ <select name="focalPointType" class="sm-input-focal-point-type cl-input"
+ {{ getAttrDisabled }} >
+ {{#each objectType in objectTypesList }}
+ <option value="{{ objectType.value }}"
+ {{ getAttrSelected objectType.value (getModelField 'focal_point_type') }}
+ >{{ objectType.label }}</option>
+ {{/each }}
+ </select>
+ <div class="cl-field-desc">Focal Point Type</div>
+ </div>
+
+ <div class="sm-field-group-constraints cl-field-group">
+ <label class="cl-field-label">Constraints</label>
+ <select name="constraints"
+ class="sm-input-constraints cl-input"
+ multiple
+ size="3"
+ {{ getAttrDisabled }} >
+ {{#each objectType in objectTypesList }}
+ <option value="{{ constraint.value }}"
+ {{ getAttrSelectedMultiple objectType.value (getModelField 'constraints') }}
+ >{{ objectType.label }}</option>
+ {{/each }}
+ </select>
+ <div class="cl-field-desc">Constraints</div>
+ </div>
+
+ {{#if isUpdateableAction }}
+ <button type="submit"
+ class="js-submit-button mdl-button mdl-js-button mdl-button--raised
+ mdl-js-ripple-effect mdl-button--colored"
+ >{{ actionLabel }}</button>
+ {{/if }}
+
+ </form>
+
+ {{#if (getState 'isMessage') }}
+ <div class="js-message-panel alert {{#if (getState 'isError')}}alert-danger{{/if}}
+ {{#if (getState 'isSuccess')}}alert-success{{/if}}"
+ role="alert">
+ {{ getState 'message' }}
+ </div>
+ {{/if }}
+
+ </div>
+ </div>
+</template>
diff --git a/ui/imports/ui/components/clique-constraint/clique-constraint.js b/ui/imports/ui/components/clique-constraint/clique-constraint.js
new file mode 100644
index 0000000..75623eb
--- /dev/null
+++ b/ui/imports/ui/components/clique-constraint/clique-constraint.js
@@ -0,0 +1,329 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: CliqueConstraint
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import * as R from 'ramda';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { CliqueConstraints } from '/imports/api/clique-constraints/clique-constraints';
+//import { Environments } from '/imports/api/environments/environments';
+import { Constants } from '/imports/api/constants/constants';
+import { insert, remove, update } from '/imports/api/clique-constraints/methods';
+import { parseReqId } from '/imports/lib/utilities';
+
+import './clique-constraint.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.CliqueConstraint.onCreated(function() {
+ let instance = this;
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ id: null,
+ //env: null,
+ action: 'insert',
+ isError: false,
+ isSuccess: false,
+ isMessage: false,
+ message: null,
+ disabled: false,
+ notifications: {},
+ model: {},
+ pageHeader: 'Clique Constraint'
+ });
+
+ instance.autorun(function () {
+ let controller = Iron.controller();
+ let params = controller.getParams();
+ let query = params.query;
+
+ new SimpleSchema({
+ action: { type: String, allowedValues: ['insert', 'view', 'remove', 'update'] },
+ id: { type: String, optional: true }
+ }).validate(query);
+
+ switch (query.action) {
+ case 'insert':
+ initInsertView(instance, query);
+ break;
+
+ case 'view':
+ initViewView(instance, query);
+ break;
+
+ case 'update':
+ initUpdateView(instance, query);
+ break;
+
+ case 'remove':
+ initRemoveView(instance, query);
+ break;
+
+ default:
+ throw 'unimplemented action';
+ }
+ });
+});
+
+/*
+Template.CliqueConstraint.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.CliqueConstraint.events({
+ 'submit .sm-item-form': function(event, instance) {
+ event.preventDefault();
+
+ let _id = instance.state.get('id');
+ //let env = instance.$('.sm-input-env')[0].value;
+ let focalPointType = instance.$('.sm-input-focal-point-type')[0].value;
+ let constraints = R.map(R.prop('value'),
+ instance.$('.sm-input-constraints')[0].selectedOptions);
+
+ submitItem(instance,
+ _id,
+ //env,
+ focalPointType,
+ constraints
+ );
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.CliqueConstraint.helpers({
+ isUpdateableAction() {
+ let instance = Template.instance();
+ let action = instance.state.get('action');
+
+ return R.contains(action, ['insert', 'update', 'remove']);
+ },
+
+ getState: function (key) {
+ let instance = Template.instance();
+ return instance.state.get(key);
+ },
+
+ objectTypesList: function () {
+ return R.ifElse(R.isNil, R.always([]), R.prop('data')
+ )(Constants.findOne({ name: 'object_types_for_links' }));
+ },
+
+ /*
+ envsList: function () {
+ return Environments.find({});
+ },
+ */
+
+ getAttrDisabled: function () {
+ let instance = Template.instance();
+ let result = {};
+ let action = instance.state.get('action');
+
+ if (R.contains(action, ['view', 'remove'])) {
+ result = R.assoc('disabled', true, result);
+ }
+
+ return result;
+ },
+
+ getModel: function () {
+ let instance = Template.instance();
+ return instance.state.get('model');
+ },
+
+ getModelField: function (fieldName) {
+ let instance = Template.instance();
+ return R.path([fieldName], instance.state.get('model'));
+ },
+
+ getAttrSelected: function (optionValue, modelValue) {
+ let result = {};
+
+ if (optionValue === modelValue) {
+ result = R.assoc('selected', 'selected', result);
+ }
+
+ return result;
+ },
+
+ getAttrSelectedMultiple: function (optionValue, modelValues) {
+ let result = {};
+
+ if (R.isNil(modelValues)) { return result; }
+
+ if (R.contains(optionValue, modelValues)) {
+ result = R.assoc('selected', 'selected', result);
+ }
+
+ return result;
+ },
+
+ actionLabel: function () {
+ let instance = Template.instance();
+ let action = instance.state.get('action');
+ return calcActionLabel(action);
+ }
+});
+
+
+function initInsertView(instance, query) {
+ instance.state.set('action', query.action);
+ instance.state.set('model', CliqueConstraints.schema.clean({
+ }));
+
+ subscribeToOptionsData(instance);
+ //instance.subscribe('link_types?env', query.env);
+}
+
+function initViewView(instance, query) {
+ let reqId = parseReqId(query.id);
+
+ instance.state.set('action', query.action);
+ instance.state.set('id', reqId);
+
+ subscribeToOptionsData(instance);
+
+ instance.subscribe('clique_constraints?_id', reqId.id);
+
+ CliqueConstraints.find({ _id: reqId.id }).forEach((model) => {
+ instance.state.set('model', model);
+ });
+}
+
+function initUpdateView(instance, query) {
+ let reqId = parseReqId(query.id);
+
+ instance.state.set('action', query.action);
+ instance.state.set('id', reqId);
+
+ subscribeToOptionsData(instance);
+ instance.subscribe('clique_constraints?_id', reqId.id);
+
+ CliqueConstraints.find({ _id: reqId.id }).forEach((model) => {
+ instance.state.set('model', model);
+ });
+}
+
+function initRemoveView(instance, query) {
+ initViewView(instance, query);
+}
+
+function subscribeToOptionsData(instance) {
+// instance.subscribe('environments_config');
+ instance.subscribe('link_types');
+ instance.subscribe('constants');
+}
+
+function submitItem(
+ instance,
+ id,
+ focal_point_type,
+ constraints
+ ) {
+
+ let action = instance.state.get('action');
+
+ instance.state.set('isError', false);
+ instance.state.set('isSuccess', false);
+ instance.state.set('isMessage', false);
+ instance.state.set('message', null);
+
+ switch (action) {
+ case 'insert':
+ insert.call({
+ focal_point_type: focal_point_type,
+ constraints: constraints,
+ }, processActionResult.bind(null, instance));
+ break;
+
+ case 'update':
+ update.call({
+ _id: id.id,
+ focal_point_type: focal_point_type,
+ constraints: constraints,
+ }, processActionResult.bind(null, instance));
+ break;
+
+ case 'remove':
+ remove.call({
+ _id: id.id
+ }, processActionResult.bind(null, instance));
+ break;
+
+ default:
+ // todo
+ break;
+ }
+}
+
+function processActionResult(instance, error) {
+ let action = instance.state.get('action');
+
+ if (error) {
+ instance.state.set('isError', true);
+ instance.state.set('isSuccess', false);
+ instance.state.set('isMessage', true);
+
+ if (typeof error === 'string') {
+ instance.state.set('message', error);
+ } else {
+ instance.state.set('message', error.message);
+ }
+
+ return;
+ }
+
+ instance.state.set('isError', false);
+ instance.state.set('isSuccess', true);
+ instance.state.set('isMessage', true);
+
+ switch (action) {
+ case 'insert':
+ instance.state.set('message', 'Record had been added successfully');
+ instance.state.set('disabled', true);
+ break;
+
+ case 'remove':
+ instance.state.set('message', 'Record had been removed successfully');
+ instance.state.set('disabled', true);
+ break;
+
+ case 'update':
+ instance.state.set('message', 'Record had been updated successfully');
+ break;
+ }
+
+ Router.go('/clique-constraints-list');
+}
+
+function calcActionLabel(action) {
+ switch (action) {
+ case 'insert':
+ return 'Add';
+ case 'remove':
+ return 'Remove';
+ case 'update':
+ return 'Update';
+ default:
+ return 'Submit';
+ }
+}
diff --git a/ui/imports/ui/components/clique-constraint/clique-constraint.styl b/ui/imports/ui/components/clique-constraint/clique-constraint.styl
new file mode 100644
index 0000000..72d2348
--- /dev/null
+++ b/ui/imports/ui/components/clique-constraint/clique-constraint.styl
@@ -0,0 +1,35 @@
+.os-clique-constraint
+ margin: 20px;
+
+ .cl-field-group
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ padding: 5px 0;
+
+ .cl-field-label
+ width: 120px;
+ margin: 0 5px;
+
+ .cl-input
+ display: block;
+ width: 100%;
+ min-height: 34px;
+ padding: 6px 12px;
+ font-size: 14px;
+ line-height: 1.42857143;
+ color: #555;
+ background-color: #fff;
+ background-image: none;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ width: 400px;
+ margin: 0 5px;
+
+ .cl-field-desc
+ margin: 0 5px;
+
+ .js-message-panel
+ margin-top: 20px;
+
diff --git a/ui/imports/ui/components/clique-constraints-list/clique-constraints-list.html b/ui/imports/ui/components/clique-constraints-list/clique-constraints-list.html
new file mode 100644
index 0000000..111c31b
--- /dev/null
+++ b/ui/imports/ui/components/clique-constraints-list/clique-constraints-list.html
@@ -0,0 +1,52 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="CliqueConstraintsList">
+<div class="os-clique-constraints-list cards white">
+ <h3>Clique Constraints</h3>
+ <a class="sm-add-new-link"
+ href="{{pathFor route='clique-constraint' query=(asHash action='insert') }}">
+ <i class="cl-action-icon fa fa-plus" area-hidden="true"></i> Create new clique constraint
+ </a>
+ <table class="sm-clique-constraints-table table">
+ <thead>
+ <tr>
+ <th>Focal Point Type</th>
+ <th>Constraints</th>
+ <th>Actions</th>
+ </tr> </thead>
+ <tbody>
+ {{#each cliqueConstraint in cliqueConstraints }}
+ <tr>
+ <td>{{ cliqueConstraint.focal_point_type }}</td>
+ <td>{{ cliqueConstraint.constraints }}</td>
+ <td>
+ <div class="sm-action-bar">
+ <a href="{{pathFor route='clique-constraint'
+ query=(asHash id=(idToStr cliqueConstraint._id) action='view') }}"
+ ><i class="cl-action-icon fa fa-eye" area-hidden="true"></i></a>
+
+ {{#if isAuthManageCliqueConstraints }}
+ <a href="{{pathFor route='clique-constraint'
+ query=(asHash id=(idToStr cliqueConstraint._id) action='update') }}"
+ ><i class="cl-action-icon fa fa-pencil" area-hidden="true"></i></a>
+
+ <a href="{{pathFor route='clique-constraint'
+ query=(asHash id=(idToStr cliqueConstraint._id) action='remove') }}"
+ ><i class="cl-action-icon fa fa-trash-o" area-hidden="true"></i></a>
+ {{/if }}
+ </div>
+ </td>
+ </tr>
+ {{/each }}
+ </tbody>
+ </table>
+</div>
+</template>
diff --git a/ui/imports/ui/components/clique-constraints-list/clique-constraints-list.js b/ui/imports/ui/components/clique-constraints-list/clique-constraints-list.js
new file mode 100644
index 0000000..79c31e4
--- /dev/null
+++ b/ui/imports/ui/components/clique-constraints-list/clique-constraints-list.js
@@ -0,0 +1,77 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: CliqueConstraintsList
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { CliqueConstraints } from '/imports/api/clique-constraints/clique-constraints';
+import { Roles } from 'meteor/alanning:roles';
+
+import './clique-constraints-list.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.CliqueConstraintsList.onCreated(function() {
+ var instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ });
+
+ instance.autorun(function () {
+ //let data = Template.currentData();
+
+ var controller = Iron.controller();
+ var params = controller.getParams();
+ var query = params.query;
+
+ new SimpleSchema({
+ }).validate(query);
+
+ instance.subscribe('clique_constraints');
+ });
+});
+
+/*
+Template.CliqueConstraintsList.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.CliqueConstraintsList.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.CliqueConstraintsList.helpers({
+ cliqueConstraints: function () {
+ //let instance = Template.instance();
+
+ //var env = instance.state.get('env');
+ //return Scans.find({ environment: env });
+ return CliqueConstraints.find({});
+ },
+
+ isAuthManageCliqueConstraints: function () {
+ return Roles.userIsInRole(Meteor.userId(), 'manage-clique-constraints', Roles.GLOBAL_GROUP);
+ },
+}); /// end: helpers
+
+
diff --git a/ui/imports/ui/components/clique-constraints-list/clique-constraints-list.styl b/ui/imports/ui/components/clique-constraints-list/clique-constraints-list.styl
new file mode 100644
index 0000000..9c3072c
--- /dev/null
+++ b/ui/imports/ui/components/clique-constraints-list/clique-constraints-list.styl
@@ -0,0 +1,22 @@
+.os-clique-constraints-list
+ margin: 20px;
+
+ .cl-action-icon,
+ .card.fa.cl-action-icon
+ font-size: 16px !important;
+
+ .sm-clique-constraints-table
+ th
+ color: spark-blue
+
+ .sm-action-bar
+ display: flex;
+
+ a
+ margin: 0px 5px;
+
+ .cl-action-icon
+ color: gray
+
+ .sm-add-new-link
+ color: spark-blue
diff --git a/ui/imports/ui/components/clique-type/clique-type.html b/ui/imports/ui/components/clique-type/clique-type.html
new file mode 100644
index 0000000..318fb38
--- /dev/null
+++ b/ui/imports/ui/components/clique-type/clique-type.html
@@ -0,0 +1,100 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="CliqueType">
+ <div class="os-clique-type cards white">
+ {{#if notificationsExists}}
+ <div class="sm-notification-panel alert alert-danger">
+ {{#each note in notifications }}
+ <div>{{ note }}</div>
+ {{/each }}
+ </div>
+ {{/if}}
+
+ <h3>{{ getState 'pageHeader' }}</h3>
+
+ <div class="sm-form-container">
+ <form role="form" class="sm-item-form form-horizontal">
+
+ <div class="sm-field-group-id cl-field-group">
+ <label class="cl-field-label">Id</label>
+ <input name="id"
+ disabled="disabled"
+ value="{{ getModelField '_id' }}"
+ class="sm-input-id cl-input" type="text" placeholder="Id" />
+ <div class="cl-field-id">Id</div>
+ </div>
+
+ <div class="sm-field-group-env cl-field-group">
+ <label class="cl-field-label">Environment</label>
+ <select name="env" class="sm-input-env cl-input"
+ {{ getAttrDisabled }} >
+ <option value="" selected disabled hidden></option>
+ {{#each env in envsList }}
+ <option value="{{ env.name }}"
+ {{ getAttrSelected env.name (getModelField 'environment') }}
+ >{{ env.name }}</option>
+ {{/each }}
+ </select>
+ <div class="cl-field-desc">Environment</div>
+ </div>
+
+ <div class="sm-field-group-focal-point-type cl-field-group">
+ <label class="cl-field-label">Focal Point Type</label>
+ <select name="focalPointType" class="sm-input-focal-point-type cl-input"
+ {{ getAttrDisabled }} >
+ {{#each objectType in objectTypesList }}
+ <option value="{{ objectType.value }}"
+ {{ getAttrSelected objectType.value (getModelField 'focal_point_type') }}
+ >{{ objectType.label }}</option>
+ {{/each }}
+ </select>
+ <div class="cl-field-desc">Focal Point Type</div>
+ </div>
+
+ <div class="sm-field-group-link-types cl-field-group">
+ <label class="cl-field-label">Link Types</label>
+ {{#if (getModelField 'link_types') }}
+ {{>SelectableOrderedInput (argsLinkTypesInput linkTypesList (getModelField 'link_types')) }}
+ {{/if }}
+ <div class="cl-field-desc">Link Types</div>
+ </div>
+
+ <div class="sm-field-group-name cl-field-group">
+ <label class="cl-field-label">Name</label>
+ <input name="name"
+ {{ getAttrDisabled }}
+ value="{{ getModelField 'name' }}"
+ class="sm-input-name cl-input"
+ type="text"
+ placeholder="Name" />
+ <div class="cl-field-desc">Name</div>
+ </div>
+
+ {{#if isUpdateableAction }}
+ <button type="submit"
+ class="js-submit-button mdl-button mdl-js-button mdl-button--raised
+ mdl-js-ripple-effect mdl-button--colored"
+ >{{ actionLabel }}</button>
+ {{/if }}
+
+ </form>
+
+ {{#if (getState 'isMessage') }}
+ <div class="js-message-panel alert {{#if (getState 'isError')}}alert-danger{{/if}}
+ {{#if (getState 'isSuccess')}}alert-success{{/if}}"
+ role="alert">
+ {{ getState 'message' }}
+ </div>
+ {{/if }}
+
+ </div>
+ </div>
+</template>
diff --git a/ui/imports/ui/components/clique-type/clique-type.js b/ui/imports/ui/components/clique-type/clique-type.js
new file mode 100644
index 0000000..31efc88
--- /dev/null
+++ b/ui/imports/ui/components/clique-type/clique-type.js
@@ -0,0 +1,371 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: CliqueType
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import * as R from 'ramda';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+//import { Constants } from '/imports/api/constants/constants';
+import { CliqueTypes } from '/imports/api/clique-types/clique-types';
+import { Environments } from '/imports/api/environments/environments';
+import { Constants } from '/imports/api/constants/constants';
+import { LinkTypes } from '/imports/api/link-types/link-types';
+import { insert, update, remove } from '/imports/api/clique-types/methods';
+import { parseReqId } from '/imports/lib/utilities';
+
+import '/imports/ui/components/selectable-ordered-input/selectable-ordered-input';
+
+import './clique-type.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.CliqueType.onCreated(function() {
+ let instance = this;
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ id: null,
+ //env: null,
+ action: 'insert',
+ isError: false,
+ isSuccess: false,
+ isMessage: false,
+ message: null,
+ disabled: false,
+ notifications: {},
+ model: {},
+ pageHeader: 'Clique Type'
+ });
+
+ instance.autorun(function () {
+ let controller = Iron.controller();
+ let params = controller.getParams();
+ let query = params.query;
+
+ new SimpleSchema({
+ action: { type: String, allowedValues: ['insert', 'view', 'update', 'remove'] },
+ env: { type: String, optional: true },
+ id: { type: String, optional: true }
+ }).validate(query);
+
+ switch (query.action) {
+ case 'insert':
+ initInsertView(instance, query);
+ break;
+
+ case 'view':
+ initViewView(instance, query);
+ break;
+
+ case 'update':
+ initUpdateView(instance, query);
+ break;
+
+ case 'remove':
+ initRemoveView(instance, query);
+ break;
+
+ default:
+ throw 'unimplemented action';
+ }
+ });
+});
+
+/*
+Template.CliqueType.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.CliqueType.events({
+ 'submit .sm-item-form': function(event, instance) {
+ event.preventDefault();
+
+ let _id = instance.state.get('id');
+ let env = instance.$('.sm-input-env')[0].value;
+ let focalPointType = instance.$('.sm-input-focal-point-type')[0].value;
+ let linkTypes = R.path(['link_types'], instance.state.get('model'));
+ let name = instance.$('.sm-input-name')[0].value;
+
+ submitItem(instance,
+ _id,
+ env,
+ focalPointType,
+ linkTypes,
+ name
+ );
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.CliqueType.helpers({
+ isUpdateableAction() {
+ let instance = Template.instance();
+ let action = instance.state.get('action');
+
+ return R.contains(action, ['insert', 'update', 'remove']);
+ },
+
+ getState: function (key) {
+ let instance = Template.instance();
+ return instance.state.get(key);
+ },
+
+ objectTypesList: function () {
+ return R.ifElse(R.isNil, R.always([]), R.prop('data')
+ )(Constants.findOne({ name: 'object_types_for_links' }));
+ },
+
+ linkTypesList: function () {
+ return LinkTypes.find({});
+ },
+
+ envsList: function () {
+ return Environments.find({});
+ },
+
+ getAttrDisabled: function () {
+ let instance = Template.instance();
+ let result = {};
+ let action = instance.state.get('action');
+
+ if (R.contains(action, ['view', 'remove'])) {
+ result = R.assoc('disabled', true, result);
+ }
+
+ return result;
+ },
+
+ getModel: function () {
+ let instance = Template.instance();
+ return instance.state.get('model');
+ },
+
+ getModelField: function (fieldName) {
+ let instance = Template.instance();
+ return R.path([fieldName], instance.state.get('model'));
+ },
+
+ getAttrSelected: function (optionValue, modelValue) {
+ let result = {};
+
+ if (optionValue === modelValue) {
+ result = R.assoc('selected', 'selected', result);
+ }
+
+ return result;
+ },
+
+ getAttrSelectedMultiple: function (optionValue, modelValues) {
+ let result = {};
+
+ if (R.isNil(modelValues)) { return result; }
+
+ if (R.contains(optionValue, modelValues)) {
+ result = R.assoc('selected', 'selected', result);
+ }
+
+ return result;
+ },
+
+ actionLabel: function () {
+ let instance = Template.instance();
+ let action = instance.state.get('action');
+ return calcActionLabel(action);
+ },
+
+ argsLinkTypesInput: function (linkTypesList, chosenLinkTypes) {
+ let instance = Template.instance();
+
+ let options = R.map((linkType) => {
+ return { value: linkType.type, label: linkType.type };
+ }, linkTypesList);
+
+ let product = R.map((linkTypeVal) => {
+ return { value: linkTypeVal, label: linkTypeVal };
+ }, chosenLinkTypes);
+
+ return {
+ choices: options,
+ product: product,
+ onProductChange: function (product) {
+ let model = instance.state.get('model');
+ let link_types = R.map(R.prop('value'), product);
+ model = R.assoc('link_types', link_types, model);
+ instance.state.set('model', model);
+ },
+ };
+ },
+}); // end: helpers
+
+function initInsertView(instance, query) {
+ instance.state.set('action', query.action);
+ instance.state.set('env', query.env);
+ instance.state.set('model', CliqueTypes.schema.clean({
+ environment: instance.state.get('env')
+ }));
+
+ subscribeToOptionsData(instance);
+ instance.subscribe('constants');
+ //instance.subscribe('link_types?env', query.env);
+}
+
+function initViewView(instance, query) {
+ let reqId = parseReqId(query.id);
+
+ instance.state.set('action', query.action);
+ instance.state.set('env', query.env);
+ instance.state.set('id', reqId);
+
+ subscribeToOptionsData(instance);
+ instance.subscribe('constants');
+ instance.subscribe('clique_types?_id', reqId.id);
+
+ CliqueTypes.find({ _id: reqId.id }).forEach((model) => {
+ instance.state.set('model', model);
+ });
+
+}
+
+function initUpdateView(instance, query) {
+ let reqId = parseReqId(query.id);
+
+ instance.state.set('action', query.action);
+ instance.state.set('env', query.env);
+ instance.state.set('id', reqId);
+
+ subscribeToOptionsData(instance);
+ instance.subscribe('constants');
+ instance.subscribe('clique_types?_id', reqId.id);
+
+ CliqueTypes.find({ _id: reqId.id }).forEach((model) => {
+ instance.state.set('model', model);
+ });
+}
+
+function initRemoveView(instance, query) {
+ initViewView(instance, query);
+}
+
+function subscribeToOptionsData(instance) {
+ instance.subscribe('environments_config');
+ instance.subscribe('link_types');
+}
+
+function submitItem(
+ instance,
+ id,
+ env,
+ focal_point_type,
+ link_types,
+ name
+ ) {
+
+ let action = instance.state.get('action');
+
+ instance.state.set('isError', false);
+ instance.state.set('isSuccess', false);
+ instance.state.set('isMessage', false);
+ instance.state.set('message', null);
+
+ switch (action) {
+ case 'insert':
+ insert.call({
+ environment: env,
+ focal_point_type: focal_point_type,
+ link_types: link_types,
+ name: name
+ }, processActionResult.bind(null, instance));
+ break;
+
+ case 'update':
+ update.call({
+ _id: id.id,
+ environment: env,
+ focal_point_type: focal_point_type,
+ link_types: link_types,
+ name: name
+ }, processActionResult.bind(null, instance));
+ break;
+
+ case 'remove':
+ remove.call({
+ _id: id.id
+ }, processActionResult.bind(null, instance));
+ break;
+
+ default:
+ // todo
+ break;
+ }
+}
+
+function processActionResult(instance, error) {
+ let action = instance.state.get('action');
+
+ if (error) {
+ instance.state.set('isError', true);
+ instance.state.set('isSuccess', false);
+ instance.state.set('isMessage', true);
+
+ if (typeof error === 'string') {
+ instance.state.set('message', error);
+ } else {
+ instance.state.set('message', error.message);
+ }
+
+ return;
+ }
+
+ instance.state.set('isError', false);
+ instance.state.set('isSuccess', true);
+ instance.state.set('isMessage', true);
+
+ switch (action) {
+ case 'insert':
+ instance.state.set('message', 'Record had been added successfully');
+ instance.state.set('disabled', true);
+ break;
+
+ case 'remove':
+ instance.state.set('message', 'Record had been removed successfully');
+ instance.state.set('disabled', true);
+ break;
+
+ case 'update':
+ instance.state.set('message', 'Record had been updated successfully');
+ break;
+ }
+
+ Router.go('/clique-types-list');
+}
+
+function calcActionLabel(action) {
+ switch (action) {
+ case 'insert':
+ return 'Add';
+ case 'remove':
+ return 'Remove';
+ case 'update':
+ return 'Update';
+ default:
+ return 'Submit';
+ }
+}
diff --git a/ui/imports/ui/components/clique-type/clique-type.styl b/ui/imports/ui/components/clique-type/clique-type.styl
new file mode 100644
index 0000000..11c42df
--- /dev/null
+++ b/ui/imports/ui/components/clique-type/clique-type.styl
@@ -0,0 +1,54 @@
+.os-clique-type
+ margin: 20px;
+
+ .cl-field-group
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ padding: 5px 0;
+
+ .cl-field-label
+ width: 120px;
+ margin: 0 5px;
+
+ >.cl-input
+ display: block;
+ width: 100%;
+ min-height: 34px;
+ padding: 6px 12px;
+ font-size: 14px;
+ line-height: 1.42857143;
+ color: #555;
+ background-color: #fff;
+ background-image: none;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ width: 400px;
+ margin: 0 5px;
+
+ .cl-field-desc
+ margin: 0 5px;
+
+ .js-message-panel
+ margin-top: 20px;
+
+ .sm-field-group-link-types
+ .os-selectable-ordered-input
+ width: 400px;
+
+ .cl-input
+ display: block;
+ width: 100%;
+ min-height: 34px;
+ padding: 6px 12px;
+ font-size: 14px;
+ line-height: 1.42857143;
+ color: #555;
+ background-color: #fff;
+ background-image: none;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ //width: 400px;
+ margin: 0 5px;
diff --git a/ui/imports/ui/components/clique-types-list/clique-types-list.html b/ui/imports/ui/components/clique-types-list/clique-types-list.html
new file mode 100644
index 0000000..e4badf9
--- /dev/null
+++ b/ui/imports/ui/components/clique-types-list/clique-types-list.html
@@ -0,0 +1,56 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="CliqueTypesList">
+<div class="os-clique-types-list cards white">
+ <h3>Clique Types</h3>
+ <a class="sm-add-new-link"
+ href="{{pathFor route='clique-type' query=(asHash action='insert') }}">
+ <i class="cl-action-icon fa fa-plus" area-hidden="true"></i> Create new clique type
+ </a>
+ <table class="sm-clique-types-table table">
+ <thead>
+ <tr>
+ <th>name</th>
+ <th>Environment</th>
+ <th>Focal Point Type</th>
+ <th>Link Types</th>
+ <th>Actions</th>
+ </tr> </thead>
+ <tbody>
+ {{#each cliqueType in cliqueTypes }}
+ <tr>
+ <td>{{ cliqueType.name }}</td>
+ <td>{{ cliqueType.environment }}</td>
+ <td>{{ cliqueType.focal_point_type }}</td>
+ <td>{{ cliqueType.link_types }}</td>
+ <td>
+ <div class="sm-action-bar">
+ <a href="{{pathFor route='clique-type'
+ query=(asHash id=(idToStr cliqueType._id) action='view') }}"
+ ><i class="cl-action-icon fa fa-eye" area-hidden="true"></i></a>
+
+ {{#if isAuthManageCliqueTypes }}
+ <a href="{{pathFor route='clique-type'
+ query=(asHash id=(idToStr cliqueType._id) action='update') }}"
+ ><i class="cl-action-icon fa fa-pencil" area-hidden="true"></i></a>
+
+ <a href="{{pathFor route='clique-type'
+ query=(asHash id=(idToStr cliqueType._id) action='remove') }}"
+ ><i class="cl-action-icon fa fa-trash-o" area-hidden="true"></i></a>
+ {{/if }}
+ </div>
+ </td>
+ </tr>
+ {{/each }}
+ </tbody>
+ </table>
+</div>
+</template>
diff --git a/ui/imports/ui/components/clique-types-list/clique-types-list.js b/ui/imports/ui/components/clique-types-list/clique-types-list.js
new file mode 100644
index 0000000..7f3f149
--- /dev/null
+++ b/ui/imports/ui/components/clique-types-list/clique-types-list.js
@@ -0,0 +1,82 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: CliqueTypesList
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { CliqueTypes } from '/imports/api/clique-types/clique-types';
+import { Roles } from 'meteor/alanning:roles';
+
+import './clique-types-list.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.CliqueTypesList.onCreated(function() {
+ var instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ env: null
+ });
+
+ instance.autorun(function () {
+ //let data = Template.currentData();
+
+ var controller = Iron.controller();
+ var params = controller.getParams();
+ var query = params.query;
+
+ new SimpleSchema({
+ env: { type: String, optional: true },
+ }).validate(query);
+
+ let env = query.env;
+ instance.state.set('env', env);
+
+ instance.subscribe('clique_types?env*', env);
+ });
+});
+
+/*
+Template.CliqueTypesList.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.CliqueTypesList.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.CliqueTypesList.helpers({
+ cliqueTypes: function () {
+ //let instance = Template.instance();
+
+ //var env = instance.state.get('env');
+ //return Scans.find({ environment: env });
+ return CliqueTypes.find({});
+ },
+
+ isAuthManageCliqueTypes: function () {
+ return Roles.userIsInRole(Meteor.userId(), 'manage-clique-types', Roles.GLOBAL_GROUP);
+ },
+});
+
+
diff --git a/ui/imports/ui/components/clique-types-list/clique-types-list.styl b/ui/imports/ui/components/clique-types-list/clique-types-list.styl
new file mode 100644
index 0000000..d4e08a2
--- /dev/null
+++ b/ui/imports/ui/components/clique-types-list/clique-types-list.styl
@@ -0,0 +1,22 @@
+.os-clique-types-list
+ margin: 20px;
+
+ .cl-action-icon,
+ .card.fa.cl-action-icon
+ font-size: 16px !important;
+
+ .sm-clique-types-table
+ th
+ color: spark-blue
+
+ .sm-action-bar
+ display: flex;
+
+ a
+ margin: 0px 5px;
+
+ .cl-action-icon
+ color: gray
+
+ .sm-add-new-link
+ color: spark-blue
diff --git a/ui/imports/ui/components/d3graph/d3graph.html b/ui/imports/ui/components/d3graph/d3graph.html
new file mode 100644
index 0000000..52d84b5
--- /dev/null
+++ b/ui/imports/ui/components/d3graph/d3graph.html
@@ -0,0 +1,13 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="d3graph">
+ <div id="dgraphid"></div>
+</template>
diff --git a/ui/imports/ui/components/d3graph/d3graph.js b/ui/imports/ui/components/d3graph/d3graph.js
new file mode 100644
index 0000000..41177ed
--- /dev/null
+++ b/ui/imports/ui/components/d3graph/d3graph.js
@@ -0,0 +1,126 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: d3graph
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import * as R from 'ramda';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { Inventory } from '/imports/api/inventories/inventories';
+import { Cliques } from '/imports/api/cliques/cliques.js';
+import { Links } from '/imports/api/links/links.js';
+
+import { d3Graph } from '/imports/lib/d3-graph';
+
+import './d3graph.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.d3graph.onCreated(function() {
+ let instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ id_path: null,
+ ready: false
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+
+ new SimpleSchema({
+ id_path: { type: String },
+ }).validate(data);
+
+ instance.state.set('ready', false);
+ let id_path = data.id_path;
+
+ instance.subscribe('inventory?id_path', id_path);
+ instance.subscribe('attributes_for_hover_on_data');
+
+ Inventory.find({ id_path: id_path }).forEach((inventory) => {
+ instance.state.set('_id', inventory._id);
+
+ if (inventory.clique) {
+
+ if (inventory.id === 'aggregate-WebEx-RTP-SSD-Aggregate-node-24') {
+ let objId = 'node-24';
+ instance.subscribe('inventory?type+host', 'instance', objId);
+
+ } else {
+ let objId = inventory._id._str;
+ instance.subscribe('cliques?focal_point', objId);
+
+ Cliques.find({
+ focal_point: new Mongo.ObjectID(objId)
+ })
+ .forEach(
+ function (cliqueItem) {
+ instance.subscribe('links?_id-in', cliqueItem.links);
+
+ Links.find({ _id: {$in: cliqueItem.links} })
+ .forEach(function(linkItem) {
+ let idsList = [ linkItem['source'], linkItem['target'] ];
+ instance.subscribe('inventory?_id-in', idsList);
+
+ Inventory.find({ _id: { $in: idsList } })
+ .forEach(function (invItem) {
+ instance.subscribe('attributes_for_hover_on_data?type', invItem.type);
+ });
+ });
+
+ instance.state.set('ready', true);
+ });
+ }
+ }
+ });
+ });
+});
+
+Template.d3graph.rendered = function () {
+ let instance = Template.instance();
+ let element = instance.$('#dgraphid')[0];
+ d3Graph.createGraphData(element.clientWidth, element.clientHeight);
+
+ Tracker.autorun(function () {
+ var nodeId = instance.state.get('_id');
+ var ready = instance.state.get('ready');
+
+ if (! ready) { return; }
+ if(R.isNil(nodeId)) { return; }
+
+ setTimeout(() => {
+ let graphData = d3Graph.getGraphDataByClique(nodeId._str);
+ setTimeout(() => {
+ d3Graph.updateNetworkGraph(graphData);
+ }, 100);
+ }, 500);
+ });
+};
+
+/*
+ * Events
+ */
+
+Template.d3graph.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.d3graph.helpers({
+});
+
+
diff --git a/ui/imports/ui/components/d3graph/d3graph.styl b/ui/imports/ui/components/d3graph/d3graph.styl
new file mode 100644
index 0000000..27908be
--- /dev/null
+++ b/ui/imports/ui/components/d3graph/d3graph.styl
@@ -0,0 +1,12 @@
+#dgraphid
+ width: 100%;
+ height: 100%;
+
+ svg.os-d3-graph {
+ /*background-color: antiquewhite;*/
+ /*background-color: rgb(161, 183, 206);*/
+ background-color:#FDFEFF;
+ /*height:100vh; */
+ /* padding-top: 100px; */
+
+ }
diff --git a/ui/imports/ui/components/dashboard/dashboard.html b/ui/imports/ui/components/dashboard/dashboard.html
new file mode 100644
index 0000000..5781374
--- /dev/null
+++ b/ui/imports/ui/components/dashboard/dashboard.html
@@ -0,0 +1,157 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="Dashboard">
+
+ <div class="flex-box">
+
+ <div class="flex-box-1 site-sidenav-collapse">
+ <i class="material-icons">menu</i>
+ </div>
+
+ <div class="flex-box-3 main-layout-no-nav">
+
+ <!-- this flex box separate environment cards from alerts -->
+ <div class="flex-box ">
+
+ <!-- this flex box for environment cards -->
+ <div class="flex-box-3 flex-box flex-col">
+ {{#each envItem in envList}}
+ <div class="cards-flex-col-h500 white flex-box-1 ">
+ <div class="flex-box justify-content-between">
+ <div class="flex-box-1">
+ <i class="material-icons">view_carousel</i>
+ </div>
+ <div class="flex-box-3">
+ <h3>Enviroment name: {{ envItem.name }}</h3>
+ <table class="table table-striped">
+ <tbody>
+ <tr>
+ <th>Distribution</th>
+ <td> {{ envItem.distribution }} </td>
+ </tr>
+ <tr>
+ <th>Number of regions: {{ regoinsCount (envItem.name) }}</th>
+ <td>
+ <div class="dropdown">
+ <button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
+ Select region from dropdown
+ <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
+ {{#each regionItem in regoins (envItem.name) }}
+ <li>
+ <a href="{{pathFor route='environment'
+ data=(asHash _id=(idToStr envItem._id))
+ query=(asHash
+ selectedNodeId=(idToStr regionItem._id ) ) }}"
+ >{{ regionItem.object_name }}</a>
+ </li>
+ {{/each}}
+ </ul>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <th>Number of projects: {{ projectsCount (envItem.name) }}</th>
+ <td>
+ <div class="dropdown">
+ <button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
+ Select project from dropdown
+ <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
+ {{#each projectItem in projects (envItem.name) }}
+ <li>
+ <a href="{{pathFor route='environment'
+ data=(asHash _id=(idToStr envItem._id))
+ query=(asHash selectedNodeId=(idToStr projectItem._id)) }}"
+ >{{ projectItem.object_name }}</a>
+ </li>
+ {{/each}}
+ </ul>
+ </div>
+
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <h5>Enviroment summary:</h5>
+ <div class="flex-box justify-content-around">
+ <div class="cards-w300-h60 white blue-text flex-box-1">
+ <div class="flex-box ">
+ <div class="flex-box-1">
+ <i class="fa fa-desktop"></i>
+ </div>
+ <div class="flex-box-3">
+ <p>Number of instances {{ instancesCount (envItem.name)}}</p>
+ </div>
+ </div>
+ </div>
+ <div class="cards-w300-h60 white blue-text flex-box-1">
+ <div class="flex-box ">
+ <div class="flex-box-1">
+ <i class="fa fa-object-group"></i>
+ </div>
+ <div class="flex-box-3">
+ <p>Number of vServices {{ vservicesCount (envItem.name) }}</p>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="flex-box justify-content-around">
+ <div class="cards-w300-h60 white blue-text flex-box-1">
+ <div class="flex-box ">
+ <div class="flex-box-1">
+ <i class="fa fa-compress"></i>
+ </div>
+ <div class="flex-box-3">
+ <p>Number of vConnectors {{ vconnectorsCount (envItem.name) }}</p>
+ </div>
+ </div>
+ </div>
+ <div class="cards-w300-h60 white blue-text flex-box-1">
+ <div class="flex-box ">
+ <div class="flex-box-1">
+ <i class="fa fa-server" aria-hidden="true"></i>
+ </div>
+ <div class="flex-box-3">
+ <p>Number of hosts {{ hostsCount (envItem.name) }}</p>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ {{/each}} <!-- envItem in envList -->
+
+ </div> <!-- flex box for environment cards -->
+
+ <!-- this flex box for alerts cards -->
+ <div class="flex-box-1 flex-box flex-col ">
+
+ <div class="sm-messages-section">
+ {{#each messagesInfoBox in (getListMessagesInfoBox) }}
+ <div class="sm-message-box">
+ {{> MessagesInfoBox (argsMessagesInfoBox messagesInfoBox
+ (messageCount messagesInfoBox.level)) }}
+ </div>
+ {{/each }}
+ </div>
+
+ </div> <!-- flex box for alerts cards -->
+
+ </div>
+
+ </div> <!-- main-layout -->
+ </div>
+
+</template>
diff --git a/ui/imports/ui/components/dashboard/dashboard.js b/ui/imports/ui/components/dashboard/dashboard.js
new file mode 100644
index 0000000..1a72a2d
--- /dev/null
+++ b/ui/imports/ui/components/dashboard/dashboard.js
@@ -0,0 +1,222 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: Dashboard
+ */
+
+//import * as R from 'ramda';
+import * as _ from 'lodash';
+import { Environments } from '/imports/api/environments/environments';
+import { //Messages,
+ calcIconForMessageLevel, lastMessageTimestamp, calcColorClassForMessagesInfoBox
+} from '/imports/api/messages/messages';
+import { Template } from 'meteor/templating';
+import { Inventory } from '/imports/api/inventories/inventories';
+import { Counts } from 'meteor/tmeasday:publish-counts';
+import { Counter } from 'meteor/natestrauser:publish-performant-counts';
+//import { Messages } from '/imports/api/messages/messages';
+import { store } from '/imports/ui/store/store';
+import { setMainAppSelectedEnvironment } from '/imports/ui/actions/main-app.actions';
+
+import '/imports/ui/components/messages-info-box/messages-info-box';
+
+import './dashboard.html';
+
+/*
+ * Lifecycle methods
+ */
+
+Template.Dashboard.onCreated(function () {
+ var instance = this;
+
+ instance.autorun(function () {
+ instance.subscribe('environments_config');
+
+ instance.subscribe('messages/count?level', 'info');
+ instance.subscribe('messages/count?level', 'warning');
+ instance.subscribe('messages/count?level', 'error');
+
+ Environments.find({}).forEach(function (envItem) {
+ instance.subscribe('inventory?env+type', envItem.name, 'instance');
+ instance.subscribe('inventory?env+type', envItem.name, 'vservice');
+ instance.subscribe('inventory?env+type', envItem.name, 'host');
+ instance.subscribe('inventory?env+type', envItem.name, 'vconnector');
+ instance.subscribe('inventory?env+type', envItem.name, 'project');
+ instance.subscribe('inventory?env+type', envItem.name, 'region');
+ });
+
+ store.dispatch(setMainAppSelectedEnvironment(null));
+ });
+});
+
+Template.Dashboard.rendered = function(){
+
+ /*
+ $.getScript('https://www.gstatic.com/charts/loader.js', function() {
+ google.charts.load('current', {'packages':['gauge', 'line']});
+ google.charts.setOnLoadCallback(drawLine);
+
+
+ function drawLine() {
+ var data = new google.visualization.DataTable();
+ data.addColumn('number', 'Traffic Webex');
+ data.addColumn('number', 'Traffic metapod');
+ data.addColumn('number', 'Some other Traffic');
+ data.addColumn('number', 'Some other Traffic');
+
+ data.addRows([
+ [1, 37.8, 80.8, 41.8],
+ [2, 30.9, 69.5, 32.4],
+ [3, 25.4, 57, 25.7],
+ [4, 11.7, 18.8, 32.5],
+ [5, 11.9, 25.6, 10.4],
+ [6, 68.8, 13.6, 27.7],
+ [7, 7.6, 42.3, 9.6],
+ [8, 12.3, 29.2, 10.6],
+ [9, 16.9, 42.9, 14.8]
+ ]);
+
+ var options = {
+ chart: {
+ title: 'Network traffic throughput',
+ subtitle: 'in Mbps'
+ }
+ };
+
+ var chart = new google.charts.Line(document.getElementById('curve_chart'));
+
+ chart.draw(data, options);
+ }
+ });
+
+ */
+};
+/*
+ * Helpers
+ */
+
+Template.Dashboard.helpers({
+
+ envList:function(){
+ //return Environments.find({type:'environment'});
+ return Environments.find({});
+ },
+
+ instancesCount: function (envName){
+ //return Inventory.find({environment: envName, type:'instance'}).count();
+ return Counts.get('inventory?env+type!counter?env=' +
+ envName + '&type=' + 'instance');
+ },
+
+ vservicesCount: function (envName) {
+ //return Inventory.find({environment: envName, type:'vservice'}).count();
+ return Counts.get('inventory?env+type!counter?env=' +
+ envName + '&type=' + 'vservice');
+ },
+
+ hostsCount: function (envName) {
+ //return Inventory.find({environment: envName, type:'host'}).count();
+ return Counts.get('inventory?env+type!counter?env=' +
+ envName + '&type=' + 'host');
+ },
+
+ vconnectorsCount: function(envName){
+ //return Inventory.find({environment: envName, type:'vconnector'}).count();
+ return Counts.get('inventory?env+type!counter?env=' +
+ envName + '&type=' + 'vconnector');
+ },
+
+ projectsCount: function (envName){
+ //return Inventory.find({environment: envName, type:'project'}).count();
+ return Counts.get('inventory?env+type!counter?env=' +
+ envName + '&type=' + 'project');
+ },
+
+ regoinsCount: function (envName){
+ //return Inventory.find({environment: envName, type:'region'}).count();
+ return Counts.get('inventory?env+type!counter?env=' +
+ envName + '&type=' + 'region');
+ },
+
+ regoins: function (envName) {
+ return Inventory.find({environment: envName, type:'region'});
+ },
+
+ projects: function (envName){
+ return Inventory.find({environment: envName, type:'project'});
+ },
+
+ notificationsCount: function(){
+ //return Messages.find({level:'notify'}).count();
+ return Counts.get('messages?level!counter?' +
+ 'level=' + 'notify');
+ },
+
+ warningsCount: function(){
+ //return Messages.find({level:'warn'}).count();
+ return Counts.get('messages?level!counter?' +
+ 'level=' + 'warn');
+ },
+
+ errorsCount: function(){
+ //return Messages.find({level:'error'}).count();
+ return Counts.get('messages?level!counter?' +
+ 'level=' + 'error');
+ },
+/*
+ notificationsTimestamp: function(){
+ var msgTimestamp = Messages.findOne({state:'added'},{fields: {'timestamp': 1} });
+ return msgTimestamp.timestamp;
+ },
+ warnings: function(){
+ return Messages.findOne({state:'warn'});
+ },
+ errors: function(){
+ return Messages.findOne({state:'down'});
+ },
+*/
+
+ getListMessagesInfoBox: function () {
+ return [
+ {
+ level: 'info'
+ },
+ {
+ level: 'warning'
+ },
+ {
+ level: 'error'
+ },
+ ];
+ },
+
+ messageCount: function (level) {
+ return Counter.get(`messages/count?level=${level}`);
+ },
+
+ argsMessagesInfoBox: function(boxDef, messageCount) {
+ //let instance = Template.instance();
+ let title = _.capitalize(boxDef.level);
+
+ return {
+ title: title,
+ count: messageCount,
+ lastScanTimestamp: lastMessageTimestamp(boxDef.level),
+ icon: calcIconForMessageLevel(boxDef.level),
+ colorClass: calcColorClassForMessagesInfoBox(boxDef.level),
+ onMoreDetailsReq: function () {
+ $('#messagesModalGlobal').modal('show', {
+ dataset: {
+ messageLevel: boxDef.level,
+ }
+ });
+ }
+ };
+ },
+}); // end: helpers
diff --git a/ui/imports/ui/components/dashboard/dashboard.styl b/ui/imports/ui/components/dashboard/dashboard.styl
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ui/imports/ui/components/dashboard/dashboard.styl
diff --git a/ui/imports/ui/components/data-cubic/data-cubic.html b/ui/imports/ui/components/data-cubic/data-cubic.html
new file mode 100644
index 0000000..1849b78
--- /dev/null
+++ b/ui/imports/ui/components/data-cubic/data-cubic.html
@@ -0,0 +1,23 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="DataCubic">
+ <div class="os-data-cubic cards-w250 cl-theme-{{ getTheme }}">
+ <div class="sm-container">
+ <div class="sm-icon-part" >
+ {{> Icon type=icon.type name=icon.name }}
+ </div>
+ <div class="sm-info-part">
+ <p>{{ header }}</p>
+ <span class="sm-data-info">{{ dataInfo }}</span>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/ui/imports/ui/components/data-cubic/data-cubic.js b/ui/imports/ui/components/data-cubic/data-cubic.js
new file mode 100644
index 0000000..ac0860a
--- /dev/null
+++ b/ui/imports/ui/components/data-cubic/data-cubic.js
@@ -0,0 +1,71 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: DataCubic
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import * as R from 'ramda';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { Icon } from '/imports/lib/icon';
+
+import './data-cubic.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.DataCubic.onCreated(function() {
+ var instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ theme: null
+ });
+
+ this.autorun(() => {
+ new SimpleSchema({
+ header: { type: String },
+ dataInfo: { type: String },
+ icon: { type: Icon },
+ theme: { type: String, optional: true }
+ }).validate(Template.currentData());
+
+ let theme = Template.currentData().theme;
+ theme = R.isNil(theme) ? 'light' : theme;
+ instance.state.set('theme', theme);
+ });
+});
+
+/*
+Template.DataCubic.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.DataCubic.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.DataCubic.helpers({
+ getTheme: function () {
+ let instance = Template.instance();
+ return instance.state.get('theme');
+ }
+});
+
+
diff --git a/ui/imports/ui/components/data-cubic/data-cubic.styl b/ui/imports/ui/components/data-cubic/data-cubic.styl
new file mode 100644
index 0000000..4bdb9b3
--- /dev/null
+++ b/ui/imports/ui/components/data-cubic/data-cubic.styl
@@ -0,0 +1,27 @@
+.os-data-cubic
+ display: flex;
+ flex-flow: row nowrap;
+ justify-content: center;
+
+ .sm-container
+ display: flex;
+ flex-flow: row nowrap;
+
+ .sm-icon-part
+ flex: 1
+
+ .sm-info-part
+ flex: 2
+
+ display: flex;
+ flex-flow: column nowrap
+
+ .sm-data-info
+ font-size: 12px;
+
+.os-data-cubic.cl-theme-dark
+ background-color: dk-gray1
+
+.os-data-cubic.cl-theme-light
+ color: spark-blue
+ background-color: #fff
diff --git a/ui/imports/ui/components/detailed-node-info-box/detailed-node-info-box.html b/ui/imports/ui/components/detailed-node-info-box/detailed-node-info-box.html
new file mode 100644
index 0000000..f8c7221
--- /dev/null
+++ b/ui/imports/ui/components/detailed-node-info-box/detailed-node-info-box.html
@@ -0,0 +1,53 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="DetailedNodeInfoBox">
+<div class="os-detailed-node-info-box cards-450 white">
+ <div class="sm-icon-segment">
+ </div>
+ <div class="sm-info-segment">
+ <div class="sm-info-title">{{ node.type }} - {{#if node.objectName }}{{ node.objectName }}{{else}} {{ node.name }}{{/if}}</div>
+ <div class="sm-info-bits">
+ <div class="cl-info-bit">
+ <div class="cl-label">Environment</div>
+ <div class="cl-data"><div class="">{{ node.environment }}</div></div>
+ </div>
+ <div class="cl-info-bit">
+ <div class="cl-label">Name</div>
+ <div class="cl-data"><div class="">{{ node.name }}</div></div>
+ </div>
+ <div class="cl-info-bit">
+ <div class="cl-label">Parent Id</div>
+ <div class="cl-data"><div class="">{{ node.parent_id }}</div></div>
+ </div>
+ <div class="cl-info-bit">
+ <div class="cl-label">Host</div>
+ <div class="cl-data"><div class="">{{ node.host }}</div></div>
+ </div>
+ <div class="cl-info-bit">
+ <div class="cl-label">Last Scanned</div>
+ <div class="cl-data"><div class="">{{ node.last_scanned }}</div></div>
+ </div>
+ <div class="cl-info-bit">
+ <div class="cl-label">Configurations</div>
+ <div class="cl-data"><div class="">{{ node.configurations }}</div></div>
+ </div>
+ <div class="cl-info-bit">
+ <div class="cl-label">Agent Type</div>
+ <div class="cl-data"><div class="">{{ node.agent_type }}</div></div>
+ </div>
+ <div class="cl-info-bit">
+ <div class="cl-label">Admin State Up</div>
+ <div class="cl-data"><div class="">{{ node.admin_state_up }}</div></div>
+ </div>
+ </div>
+ </div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/detailed-node-info-box/detailed-node-info-box.js b/ui/imports/ui/components/detailed-node-info-box/detailed-node-info-box.js
new file mode 100644
index 0000000..1f0917f
--- /dev/null
+++ b/ui/imports/ui/components/detailed-node-info-box/detailed-node-info-box.js
@@ -0,0 +1,57 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: DetailedNodeInfoBox
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { ReactiveDict } from 'meteor/reactive-dict';
+
+import './detailed-node-info-box.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.DetailedNodeInfoBox.onCreated(function() {
+ var instance = this;
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+ new SimpleSchema({
+ node: { type: Object, blackbox: true },
+ }).validate(data);
+ });
+});
+
+/*
+Template.DetailedNodeInfoBox.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.DetailedNodeInfoBox.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.DetailedNodeInfoBox.helpers({
+});
+
+
diff --git a/ui/imports/ui/components/detailed-node-info-box/detailed-node-info-box.styl b/ui/imports/ui/components/detailed-node-info-box/detailed-node-info-box.styl
new file mode 100644
index 0000000..d51b393
--- /dev/null
+++ b/ui/imports/ui/components/detailed-node-info-box/detailed-node-info-box.styl
@@ -0,0 +1,33 @@
+.os-detailed-node-info-box
+ display: flex;
+ flex-flow: row nowrap;
+
+ .sm-icon-segment
+ flex: 0 1 70px;
+
+ .sm-info-segment
+ flex: 1;
+ display: flex;
+ flex-flow: column nowrap;
+
+ .sm-info-title
+ color: #0a9ad7;
+ font-size: 2em;
+ border-bottom: 3px solid #0a9ad7;
+ line-height: 1.5em;
+
+ .sm-info-bits
+ padding: 5px 0px;
+
+ display: flex;
+ flex-flow: column nowrap;
+
+ .cl-info-bit
+ display: flex;
+ flex-flow: row nowrap;
+
+ .cl-label
+ flex: 0 0 110px;
+ color: black;
+ font-weight: bold;
+
diff --git a/ui/imports/ui/components/env-aci-info/env-aci-info.html b/ui/imports/ui/components/env-aci-info/env-aci-info.html
new file mode 100644
index 0000000..51b7afa
--- /dev/null
+++ b/ui/imports/ui/components/env-aci-info/env-aci-info.html
@@ -0,0 +1,88 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="EnvAciInfo">
+<div class="form-horizontal">
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >Host</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.host
+ key="host"
+ type="text"
+ disabled=disabled
+ placeholder="Host")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>Some help info</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >User</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.user
+ key="user"
+ type="text"
+ disabled=disabled
+ placeholder="User")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>Some help info</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >Password</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.pwd
+ key="pwd"
+ type="password"
+ disabled=disabled
+ placeholder="Password")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>Some help info</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <button type="button"
+ class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored toast"
+ >Test connection</button>
+ </div>
+
+ <div class="col-sm-offset-2 col-sm-10 btn-mgt-5">
+ <button type="button"
+ class="sm-next-button mdl-button mdl-js-button
+ btnNext"
+ >Next</button>
+ </div>
+ </div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/env-aci-info/env-aci-info.js b/ui/imports/ui/components/env-aci-info/env-aci-info.js
new file mode 100644
index 0000000..7d93687
--- /dev/null
+++ b/ui/imports/ui/components/env-aci-info/env-aci-info.js
@@ -0,0 +1,61 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: EnvAcinfo
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+import * as R from 'ramda';
+
+import { createInputArgs } from '/imports/ui/lib/input-model';
+
+import './env-aci-info.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.EnvAciInfo.onCreated(function() {
+});
+
+/*
+Template.EnvAciInfo.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.EnvAciInfo.events({
+ 'click .sm-next-button': function () {
+ let instance = Template.instance();
+ instance.data.onNextRequested();
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.EnvAciInfo.helpers({
+ createInputArgs: createInputArgs,
+
+ markIfDisabled: function () {
+ let instance = Template.instance();
+ let attrs = {};
+ if (instance.data.disabled) {
+ attrs = R.assoc('disabled', true, attrs);
+ }
+
+ return attrs;
+ }
+});
diff --git a/ui/imports/ui/components/env-amqp-credentials-info/env-amqp-credentials-info.html b/ui/imports/ui/components/env-amqp-credentials-info/env-amqp-credentials-info.html
new file mode 100644
index 0000000..b6187e6
--- /dev/null
+++ b/ui/imports/ui/components/env-amqp-credentials-info/env-amqp-credentials-info.html
@@ -0,0 +1,108 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="EnvAmqpCredentialsInfo">
+<div class="form-horizontal">
+ <!-- Host -->
+ <div class="form-group">
+
+ <label for="dbHost" class="col-sm-2
+ control-label"
+ >Host</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.host
+ key="host"
+ type="text"
+ disabled=disabled
+ placeholder="Host")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>This is AMQP host</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="dbPort"
+ class="col-sm-2 control-label"
+ >port</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.port
+ key="port"
+ type="text"
+ disabled=disabled
+ placeholder="Port")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>This is AMQP port</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="dbUsername"
+ class="col-sm-2 control-label"
+ >Username</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.user
+ key="user"
+ type="text"
+ disabled=disabled
+ placeholder="Username")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>This is AMQP user name</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="dbPassword"
+ class="col-sm-2 control-label"
+ >Password</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.password
+ key="password"
+ type="password"
+ disabled=disabled
+ placeholder="Password")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>This is AMQP password</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-2">
+ <button type="button"
+ class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored toast"
+ >Test connection</button>
+ </div>
+
+ <div class="col-sm-offset-2 col-sm-10 btn-mgt-5">
+ <button type="button" class="mdl-button mdl-js-button btnNext sm-next-button">Next</button>
+ </div>
+
+ </div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/env-amqp-credentials-info/env-amqp-credentials-info.js b/ui/imports/ui/components/env-amqp-credentials-info/env-amqp-credentials-info.js
new file mode 100644
index 0000000..3e1522a
--- /dev/null
+++ b/ui/imports/ui/components/env-amqp-credentials-info/env-amqp-credentials-info.js
@@ -0,0 +1,52 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: EnvAmqpCredentialsInfo
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+
+import { createInputArgs } from '/imports/ui/lib/input-model';
+
+import './env-amqp-credentials-info.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.EnvAmqpCredentialsInfo.onCreated(function() {
+});
+
+/*
+Template.EnvAmqpCredentialsInfo.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.EnvAmqpCredentialsInfo.events({
+ 'click .sm-next-button': function () {
+ let instance = Template.instance();
+ instance.data.onNextRequested();
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.EnvAmqpCredentialsInfo.helpers({
+ createInputArgs: createInputArgs
+});
+
+
diff --git a/ui/imports/ui/components/env-delete-modal/env-delete-modal.html b/ui/imports/ui/components/env-delete-modal/env-delete-modal.html
new file mode 100644
index 0000000..40ac5d9
--- /dev/null
+++ b/ui/imports/ui/components/env-delete-modal/env-delete-modal.html
@@ -0,0 +1,48 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="EnvDeleteModal">
+<div class="modal fade sm-event-modals"
+ id="env-delete-modal"
+ tabindex="-1"
+ role="dialog"
+ aria-labelledby="Delete environment">
+
+ <div class="modal-dialog"
+ role="document">
+
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button"
+ class="close"
+ data-dismiss="modal"
+ aria-label="Close"
+ ><span aria-hidden="true">&times;</span>
+ </button>
+ <i class="fa fa-trash-o" aria-hidden="true"></i>
+ </div>
+
+ <div class="modal-body">
+ <h5 class="modal-title"
+ id="myModalLabel"
+ >Delete environment confirmation</h5>
+ <p>Are you sure you want to delete this environment?</p>
+ </div>
+ <div class="modal-footer">
+ <button type="button"
+ class="btn btn-default pull-left"
+ data-dismiss="modal">Cancel</button>
+ <button type="button"
+ class="sm-button-delete btn btn-primary">Delete</button>
+ </div>
+ </div>
+ </div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/env-delete-modal/env-delete-modal.js b/ui/imports/ui/components/env-delete-modal/env-delete-modal.js
new file mode 100644
index 0000000..7784c4d
--- /dev/null
+++ b/ui/imports/ui/components/env-delete-modal/env-delete-modal.js
@@ -0,0 +1,55 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: EnvDeleteModal
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+
+import './env-delete-modal.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.EnvDeleteModal.onCreated(function() {
+ this.autorun(() => {
+ new SimpleSchema({
+ onDeleteReq: { type: Function },
+ }).validate(Template.currentData());
+ });
+});
+
+/*
+Template.EnvDeleteModal.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.EnvDeleteModal.events({
+ 'click .sm-button-delete': function (_event, _instance) {
+ let onDeleteReq = Template.currentData().onDeleteReq;
+ onDeleteReq();
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.EnvDeleteModal.helpers({
+});
+
+
diff --git a/ui/imports/ui/components/env-delete-modal/env-delete-modal.styl b/ui/imports/ui/components/env-delete-modal/env-delete-modal.styl
new file mode 100644
index 0000000..5fde285
--- /dev/null
+++ b/ui/imports/ui/components/env-delete-modal/env-delete-modal.styl
@@ -0,0 +1,2 @@
+/* Set the component style here */
+// "EnvDeleteModal"
diff --git a/ui/imports/ui/components/env-form/env-form.html b/ui/imports/ui/components/env-form/env-form.html
new file mode 100644
index 0000000..a0fd3bd
--- /dev/null
+++ b/ui/imports/ui/components/env-form/env-form.html
@@ -0,0 +1,37 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="envForm">
+
+<!-- li class dropdown -->
+<a id="dLabel" data-target="#" href="#" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
+ {{ selectedEnvName }}
+ <span class="caret"></span>
+</a>
+
+<ul class="os-env-form-dropdown-menu dropdown-menu"
+ aria-labelledby="dLabel"
+ style="color: black; padding: 10px;" >
+
+ <li><a href="{{pathFor route='wizard'}}" class="droplist">Add new environment</a></li>
+ <li class="divider"></li>
+ <li style="border-bottom: 3px solid #2196f3;">Existing environments:</li>
+ {{#each envItem in envList}}
+ <li><a class="sm-env-item envList droplist"
+ data-env-name="{{ envItem.name }}"
+ data-env-id="{{ idToStr envItem._id }}"
+ >{{envItem.name}}</a></li>
+ {{/each}}
+</ul>
+
+</template>
+
+
+
diff --git a/ui/imports/ui/components/env-form/env-form.js b/ui/imports/ui/components/env-form/env-form.js
new file mode 100644
index 0000000..3007021
--- /dev/null
+++ b/ui/imports/ui/components/env-form/env-form.js
@@ -0,0 +1,94 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: envForm
+ */
+
+import * as R from 'ramda';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { Environments } from '/imports/api/environments/environments';
+import { parseReqId } from '/imports/lib/utilities';
+
+import './env-form.html';
+
+/*
+ * Lifecycle methods
+ */
+
+Template.envForm.onCreated(function () {
+ var instance = this;
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ selectedEnv: null
+ });
+
+
+ instance.autorun(function() {
+ let data = R.when(R.isNil, R.always({}), Template.currentData());
+
+ new SimpleSchema({
+ selectedEnvironment: {
+ type: Object,
+ blackbox: true,
+ optional: true
+ },
+ onEnvSelected: { type: Function }
+ }).validate(data);
+
+ instance.state.set('selectedEnv', data.selectedEnvironment);
+
+ instance.subscribe('environments_config');
+ });
+});
+
+/*
+ * Events
+ */
+
+Template.envForm.events = {
+ 'click .os-env-form-dropdown-menu .sm-env-item': function (event, _instance) {
+ event.preventDefault();
+
+ let envName = R.path(['target','dataset', 'envName'], event);
+ let _id = R.path(['target', 'dataset', 'envId'], event);
+
+ if (R.isNil(envName)) { return; }
+ _id = parseReqId(_id);
+
+ let data = Template.currentData();
+ if (data.onEnvSelected) {
+ data.onEnvSelected({
+ _id: _id.id,
+ name: envName
+ });
+ }
+ }
+};
+
+/*
+ * Helpers
+ */
+
+Template.envForm.helpers({
+ selectedEnvName: function () {
+ let instance = Template.instance();
+ let selectedEnv = instance.state.get('selectedEnv');
+
+ let envName = R.when(
+ R.isNil,
+ R.always('My Environments')
+ )(R.path(['name'], selectedEnv));
+
+ return envName;
+ },
+
+ envList: function () {
+ return Environments.find({});
+ },
+});
diff --git a/ui/imports/ui/components/env-form/env-form.styl b/ui/imports/ui/components/env-form/env-form.styl
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ui/imports/ui/components/env-form/env-form.styl
diff --git a/ui/imports/ui/components/env-main-info/env-main-info.html b/ui/imports/ui/components/env-main-info/env-main-info.html
new file mode 100644
index 0000000..c3bda99
--- /dev/null
+++ b/ui/imports/ui/components/env-main-info/env-main-info.html
@@ -0,0 +1,193 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="EnvMainInfo">
+ <div class="form-horizontal">
+
+ <div class="form-group">
+ <label for="inputEmail"
+ class="col-sm-2 control-label"
+ >Owner</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.user
+ key="user"
+ type="email"
+ disabled=true
+ placeholder="Email")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>Owner of environment</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="inputEnvName"
+ class="col-sm-2 control-label"
+ >Enviroment name</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.name
+ key='name'
+ type="text"
+ disabled=(isFieldDisabled 'name' disabled)
+ placeholder="Name")
+ }}
+ </div>
+ <div class="col-sm-4">
+ <p>Enter name of your encironment, it could be anything you want</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >Distribution</label>
+
+ <div class="col-sm-3">
+ {{> SelectModel(createSelectArgs
+ values=model.distribution
+ key="distribution"
+ disabled=(isFieldDisabled 'distribution' disabled)
+ options=distributionOptions
+ showNullOption=false)
+ }}
+ </div>
+ <div class="col-sm-4">
+ <p>Enter type of the distribution</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >Type Drivers</label>
+
+ <div class="col-sm-3">
+ {{> SelectModel(createSelectArgs
+ values=model.type_drivers
+ key="type_drivers"
+ options=typeDriversOptions
+ disabled=disabled
+ multi=false
+ showNullOption=false)
+ }}
+ </div>
+ <div class="col-sm-4">
+ <p>Enter type driver</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >Mechanism Drivers</label>
+
+ <div class="col-sm-3">
+ {{> SelectModel(createSelectArgs
+ values=model.mechanism_drivers
+ key="mechanism_drivers"
+ options=mechanismDriversOptions
+ disabled=disabled
+ multi=true)
+ }}
+ </div>
+ <div class="col-sm-4">
+ <p>Enter mechanism drivers</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >Event based scan</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.listen
+ key='listen'
+ type="checkbox"
+ disabled=isListeningDisabled)
+ }}
+ </div>
+ <div class="col-sm-4">
+ <p>Update the inventory in real-time whenever a user makes a change to the OpenStack environment</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >Enable monitoring</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.enable_monitoring
+ key='enable_monitoring'
+ type="checkbox"
+ disabled=isMonitoringDisabled)
+ }}
+ </div>
+ <div class="col-sm-4">
+ <p>Enable monitoring</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >Enable ACI</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.aci
+ key='aci'
+ type="checkbox"
+ disabled=disabled)
+ }}
+ </div>
+ <div class="col-sm-4">
+ <p>Enable ACI</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="inputEnvName"
+ class="col-sm-2 control-label"
+ >Operational</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.operational
+ key='operational'
+ type="text"
+ disabled=true
+ placeholder="Operational")
+ }}
+ </div>
+ <div class="col-sm-4">
+ <p>Enter name of your encironment, it could be anything you want</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10 btn-mgt-5">
+ <button type="button"
+ class="sm-next-button mdl-button mdl-js-button
+ btnNext"
+ >Next</button>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/ui/imports/ui/components/env-main-info/env-main-info.js b/ui/imports/ui/components/env-main-info/env-main-info.js
new file mode 100644
index 0000000..d866c0e
--- /dev/null
+++ b/ui/imports/ui/components/env-main-info/env-main-info.js
@@ -0,0 +1,123 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: EnvMainInfo
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import * as R from 'ramda';
+
+import '/imports/ui/components/input-model/input-model';
+import '/imports/ui/components/select-model/select-model';
+import { createInputArgs } from '/imports/ui/lib/input-model';
+import { createSelectArgs } from '/imports/ui/lib/select-model';
+import { Constants } from '/imports/api/constants/constants';
+
+import './env-main-info.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.EnvMainInfo.onCreated(function () {
+ let instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ action: null,
+ });
+
+ instance.autorun(function () {
+ let action = Template.currentData().action;
+ instance.state.set('action', action);
+
+ instance.subscribe('constants');
+ });
+
+});
+
+/*
+Template.EnvironmentWizard.rendered = function(){
+};
+*/
+
+/*
+ * Helpers
+ */
+
+Template.EnvMainInfo.helpers({
+ /*
+ createInputArgs: function (params) {
+ let instance = Template.instance();
+ return {
+ context: params.hash.context,
+ key: params.hash.key,
+ type: params.hash.type,
+ placeholder: params.hash.placeholder,
+ setModel: instance.data.setModel
+ };
+ }*/
+ createInputArgs: createInputArgs,
+
+ createSelectArgs: createSelectArgs,
+
+ distributionOptions: function () {
+ let item = Constants.findOne({ name: 'distributions' });
+ if (R.isNil(item)) { return []; }
+ return item.data;
+ },
+
+ /* depracated
+ networkOptions: function () {
+ let item = Constants.findOne({ name: 'network_plugins' });
+ if (R.isNil(item)) { return []; }
+ return item.data;
+ },
+ */
+
+ typeDriversOptions: function () {
+ let item = Constants.findOne({ name: 'type_drivers' });
+ if (R.isNil(item)) { return []; }
+ return item.data;
+ },
+
+ mechanismDriversOptions: function () {
+ let item = Constants.findOne({ name: 'mechanism_drivers' });
+ if (R.isNil(item)) { return []; }
+ return item.data;
+ },
+
+ isFieldDisabled: function (fieldName, globalDisabled) {
+ let instance = Template.instance();
+ if (globalDisabled) { return true; }
+
+ return isDisabledByField(fieldName, instance.state.get('action'));
+ }
+});
+
+/*
+ * Events
+ */
+
+Template.EnvMainInfo.events({
+ 'click .sm-next-button': function () {
+ let instance = Template.instance();
+ instance.data.onNextRequested();
+ }
+});
+
+function isDisabledByField(fieldName, actionName) {
+ if (R.contains(fieldName, ['name', 'distribution']) && actionName !== 'insert') {
+ return true;
+ }
+
+ return false;
+}
diff --git a/ui/imports/ui/components/env-master-host-credentials-info/env-master-host-credentials-info.html b/ui/imports/ui/components/env-master-host-credentials-info/env-master-host-credentials-info.html
new file mode 100644
index 0000000..e24c016
--- /dev/null
+++ b/ui/imports/ui/components/env-master-host-credentials-info/env-master-host-credentials-info.html
@@ -0,0 +1,109 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="EnvMasterHostCredentialsInfo">
+<div class="form-horizontal">
+
+
+ <div class="form-group">
+ <label for="mhUsername"
+ class="col-sm-2 control-label"
+ >Host</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.host
+ key="host"
+ type="text"
+ disabled=disabled
+ placeholder="Host")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>This is master jump host</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="sshKey"
+ class="col-sm-2 control-label"
+ >SSH Key</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.key
+ key="key"
+ type="text"
+ disabled=disabled
+ placeholder="SSH Key")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>This is master ssh key</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="mhUsername"
+ class="col-sm-2 control-label"
+ >Master Host Username</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.user
+ key="user"
+ type="text"
+ disabled=disabled
+ placeholder="Master Host Username")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>This is master user name</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="mhPassword"
+ class="col-sm-2 control-label"
+ >Master Host Password</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.pwd
+ key="pwd"
+ type="password"
+ disabled=disabled
+ placeholder="Master Host Password")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>This is master password</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-2">
+ <button type="button"
+ class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored toast"
+ >Test connection</button>
+ </div>
+
+ <div class="col-sm-offset-2 col-sm-10 btn-mgt-5">
+ <button type="button" class="mdl-button mdl-js-button btnNext sm-next-button"
+ >Next</button>
+ </div>
+
+ </div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/env-master-host-credentials-info/env-master-host-credentials-info.js b/ui/imports/ui/components/env-master-host-credentials-info/env-master-host-credentials-info.js
new file mode 100644
index 0000000..89b9fba
--- /dev/null
+++ b/ui/imports/ui/components/env-master-host-credentials-info/env-master-host-credentials-info.js
@@ -0,0 +1,52 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: EnvMasterHostCredentialsInfo
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+
+import { createInputArgs } from '/imports/ui/lib/input-model';
+
+import './env-master-host-credentials-info.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.EnvMasterHostCredentialsInfo.onCreated(function() {
+});
+
+/*
+Template.EnvMasterHostCredentialsInfo.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.EnvMasterHostCredentialsInfo.events({
+ 'click .sm-next-button': function () {
+ let instance = Template.instance();
+ instance.data.onNextRequested();
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.EnvMasterHostCredentialsInfo.helpers({
+ createInputArgs: createInputArgs
+});
+
+
diff --git a/ui/imports/ui/components/env-monitoring-info/env-monitoring-info.html b/ui/imports/ui/components/env-monitoring-info/env-monitoring-info.html
new file mode 100644
index 0000000..ee0cc04
--- /dev/null
+++ b/ui/imports/ui/components/env-monitoring-info/env-monitoring-info.html
@@ -0,0 +1,283 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="EnvMonitoringInfo">
+<div class="form-horizontal">
+
+ {{#if disabled }}
+ {{#if disabledMessage }}
+ <div class="alert alert-danger">
+ {{ disabledMessage }}
+ </div>
+ {{/if }}
+ {{/if }}
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >Environment Type</label>
+
+ <div class="col-sm-3">
+ {{> SelectModel(createSelectArgs
+ values=model.env_type
+ key="env_type"
+ disabled=disabled
+ options=envTypeOptions
+ )
+ }}
+ </div>
+ <div class="col-sm-4">
+ <p>Enter environment type</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >RabbitMQ Port</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.rabbitmq_port
+ key="rabbitmq_port"
+ type="text"
+ disabled=disabled
+ placeholder="RAbbitMQ port")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>Port used for RabbitMQ transport</p>
+ </div>
+ </div>
+
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >RabbitMQ User</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.rabbitmq_user
+ key="rabbitmq_user"
+ type="text"
+ disabled=disabled
+ placeholder="User")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>User used to access RabbitMQ</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >RabbitMQ Password</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.rabbitmq_pass
+ key="rabbitmq_pass"
+ type="password"
+ disabled=disabled
+ placeholder="RabbitMQ Password")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>Password used to access RabbitMQ</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >Server IP</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.server_ip
+ key="server_ip"
+ type="text"
+ disabled=disabled
+ placeholder="Server IP")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>Network name or IP address of server on which Sensu will run</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >Server Name</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.server_name
+ key="server_name"
+ type="text"
+ disabled=disabled
+ placeholder="Server name")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>Name of the server on which Sensu runs. Example: 'devtest-sensu'</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >Type</label>
+
+ <div class="col-sm-3">
+ {{> SelectModel(createSelectArgs
+ values=model.type
+ key="type"
+ disabled=disabled
+ options=monitoringTypeOptions
+ showNullOption=false)
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>Type of monitoring system used</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >Provision</label>
+
+ <div class="col-sm-3">
+ {{> SelectModel(createSelectArgs
+ values=model.provision
+ key="provision"
+ disabled=disabled
+ options=provisionOptions
+ showNullOption=false)
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>Provision</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >Config folder</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.config_folder
+ key="config_folder"
+ type="text"
+ disabled=disabled
+ placeholder="Config folder")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>Config folder</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >SSH Port</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.ssh_port
+ key="ssh_port"
+ type="text"
+ disabled=disabled
+ placeholder="SSH port")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>SSH Port</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >SSH User</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.ssh_user
+ key="ssh_user"
+ type="text"
+ disabled=disabled
+ placeholder="SSH User")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>SSH User</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >SSH Password</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.ssh_password
+ key="ssh_password"
+ type="password"
+ disabled=disabled
+ placeholder="SSH Password")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>SSH Password</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >API Port</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.api_port
+ key="api_port"
+ type="number"
+ disabled=disabled
+ placeholder="API port")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>Port used for monitoring API</p>
+ </div>
+ </div>
+
+</div>
+</template>
diff --git a/ui/imports/ui/components/env-monitoring-info/env-monitoring-info.js b/ui/imports/ui/components/env-monitoring-info/env-monitoring-info.js
new file mode 100644
index 0000000..cbe5e47
--- /dev/null
+++ b/ui/imports/ui/components/env-monitoring-info/env-monitoring-info.js
@@ -0,0 +1,76 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: EnvMonitoringInfo
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+import * as R from 'ramda';
+
+import { createInputArgs } from '/imports/ui/lib/input-model';
+import { createSelectArgs } from '/imports/ui/lib/select-model';
+import { Constants } from '/imports/api/constants/constants';
+
+import './env-monitoring-info.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.EnvMonitoringInfo.onCreated(function() {
+ let instance = this;
+
+ instance.autorun(function () {
+ instance.subscribe('constants');
+ });
+});
+
+/*
+Template.EnvMonitoringInfo.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.EnvMonitoringInfo.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.EnvMonitoringInfo.helpers({
+ createInputArgs: createInputArgs,
+
+ createSelectArgs: createSelectArgs,
+
+ envTypeOptions: function () {
+ let item = Constants.findOne({ name: 'env_types' });
+ if (R.isNil(item)) { return []; }
+ return item.data;
+ },
+
+ monitoringTypeOptions: function () {
+ let item = Constants.findOne({ name: 'environment_monitoring_types' });
+ if (R.isNil(item)) { return []; }
+ return item.data;
+ },
+
+ provisionOptions: function () {
+ let item = Constants.findOne({ name: 'environment_provision_types' });
+ if (R.isNil(item)) { return []; }
+ return item.data;
+ },
+});
+
+
diff --git a/ui/imports/ui/components/env-nfv-info/env-nfv-info.html b/ui/imports/ui/components/env-nfv-info/env-nfv-info.html
new file mode 100644
index 0000000..9b820ba
--- /dev/null
+++ b/ui/imports/ui/components/env-nfv-info/env-nfv-info.html
@@ -0,0 +1,128 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="EnvNfvInfo">
+<div class="form-horizontal">
+ <div class="form-group">
+ <label for="nfvProvider"
+ class="col-sm-2 control-label"
+ >Master host</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.host
+ key="host"
+ type="text"
+ disabled=disabled
+ placeholder="Host")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>Some help info</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="mhUsername"
+ class="col-sm-2 control-label"
+ >NFV token</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.nfv_token
+ key="nfv_token"
+ type="text"
+ disabled=disabled
+ placeholder="NFV token")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>Some help info</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >Port</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.port
+ key="port"
+ type="text"
+ disabled=disabled
+ placeholder="Port")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>Some help info</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >User</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.user
+ key="user"
+ type="text"
+ disabled=disabled
+ placeholder="User")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>Some help info</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for=""
+ class="col-sm-2 control-label"
+ >Password</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.pwd
+ key="pwd"
+ type="password"
+ disabled=disabled
+ placeholder="Password")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>Some help info</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <button type="button"
+ class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored toast"
+ >Test connection</button>
+ </div>
+
+ <div class="col-sm-offset-2 col-sm-10 btn-mgt-5">
+ <button type="button"
+ class="sm-next-button mdl-button mdl-js-button
+ btnNext"
+ >Next</button>
+ </div>
+ </div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/env-nfv-info/env-nfv-info.js b/ui/imports/ui/components/env-nfv-info/env-nfv-info.js
new file mode 100644
index 0000000..296379c
--- /dev/null
+++ b/ui/imports/ui/components/env-nfv-info/env-nfv-info.js
@@ -0,0 +1,63 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: EnvNfvInfo
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+import * as R from 'ramda';
+
+import { createInputArgs } from '/imports/ui/lib/input-model';
+
+import './env-nfv-info.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.EnvNfvInfo.onCreated(function() {
+});
+
+/*
+Template.EnvNfvInfo.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.EnvNfvInfo.events({
+ 'click .sm-next-button': function () {
+ let instance = Template.instance();
+ instance.data.onNextRequested();
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.EnvNfvInfo.helpers({
+ createInputArgs: createInputArgs,
+
+ markIfDisabled: function () {
+ let instance = Template.instance();
+ let attrs = {};
+ if (instance.data.disabled) {
+ attrs = R.assoc('disabled', true, attrs);
+ }
+
+ return attrs;
+ }
+});
+
+
diff --git a/ui/imports/ui/components/env-open-stack-db-credentials-info/env-open-stack-db-credentials-info.html b/ui/imports/ui/components/env-open-stack-db-credentials-info/env-open-stack-db-credentials-info.html
new file mode 100644
index 0000000..11421a0
--- /dev/null
+++ b/ui/imports/ui/components/env-open-stack-db-credentials-info/env-open-stack-db-credentials-info.html
@@ -0,0 +1,109 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="EnvOpenStackDbCredentialsInfo">
+<div class="form-horizontal">
+
+ <div class="form-group">
+
+ <label for="dbHost" class="col-sm-2
+ control-label"
+ >DB Host</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.host
+ key="host"
+ type="text"
+ disabled=disabled
+ placeholder="Db Host")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>This is db server</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="dbPort"
+ class="col-sm-2 control-label"
+ >DB port</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.port
+ key="port"
+ type="text"
+ disabled=disabled
+ placeholder="Db port")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>This is db port</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="dbUsername"
+ class="col-sm-2 control-label"
+ >DB Username</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.user
+ key="user"
+ type="text"
+ disabled=disabled
+ placeholder="Db Username")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>This is db user name</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="dbPassword"
+ class="col-sm-2 control-label"
+ >Password</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.password
+ key="password"
+ type="password"
+ disabled=disabled
+ placeholder="Password")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>This is db password</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-2">
+ <button type="button"
+ class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored toast"
+ >Test connection</button>
+ </div>
+
+ <div class="col-sm-offset-2 col-sm-10 btn-mgt-5">
+ <button type="button"
+ class="mdl-button mdl-js-button btnNext sm-next-button"
+ >Next</button>
+ </div>
+ </div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/env-open-stack-db-credentials-info/env-open-stack-db-credentials-info.js b/ui/imports/ui/components/env-open-stack-db-credentials-info/env-open-stack-db-credentials-info.js
new file mode 100644
index 0000000..961e5b6
--- /dev/null
+++ b/ui/imports/ui/components/env-open-stack-db-credentials-info/env-open-stack-db-credentials-info.js
@@ -0,0 +1,52 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: EnvOpenStackDbCredentialsInfo
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+
+import { createInputArgs } from '/imports/ui/lib/input-model';
+
+import './env-open-stack-db-credentials-info.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.EnvOpenStackDbCredentialsInfo.onCreated(function() {
+});
+
+/*
+Template.EnvOpenStackDbCredentialsInfo.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.EnvOpenStackDbCredentialsInfo.events({
+ 'click .sm-next-button': function () {
+ let instance = Template.instance();
+ instance.data.onNextRequested();
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.EnvOpenStackDbCredentialsInfo.helpers({
+ createInputArgs: createInputArgs
+});
+
+
diff --git a/ui/imports/ui/components/env-os-api-endpoint-info/env-os-api-endpoint-info.html b/ui/imports/ui/components/env-os-api-endpoint-info/env-os-api-endpoint-info.html
new file mode 100644
index 0000000..3f35b9a
--- /dev/null
+++ b/ui/imports/ui/components/env-os-api-endpoint-info/env-os-api-endpoint-info.html
@@ -0,0 +1,125 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="EnvOsApiEndpointInfo">
+<div class="form-horizontal">
+ <!-- Host -->
+ <div class="form-group">
+ <label for="apiHost"
+ class="col-sm-2 control-label">API host</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.host
+ key="host"
+ type="text"
+ disabled=disabled
+ placeholder="API Host")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>This is API server</p>
+ </div>
+ </div>
+
+ <!-- Port -->
+ <div class="form-group">
+ <label for="dbPort"
+ class="col-sm-2 control-label"
+ >Port</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.port
+ key="port"
+ type="text"
+ disabled=disabled
+ placeholder="Port")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>This is API port</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="adminToken"
+ class="col-sm-2 control-label"
+ >Admin Token</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.admin_token
+ key="admin_token"
+ type="text"
+ disabled=disabled
+ placeholder="Admin Token")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>This is API admin token</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="Username"
+ class="col-sm-2 control-label"
+ >Username</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.user
+ key="user"
+ type="text"
+ disabled=disabled
+ placeholder="Username")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>This is API user name</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="apiPassword" class="col-sm-2 control-label"
+ >Password</label>
+
+ <div class="col-sm-3">
+ {{> InputModel(createInputArgs
+ value=model.pwd
+ key="pwd"
+ type="password"
+ disabled=disabled
+ placeholder="Password")
+ }}
+ </div>
+
+ <div class="col-sm-4">
+ <p>This is API password</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-2">
+ <button type="button"
+ class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored toast">Test connection</button>
+ </div>
+
+ <div class="col-sm-offset-2 col-sm-10 btn-mgt-5">
+ <button type="button" class="mdl-button mdl-js-button btnNext sm-next-button">Next</button>
+ </div>
+
+ </div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/env-os-api-endpoint-info/env-os-api-endpoint-info.js b/ui/imports/ui/components/env-os-api-endpoint-info/env-os-api-endpoint-info.js
new file mode 100644
index 0000000..0f503f1
--- /dev/null
+++ b/ui/imports/ui/components/env-os-api-endpoint-info/env-os-api-endpoint-info.js
@@ -0,0 +1,52 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: EnvOsApiEndpointInfo
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+
+import { createInputArgs } from '/imports/ui/lib/input-model';
+
+import './env-os-api-endpoint-info.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.EnvOsApiEndpointInfo.onCreated(function() {
+});
+
+/*
+Template.EnvOsApiEndpointInfo.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.EnvOsApiEndpointInfo.events({
+ 'click .sm-next-button': function () {
+ let instance = Template.instance();
+ instance.data.onNextRequested();
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.EnvOsApiEndpointInfo.helpers({
+ createInputArgs: createInputArgs
+});
+
+
diff --git a/ui/imports/ui/components/environment-dashboard/environment-dashboard.html b/ui/imports/ui/components/environment-dashboard/environment-dashboard.html
new file mode 100644
index 0000000..0d2c8f6
--- /dev/null
+++ b/ui/imports/ui/components/environment-dashboard/environment-dashboard.html
@@ -0,0 +1,59 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="EnvironmentDashboard">
+
+<div class="os-environment-dashboard mainContentData flex">
+ <div class="sm-env-header-line cards white title">
+ <div class="sm-env-header-name-cmp">
+ <h4>Enviroment name: {{ getState 'envName' }}</h4>
+ </div>
+
+ <a class="sm-scan-button cl-action-button btn btn-app"
+ ><i class="fa fa-search-plus" aria-hidden="true"></i></a>
+
+ <a class="sm-edit-button cl-action-button btn btn-app {{#if notAllowEdit}}cl-action-disabled{{/if}}"
+ ><i class="fa fa-fw fa-edit"></i></a>
+
+ <a class="sm-delete-button cl-action-button btn btn-app {{#if notAllowEdit}}cl-action-disabled{{/if}}"
+ href="#" >
+ <i class="fa fa-trash-o" aria-hidden="true"></i>
+ </a>
+ </div>
+
+ <div class="sm-items-segment mainContentData flex-box">
+
+ {{#each briefInfo in (getBriefInfoList) }}
+ {{> DataCubic (argsBriefInfo briefInfo) }}
+ {{/each }}
+
+ </div>
+
+ <div class="sm-messages-section mainContentData">
+ {{#if (getState 'envName') }}
+ {{#each messagesInfoBox in (getListMessagesInfoBox) }}
+ <div class="sm-message-box">
+ {{> MessagesInfoBox (argsMessagesInfoBox messagesInfoBox (getState 'envName')) }}
+ </div>
+ {{/each }}
+ {{/if}}
+ </div>
+
+ <div class="sm-list-info-boxes mainContentData">
+ {{#each listInfoBox in (getListInfoBoxes) }}
+ {{> ListInfoBox (argsListInfoBox listInfoBox) }}
+ {{/each }}
+ </div>
+
+ {{> EnvDeleteModal argsEnvDeleteModal }}
+
+</div>
+
+</template>
diff --git a/ui/imports/ui/components/environment-dashboard/environment-dashboard.js b/ui/imports/ui/components/environment-dashboard/environment-dashboard.js
new file mode 100644
index 0000000..433096e
--- /dev/null
+++ b/ui/imports/ui/components/environment-dashboard/environment-dashboard.js
@@ -0,0 +1,380 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: EnvironmentDashboard
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import * as R from 'ramda';
+import * as _ from 'lodash';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { remove } from '/imports/api/environments/methods';
+import { Icon } from '/imports/lib/icon';
+import { store } from '/imports/ui/store/store';
+import { Environments } from '/imports/api/environments/environments';
+import { Inventory } from '/imports/api/inventories/inventories';
+import { calcIconForMessageLevel, lastMessageTimestamp, calcColorClassForMessagesInfoBox }
+ from '/imports/api/messages/messages';
+import { Counts } from 'meteor/tmeasday:publish-counts';
+import { Roles } from 'meteor/alanning:roles';
+//import { idToStr } from '/imports/lib/utilities';
+
+import '/imports/ui/components/data-cubic/data-cubic';
+import '/imports/ui/components/icon/icon';
+import '/imports/ui/components/list-info-box/list-info-box';
+import './environment-dashboard.html';
+import '/imports/ui/components/messages-info-box/messages-info-box';
+import '/imports/ui/components/messages-modal/messages-modal';
+
+let briefInfoList = [{
+ header: ['components', 'environment', 'briefInfos', 'instancesNum', 'header'],
+ dataSource: 'infoInstancesCount',
+ icon: new Icon({ type: 'fa', name: 'desktop' }),
+}, {
+ header: ['components', 'environment', 'briefInfos', 'vServicesNum', 'header'],
+ dataSource: 'infoVServicesCount',
+ icon: new Icon({ type: 'fa', name: 'object-group' }),
+}, {
+ header: ['components', 'environment', 'briefInfos', 'hostsNum', 'header'],
+ dataSource: 'infoHostsCount',
+ icon: new Icon({ type: 'fa', name: 'server' }),
+}, {
+ header: ['components', 'environment', 'briefInfos', 'vConnectorsNum', 'header'],
+ dataSource: 'infoVConnectorsCount',
+ icon: new Icon({ type: 'fa', name: 'compress' }),
+}, {
+ header: ['components', 'environment', 'briefInfos', 'lastScanning', 'header'],
+ dataSource: 'infoLastScanning',
+ icon: new Icon({ type: 'fa', name: 'search' }),
+}];
+
+let listInfoBoxes = [{
+ header: ['components', 'environment', 'listInfoBoxes', 'regions', 'header'],
+ listName: 'regions',
+ listItemFormat: {
+ getLabelFn: (item) => { return item.name; },
+ getValueFn: (item) => { return item._id._str; },
+ },
+ icon: { type: 'material', name: 'public' },
+}, {
+ header: ['components', 'environment', 'listInfoBoxes', 'projects', 'header'],
+ listName: 'projects',
+ listItemFormat: {
+ getLabelFn: (item) => { return item.name; },
+ getValueFn: (item) => { return item._id._str; },
+ },
+ icon: { type: 'material', name: 'folder' },
+}];
+
+/*
+ * Lifecycles
+ */
+
+Template.EnvironmentDashboard.onCreated(function() {
+ var instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ _id: null,
+ envName: null,
+ allowEdit: false,
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+ new SimpleSchema({
+ _id: { type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } } },
+ onNodeSelected: { type: Function },
+ }).validate(data);
+
+ instance.state.set('_id', data._id);
+ });
+
+ instance.autorun(function () {
+ let _id = instance.state.get('_id');
+
+ instance.subscribe('environments?_id', _id);
+ Environments.find({ _id: _id }).forEach((env) => {
+ instance.state.set('envName', env.name);
+ instance.state.set('infoLastScanning', env.last_scanned);
+
+ let allowEdit = false;
+ let auth = R.path(['auth', 'edit-env'], env);
+ if (auth && R.contains(Meteor.userId(), auth)) {
+ allowEdit = true;
+ }
+ if (Roles.userIsInRole(Meteor.userId(), 'edit-env', 'default-group')) {
+ allowEdit = true;
+ }
+
+ instance.state.set('allowEdit', allowEdit );
+
+ instance.subscribe('inventory?env+type', env.name, 'instance');
+ instance.subscribe('inventory?env+type', env.name, 'vservice');
+ instance.subscribe('inventory?env+type', env.name, 'host');
+ instance.subscribe('inventory?env+type', env.name, 'vconnector');
+ instance.subscribe('inventory?env+type', env.name, 'project');
+ instance.subscribe('inventory?env+type', env.name, 'region');
+ instance.subscribe('messages?env+level', env.name, 'info');
+ instance.subscribe('messages?env+level', env.name, 'warning');
+ instance.subscribe('messages?env+level', env.name, 'error');
+
+ let vConnectorCounterName = 'inventory?env+type!counter?env=' +
+ env.name + '&type=' + 'vconnector';
+ let infoVConnectorsCount = Counts.get(vConnectorCounterName);
+ instance.state.set('infoVConnectorsCount', infoVConnectorsCount);
+
+ let hostsCounterName = 'inventory?env+type!counter?env=' +
+ env.name + '&type=' + 'host';
+ let infoHostsCount = Counts.get(hostsCounterName);
+ instance.state.set('infoHostsCount', infoHostsCount);
+
+ let vServicesCounterName = 'inventory?env+type!counter?env=' +
+ env.name + '&type=' + 'vservice';
+ let infoVServicesCount = Counts.get(vServicesCounterName);
+ instance.state.set('infoVServicesCount', infoVServicesCount);
+
+ let instancesCounterName = 'inventory?env+type!counter?env=' +
+ env.name + '&type=' + 'instance';
+ let infoInstancesCount = Counts.get(instancesCounterName);
+ instance.state.set('infoInstancesCount', infoInstancesCount);
+
+ let projectsCounterName = 'inventory?env+type!counter?env=' +
+ env.name + '&type=' + 'project';
+ let projectsCount = Counts.get(projectsCounterName);
+ instance.state.set('projectsCount', projectsCount);
+
+ let regionsCounterName = 'inventory?env+type!counter?env=' +
+ env.name + '&type=' + 'region';
+ let regionsCount = Counts.get(regionsCounterName);
+ instance.state.set('regionsCount', regionsCount);
+ });
+
+ });
+});
+
+/*
+Template.EnvironmentDashboard.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.EnvironmentDashboard.events({
+ 'click .sm-edit-button': function (event, instance) {
+ let envName = instance.state.get('envName');
+ let allowEdit = instance.state.get('allowEdit');
+ if (! allowEdit) { return; }
+
+ Router.go('/wizard/' + envName,{},{});
+ },
+
+ 'click .sm-scan-button': function (event, instance) {
+ let envName = instance.state.get('envName');
+
+ Router.go('new-scanning',{},{ query: { env: envName } });
+ },
+
+ 'click .sm-delete-button': function (event, instance) {
+ let allowEdit = instance.state.get('allowEdit');
+ if (! allowEdit) { return; }
+
+ let $deleteModal = instance.$('#env-delete-modal');
+ $deleteModal.modal({ show: true });
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.EnvironmentDashboard.helpers({
+ getState: function (key) {
+ let instance = Template.instance();
+ return instance.state.get(key);
+ },
+
+ getListInfoBoxes: function () {
+ return listInfoBoxes;
+ },
+
+ getBriefInfoList: function () {
+ return briefInfoList;
+ },
+
+ infoMessagesCount: function(){
+ let instance = Template.instance();
+ let envName = instance.state.get('envName');
+ if (R.isNil(envName)) { return; }
+
+ return Counts.get('messages?env+level!counter?env=' +
+ envName + '&level=' + 'info');
+ },
+
+ warningsCount: function(){
+ let instance = Template.instance();
+ let envName = instance.state.get('envName');
+ if (R.isNil(envName)) { return; }
+
+ return Counts.get('messages?env+level!counter?env=' +
+ envName + '&level=' + 'warn');
+ },
+
+ errorsCount: function(){
+ let instance = Template.instance();
+ let envName = instance.state.get('envName');
+ if (R.isNil(envName)) { return; }
+
+ return Counts.get('messages?env+level!counter?env=' +
+ envName + '&level=' + 'error');
+ },
+
+ argsEnvDeleteModal: function () {
+ let instance = Template.instance();
+ return {
+ onDeleteReq: function () {
+ instance.$('#env-delete-modal').modal('hide');
+ let _id = instance.state.get('_id');
+ remove.call({ _id: _id }, function (error, _res) {
+ if (R.isNil(error)) {
+ setTimeout(() => {
+ Router.go('/dashboard');
+ }, 700);
+ } else {
+ alert('error removing environment. ' + error.message);
+ }
+ });
+ console.log('delete req performed');
+ }
+ };
+ },
+
+ argsBriefInfo: function (briefInfo) {
+ let instance = Template.instance();
+ return {
+ header: R.path(briefInfo.header, store.getState().api.i18n),
+ dataInfo: R.toString(instance.state.get(briefInfo.dataSource)),
+ icon: new Icon(briefInfo.icon)
+ };
+ },
+
+ argsListInfoBox: function (listInfoBox) {
+ let instance = Template.instance();
+ let data = Template.currentData();
+ let envName = instance.state.get('envName');
+
+ //let lastScanned = calcLastScanned(listInfoBox.listName, envName);
+
+ return {
+ header: R.path(listInfoBox.header, store.getState().api.i18n),
+ list: getList(listInfoBox.listName, envName),
+ icon: new Icon(listInfoBox.icon),
+ listItemFormat: listInfoBox.listItemFormat,
+ //lastScanning: lastScanned,
+ onItemSelected: function (itemKey) {
+ data.onNodeSelected(new Mongo.ObjectID(itemKey));
+ }
+ };
+ },
+
+ notAllowEdit: function () {
+ let instance = Template.instance();
+ let allowEdit = instance.state.get('allowEdit');
+ return ! allowEdit;
+ },
+
+ getListMessagesInfoBox: function () {
+ return [
+ {
+ level: 'info'
+ },
+ {
+ level: 'warning'
+ },
+ {
+ level: 'error'
+ },
+ ];
+ },
+
+ argsMessagesInfoBox: function(boxDef, env) {
+ let instance = Template.instance();
+ let envName = instance.state.get('envName');
+ if (R.isNil(envName)) {
+ return {
+ title: '', count: 0, lastScanTimestamp: '', onMoreDetailsReq: function () {}
+ };
+ }
+
+ let count = Counts.get('messages?env+level!counter?env=' +
+ envName + '&level=' + boxDef.level);
+
+ let title = _.capitalize(boxDef.level);
+
+ return {
+ title: title,
+ count: count,
+ lastScanTimestamp: lastMessageTimestamp(boxDef.level, env),
+ icon: calcIconForMessageLevel(boxDef.level),
+ colorClass: calcColorClassForMessagesInfoBox(boxDef.level),
+ onMoreDetailsReq: function () {
+ $('#messagesModalGlobal').modal('show', {
+ dataset: {
+ messageLevel: boxDef.level,
+ envName: env,
+ }
+ });
+ }
+ };
+ },
+}); // end: helpers
+
+function getList(listName, envName) {
+ switch (listName) {
+ case 'regions':
+ return Inventory.find({
+ environment: envName,
+ type: 'region'
+ });
+
+ case 'projects':
+ return Inventory.find({
+ environment: envName,
+ type: 'project'
+ });
+
+ default:
+ throw 'unknowned list type';
+ }
+}
+
+/*
+function calcLastScanned(listName, envName) {
+ switch (listName) {
+ case 'regions':
+ return R.path(['last_scanned'], Inventory.findOne({
+ environment: envName,
+ type:'region'
+ }));
+
+ case 'projects':
+ return R.path(['last_scanned'], Inventory.findOne({
+ environment: envName,
+ type:'project'
+ }));
+
+ default:
+ throw 'unknown';
+ }
+}
+*/
diff --git a/ui/imports/ui/components/environment-dashboard/environment-dashboard.styl b/ui/imports/ui/components/environment-dashboard/environment-dashboard.styl
new file mode 100644
index 0000000..f27c19d
--- /dev/null
+++ b/ui/imports/ui/components/environment-dashboard/environment-dashboard.styl
@@ -0,0 +1,12 @@
+.os-environment-dashboard
+ .cl-action-button.cl-action-disabled
+ color: lightgray
+
+ .sm-messages-section
+ display: flex;
+ flex-flow: row wrap;
+ justify-content: center;
+
+ .sm-message-box
+ flex: 1;
+ padding: 0 15px;
diff --git a/ui/imports/ui/components/environment-wizard/environment-wizard.html b/ui/imports/ui/components/environment-wizard/environment-wizard.html
new file mode 100644
index 0000000..4898a5a
--- /dev/null
+++ b/ui/imports/ui/components/environment-wizard/environment-wizard.html
@@ -0,0 +1,83 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="EnvironmentWizard">
+
+ <div class="sm-environment-wizard-container">
+
+ <div class="flex-box-1 site-sidenav">
+ <div class="menu-header">
+ <p>
+ {{ model.name }}
+ </p>
+ <a href="#"><i class="material-icons">menu</i></a>
+ </div>
+ </div>
+
+ <form class="sm-environment-form">
+
+ <div class="sm-main-layout-no-nav cards white">
+ <!-- Nav tabs -->
+ <ul class="nav nav-tabs" role="tablist">
+ {{#each tab in tabs }}
+ <li role="presentation"
+ class="{{#if tab.defaultTab }}active{{/if}} {{#if tab.disabled }}disabled{{/if}}">
+ <a href="#{{ tab.localLink }}"
+ aria-controls="{{ tab.localLink }}"
+ role="tab"
+ data-toggle="tab"
+ id="link-{{ tab.localLink }}"
+ data-is-disabled="{{ tab.disabled }}"
+ class="sm-tab-link"
+ >{{tab.label}}</a>
+ </li>
+ {{/each}}
+ </ul>
+
+ <!-- Tab panes -->
+ <div class="tab-content">
+ {{#each tab in tabs }}
+ <div role="tabpanel"
+ class="tab-pane fade {{#if tab.defaultTab }}in active{{/if}}"
+ id="{{ tab.localLink }}">
+ {{> Template.dynamic template=tab.templateName data=tab.templateData }}
+ </div>
+ {{/each }}
+
+ </div>
+
+ <div class="row">
+ <div class="row">
+ <div class="col-sm-offset-2 col-sm-10 btn-mgt-5">
+ <button type="button"
+ class="sm-submit-button mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored"
+ {{ markIfDisabled }}
+ >Submit</button>
+ </div>
+ </div>
+
+ {{#if (getState 'isMessage') }}
+ <div class="row">
+ <div class="js-message-panel alert {{#if (getState 'isError')}}alert-danger{{/if}}
+ {{#if (getState 'isSuccess')}}alert-success{{/if}}"
+ role="alert"
+ >{{ getState 'message' }}</div>
+ </div>
+ {{/if }}
+
+ </div>
+
+ </div>
+
+ </form>
+
+ </div>
+
+</template>
diff --git a/ui/imports/ui/components/environment-wizard/environment-wizard.js b/ui/imports/ui/components/environment-wizard/environment-wizard.js
new file mode 100644
index 0000000..ac3db63
--- /dev/null
+++ b/ui/imports/ui/components/environment-wizard/environment-wizard.js
@@ -0,0 +1,452 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ */
+
+import { Meteor } from 'meteor/meteor';
+import { Session } from 'meteor/session';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import * as R from 'ramda';
+
+import { Environments } from '/imports/api/environments/environments';
+import { subsNameSupportedEnvs,
+ isMonitoringSupported,
+ isListeningSupported,
+} from '/imports/api/supported_environments/supported_environments';
+import { createNewConfGroup } from '/imports/api/environments/environments';
+import { store } from '/imports/ui/store/store';
+
+import './environment-wizard.html';
+
+import '/imports/ui/components/env-main-info/env-main-info';
+import '/imports/ui/components/env-os-api-endpoint-info/env-os-api-endpoint-info';
+import '/imports/ui/components/env-open-stack-db-credentials-info/env-open-stack-db-credentials-info';
+import '/imports/ui/components/env-master-host-credentials-info/env-master-host-credentials-info';
+//import '/imports/ui/components/env-nfv-info/env-nfv-info';
+import '/imports/ui/components/env-aci-info/env-aci-info';
+import '/imports/ui/components/env-amqp-credentials-info/env-amqp-credentials-info';
+import '/imports/ui/components/env-monitoring-info/env-monitoring-info';
+
+import {
+ insert,
+ update
+} from '/imports/api/environments/methods';
+
+/*
+ * Lifecycles
+ */
+
+Template.EnvironmentWizard.onCreated(function(){
+ let instance = this;
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ environment: null,
+ action: 'insert',
+ isError: false,
+ isSuccess: false,
+ isMessage: false,
+ message: null,
+ disabled: false,
+ });
+
+ instance.autorun(function () {
+ let controller = Iron.controller();
+ controller.state.set('needsConfirmation', true);
+ Session.set('isDirty', false);
+ //let params = controller.getParams();
+
+ //let envName = params.env;
+ let envName = Session.get('wizardEnv');
+ if (envName) {
+ instance.subscribe('environments?name', envName);
+ instance.state.set('action', 'update');
+
+ } else {
+ instance.state.set('action', 'insert');
+ }
+
+ instance.subscribe(subsNameSupportedEnvs);
+
+ let action = instance.state.get('action');
+ if (action === 'update') {
+ Environments.find({'name': envName})
+ .forEach(function (envItem) {
+ instance.state.set('environmentModel', R.clone(envItem));
+ });
+ } else if (action === 'insert') {
+ instance.state.set('environmentModel', generateNewEnv());
+ }
+ });
+
+ instance.storeUnsubscribe = store.subscribe(() => {
+ let i18n = store.getState().api.i18n;
+ instance.state.set('i18n', i18n);
+ });
+
+ let i18n = store.getState().api.i18n;
+ instance.state.set('i18n', i18n);
+});
+
+Template.EnvironmentWizard.rendered = function(){
+
+ // todo: refactor to use component - not jquery click
+ $('.btnPrevious').click(function(){
+ $('.nav-tabs > .active').prev('li').find('a').trigger('click');
+ });
+
+};
+
+Template.EnvironmentWizard.onDestroyed(function () {
+ let instance = this;
+ instance.storeUnsubscribe();
+});
+
+/*
+ * Helpers
+ */
+
+Template.EnvironmentWizard.helpers({
+ model: function () {
+ let instance = Template.instance();
+ let environmentModel = instance.state.get('environmentModel');
+ return environmentModel;
+ },
+
+ tabs: function () {
+ let instance = Template.instance();
+
+ let environmentModel = instance.state.get('environmentModel');
+ let action = instance.state.get('action');
+ let disabled = instance.state.get('disabled');
+ let activateNextTab = function (nextTabId) {
+ instance.$('#link-' + nextTabId).tab('show');
+ };
+
+ if (R.isNil(environmentModel)) {
+ return [];
+ }
+
+ let isMonSupportedRes = isMonitoringSupported(
+ environmentModel.distribution,
+ environmentModel.type_drivers,
+ environmentModel.mechanism_drivers
+ );
+
+ let isMonitoringDisabled = disabled || !isMonSupportedRes;
+
+
+ let monitoringDisabledMessage = null;
+ if (isMonitoringDisabled && !isMonSupportedRes) {
+ monitoringDisabledMessage = 'Distribution, type drivers and mechanism driver are not supported at this moment';
+ }
+
+ let isListeningSupportedRes = isListeningSupported(
+ environmentModel.distribution,
+ environmentModel.type_drivers,
+ environmentModel.mechanism_drivers
+ );
+ let isListeningDisabled = disabled || !isListeningSupportedRes;
+
+ let amqpTabDisabled = !(environmentModel.listen && isListeningSupportedRes);
+ let monitoringTabDisabled = !(environmentModel.enable_monitoring && isMonSupportedRes);
+ let isAciTabDisabled = !(environmentModel.aci);
+
+ return [{
+ label: 'Main Info',
+ localLink: 'maininfo',
+ defaultTab: true,
+ disabled: false,
+ templateName: 'EnvMainInfo',
+ templateData: {
+ model: environmentModel,
+ disabled: disabled,
+ isListeningDisabled: isListeningDisabled,
+ isMonitoringDisabled: isMonitoringDisabled,
+ setModel: function (newModel) {
+ Session.set('isDirty', true);
+ instance.state.set('environmentModel', newModel);
+ },
+ onNextRequested: activateNextTab.bind(null, 'endpoint-panel'),
+ action: action,
+ }
+ }, {
+ label: 'OS API Endpoint',
+ localLink: 'endpoint-panel',
+ disabled: false,
+ templateName: 'EnvOsApiEndpointInfo',
+ templateData: {
+ model: getGroupInArray('OpenStack', environmentModel.configuration),
+ disabled: disabled,
+ setModel: function (newSubModel) {
+ Session.set('isDirty', true);
+ let model = instance.state.get('environmentModel');
+ let newModel = setConfigurationGroup('OpenStack', newSubModel, model);
+ instance.state.set('environmentModel', newModel);
+ },
+ onNextRequested: activateNextTab.bind(null, 'db-credentials'),
+ action: action,
+ }
+ }, {
+ label: 'OS DB Credentials',
+ localLink: 'db-credentials',
+ disabled: false,
+ templateName: 'EnvOpenStackDbCredentialsInfo',
+ templateData: {
+ model: getGroupInArray('mysql', environmentModel.configuration),
+ disabled: disabled,
+ setModel: function (newSubModel) {
+ Session.set('isDirty', true);
+ let model = instance.state.get('environmentModel');
+ let newModel = setConfigurationGroup('mysql', newSubModel, model);
+ instance.state.set('environmentModel', newModel);
+ },
+ onNextRequested: activateNextTab.bind(null, 'master-host'),
+ action: action,
+ }
+ }, {
+ label: 'Master Host Credentials',
+ localLink: 'master-host',
+ disabled: false,
+ templateName: 'EnvMasterHostCredentialsInfo',
+ templateData: {
+ model: getGroupInArray('CLI', environmentModel.configuration),
+ disabled: disabled,
+ setModel: function (newSubModel) {
+ Session.set('isDirty', true);
+ let model = instance.state.get('environmentModel');
+ let newModel = setConfigurationGroup('CLI', newSubModel, model);
+ instance.state.set('environmentModel', newModel);
+ },
+ onNextRequested: activateNextTab.bind(null, 'amqp'),
+ action: action,
+ }
+ }, {
+ label: 'AMQP Credentials',
+ localLink: 'amqp',
+ disabled: amqpTabDisabled,
+ templateName: 'EnvAmqpCredentialsInfo',
+ templateData: {
+ model: getGroupInArray('AMQP', environmentModel.configuration),
+ disabled: disabled,
+ setModel: function (newSubModel) {
+ Session.set('isDirty', true);
+ let model = instance.state.get('environmentModel');
+ let newModel = setConfigurationGroup('AMQP', newSubModel, model);
+ instance.state.set('environmentModel', newModel);
+ },
+ onNextRequested: activateNextTab.bind(null, 'aci'),
+ action: action,
+ }
+ },
+ /* {
+ label: 'NFV Credentials',
+ localLink: 'nfv',
+ disabled: false,
+ templateName: 'EnvNfvInfo',
+ templateData: {
+ model: getGroupInArray('NFV_provider', environmentModel.configuration),
+ disabled: disabled,
+ setModel: function (newSubModel) {
+ Session.set('isDirty', true);
+ let model = instance.state.get('environmentModel');
+ let newModel = setConfigurationGroup('NFV_provider', newSubModel, model);
+ instance.state.set('environmentModel', newModel);
+ },
+ onNextRequested: activateNextTab.bind(null, 'monitoringInfo'),
+ action: action,
+ }
+ }, */
+ {
+ label: 'ACI Credentials',
+ localLink: 'aci',
+ disabled: isAciTabDisabled,
+ templateName: 'EnvAciInfo',
+ templateData: {
+ model: getGroupInArray('ACI', environmentModel.configuration),
+ disabled: isAciTabDisabled,
+ setModel: function (newSubModel) {
+ Session.set('isDirty', true);
+ let model = instance.state.get('environmentModel');
+ let newModel = setConfigurationGroup('ACI', newSubModel, model);
+ instance.state.set('environmentModel', newModel);
+ },
+ onNextRequested: activateNextTab.bind(null, 'monitoringInfo'),
+ action: action,
+ }
+ }, {
+ label: 'Monitoring',
+ localLink: 'monitoringInfo',
+ disabled: monitoringTabDisabled,
+ templateName: 'EnvMonitoringInfo',
+ templateData: {
+ model: getGroupInArray('Monitoring', environmentModel.configuration),
+ disabled: isMonitoringDisabled,
+ disabledMessage: monitoringDisabledMessage,
+ setModel: function (newSubModel) {
+ Session.set('isDirty', true);
+ let model = instance.state.get('environmentModel');
+ let newModel = setConfigurationGroup('Monitoring', newSubModel, model);
+ instance.state.set('environmentModel', newModel);
+ },
+ action: action,
+ }
+ }];
+ },
+
+ isDefaultTab: function (tab) {
+ return tab.defaultTab;
+ },
+
+ environment: function () {
+ let instance = Template.instance();
+ return instance.state.get('environment');
+ },
+
+ getConfSection: function(sectionName, environment) {
+ if (R.isNil(environment)) { return null; }
+ let section = R.find(R.propEq('name', sectionName),
+ environment.configuration);
+ return section;
+ },
+
+ getState: function (key) {
+ let instance = Template.instance();
+ return instance.state.get(key);
+ },
+});
+
+/*
+ * Events
+ */
+
+Template.EnvironmentWizard.events({
+ 'click .toast' : function () {
+ toastr.success('Have fun storming the castle!', 'Open Stack server says');
+ },
+
+ // todo: research: seems not implemented
+ 'click .fa-trash' : function () {
+ Meteor.call('deleteRecipe', this._id);
+ },
+
+ 'click .sm-submit-button': function () {
+ let instance = Template.instance();
+ doSubmit(instance);
+ },
+
+ 'click .sm-tab-link': function (event, _instance) {
+ let isDisabled = event.target.dataset.isDisabled;
+ if (isDisabled) {
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ }
+ },
+});
+
+function generateNewEnv() {
+ return Environments.schema.clean({});
+}
+
+function processActionResult(instance, error) {
+ let action = instance.state.get('action');
+
+ if (error) {
+ instance.state.set('isError', true);
+ instance.state.set('isSuccess', false);
+ instance.state.set('isMessage', true);
+
+ if (typeof error === 'string') {
+ instance.state.set('message', error);
+ } else {
+ let message = error.message;
+ if (error.errors) {
+ message = R.reduce((acc, errorItem) => {
+ return acc + '\n- ' + errorItem.name;
+ }, message, error.errors);
+ }
+ instance.state.set('message', message);
+ }
+
+ } else {
+ instance.state.set('isError', false);
+ instance.state.set('isSuccess', true);
+ instance.state.set('isMessage', true);
+
+ if (action === 'insert') {
+ instance.state.set('message', 'Record had been added successfully');
+ instance.state.set('disabled', true);
+ } else if (action === 'update') {
+ instance.state.set('message', 'Record had been updated successfully');
+ }
+
+ Session.set('isDirty', false);
+ }
+}
+
+function getGroupInArray(groupName, array) {
+ let group = R.find(R.propEq('name', groupName), array);
+ return group ? group : createNewConfGroup(groupName);
+}
+
+function removeGroupInArray(groupName, array) {
+ return R.reject(R.propEq('name', groupName), array);
+}
+
+function setConfigurationGroup(groupName, group, model) {
+ let tempConfiguration = removeGroupInArray(groupName, model.configuration);
+ let newConfiguration = R.append(group, tempConfiguration);
+ let newModel = R.assoc('configuration', newConfiguration, model);
+ return newModel;
+}
+
+function doSubmit(instance) {
+ let action = instance.state.get('action');
+ let environment = instance.state.get(
+ 'environmentModel');
+
+ instance.state.set('isError', false);
+ instance.state.set('isSuccess', false);
+ instance.state.set('isMessage', false);
+ instance.state.set('message', null);
+
+ switch (action) {
+ case 'insert':
+ insert.call({
+ configuration: environment.configuration,
+ distribution: environment.distribution,
+ name: environment.name,
+ type_drivers: environment.type_drivers,
+ mechanism_drivers: environment.mechanism_drivers,
+ listen: environment.listen,
+ enable_monitoring: environment.enable_monitoring,
+ aci: environment.aci,
+ }, processActionResult.bind(null, instance));
+ break;
+
+ case 'update':
+ update.call({
+ _id: environment._id,
+ configuration: environment.configuration,
+ //distribution: environment.distribution,
+ //name: environment.name,
+ type_drivers: environment.type_drivers,
+ mechanism_drivers: environment.mechanism_drivers,
+ listen: environment.listen,
+ enable_monitoring: environment.enable_monitoring,
+ aci: environment.aci,
+ }, processActionResult.bind(null, instance));
+ break;
+
+ default:
+ // todo
+ break;
+ }
+}
diff --git a/ui/imports/ui/components/environment-wizard/environment-wizard.styl b/ui/imports/ui/components/environment-wizard/environment-wizard.styl
new file mode 100644
index 0000000..ec11e43
--- /dev/null
+++ b/ui/imports/ui/components/environment-wizard/environment-wizard.styl
@@ -0,0 +1,27 @@
+.sm-environment-wizard-container
+ display: flex;
+ flex-flow: row nowrap;
+
+ .site-sidenav
+ p
+ font-size: 0.8em;
+ i
+ color: white;
+
+ .menu-header
+ display: flex;
+ flex-flow: row nowrap;
+ justify-content: space-between;
+
+ padding: 10px;
+ color: white;
+ font-size: 1.7em;
+
+ .sm-environment-form
+ .sm-main-layout-no-nav
+ margin: 20px;
+
+ .js-message-panel
+ margin: 20px 40px;
+ margin-bottom: 0px;
+ white-space: pre-line;
diff --git a/ui/imports/ui/components/environment/environment.html b/ui/imports/ui/components/environment/environment.html
new file mode 100644
index 0000000..84bafdf
--- /dev/null
+++ b/ui/imports/ui/components/environment/environment.html
@@ -0,0 +1,63 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="Environment">
+
+ <div class="sm-environment-container flex-box justify-content-between">
+ <div class="flex-box-1 site-sidenav">
+ {{#if isLoaded }}
+ {{> accordionNavMenu (argsNavMenu envName mainNode) }}
+ {{/if }}
+ </div>
+
+ <div class="flex-box-3 main-layout-no-nav">
+ <div class="sm-breadcrumb-segment">
+ {{> breadcrumb (argsBreadCrumb rdxSelectedNodeId) }}
+ </div>
+
+ <div class="sm-main-content-segment">
+
+ {{#if selectedNodeType }}
+ {{#if (getShow 'dashboard') }}
+ <div class="sm-dashboard">
+ {{> UI.dynamic template=dashboardTemplate
+ data=(argsDashboard rdxSelectedNodeId) }}
+ </div>
+ {{/if }}
+ {{#if (getShow 'graph') }}
+ {{#if isSelectedNodeAGraph }}
+
+ <!--div class="sm-graph">
+ > d3graph argsD3Graph
+ </div-->
+
+ <div class="sm-network-graph">
+ {{> NetworkGraphManager argsNetworkGraphManager }}
+ </div>
+
+ {{> GraphTooltipWindow (argsGraphTooltipWindow graphTooltipWindow) }}
+
+ {{#if showVedgeInfoWindow }}
+ {{> VedgeInfoWindow (argsVedgeInfoWindow vedgeInfoWindow) }}
+ {{/if }}
+
+ {{else }}
+
+ <div class="sm-node-no-graph-data-msg">{{ rPath i18n 'components.environment.noGraphForLeafMsg' }}</div>
+
+ {{/if }}
+ {{/if }}
+ {{/if }}
+ </div>
+
+ </div>
+ </div>
+
+</template>
diff --git a/ui/imports/ui/components/environment/environment.js b/ui/imports/ui/components/environment/environment.js
new file mode 100644
index 0000000..6dc4a82
--- /dev/null
+++ b/ui/imports/ui/components/environment/environment.js
@@ -0,0 +1,570 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Tempalte Component: Environment
+ */
+
+/*
+ * Lifecycles methods
+ */
+
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { ReactiveVar } from 'meteor/reactive-var';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import * as R from 'ramda';
+import { EJSON } from 'meteor/ejson';
+import factory from 'reactive-redux';
+import { _idFieldDef } from '/imports/lib/simple-schema-utils';
+//import { idToStr } from '/imports/lib/utilities';
+
+import { Environments } from '/imports/api/environments/environments';
+import { Inventory } from '/imports/api/inventories/inventories';
+//import { Messages } from '/imports/api/messages/messages';
+
+import { store } from '/imports/ui/store/store';
+//import { setCurrentNode } from '/imports/ui/actions/navigation';
+import {
+ setEnvEnvId,
+ setEnvName,
+ updateEnvTreeNode,
+ startOpenEnvTreeNode,
+ setEnvSelectedNodeInfo,
+ setEnvAsLoaded,
+ setEnvAsNotLoaded,
+ setEnvSelectedNodeAsEnv,
+ toggleEnvShow,
+ endOpenEnvTreeNode,
+ reportEnvNodePositionRetrieved,
+ setEnvScrollToNodeIsNeededAsOn,
+ reportEnvScrollToNodePerformed,
+ resetEnvNeedChildDetection,
+ setShowDashboard,
+// setShowGraph,
+} from '/imports/ui/actions/environment-panel.actions';
+import { setMainAppSelectedEnvironment } from '/imports/ui/actions/main-app.actions';
+import { closeVedgeInfoWindow } from '/imports/ui/actions/vedge-info-window.actions';
+import { setEnvSelectedNode }
+ from '/imports/ui/actions/environment-panel.actions';
+
+import '/imports/ui/components/accordion-nav-menu/accordion-nav-menu';
+import '/imports/ui/components/graph-tooltip-window/graph-tooltip-window';
+import '/imports/ui/components/vedge-info-window/vedge-info-window';
+import '/imports/ui/components/env-delete-modal/env-delete-modal';
+import '/imports/ui/components/environment-dashboard/environment-dashboard';
+import '/imports/ui/components/general-folder-node-dashboard/general-folder-node-dashboard';
+import '/imports/ui/components/general-node-dashboard/general-node-dashboard';
+import '/imports/ui/components/network-graph-manager/network-graph-manager';
+
+import './environment.html';
+
+let maxOpenTreeNodeTrialCount = 3;
+
+/*
+var nodeTypesForSelection = [
+ 'project',
+ 'availability_zone',
+ 'host',
+ 'environment',
+ 'aggregate',
+ 'host',
+ 'region',
+ 'instance',
+ 'network'
+];
+*/
+
+/*
+ * Lifecycles
+ */
+
+Template.Environment.onCreated(function () {
+ var instance = this;
+
+ // reactive state
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ graphTooltipWindow: { label: '', title: '', left: 0, top: 0, show: false },
+ vedgeInfoWindow: { node: null, left: 0, top: 0, show: false },
+ dashboardName: 'environment',
+ });
+ instance.currentData = new ReactiveVar(null, EJSON.equals);
+ instance.onNodeOpeningDone = _.debounce(() => {
+ scrollTreeToLastOpenedChild(instance);
+ }, 400);
+
+ createAttachedFns(instance);
+
+ const envIdSelector = (state) => (state.components.environmentPanel._id);
+ instance.rdxEnvId = factory(envIdSelector, store);
+
+ const mainNodeSelector = (state) => (state.components.environmentPanel.treeNode);
+ instance.rdxMainNode = factory(mainNodeSelector, store);
+
+ const selectedNodeIdSelector =
+ (state) => (state.components.environmentPanel.selectedNode._id);
+ instance.rdxSelectedNodeId = factory(selectedNodeIdSelector, store);
+
+ const selectedNodeTypeSelector =
+ (state) => (state.components.environmentPanel.selectedNode.type);
+ instance.rdxSelectedNodeType = factory(selectedNodeTypeSelector, store);
+
+ const envNameSelector = (state) => (state.components.environmentPanel.envName);
+ instance.rdxEnvName = factory(envNameSelector, store);
+
+ const isLoadedSelector = (state) => (state.components.environmentPanel.isLoaded);
+ instance.rdxIsLoaded = factory(isLoadedSelector, store);
+
+ const showTypeSelector = (state) => (state.components.environmentPanel.showType);
+ instance.rdxShowType = factory(showTypeSelector, store);
+
+ const selectedNodeCliqueSelector =
+ (state) => (state.components.environmentPanel.selectedNode.clique);
+ instance.rdxSelectedNodeClique = factory(selectedNodeCliqueSelector, store);
+
+ const selectedNodeIdPathSelector =
+ (state) => (state.components.environmentPanel.selectedNode.id_path);
+ instance.rdxSelectedNodeIdPath = factory(selectedNodeIdPathSelector, store);
+
+ const i18nSelector = (state) => (state.api.i18n);
+ instance.rdxI18n = factory(i18nSelector, store);
+
+ instance.autorun((function(_this) {
+ return function(_computation) {
+ return _this.currentData.set(Template.currentData());
+ };
+ })(instance));
+
+ let lastData = null;
+
+ // Autorun component input
+ instance.autorun(function () {
+ let data = instance.currentData.get();
+
+ if (R.equals(data, lastData)) { return; }
+ lastData = data;
+
+ new SimpleSchema({
+ _id: _idFieldDef,
+ selectedNodeId: R.assoc('optional', true, _idFieldDef),
+ }).validate(data);
+
+ store.dispatch(setEnvEnvId(data._id));
+ if (R.isNil(data.selectedNodeId)) {
+ store.dispatch(setEnvSelectedNodeAsEnv());
+ } else {
+ store.dispatch(setEnvSelectedNode(data.selectedNodeId));
+ }
+ });
+
+ // Autorun object id
+ instance.autorun(function () {
+ let _id = instance.rdxEnvId.get();
+ store.dispatch(setEnvAsNotLoaded());
+
+ instance.subscribe('environments?_id', _id);
+ Environments.find({ _id: _id }).forEach((env) => {
+ store.dispatch(setEnvName(env.name));
+ store.dispatch(updateEnvTreeNode(env));
+ store.dispatch(setEnvAsLoaded());
+ store.dispatch(startOpenEnvTreeNode([]));
+ store.dispatch(setMainAppSelectedEnvironment(env._id));
+ store.dispatch(setShowDashboard());
+ });
+ });
+
+ // Autorun selected node
+ instance.autorun(function () {
+ let selectedNodeId = instance.rdxSelectedNodeId.get();
+ //let selectedNodeType = instance.rdxSelectedNodeType.get();
+
+ if (R.isNil(selectedNodeId)) { return; }
+ //if (selectedNodeType === 'environment') { return; }
+
+ instance.subscribe('inventory?_id', selectedNodeId);
+ Inventory.find({ _id: selectedNodeId }).forEach((selectedNode) => {
+ store.dispatch(setEnvSelectedNodeInfo(selectedNode));
+
+ Meteor.apply('expandNodePath',
+ [ selectedNode._id ],
+ { wait: false },
+ function (err, res) {
+ if (err) {
+ console.error(err);
+ return;
+ }
+
+ if (R.isNil(res)) { return; }
+
+ let idList = R.map(R.path(['_id', '_str']), res);
+ openTreeNode([R.head(idList)], R.tail(idList), 0);
+ });
+ });
+ });
+
+ /////////////////
+
+ instance.storeUnsubscribe = store.subscribe(() => {
+ let state = store.getState();
+
+ let graphTooltipWindow = state.components.graphTooltipWindow;
+ instance.state.set('graphTooltipWindow', graphTooltipWindow);
+
+ let vedgeInfoWindow = state.components.vedgeInfoWindow;
+ instance.state.set('vedgeInfoWindow', vedgeInfoWindow);
+
+ });
+
+ /*
+ (() => {
+ if (R.isNil(controller.params.query.selectedNodeId) &&
+ R.isNil(selectedNodeId)) {
+ return;
+ }
+
+ let srlSelectedNodeId = idToStr(selectedNodeId);
+ if (R.equals(controller.params.query.selectedNodeId, srlSelectedNodeId)) {
+ return;
+ }
+
+ setTimeout(() => {
+ Router.go('environment',
+ { _id: controller.params._id },
+ { query: { selectedNodeId: srlSelectedNodeId } });
+ }, 1);
+
+ })();
+ */
+
+});
+
+Template.Environment.onDestroyed(function () {
+ let instance = this;
+ instance.storeUnsubscribe();
+ instance.rdxMainNode.cancel();
+ instance.rdxEnvId.cancel();
+ instance.rdxSelectedNodeId.cancel();
+ instance.rdxEnvName.cancel();
+ instance.rdxIsLoaded.cancel();
+ instance.rdxShowType.cancel();
+ instance.rdxSelectedNodeIdPath.cancel();
+});
+
+Template.Environment.rendered = function(){
+};
+
+/*
+ * Helpers
+ */
+
+Template.Environment.helpers({
+ isLoaded: function () {
+ let instance = Template.instance();
+ return instance.rdxIsLoaded.get();
+ },
+
+ envName: function(){
+ let instance = Template.instance();
+ return instance.rdxEnvName.get();
+ },
+
+ mainNode: function () {
+ let instance = Template.instance();
+ return instance.rdxMainNode.get();
+ },
+
+ selectedNodeType: function () {
+ let instance = Template.instance();
+ return instance.rdxSelectedNodeType.get();
+ },
+
+ getState: function (key) {
+ let instance = Template.instance();
+ return instance.state.get(key);
+ },
+
+ argsNavMenu: function (envName, mainNode) {
+ let instance = Template.instance();
+ return {
+ envName: envName,
+ mainNode: mainNode,
+ onOpeningDone: instance._fns.onOpeningDone,
+ onNodeSelected: instance._fns.onNodeSelected,
+ onToggleGraphReq: function () {
+ store.dispatch(toggleEnvShow());
+ },
+ onResetSelectedNodeReq: function () {
+ store.dispatch(setEnvSelectedNodeAsEnv());
+ },
+ onPositionRetrieved: instance._fns.onPositionRetrieved,
+ onScrollToNodePerformed: instance._fns.onScrollToNodePerformed,
+ onOpenLinkReq: instance._fns.onOpenLinkReq,
+ onResetNeedChildDetection: instance._fns.onResetNeedChildDetection,
+ };
+ },
+
+ graphTooltipWindow: function () {
+ let instance = Template.instance();
+ let graphTooltipWindow = instance.state.get('graphTooltipWindow');
+
+ return graphTooltipWindow;
+ },
+
+ vedgeInfoWindow: function () {
+ let instance = Template.instance();
+ let vedgeInfoWindow = instance.state.get('vedgeInfoWindow');
+
+ return vedgeInfoWindow;
+ },
+
+ argsGraphTooltipWindow: function (graphTooltipWindow) {
+ return {
+ label: R.path(['label'], graphTooltipWindow),
+ title: R.path(['title'], graphTooltipWindow),
+ left: R.path(['left'], graphTooltipWindow),
+ top: R.path(['top'], graphTooltipWindow),
+ show: R.path(['show'], graphTooltipWindow)
+ };
+ },
+
+ argsVedgeInfoWindow: function (vedgeInfoWindow) {
+ return {
+ environment: R.path(['node', 'environment'], vedgeInfoWindow),
+ object_id: R.path(['node', 'id'], vedgeInfoWindow),
+ name: R.path(['node', 'name'], vedgeInfoWindow),
+ left: R.path(['left'], vedgeInfoWindow),
+ top: R.path(['top'], vedgeInfoWindow),
+ show: R.path(['show'], vedgeInfoWindow),
+ onCloseRequested: function () {
+ store.dispatch(closeVedgeInfoWindow());
+ }
+ };
+ },
+
+ argsD3Graph: function () {
+ let instance = Template.instance();
+ let idPath = instance.rdxSelectedNodeIdPath.get();
+
+ return {
+ id_path: idPath
+ };
+ },
+
+ argsNetworkGraphManager: function () {
+ let instance = Template.instance();
+ let idPath = instance.rdxSelectedNodeIdPath.get();
+
+ return {
+ id_path: idPath
+ };
+ },
+
+ showVedgeInfoWindow: function () {
+ let instance = Template.instance();
+ let node = instance.state.get('vedgeInfoWindow').node;
+ return ! R.isNil(node);
+ },
+
+ isSelectedNodeAGraph: function () {
+ let instance = Template.instance();
+ let nodeClique = instance.rdxSelectedNodeClique.get();
+
+ return !R.isNil(nodeClique);
+ },
+
+ dashboardTemplate: function () {
+ let instance = Template.instance();
+ let selectedNodeType = instance.rdxSelectedNodeType.get();
+ let dashTemplate = 'EnvironmentDashboard';
+
+ switch (selectedNodeType) {
+ case 'project':
+ dashTemplate = 'ProjectDashboard';
+ break;
+
+ case 'region':
+ dashTemplate = 'RegionDashboard';
+ break;
+
+ case 'aggregate':
+ dashTemplate = 'AggregateDashboard';
+ break;
+
+ case 'host':
+ dashTemplate = 'HostDashboard';
+ break;
+
+ case 'availability_zone':
+ dashTemplate = 'ZoneDashboard';
+ break;
+
+ case 'environment':
+ dashTemplate = 'EnvironmentDashboard';
+ break;
+
+ case 'vservice_routers_folder':
+ case 'vnics_folder':
+ case 'regions_folder':
+ case 'vedges_folder':
+ case 'network_agents_folder':
+ case 'network_services_folder':
+ case 'availability_zones_folder':
+ case 'pnics_folder':
+ case 'networks_folder':
+ case 'vconnectors_folder':
+ case 'projects_folder':
+ case 'aggregates_folder':
+ case 'vservices_folder':
+ case 'vservice_dhcps_folder':
+ case 'ports_folder':
+ case 'instances_folder':
+ dashTemplate = 'GeneralFolderNodeDashboard';
+ break;
+
+ default:
+ dashTemplate = 'GeneralNodeDashboard';
+ }
+
+ return dashTemplate;
+ },
+
+ rdxSelectedNodeId: function () {
+ let instance = Template.instance();
+ return instance.rdxSelectedNodeId.get();
+ },
+
+ argsDashboard: function (nodeId) {
+ //let instance = Template.instance();
+
+ return {
+ _id: nodeId,
+ onNodeSelected: function (selectedNodeId) {
+ store.dispatch(setEnvSelectedNode(selectedNodeId, null));
+ }
+ };
+ },
+
+ argsBreadCrumb: function (selectedNodeId) {
+ return {
+ nodeId: selectedNodeId,
+ onNodeSelected: function (node) {
+ store.dispatch(setEnvSelectedNode(node._id, null));
+ }
+ };
+ },
+
+ getShow: function (qShowType) {
+ let instance = Template.instance();
+ let showType = instance.rdxShowType.get();
+
+ return R.equals(showType, qShowType);
+ },
+
+ i18n: function () {
+ let instance = Template.instance();
+ return instance.rdxI18n.get();
+
+ },
+}); // end: helpers
+
+
+Template.Environment.events({
+});
+
+function openTreeNode(path, rest, trialCount) {
+ if (trialCount > maxOpenTreeNodeTrialCount) {
+ return;
+ }
+
+ let tree = store.getState().components.environmentPanel
+ .treeNode;
+
+ let node = getNodeInTree(path, tree);
+ if (R.isNil(node)) {
+ setTimeout(() => {
+ openTreeNode(path, rest, trialCount + 1);
+ }, 800);
+ return;
+ }
+
+ if (node.openState === 'closed') {
+ store.dispatch(startOpenEnvTreeNode(path));
+ setTimeout(() => {
+ openTreeNode(path, rest, trialCount + 1);
+ }, 200);
+ return;
+ }
+
+ if (R.length(rest) === 0) { return; }
+
+ let newPath = R.append(R.head(rest), path);
+ let newRest = R.drop(1, rest);
+ openTreeNode(newPath, newRest, 0);
+}
+
+function getNodeInTree(path, tree) {
+ if (R.length(path) === 0) { return tree; }
+
+ let first = R.head(path);
+ let rest = R.tail(path);
+ let child = R.find(R.pathEq(['nodeInfo', '_id', '_str'], first),
+ tree.children);
+
+ if (R.isNil(child)) { return null; }
+
+ return getNodeInTree(rest, child);
+}
+
+function createAttachedFns(instance) {
+ instance._fns = {
+ onOpeningDone: (nodePath, _nodeInfo) => {
+ store.dispatch(endOpenEnvTreeNode(R.tail(nodePath)));
+ instance.lastOpenedNodePath = nodePath;
+ instance.onNodeOpeningDone();
+ },
+
+ onNodeSelected: (nodeInfo) => {
+ //if (R.contains(nodeInfo.type, nodeTypesForSelection)) {
+ store.dispatch(setEnvSelectedNode(nodeInfo._id, null));
+ //}
+ },
+
+ onPositionRetrieved: (nodePath, rect) => {
+ store.dispatch(
+ reportEnvNodePositionRetrieved(R.tail(nodePath), rect));
+ },
+
+ onScrollToNodePerformed: (nodePath) => {
+ store.dispatch(reportEnvScrollToNodePerformed(R.tail(nodePath)));
+ },
+
+ onOpenLinkReq: (envName, nodeName) => {
+ Meteor.apply('inventoryFindNode?type&env&name', [
+ 'host', envName, nodeName
+ ], {
+ wait: false
+ }, function (err, res) {
+ if (err) {
+ console.log('error in inventoryFindNode', err);
+ return;
+ }
+
+ store.dispatch(setEnvSelectedNode(res.node._id, null));
+ });
+ },
+
+ onResetNeedChildDetection: (nodePath) => {
+ store.dispatch(resetEnvNeedChildDetection(R.tail(nodePath)));
+ }
+ };
+}
+
+function scrollTreeToLastOpenedChild(instance) {
+ store.dispatch(setEnvScrollToNodeIsNeededAsOn(R.tail(instance.lastOpenedNodePath)));
+}
diff --git a/ui/imports/ui/components/environment/environment.styl b/ui/imports/ui/components/environment/environment.styl
new file mode 100644
index 0000000..b2ccf94
--- /dev/null
+++ b/ui/imports/ui/components/environment/environment.styl
@@ -0,0 +1,61 @@
+.sm-environment-container
+
+ .sm-node-no-graph-data-msg
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+ font-size: large;
+
+ .sm-env-header-line
+ display: flex
+ flex-flow: row nowrap
+ justify-content: space-between
+ align-items: center
+
+ .sm-env-header-name-cmp
+ flex-grow: 2
+ display: flex
+ flex-flow: row nowrap
+ justify-content: center
+ align-items: center
+
+ .cl-action-button
+ color: spark-blue
+
+ .sm-delete-button.cl-action-button
+ color: status-red;
+
+ .main-layout-no-nav
+ display: flex;
+ flex-flow: column nowrap;
+
+ margin: 0px;
+
+ .sm-breadcrumb-segment
+ background-color brand-blue
+
+ .sm-main-content-segment
+ display: flex;
+ flex-flow: column nowrap;
+
+ flex: 1 0;
+
+ .sm-dashboard
+ padding: 20px;
+
+ //.sm-graph
+ // flex: 1 0;
+ .sm-network-graph
+ flex: 1 0;
+
+ .sm-items-segment
+ display: flex
+ flex-flow: row wrap
+ justify-content: space-around
+
+ .sm-list-info-boxes
+ display: flex
+ flex-flow: row wrap
+ justify-content: space-around
+
+
diff --git a/ui/imports/ui/components/flow-graph/flow-graph.html b/ui/imports/ui/components/flow-graph/flow-graph.html
new file mode 100644
index 0000000..5dd006d
--- /dev/null
+++ b/ui/imports/ui/components/flow-graph/flow-graph.html
@@ -0,0 +1,17 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="FlowGraph">
+ <div class="os-flow-graph">
+ <div class="sm-graph-container">
+ <svg width="500" height="400" class="sm-graph"></svg>
+ </div>
+ </div>
+</template>
diff --git a/ui/imports/ui/components/flow-graph/flow-graph.js b/ui/imports/ui/components/flow-graph/flow-graph.js
new file mode 100644
index 0000000..fd2c859
--- /dev/null
+++ b/ui/imports/ui/components/flow-graph/flow-graph.js
@@ -0,0 +1,383 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: FlowGraph
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+// We import d3 v4 not into d3 because old code network visualization use globaly d3 v3.
+import * as d3v4 from 'd3';
+import * as R from 'ramda';
+import { Statistics } from '/imports/api/statistics/statistics';
+import { createGraphQuerySchema } from '/imports/api/statistics/helpers';
+//import * as BSON from 'bson';
+
+import './flow-graph.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.FlowGraph.onCreated(function() {
+ let instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ environment: instance.data.environment,
+ object_id: instance.data.object_id,
+ type: instance.data.type,
+ flowType: instance.data.flowType,
+ sourceMacAddress: instance.data.sourceMacAddress,
+ destinationMacAddress: instance.data.destinationMacAddress,
+ sourceIPv4Address: instance.data.sourceIPv4Address,
+ destinationIPv4Address: instance.data.destinationIPv4Address,
+ simulateGraph: instance.data.simulateGraph,
+ yScale: instance.data.yScale,
+ timeDeltaNano: 0,
+ timeDeltaSeconds: 0
+ });
+
+ instance.autorun(() => {
+ new SimpleSchema({
+ env: { type: String },
+ object_id: { type: String },
+ type: { type: String },
+ flowType: { type: String },
+ sourceMacAddress: { type: String, optional: true },
+ destinationMacAddress: { type: String, optional: true },
+ sourceIPv4Address: { type: String, optional: true },
+ destinationIPv4Address: { type: String, optional: true },
+ simulateGraph: { type: Boolean, optional: true },
+ yScale: { type: Number, optional: true },
+ startDateTime: { type: String, optional: true },
+ }).validate(Template.currentData());
+
+ let data = Template.currentData();
+
+ instance.state.set('environment', data.env);
+ instance.state.set('object_id', data.object_id);
+ instance.state.set('type', data.type);
+ instance.state.set('flowType', data.flowType);
+ instance.state.set('sourceMacAddress', data.sourceMacAddress);
+ instance.state.set('destinationMacAddress', data.destinationMacAddress);
+ instance.state.set('sourceIPv4Address', data.sourceIPv4Address);
+ instance.state.set('destinationIPv4Address', data.destinationIPv4Address);
+ instance.state.set('simulateGraph', data.simulateGraph);
+ instance.state.set('yScale', data.yScale);
+
+ let startDateTime = R.ifElse(R.isNil, (_p) => { return moment();}, moment)(data.startDateTime);
+ let deltaSeconds = moment().diff(startDateTime, 'seconds');
+ //let deltaNano = deltaMili * 1000000;
+ //instance.state.set('timeDeltaNano', deltaNano);
+ instance.state.set('timeDeltaSeconds', deltaSeconds);
+
+ //let timeStart = startDateTime.valueOf() * 1000000;
+ let timeStart = startDateTime.unix();
+
+ //debugger;
+ // debug purpose:
+ //let timeStart = 1486661034810432900;// 1486661034810432945;
+ //let timeDeltaNano = Date.now() * 1000000 - timeStart;
+ //instance.state.set('timeDeltaNano', timeDeltaNano);
+ // debug end
+
+ instance.subscribe('statistics!graph-frames', {
+ env: data.env,
+ object_id: data.object_id,
+ type: data.type,
+ flowType: data.flowType,
+ timeStart: timeStart,
+ sourceMacAddress: data.sourceMacAddress,
+ destinationMacAddress: data.destinationMacAddress,
+ sourceIPv4Address: data.sourceIPv4Address,
+ destinationIPv4Address: data.destinationIPv4Address
+ });
+ });
+
+});
+
+Template.FlowGraph.onDestroyed(function () {
+ (function (d3) {
+ let instance = Template.instance();
+ let graphContainer = instance.$('.sm-graph');
+ var svg = d3.select(graphContainer[0]);
+
+ svg.interrupt();
+ var lineSvg = svg.select('g g path.line');
+ lineSvg.interrupt();
+ })(d3v4);
+});
+
+Template.FlowGraph.rendered = function() {
+ let instance = Template.instance();
+
+ instance.autorun(() => {
+
+ let environment = instance.state.get('environment');
+ let object_id = instance.state.get('object_id');
+ let type = instance.state.get('type');
+ let flowType = instance.state.get('flowType');
+ let sourceMacAddress = instance.state.get('sourceMacAddress');
+ let destinationMacAddress = instance.state.get('destinationMacAddress');
+ let sourceIPv4Address = instance.state.get('sourceIPv4Address');
+ let destinationIPv4Address = instance.state.get('destinationIPv4Address');
+ let simulateGraph = instance.state.get('simulateGraph');
+ let yScale = instance.state.get('yScale');
+ //let timeDeltaNano = instance.state.get('timeDeltaNano');
+ let timeDeltaSeconds = instance.state.get('timeDeltaSeconds');
+
+ let graphContainer = instance.$('.sm-graph');
+
+ generateAllGraph(
+ d3v4,
+ graphContainer,
+ environment,
+ object_id,
+ type,
+ flowType,
+ sourceMacAddress,
+ destinationMacAddress,
+ sourceIPv4Address,
+ destinationIPv4Address,
+ simulateGraph,
+ yScale,
+ //timeDeltaNano
+ timeDeltaSeconds
+ );
+
+ });
+};
+
+/*
+ * Events
+ */
+
+Template.FlowGraph.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.FlowGraph.helpers({
+});
+
+function generateAllGraph(
+ d3,
+ graphContainer,
+ environment,
+ object_id,
+ type,
+ flowType,
+ sourceMacAddress,
+ destinationMacAddress,
+ sourceIPv4Address,
+ destinationIPv4Address,
+ simulateGraph,
+ yScale,
+ //timeDeltaNano) {
+ timeDeltaSeconds) {
+
+ let dataRetrivalFn = createDataRetrivalFn(
+ d3,
+ simulateGraph,
+ environment,
+ object_id,
+ type,
+ flowType,
+ sourceMacAddress,
+ destinationMacAddress,
+ sourceIPv4Address,
+ destinationIPv4Address,
+ yScale
+ );
+
+ generateGraph(
+ d3,
+ dataRetrivalFn,
+ graphContainer,
+ //timeDeltaNano,
+ timeDeltaSeconds,
+ yScale
+ );
+}
+
+function createDataRetrivalFn(
+ d3,
+ simulateGraph,
+ environment,
+ object_id,
+ type,
+ flowType,
+ sourceMacAddress,
+ destinationMacAddress,
+ sourceIPv4Address,
+ destinationIPv4Address,
+ yScale
+) {
+
+ if (simulateGraph) {
+ let random = d3.randomNormal(0, yScale);
+ return function (_start, _end) {
+ return {
+ averageThroughput: random()
+ };
+ };
+ }
+
+ //return function (startNano, endNano) {
+ return function (startSeconds, endSeconds) {
+ //let startBson = BSON.Long.fromNumber(startNano);
+ //let endBson = BSON.Long.fromNumber(endNano);
+ //let startBson = startNano;
+ //let endBson = endNano;
+
+ let query = createGraphQuerySchema(
+ environment,
+ object_id,
+ type,
+ flowType,
+ //startBson,
+ //endBson,
+ startSeconds,
+ endSeconds,
+ sourceMacAddress,
+ destinationMacAddress,
+ sourceIPv4Address,
+ destinationIPv4Address);
+
+ return Statistics.findOne(query);
+ };
+
+/*
+ return function (timeStart, timeEnd, callback) {
+ Meteor.call('statistics!graph-frames', {
+ env: environment,
+ object_id: object_id,
+ type: type,
+ flowType: flowType,
+ timeStart: timeStart,
+ timeEnd: timeEnd,
+ sourceMacAddress: sourceMacAddress,
+ destinationMacAddress: destinationMacAddress,
+ sourceIPv4Address: sourceIPv4Address,
+ destinationIPv4Address: destinationIPv4Address
+ }, (_err, res) => {
+ callback(res);
+ });
+
+ };
+ */
+}
+
+function generateGraph(
+ d3,
+ dataRetrivalFn,
+ graphContainer,
+ //timeDeltaNano,
+ timeDeltaSeconds,
+ yScale
+) {
+ var n = 40;
+
+ let data = d3.range(n).map(R.always(0));
+ let svg = d3.select(graphContainer[0]);
+ let margin = {top: 20, right: 20, bottom: 20, left: 80};
+ let width = +svg.attr('width') - margin.left - margin.right;
+ let height = +svg.attr('height') - margin.top - margin.bottom;
+
+ svg.interrupt();
+ var lineSvg = svg.select('g g path.line');
+ lineSvg.interrupt();
+
+ svg.select('g').remove();
+
+ var g = svg.append('g').attr(
+ 'transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+ var x = d3.scaleLinear()
+ .domain([0, n - 1])
+ .range([0, width]);
+
+ var y = d3.scaleLinear()
+ .domain([0, yScale])
+ .range([height, 0]);
+
+ var line = d3.line()
+ .x(function(d, i) { return x(i); })
+ .y(function(d, _i) { return y(d); });
+
+ g.append('defs').append('clipPath')
+ .attr('id', 'clip')
+ .append('rect')
+ .attr('width', width)
+ .attr('height', height);
+
+ g.append('g')
+ .attr('class', 'axis axis--x')
+ .attr('transform', 'translate(0,' + y(0) + ')')
+ .call(d3.axisBottom(x));
+
+ g.append('g')
+ .attr('class', 'axis axis--y')
+ .call(d3.axisLeft(y));
+
+ g.append('g')
+ .attr('clip-path', 'url(#clip)')
+ .append('path')
+ .datum(data)
+ .attr('class', 'line')
+ .transition()
+ .duration(500)
+ .ease(d3.easeLinear)
+ .on('start', tick);
+
+ //let timeStart = (Date.now() * 1000000) - timeDeltaNano;
+ let timeStart = moment().unix() - timeDeltaSeconds;
+ let timeEnd;
+ let dataPoint;
+ let lastDataPoint = 0;
+
+ function tick() {
+ //timeEnd = (Date.now() * 1000000) - timeDeltaNano;
+ timeEnd = (moment().unix()) - timeDeltaSeconds;
+
+ let statItem = dataRetrivalFn(timeStart, timeEnd);
+
+ if (!R.isNil(statItem)) {
+ dataPoint = statItem.averageThroughput;
+ } else {
+ dataPoint = lastDataPoint;
+ }
+
+ data.push(dataPoint);
+
+ //timeStart = timeEnd - (4 * 1000000000);
+ timeStart = timeEnd;
+
+ // Redraw the line.
+ d3.select(this)
+ .attr('d', line)
+ .attr('transform', null);
+
+ // Slide it to the left.
+ d3.active(this)
+ .attr('transform', 'translate(' + x(-1) + ',0)')
+ .transition()
+ .on('start', tick);
+
+ // Pop the old data point off the front.
+ data.shift();
+
+ lastDataPoint = dataPoint;
+ }
+}
diff --git a/ui/imports/ui/components/flow-graph/flow-graph.styl b/ui/imports/ui/components/flow-graph/flow-graph.styl
new file mode 100644
index 0000000..e858fb9
--- /dev/null
+++ b/ui/imports/ui/components/flow-graph/flow-graph.styl
@@ -0,0 +1,18 @@
+/* Set the component style here */
+// "FlowGraph"
+.os-flow-graph
+
+ path
+ stroke: steelblue;
+ stroke-width: 2;
+ fill: none;
+
+ line
+ stroke: black;
+
+ text
+ font-family: Arial;
+ font-size: 9pt;
+
+ .sm-graph
+ background-color:#FDFEFF;
diff --git a/ui/imports/ui/components/general-folder-node-dashboard/general-folder-node-dashboard.html b/ui/imports/ui/components/general-folder-node-dashboard/general-folder-node-dashboard.html
new file mode 100644
index 0000000..81aaaa0
--- /dev/null
+++ b/ui/imports/ui/components/general-folder-node-dashboard/general-folder-node-dashboard.html
@@ -0,0 +1,24 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="GeneralFolderNodeDashboard">
+<div class="os-general-folder-node-dashboard">
+ <div class="sm-main-cubic">
+ {{> DataCubic (argsMainCubic childrenCount)}}
+ </div>
+ <div class="sm-children-section">
+ {{#each child in children }}
+ <div class="sm-child-info-box">
+ {{> GeneralNodeInfoBox (argsGeneralNodeInfoBox child) }}
+ </div>
+ {{/each }}
+ </div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/general-folder-node-dashboard/general-folder-node-dashboard.js b/ui/imports/ui/components/general-folder-node-dashboard/general-folder-node-dashboard.js
new file mode 100644
index 0000000..f383877
--- /dev/null
+++ b/ui/imports/ui/components/general-folder-node-dashboard/general-folder-node-dashboard.js
@@ -0,0 +1,112 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: GeneralFolderNodeDashboard
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import * as R from 'ramda';
+import { store } from '/imports/ui/store/store';
+import { InventoryTreeNodeBehavior } from '/imports/ui/lib/inventory-tree-node-behavior';
+import { Inventory } from '/imports/api/inventories/inventories';
+import { Icon } from '/imports/lib/icon';
+
+import '/imports/ui/components/data-cubic/data-cubic';
+import '/imports/ui/components/general-node-info-box/general-node-info-box';
+
+import './general-folder-node-dashboard.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.GeneralFolderNodeDashboard.onCreated(function() {
+ let instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ _id: null,
+ node: null,
+ childrenCount: 0,
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+ new SimpleSchema({
+ _id: { type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } } },
+ onNodeSelected: { type: Function },
+ }).validate(data);
+
+ instance.state.set('_id', data._id);
+ });
+
+ instance.autorun(function () {
+ let _id = instance.state.get('_id');
+ if (R.isNil(_id)) { return; }
+
+ Inventory.find({ _id: _id}).forEach((node) => {
+ InventoryTreeNodeBehavior.subscribeGetChildrenFn(instance, node);
+ let childrenCount = InventoryTreeNodeBehavior.getChildrenFn(node).count();
+ instance.state.set('childrenCount', childrenCount);
+ instance.state.set('node', node);
+ });
+ });
+});
+
+/*
+Template.GeneralFolderNodeDashboard.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.GeneralFolderNodeDashboard.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.GeneralFolderNodeDashboard.helpers({
+ argsMainCubic: function (childrenCount) {
+ return {
+ header: R.path(['components', 'generalFolderNodeDashboard', 'mainCubic', 'header']
+ )(store.getState().api.i18n),
+ dataInfo: R.toString(childrenCount),
+ icon: new Icon({ type: 'fa', name: 'desktop' }),
+ };
+ },
+
+ argsGeneralNodeInfoBox: function (node) {
+ return {
+ objectName: node.object_name,
+ type: node.type,
+ lastScanned: node.last_scanned,
+ description: node.description,
+ };
+ },
+
+ childrenCount: function () {
+ let instance = Template.instance();
+ return instance.state.get('childrenCount');
+ },
+
+ children: function () {
+ let instance = Template.instance();
+ let node = instance.state.get('node');
+ return InventoryTreeNodeBehavior.getChildrenFn(node);
+ }
+}); // end: helpers
+
+
diff --git a/ui/imports/ui/components/general-folder-node-dashboard/general-folder-node-dashboard.styl b/ui/imports/ui/components/general-folder-node-dashboard/general-folder-node-dashboard.styl
new file mode 100644
index 0000000..49967c6
--- /dev/null
+++ b/ui/imports/ui/components/general-folder-node-dashboard/general-folder-node-dashboard.styl
@@ -0,0 +1,12 @@
+.os-general-folder-node-dashboard
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+
+ .sm-children-section
+ display: flex;
+ flex-flow: row wrap;
+ justify-content: center;
+
+ >.sm-child-info-box
+ padding: 10px;
diff --git a/ui/imports/ui/components/general-node-dashboard/general-node-dashboard.html b/ui/imports/ui/components/general-node-dashboard/general-node-dashboard.html
new file mode 100644
index 0000000..417ba8b
--- /dev/null
+++ b/ui/imports/ui/components/general-node-dashboard/general-node-dashboard.html
@@ -0,0 +1,17 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="GeneralNodeDashboard">
+<div class="os-general-node-dashboard">
+ <div class="sm-node-info-box">
+ {{> DetailedNodeInfoBox (argsGenNodeInfoBox getNode) }}
+ </div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/general-node-dashboard/general-node-dashboard.js b/ui/imports/ui/components/general-node-dashboard/general-node-dashboard.js
new file mode 100644
index 0000000..3009ee3
--- /dev/null
+++ b/ui/imports/ui/components/general-node-dashboard/general-node-dashboard.js
@@ -0,0 +1,84 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: GeneralNodeDashboard
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { Inventory } from '/imports/api/inventories/inventories';
+
+import '/imports/ui/components/detailed-node-info-box/detailed-node-info-box';
+
+import './general-node-dashboard.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.GeneralNodeDashboard.onCreated(function() {
+ var instance = this;
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ id: null,
+ node: null,
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+ new SimpleSchema({
+ _id: { type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } } },
+ onNodeSelected: { type: Function },
+ }).validate(data);
+
+ instance.state.set('_id', data._id);
+ });
+
+ instance.autorun(function () {
+ let _id = instance.state.get('_id');
+
+ instance.subscribe('inventory?_id', _id);
+ Inventory.find({ _id: _id }).forEach((node) => {
+ instance.state.set('node', node);
+ });
+ });
+});
+
+/*
+Template.GeneralNodeDashboard.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.GeneralNodeDashboard.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.GeneralNodeDashboard.helpers({
+ getNode: function () {
+ let instance = Template.instance();
+ return instance.state.get('node');
+ },
+
+ argsGenNodeInfoBox: function (node) {
+ return {
+ node: node,
+ };
+ }
+});
+
+
diff --git a/ui/imports/ui/components/general-node-dashboard/general-node-dashboard.styl b/ui/imports/ui/components/general-node-dashboard/general-node-dashboard.styl
new file mode 100644
index 0000000..6d3727b
--- /dev/null
+++ b/ui/imports/ui/components/general-node-dashboard/general-node-dashboard.styl
@@ -0,0 +1,6 @@
+.os-general-node-dashboard
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+
+ //.sm-node-info-box
diff --git a/ui/imports/ui/components/general-node-info-box/general-node-info-box.html b/ui/imports/ui/components/general-node-info-box/general-node-info-box.html
new file mode 100644
index 0000000..8aed8f2
--- /dev/null
+++ b/ui/imports/ui/components/general-node-info-box/general-node-info-box.html
@@ -0,0 +1,37 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="GeneralNodeInfoBox">
+<div class="os-general-node-info-box cards-450 white">
+ <div class="sm-icon-segment">
+ </div>
+ <div class="sm-info-segment">
+ <div class="sm-info-title">{{ type }} - {{ objectName }}</div>
+ <div class="sm-info-bits">
+ <div class="cl-info-bit">
+ <div class="cl-label">Name</div>
+ <div class="cl-data"><div class="sm-object-name">{{ objectName }}</div></div>
+ </div>
+ <div class="cl-info-bit">
+ <div class="cl-label">Type</div>
+ <div class="cl-data"><div class="sm-object-type">{{ type }}</div></div>
+ </div>
+ <div class="cl-info-bit">
+ <div class="cl-label">Last scanned</div>
+ <div class="cl-data"><div class="sm-last-scanned">{{ lastScanned }}</div></div>
+ </div>
+ <div class="cl-info-bit">
+ <div class="cl-label">Description</div>
+ <div class="cl-data"><div class="sm-description">{{ description }}</div></div>
+ </div>
+ </div>
+ </div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/general-node-info-box/general-node-info-box.js b/ui/imports/ui/components/general-node-info-box/general-node-info-box.js
new file mode 100644
index 0000000..4b88945
--- /dev/null
+++ b/ui/imports/ui/components/general-node-info-box/general-node-info-box.js
@@ -0,0 +1,63 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: GeneralNodeInfoBox
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+
+import './general-node-info-box.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.GeneralNodeInfoBox.onCreated(function() {
+ let instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+ new SimpleSchema({
+ objectName: { type: String },
+ type: { type: String },
+ lastScanned: { type: Date, optional: true },
+ description: { type: String, optional: true },
+ }).validate(data);
+
+ });
+
+});
+
+/*
+Template.GeneralNodeInfoBox.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.GeneralNodeInfoBox.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.GeneralNodeInfoBox.helpers({
+});
+
+
diff --git a/ui/imports/ui/components/general-node-info-box/general-node-info-box.styl b/ui/imports/ui/components/general-node-info-box/general-node-info-box.styl
new file mode 100644
index 0000000..4d4cc7a
--- /dev/null
+++ b/ui/imports/ui/components/general-node-info-box/general-node-info-box.styl
@@ -0,0 +1,33 @@
+.os-general-node-info-box
+ display: flex;
+ flex-flow: row nowrap;
+
+ .sm-icon-segment
+ flex: 0 1 70px;
+
+ .sm-info-segment
+ flex: 1;
+ display: flex;
+ flex-flow: column nowrap;
+
+ .sm-info-title
+ color: #0a9ad7;
+ font-size: 2em;
+ border-bottom: 3px solid #0a9ad7;
+ line-height: 1.5em;
+
+ .sm-info-bits
+ padding: 5px 0px;
+
+ display: flex;
+ flex-flow: column nowrap;
+
+ .cl-info-bit
+ display: flex;
+ flex-flow: row nowrap;
+
+ .cl-label
+ flex: 0 0 90px;
+ color: black;
+ font-weight: bold;
+
diff --git a/ui/imports/ui/components/get-started/get-started.html b/ui/imports/ui/components/get-started/get-started.html
new file mode 100644
index 0000000..2277547
--- /dev/null
+++ b/ui/imports/ui/components/get-started/get-started.html
@@ -0,0 +1,412 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="getstarted">
+
+ {{> accordionWikiMenu}}
+
+ <div class="main-layout">
+
+ <div class="cards white">
+ <h3 id="wikiFirstStep">First Step</h3>
+ <p class="text">
+ Administrators of Calipso can add new Environments, one or many, as pre-requisite for scanning, monitoring and visualizing.
+ “Environment” in Calipso means an OpenStack (or other cloud-based) infrastructure managed under a single administrative entity, controlling all endpoints and credentials on that infrastructure. For Calipso on OpenStack, multiple ‘regions’ means multiple environments, while regions are automatically discovered.
+ </p>
+ <p class="text">
+ Calipso scans the environment through API adapter, Database adapter and Command-Line adapter, so credentials are needed for each of those methods. Click on "My Environments" to get started.
+ </p>
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/environments-pick.png" alt="" class="img_responsive" width="800">
+ </div>
+ </div>
+ <p class="text margin-top-40">
+ Click on "My Environments" and "Add new environment" to provide the details for a new environment.
+ </p>
+ <p class="text">Rembemer:</p>
+ <p class="text">
+ Calipso environment setup is an automated extension to your hand accessing the endpoints like you would do manually through your administrative station, secure Calipso station like you would secure your administrative station, it makes sense to place it inside infrastructure management domain, treat it like any other OpenStack module.
+ </p>
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/new-environment-action.png" alt="" class="img_responsive" width="800">
+ </div>
+ </div>
+ </div>
+
+ <div class="cards white">
+ <h3 id="wikiAddNewEnv">Add new environment</h3>
+ <p class="text">
+ In this wizard Calipso captures the Environment Name and distribution variance.
+ </p>
+
+ <p class="text">
+ Calipso provides, in its core, the capability to support discovery of many variances of cloud virtual networking. Each release of Calipso will include a set of pre-defined lists that the back-end scanning engine uses for detailed discovery. Currently the environment variance has those main parameters:
+ </p>
+
+ <ol>
+ <li>
+ Distribution: captures the specific vendor/distribution of the cloud infrastructure controllers.
+ </li>
+ <li>
+ Mechanism-Driver: captures the underlying vendor/plugin of the networking system in use.
+ </li>
+ <li>
+ Type-Driver: captures the underlying packet encapsulation used for layer-2 isolation.
+ </li>
+ </ol>
+
+ <p class="text">
+ “Event based scan” allows updating the Calipso inventory in real time, it creates per-environment listener connected to the OpenStack message BUS and a smart update mechanism to keep the data synchronized. This is important for future discovery, while initial discovery is triggered by the administrator. You can uncheck this method to use other means of updating the inventory data like scheduled scans.
+ </p>
+
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/env-editing.png" alt="" class="img_responsive" width="800">
+ </div>
+ </div>
+
+ <p class="text">
+ Calipso needs credentials for accessing OpenStack API, underlying Database and host Command Line Interface, as it is currently agentless and underlying infrastructure lacks operations API to provide the necessary data.
+ </p>
+
+ <p class="text">
+ “OS API Endpoint” captures Keystone’s admin endpoint for accessing the entire OpenStack API:
+ </p>
+
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/env-editing-os-end-point.png" alt="" class="img_responsive" width="800">
+ </div>
+ </div>
+
+ <p class="text">
+ Default settings gives you some examples of the formats, Calipso will validate your input and provide suggestions, TEST function will try to create a connection to that endpoint for validation. Calipso will first try to collect data through API, then any missing details through DB, then more missing details from CLI on the infrastructure hosts.
+ </p>
+
+ <p class="text">
+ DB credentials are needed to provide a much more granular insight into your OpenStack environment. Using the DB access Calipso collects more important data (read only) that is not available currently through API.
+ </p>
+
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/env-diting-os-db-credentails.png" alt="" class="img_responsive" width="800">
+ </div>
+ </div>
+
+ <p class="text">
+ Calipso is currently agentless, to minimize the impact of scanning. In order to access CLI on hosts, Calipso needs a “Master Host”, commonly referred to as a ‘jump-host’. This “jump-host” already have all the keys and routing access to all hosts running under the environment controllers. As administrator station, we recommend using PKI and password-less access to secure communications from the ‘jump-host’ itself to all other infrastructure hosts.
+ </p>
+
+ <p class="text">
+ Calipso will first SSH to that “Mater Host” and using its knowledge of the infrastructure will then SSH to all other hosts, using the “Master Host” as the source/initiator of subsequent connections. If required, Calispo host itself can be used as a “Master Host”, keeping access credentials local.
+ </p>
+
+ <p class="text">
+ This method of data gathering is needed for details that are currently not exposed through API. When other methods will provide those details (like an “Operations API” in future releases of Calipso agent on the hosts), Calipso can revise its discovery logic accordingly.
+ </p>
+
+ <p class="text">
+ The “Master Host” credentials can be username/password or ssh keys (recommended).
+ </p>
+
+ <p class="text">
+ Calipso assumes all other OpenStack hosts are further accessible through this single entry point.
+ </p>
+
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/env-editing-master-host-credentials.png" alt="" class="img_responsive" width="800">
+ </div>
+ </div>
+
+ <p class="text">
+ If “Event based scan” was chosen for future data synchronization, AMQP credentials needs to be provided, this is used by the per-environment listener in Calipso:
+ </p>
+
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/env-editing-amqp-credentials.png" alt="" class="img_responsive" width="800">
+ </div>
+ </div>
+
+ <p class="text">
+ NFV Credentials are optional. Calipso can include an adapter to NFV application using the cloud infrastructure as its “VIM”, it then gives Calipso details about the underlying instances functions and capabilities, and this will be possible on some releases of Calipso.
+ </p>
+
+ <p class="text">
+ The Monitoring module in Calipso uses “Sensu” as underlying agent and framework.
+ </p>
+
+ <p class="text">
+ See: <a href="https://sensuapp.org/docs/latest/overview/architecture.html">
+ https://sensuapp.org/docs/latest/overview/architecture.html</a>
+ </p>
+
+ <p class="text">
+ Calipso requires a pre-installed Sensu server and a Sensu client installed on all monitored infrastructure hosts to allow Calipso to check the health of many inventory objects that are an essential infrastructure networking components.
+ </p>
+
+ <p class="text">
+ Calipso includes a special configuration helper to aid the deployment of the Sensu framework for virtual networking monitoring. It provides cloud administrators with the necessary configurations to install on the hosts and on the Sensu server.
+ </p>
+
+ <p class="text">
+ In the “Monitoring” tab you define your preferred monitoring setup to be used by Calipso:
+ </p>
+
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/env-editing-monitoring.png" alt="" class="img_responsive" width="800">
+ </div>
+ </div>
+
+ <p class="text">
+ “Environment Type” adds tags to messages received from any specific Sensu check on the clients, you can use it to level and/or isolate messages and loggings.
+ Calipso uses RabbitMQ over SSL as the default transport mechanism from Sensu clients to Sensu server, it requires the following details for that transport mechanism: Port, User, Password, Sensu Server IP and Name.
+ </p>
+
+ <p class="text">
+ Calipso assumes the SSL keys used for client-server communications are part of the Sensu pre-installed infrastructure and are placed at the default location on the Sensu server.
+ See: <a href="https://sensuapp.org/docs/latest/reference/ssl.html">https://sensuapp.org/docs/latest/reference/ssl.html</a>
+ </p>
+
+ <p class="text">
+ The “Type” attribute provides future ability to use different monitoring frameworks, currently only “Sensu” is supported.
+ </p>
+
+ <p class="text">
+ “Provision” attribute allows you to control the way Calispo configuration helper is used, here are the current options:
+ </p>
+
+ <ol>
+ <li>
+ “None”: Calipso configuration helper will not be used.
+ </li>
+ <li>
+ “Files”: Calipso configuration helper will create and place all configuration files and remote checks, for Sensu server and clients in the “Config folder” location on the Calipso host, to be used by the administrator for manual deployment onto the Sensu framework.
+ </li>
+ <li>
+ “DB”: Calipso configuration helper will create and place all configuration files and remote checks, as JSON documents, in the Calipso Mongo DB “monitoring_config” collection, to be used by the administrator for manual deployment onto the Sensu framework.
+ </li>
+ <li>
+ “Deploy”: Calipso configuration helper will create and automatically place all configuration files and remote checks onto the Sensu framework, in the Sensu server and all Sensu clients (hosts). Configurations will be placed on the hosts in the default location: /etc/sensu/conf.d
+ </li>
+ </ol>
+
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/env-editing-provision.png" alt="" class="img_responsive" width="800">
+ </div>
+ </div>
+ </div>
+
+
+ <div class="cards white">
+ <h3 id="wikiAccessSwitchEnv">Access and Switch environment</h3>
+
+ <p class="text">
+ Calipso runs a scan of each environment, you can then access each environment through “My Environment”, and switch between environments as you like:
+ </p>
+
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/selecting-env.png" alt="" class="img_responsive" width="800">
+ </div>
+ </div>
+
+ </div>
+
+ <div class="cards white">
+ <h3 id="wikiMainDashboard">Main Dashboard</h3>
+
+ <p class="text">
+ Once one or more environments are defined, the main dashboard will provide a summary of environments inventory and provides access to underlying cloud hierarchy as needed:
+ </p>
+
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/selecting-dashboard.png" alt="" class="img_responsive" width="800">
+ </div>
+ </div>
+
+ </div>
+
+ <div class="cards white">
+ <h3 id="wikiMainMessages">Main messages</h3>
+
+ <p class="text">
+ Messages aggregates environment notifications, warnings and errors, generated by Calipso or by the Cloud infrastructure itself. Calipso messaging details include the source system and the related inventory object referenced by that specific message.
+ </p>
+
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/dashboard-notifications.png" alt="" class="img_responsive" width="800">
+ </div>
+ </div>
+ </div>
+
+ <div class="cards white">
+ <h3 id="wikiWorkWithEnvs">Work with environments</h3>
+ <p class="text">
+ When you choose a specific environment through “My Environments” tab, you will be directed to the environment dashboard to start viewing and analyzing the underlying discovered objects and dependencies.
+ </p>
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/env-dashboard.png" alt="" class="img_responsive" width="800">
+ </div>
+ </div>
+ </div>
+
+ <div class="cards white">
+ <h3 id="wikiScanningEnv">Scanning an environment</h3>
+ <p class="text">
+ Calipso system needs at least an initial scan of a pre-defined environment in order to provide visualization, monitoring and analysis. Clicking on the scan button in a specific environment dashboard captures the needed details for scanning:
+ </p>
+
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/scan-action.png" alt="" class="img_responsive" width="800">
+ </div>
+ </div>
+
+ <p class="text">
+ For all scanning requests the following request form is used:
+ </p>
+
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/scan-request-screen.png" alt="" class="img_responsive" width="800">
+ </div>
+ </div>
+
+ <p class="text">
+ Calipso currently uses mongo DB to maintain its inventory data, by default the results of scanning are placed in the “inventory” collection. For debugging and testing you can provide a different inventory collection name for holding the results.
+ </p>
+
+ <p class="text">
+ “Object Id” can be used only in subsequent future scanning requests to validate and update new details for only a specific object from the inventory.
+ </p>
+
+ <p class="text">
+ “Clear” option will initially clean all environment specific data from the inventory, if this is not checked Calipso will override same objects and keep older objects as needed.
+ </p>
+
+ <p class="text">
+ Additional optional filters are provided to allow the analysis of links and cliques in the scanning process, read the Calipso operations guide for details on those options.
+ </p>
+
+ <p class="text">
+ Once scan request is submitted, the backend system will kick off the scanning process, you can later check the status of scan requests under “Settings”.
+ </p>
+
+ </div>
+
+ <div class="cards white">
+ <h3 id="wikiDeletingEnv">Deleting an environment</h3>
+
+ <p class="text">
+ Deleting an environment from Calipso system will remove all credentials and access details and will also clean up related inventory objects and messages.
+ </p>
+
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/env-delete-action.png" alt="" class="img_responsive" width="800">
+ </div>
+ </div>
+ </div>
+
+ <div class="cards white">
+ <h3 id="wikiEditingEnv">Editing an environment</h3>
+ <p class="text">
+ Editing an environment allows changing initial settings and credentials according to underlying infrastructure access changes.
+ </p>
+
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/env-edit-action.png" alt="" class="img_responsive" width="800">
+ </div>
+ </div>
+
+ <p class="text">
+ Edit button redirects you to environment configurations dashboard to allow changing its parameters.
+ </p>
+
+ </div>
+
+ <div class="cards white">
+ <h3 id="wikiCalipsoSetting">Calipso setting</h3>
+ <p class="text">
+ Details settings and information of Calipso system is provided on all dashboards in the top right corner of the screen:
+ </p>
+
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/setting-action.png" alt="" class="img_responsive" width="800">
+ </div>
+ </div>
+
+ <p class="text">
+ Scans settings provides detailed information and status of scan requests:
+ </p>
+
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/setting-scans-action.png" alt="" class="img_responsive" width="800">
+ </div>
+ </div>
+
+ <p class="text">
+ “Link Types” and “Clique Types” provides controlled and modeled mechanism for generating the Calipso graph topologies, for more details on “Links” and “Cliques” checkout the Calipso operations guide.
+ </p>
+
+ <p class="text">
+ “Messages” provides granular details for per-environment error, notification and warning messages.
+ </p>
+ </div>
+
+ <div class="cards white">
+ <h3 id="wikiBrowsingEnv">Browsing your cloud environment</h3>
+ <p class="text">
+ Once a cloud environment was defined and scanned, its underlying virtual networking details can be accessed through the <u>navigation tree</u>, <u>breadcrumb bar</u> or the <u>search engine</u>.
+ </p>
+
+ <p class="text">
+ Use a specific environment dashboard to browse the environment, to maintain, debug and troubleshoot virtual networking issues.
+ </p>
+
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/main-screen-explained.png" alt="" class="img_responsive"
+ width="800">
+ </div>
+ </div>
+
+ <p class="text">
+ Navigation through the cloud environment hierarchy allows granular locating of an interesting inventory object, once a location is pointed out the Calipso UI will provide topology graphs specific to that focal point (see the Calipso operations guide for more details):
+ </p>
+
+ <div class="flex-box justify-content-between wrap">
+ <div class="flex-box-1">
+ <img src="/get-started/navbar-explained.png" alt="" class="img_responsive"
+ width="800">
+ </div>
+ </div>
+
+ <p class="text">
+ This short guide got you started with Calipso system, for more details on maintenance and troubleshooting your virtual networking components using Calipso - please follow the detailed operations guide: <a href="http://calipso.io/guide">http://calipso.io/guide</a>
+ </p>
+
+ </div>
+
+ </div>
+
+</template>
diff --git a/ui/imports/ui/components/get-started/get-started.js b/ui/imports/ui/components/get-started/get-started.js
new file mode 100644
index 0000000..a1ae0f0
--- /dev/null
+++ b/ui/imports/ui/components/get-started/get-started.js
@@ -0,0 +1,32 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: getStarted
+ */
+
+import { Template } from 'meteor/templating';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+
+//import { setCurrentNode } from '/imports/ui/actions/navigation';
+import { store } from '/imports/ui/store/store';
+import { setMainAppSelectedEnvironment } from '/imports/ui/actions/main-app.actions';
+
+import '/imports/ui/components/accordion-wiki-menu/accordion-wiki-menu';
+
+import './get-started.html';
+
+Template.getstarted.onCreated(function () {
+ store.dispatch(setMainAppSelectedEnvironment(null));
+});
+
+Template.getstarted.onDestroyed(function () {
+});
+
+Template.getstarted.helpers({
+});
diff --git a/ui/imports/ui/components/graph-tooltip-window/graph-tooltip-window.html b/ui/imports/ui/components/graph-tooltip-window/graph-tooltip-window.html
new file mode 100644
index 0000000..53537ca
--- /dev/null
+++ b/ui/imports/ui/components/graph-tooltip-window/graph-tooltip-window.html
@@ -0,0 +1,17 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="GraphTooltipWindow">
+ <div class="os-graph-tooltip-window {{#if show}}cl-visible{{/if}}"
+ style="top: {{ top }}px; left: {{ left }}px;">
+ <div class="sm-label"><u>{{ label }}</u></div>
+ <div class="sm-title">{{{ title }}}</div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/graph-tooltip-window/graph-tooltip-window.js b/ui/imports/ui/components/graph-tooltip-window/graph-tooltip-window.js
new file mode 100644
index 0000000..48b1903
--- /dev/null
+++ b/ui/imports/ui/components/graph-tooltip-window/graph-tooltip-window.js
@@ -0,0 +1,57 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: GraphTooltipWindow
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+
+import './graph-tooltip-window.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.GraphTooltipWindow.onCreated(function() {
+ let instance = this;
+
+ instance.autorun(() => {
+ new SimpleSchema({
+ label: { type: String },
+ title: { type: String },
+ left: { type: Number },
+ top: { type: Number },
+ show: { type: Boolean }
+ }).validate(Template.currentData());
+ });
+});
+
+/*
+Template.GraphTooltipWindow.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.GraphTooltipWindow.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.GraphTooltipWindow.helpers({
+});
+
+
diff --git a/ui/imports/ui/components/graph-tooltip-window/graph-tooltip-window.styl b/ui/imports/ui/components/graph-tooltip-window/graph-tooltip-window.styl
new file mode 100644
index 0000000..ec94023
--- /dev/null
+++ b/ui/imports/ui/components/graph-tooltip-window/graph-tooltip-window.styl
@@ -0,0 +1,25 @@
+/* Set the component style here */
+// "GraphTooltipWindow"
+.os-graph-tooltip-window
+ visibility: hidden;
+
+ position: absolute;
+ text-align: left;
+ opacity: 0
+ font: normal 18px sans-serif !important;
+ /* width: 60px; */
+ /* height: 28px; */
+ padding: 20px;
+ font: 16px sans-serif;
+ background: dk-gray1;
+ color white
+ border: 2px solid stark-blue
+ pointer-events: none;
+
+ transition: visibility 0.5s, opacity 0.5s linear
+
+.os-graph-tooltip-window.cl-visible
+ visibility: visible
+ opacity: 0.9
+ transition: visibility 0.2s, opacity 0.2s linear
+
diff --git a/ui/imports/ui/components/host-dashboard/host-dashboard.html b/ui/imports/ui/components/host-dashboard/host-dashboard.html
new file mode 100644
index 0000000..d33ee57
--- /dev/null
+++ b/ui/imports/ui/components/host-dashboard/host-dashboard.html
@@ -0,0 +1,29 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="HostDashboard">
+ <div class="os-host-dashboard flex-box justify-content-between">
+ <div class="flex-box-3 main-layout-no-nav">
+
+ <div class="flex">
+ <div class="flex-box-1 cards white title">
+ <h4>Host name: {{ host.name }}</h4>
+ </div>
+ </div>
+
+ <div class="sm-info-boxes">
+ {{#each infoBox in infoBoxes }}
+ {{> DataCubic (argsInfoBox infoBox) }}
+ {{/each }}
+ </div>
+
+ </div>
+ </div>
+</template>
diff --git a/ui/imports/ui/components/host-dashboard/host-dashboard.js b/ui/imports/ui/components/host-dashboard/host-dashboard.js
new file mode 100644
index 0000000..4830543
--- /dev/null
+++ b/ui/imports/ui/components/host-dashboard/host-dashboard.js
@@ -0,0 +1,197 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: HostDashboard
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { Inventory } from '/imports/api/inventories/inventories';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { regexEscape } from '/imports/lib/regex-utils';
+import * as R from 'ramda';
+import { store } from '/imports/ui/store/store';
+import { Icon } from '/imports/lib/icon';
+
+//import '/imports/ui/components/accordionNavMenu/accordionNavMenu';
+import '/imports/ui/components/data-cubic/data-cubic';
+
+import './host-dashboard.html';
+
+let infoBoxes = [{
+ header: ['components', 'hostDashboard', 'infoBoxes', 'instances', 'header'],
+ dataSource: 'instancesCount',
+ icon: { type: 'fa', name: 'desktop' },
+ theme: 'dark'
+}, {
+ header: ['components', 'hostDashboard', 'infoBoxes', 'vServices', 'header'],
+ dataSource: 'vServicesCount',
+ icon: { type: 'fa', name: 'object-group' },
+ theme: 'dark'
+}, {
+ header: ['components', 'hostDashboard', 'infoBoxes', 'vConnectors', 'header'],
+ dataSource: 'vConnectorsCount',
+ icon: { type: 'fa', name: 'compress' },
+ theme: 'dark'
+}, {
+ header: ['components', 'hostDashboard', 'infoBoxes', 'ports', 'header'],
+ dataSource: 'portsCount',
+ icon: { type: 'fa', name: 'compress' },
+ theme: 'dark'
+}, {
+ header: ['components', 'hostDashboard', 'infoBoxes', 'networkAgents', 'header'],
+ dataSource: 'networkAgentsCount',
+ icon: { type: 'fa', name: 'compress' }, // todo: icon
+ theme: 'dark'
+}, {
+ header: ['components', 'hostDashboard', 'infoBoxes', 'pnics', 'header'],
+ dataSource: 'pnicsCount',
+ icon: { type: 'fa', name: 'compress' }, // todo: icon
+ theme: 'dark'
+}, {
+ header: ['components', 'hostDashboard', 'infoBoxes', 'vEdges', 'header'],
+ dataSource: 'vEdgesCount',
+ icon: { type: 'fa', name: 'external-link' },
+ theme: 'dark'
+}];
+
+/*
+ * Lifecycles
+ */
+
+Template.HostDashboard.onCreated(function() {
+ var instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ id_path: null,
+ instancesCount: 0,
+ vServicesCount: 0,
+ vConnectors: 0,
+ portsCount: 0,
+ networkAgentsCount: 0,
+ pnicsCount: 0,
+ vEdgesCount: 0,
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+
+ new SimpleSchema({
+ _id: { type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } } },
+ onNodeSelected: { type: Function },
+ }).validate(data);
+
+ instance.state.set('_id', data._id);
+ });
+
+ instance.autorun(function () {
+ let _id = instance.state.get('_id');
+
+ instance.subscribe('inventory?_id', _id);
+ Inventory.find({ _id: _id }).forEach((host) => {
+ instance.state.set('id_path', host.id_path);
+
+ instance.subscribe('inventory?id_path', host.id_path);
+ instance.subscribe('inventory?id_path_start&type', host.id_path, 'instance');
+ instance.subscribe('inventory?id_path_start&type', host.id_path, 'vservice');
+ instance.subscribe('inventory?id_path_start&type', host.id_path, 'vconnector');
+ instance.subscribe('inventory?id_path_start&type', host.id_path, 'network_agent');
+ instance.subscribe('inventory?id_path_start&type', host.id_path, 'pnic');
+ instance.subscribe('inventory?id_path_start&type', host.id_path, 'vedge');
+
+ Inventory.find({ id_path: host.id_path }).forEach((host) => {
+ instance.subscribe('inventory?env&binding:host_id&type',
+ host.environment, host.id, 'port');
+
+ instance.state.set('portsCount', Inventory.find({
+ environment: host.environment,
+ 'binding:host_id': host.id,
+ type: 'port'
+ }).count());
+ });
+
+ let idPathExp = new RegExp(`^${regexEscape(host.id_path)}`);
+
+ instance.state.set('instancesCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'instance'
+ }).count());
+
+ instance.state.set('vServicesCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'vservice'
+ }).count());
+
+ instance.state.set('vConnectorsCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'vconnector'
+ }).count());
+
+ instance.state.set('networkHostsCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'network_host'
+ }).count());
+
+ instance.state.set('pnicsCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'pnic'
+ }).count());
+
+ instance.state.set('vEdgesCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'vedge'
+ }).count());
+ });
+
+ });
+});
+
+/*
+Template.HostDashboard.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.HostDashboard.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.HostDashboard.helpers({
+ host: function () {
+ let instance = Template.instance();
+ let host_id_path = instance.state.get('id_path');
+
+ return Inventory.findOne({ id_path: host_id_path });
+ },
+
+ infoBoxes: function () {
+ return infoBoxes;
+ },
+
+ argsInfoBox: function (infoBox) {
+ let instance = Template.instance();
+
+ return {
+ header: R.path(infoBox.header, store.getState().api.i18n),
+ dataInfo: instance.state.get(infoBox.dataSource).toString(),
+ icon: new Icon(infoBox.icon),
+ theme: infoBox.theme
+ };
+ },
+});
+
+
diff --git a/ui/imports/ui/components/host-dashboard/host-dashboard.styl b/ui/imports/ui/components/host-dashboard/host-dashboard.styl
new file mode 100644
index 0000000..aa335b2
--- /dev/null
+++ b/ui/imports/ui/components/host-dashboard/host-dashboard.styl
@@ -0,0 +1,6 @@
+/* Set the component style here */
+.os-host-dashboard
+ .sm-info-boxes
+ display: flex
+ flex-flow: row wrap;
+ justify-content: space-around
diff --git a/ui/imports/ui/components/icon/icon.html b/ui/imports/ui/components/icon/icon.html
new file mode 100644
index 0000000..04487d2
--- /dev/null
+++ b/ui/imports/ui/components/icon/icon.html
@@ -0,0 +1,18 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="Icon">
+ {{#if (iconType type 'fa' )}}
+ <i class="fa fa-{{ name }}"></i>
+ {{/if }}
+ {{#if (iconType type 'material')}}
+ <i class="material-icons">{{ name }}</i>
+ {{/if }}
+</template>
diff --git a/ui/imports/ui/components/icon/icon.js b/ui/imports/ui/components/icon/icon.js
new file mode 100644
index 0000000..06010a4
--- /dev/null
+++ b/ui/imports/ui/components/icon/icon.js
@@ -0,0 +1,48 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: Icon
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+
+import './icon.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.Icon.onCreated(function() {
+});
+
+/*
+Template.Icon.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.Icon.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.Icon.helpers({
+ iconType: function (type, targetType) {
+ return type === targetType;
+ }
+});
+
+
diff --git a/ui/imports/ui/components/icon/icon.styl b/ui/imports/ui/components/icon/icon.styl
new file mode 100644
index 0000000..1841a36
--- /dev/null
+++ b/ui/imports/ui/components/icon/icon.styl
@@ -0,0 +1,2 @@
+/* Set the component style here */
+// "Icon"
diff --git a/ui/imports/ui/components/index.styl b/ui/imports/ui/components/index.styl
new file mode 100644
index 0000000..34e4db9
--- /dev/null
+++ b/ui/imports/ui/components/index.styl
@@ -0,0 +1,57 @@
+@import 'breadcrumb/*'
+@import 'breadcrumbNode/*'
+@import 'environment/*'
+@import 'environment-dashboard/*'
+@import 'scanning-request/*'
+@import 'environment-wizard/*'
+@import 'search-auto-complete-list/*';
+@import 'auto-search-result-line/*';
+@import 'top-navbar-menu/*';
+@import 'data-cubic/*';
+@import 'project-dashboard/*';
+@import 'region-dashboard/*';
+@import 'zone-dashboard/*';
+@import 'host-dashboard/*';
+@import 'list-info-box/*';
+@import 'aggregate-dashboard/*';
+@import 'graph-tooltip-window/*';
+@import 'vedge-info-window/*';
+@import 'flow-graph/*';
+@import 'time-selection-widget/*';
+@import 'scans-list/*';
+@import 'link-types-list/*';
+@import 'link-type/*';
+@import 'clique-types-list/*';
+@import 'clique-type/*';
+@import 'clique-constraints-list/*';
+@import 'clique-constraint/*';
+@import 'env-delete-modal/*';
+@import 'accordion-nav-menu/*';
+@import 'accordionTreeNode/*';
+@import 'main/*';
+@import 'loading/*';
+@import 'user-list/*';
+@import 'user/*';
+@import 'alarm-icons/*';
+@import 'messages-list/*';
+@import 'message/*';
+@import 'env-form/*';
+@import 'tree-node/*';
+@import 'dashboard/*';
+@import 'messages-modal/*';
+@import 'messages-info-box/*';
+@import 'general-folder-node-dashboard/*';
+@import 'general-node-info-box/*';
+@import 'general-node-dashboard/*';
+@import 'detailed-node-info-box/*';
+@import 'landing/*';
+@import 'pager/*';
+@import 'd3graph/*';
+@import 'inventory-properties-display/*';
+@import 'scheduled-scan/*';
+@import 'mt-select/*';
+@import 'scheduled-scans-list/*';
+@import 'new-scanning/*';
+@import 'selectable-ordered-input/*';
+@import 'network-graph-manager/*';
+@import 'network-graph/*';
diff --git a/ui/imports/ui/components/input-model/input-model.html b/ui/imports/ui/components/input-model/input-model.html
new file mode 100644
index 0000000..30a7ad2
--- /dev/null
+++ b/ui/imports/ui/components/input-model/input-model.html
@@ -0,0 +1,21 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="InputModel">
+<input
+ type="{{ calcType }}"
+ id="{{ calcId }}"
+ name="{{ calcName }}"
+ class="inputField {{ calcClass }}"
+ placeholder="{{ calcPlaceholder }}"
+ {{ calcAttrs }}
+ {{ markIfDisabled }}
+ >
+</template>
diff --git a/ui/imports/ui/components/input-model/input-model.js b/ui/imports/ui/components/input-model/input-model.js
new file mode 100644
index 0000000..9c515c5
--- /dev/null
+++ b/ui/imports/ui/components/input-model/input-model.js
@@ -0,0 +1,116 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: InputModel
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+import * as R from 'ramda';
+
+import './input-model.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.InputModel.onCreated(function() {
+});
+
+/*
+Template.InputModel.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.InputModel.events({
+ 'input .inputField': function (event, instance) {
+ if (instance.data.type === 'checkbox') { return; }
+
+ let value;
+ switch (event.target.type) {
+ case 'number':
+ value = event.target.valueAsNumber;
+ break;
+
+ default:
+ value = event.target.value;
+ }
+
+ instance.data.setModel(value);
+ },
+ 'click .inputField': function (event, instance) {
+ if (instance.data.type !== 'checkbox') { return; }
+
+ let element = instance.$('.inputField')[0];
+ instance.data.setModel(element.checked);
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.InputModel.helpers({
+ calcAttrs: function () {
+ let instance = Template.instance();
+ let attrs = {};
+
+ if (instance.data.type === 'checkbox') {
+ if (instance.data.value) {
+ attrs.checked = true;
+ }
+ } else {
+ attrs.value = instance.data.value;
+ }
+
+ return attrs;
+ },
+
+ calcType: function () {
+ let instance = Template.instance();
+ return instance.data.type;
+ },
+
+ calcId: function () {
+ },
+
+ calcName: function () {
+ },
+
+ calcClass: function () {
+ let instance = Template.instance();
+ if (R.isNil(instance.data.classes)) {
+ return 'form-control';
+ } else {
+ return instance.data.classes;
+ }
+ },
+
+ calcPlaceholder: function () {
+ let instance = Template.instance();
+ if (R.isNil(instance.data.placeholder)) { return ''; }
+
+ return instance.data.placeholder;
+ },
+
+ markIfDisabled: function () {
+ let instance = Template.instance();
+ let attrs = {};
+ if (instance.data.disabled) {
+ attrs = R.assoc('disabled', true, attrs);
+ }
+
+ return attrs;
+ }
+});
diff --git a/ui/imports/ui/components/inventory-properties-display/inventory-properties-display.html b/ui/imports/ui/components/inventory-properties-display/inventory-properties-display.html
new file mode 100644
index 0000000..b4c5267
--- /dev/null
+++ b/ui/imports/ui/components/inventory-properties-display/inventory-properties-display.html
@@ -0,0 +1,13 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="InventoryPropertiesDisplay">
+ <div class="os-inv-prop-display">{{ displayFn getNode }}</div>
+</template>
diff --git a/ui/imports/ui/components/inventory-properties-display/inventory-properties-display.js b/ui/imports/ui/components/inventory-properties-display/inventory-properties-display.js
new file mode 100644
index 0000000..34eb4c5
--- /dev/null
+++ b/ui/imports/ui/components/inventory-properties-display/inventory-properties-display.js
@@ -0,0 +1,90 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: InventoryPropertiesDisplay
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import * as R from 'ramda';
+import { Inventory } from '/imports/api/inventories/inventories';
+
+import './inventory-properties-display.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.InventoryPropertiesDisplay.onCreated(function() {
+ let instance = this;
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ data: null,
+ env: null,
+ nodeId: null,
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+
+ try {
+ new SimpleSchema({
+ env: { type: String },
+ nodeId: { type: String },
+ displayFn: { type: Function },
+ }).validate(data);
+ } catch (e) {
+ // meteor sometimes does not show the validation error and throws unclear view error.
+ console.error(`error in validate ${e}`);
+ throw e;
+ }
+
+ instance.state.set('env', data.env);
+ instance.state.set('nodeId', data.nodeId);
+ });
+
+ instance.autorun(function () {
+ let env = instance.state.get('env');
+ let nodeId = instance.state.get('nodeId');
+ if (R.any(R.isNil)([env, nodeId])) { return; }
+
+ instance.subscribe('inventory?env&id', env, nodeId);
+
+ Inventory.find({ environment: env, id: nodeId }).forEach((node) => {
+ instance.state.set('node', node);
+ });
+ });
+});
+
+/*
+Template.InventoryPropertiesDisplay.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.InventoryPropertiesDisplay.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.InventoryPropertiesDisplay.helpers({
+ getNode: function () {
+ let instance = Template.instance();
+ return instance.state.get('node');
+ }
+});
+
+
diff --git a/ui/imports/ui/components/inventory-properties-display/inventory-properties-display.styl b/ui/imports/ui/components/inventory-properties-display/inventory-properties-display.styl
new file mode 100644
index 0000000..734c337
--- /dev/null
+++ b/ui/imports/ui/components/inventory-properties-display/inventory-properties-display.styl
@@ -0,0 +1,2 @@
+/* Set the component style here */
+// "InentoryPropertiesDisplay"
diff --git a/ui/imports/ui/components/landing/landing.html b/ui/imports/ui/components/landing/landing.html
new file mode 100644
index 0000000..2f0d4df
--- /dev/null
+++ b/ui/imports/ui/components/landing/landing.html
@@ -0,0 +1,201 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="landing" class="landing">
+<div class="os-landing">
+
+ <!-- hero section -->
+ <section id="landing-hero-section"
+ class="sm-landing-hero-section animated slideInUp">
+
+ <div class="sm-section-content">
+
+ <!-- Logo and sign in -->
+ <div class="sm-login-subsection">
+ <img style="margin: 20px 0px" src="/logo-cisco.svg" alt="" height="30">
+ <h4><a class="sign-in" href="#">{{> loginButtons}}</a></h4>
+ </div>
+
+ <!-- Main content -->
+ <div class="sm-main-content">
+ <div class="sm-main-header">
+ <img class="sm-logo" src="/landing/calipso-logo.png">
+ <h1 class="text-align-center" style="margin: 50px 0px; font-size: 46px;">Calipso Networking <br> Discovery and Assurance</h1>
+ </div>
+
+ <div class="sm-description">
+ <p>
+ Calipso is enhancing the way Cloud Networking Administrators (CNAs) and Tenant Networking Administrators (TNAs) understands, maintains, monitors and troubleshoots highly distributed Cloud environments based on OpenStack and others.
+ </p>
+ <p>
+ It provides real-time virtual networking assurance.
+ </p>
+ </div>
+
+ <div class="sm-learn-more">
+ <a class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--accent" data-scroll href="#landing-section-2">Learn more
+ </a>
+ </div>
+
+ <div class="sm-main-showcase">
+ <img src="/landing/first.png" alt="" class=" animated slideInUp" width="940" height="470">
+ </div>
+ </div>
+
+ </div>
+
+ </section>
+
+ <!-- section 2 -->
+ <section id="landing-section-2"
+ class="sm-landing-section-2">
+
+ <div class="sm-section-content">
+
+ <!-- first point -->
+ <div class="sm-desc-point">
+ <div class="sm-desc-point-content">
+ <h3 class="title-border-bottom">Application Intent</h3>
+ <p class="font20">
+ Provide CNAs and TNAs with operation tools for :
+ </p>
+ <ol class="font20">
+ <li>
+ Building detailed virtual networking inventory and inter-connections.
+ </li>
+ <li>
+ Visualizing low level dependencies and creating topologies in real time.
+ </li>
+ <li>
+ Monitoring virtual networking components for health and status.
+ </li>
+ <li>
+ Troubleshooting failures, analyzing faults and assess impact for virtual networking issues.
+ </li>
+ </ol>
+ <a href="#landing-section3" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored" data-scroll>Show more</a>
+ </div>
+
+ <div class="sm-desc-point-icon">
+ <img src="/logo_microscope_3.png" alt="" class="img_responsive wow slideInRight " data-wow-delay="0.3s" width="350">
+ </div>
+ </div>
+
+ <!-- second point -->
+ <div class="sm-desc-point">
+ <div class="sm-desc-point-icon">
+ <img src="/landing/openstack.png" alt="" class="img_responsive wow slideInLeft"
+ data-wow-delay="0.3s" >
+ <img src="/landing/docker.png" alt="" class="img_responsive wow slideInLeft"
+ data-wow-delay="0.3s" >
+ </div>
+ <div class="sm-desc-point-content">
+ <h3 class="title-border-bottom">Vision</h3>
+ <p class="font20">
+ substantially simplifying OpenStack and/or Docker networking operations and troubleshooting with detailed visibility and availability
+ </p>
+ </div>
+ </div>
+
+ <!-- third point -->
+ <div class="sm-desc-point">
+ <div class="sm-desc-point-content" >
+ <h3 class="title-border-bottom">Strategy</h3>
+ <ul class="font20">
+ <li>
+ Building an operations application that dynamically discover, display, monitor and analyze virtual networking in common cloud infrastructure environments like OpenStack, Docker, VMware and others.
+ </li>
+ <li>
+ Open community for contributors building a highly customized and open <u>operations API for OpenStack</u> as a baseline for virtual networking assurance and analytics.
+ </li>
+ </ul>
+ </div>
+ <div class="sm-desc-point-icon">
+ <img src="/landing/eye.png" alt=""
+ class="img_responsive wow slideInRight "
+ data-wow-delay="0.3">
+ </div>
+ </div>
+
+ </div>
+ </section>
+
+ <!-- section Cisco partners -->
+ <section id="landing-section3" class="landing background-blue">
+ <div class="mdl-grid">
+
+ <div class="mdl-cell mdl-cell--4-col">
+ <div class="box text-align-center wow slideInUp">
+ <div>
+ <i class="material-icons">view_carousel</i>
+ </div>
+ <div class="iconbox_content">
+ <h3>Mirantis</h3>
+ </div>
+ </div>
+ </div>
+
+ <div class="mdl-cell mdl-cell--4-col">
+ <div class="box text-align-center wow slideInUp" data-wow-delay="0.5s">
+ <div>
+ <i class="material-icons">view_carousel</i>
+ </div>
+ <div class="iconbox_content">
+ <h3>Metapod</h3>
+ </div>
+ </div>
+ </div>
+
+ <div class="mdl-cell mdl-cell--4-col">
+ <div class="box text-align-center wow slideInUp" data-wow-delay="0.8s">
+ <div>
+ <i class="material-icons">view_carousel</i>
+ </div>
+ <div class="iconbox_content">
+ <h3>Red Hat</h3>
+ </div>
+ </div>
+ </div>
+
+ </div>
+ </section>
+
+ <!-- section number 3 -->
+ <section class="landing background-white section-margin-50">
+ <div class="sm-section-content">
+ <div class="sm-desc-point">
+ <!-- third point -->
+ <div class="sm-desc-point-content" >
+ <h3 class="title-border-bottom">
+ Visibility provides Predictability that leads to Stability
+ </h3>
+ <!--Button-->
+ <a href="#landing-hero-section" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored" data-scroll>Sign in</a>
+ <!-- //Button-->
+ </div>
+ <div class="sm-desc-point-icon">
+ <img src="/landing/main-screen.png" alt=""
+ class="sm-big-image-icon wow slideInRight "
+ data-wow-delay="0.3">
+ </div>
+ </div>
+ </div>
+ </section>
+
+ <section class="background-blue">
+ <div class="mdl-grid">
+ <div class="mdl-cell mdl-cell--12-col text-align-center">
+ <p>Cisco Systems, Inc © Copyright 2017</p>
+ </div>
+ </div>
+ </section>
+
+</div>
+</template>
diff --git a/ui/imports/ui/components/landing/landing.js b/ui/imports/ui/components/landing/landing.js
new file mode 100644
index 0000000..8a42e12
--- /dev/null
+++ b/ui/imports/ui/components/landing/landing.js
@@ -0,0 +1,35 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import './landing.html';
+
+Template.landing.onCreated(function() {
+});
+
+Template.landing.rendered = function(){
+
+ // init wow lib
+ new WOW().init();
+
+ // smooth scrolling function
+ $(function() {
+ $('a[href*="#"]:not([href="#"])').click(function() {
+ if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') && location.hostname == this.hostname) {
+ var target = $(this.hash);
+ target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
+ if (target.length) {
+ $('html, body').animate({
+ scrollTop: target.offset().top
+ }, 1000);
+ return false;
+ }
+ }
+ });
+ });
+
+};
diff --git a/ui/imports/ui/components/landing/landing.styl b/ui/imports/ui/components/landing/landing.styl
new file mode 100644
index 0000000..eb8e254
--- /dev/null
+++ b/ui/imports/ui/components/landing/landing.styl
@@ -0,0 +1,80 @@
+.os-landing
+
+ section
+ display: flex;
+ flex-flow: column;
+ align-items: center;
+
+ .sm-section-content
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+ max-width: 1000px;
+
+ .sm-landing-hero-section
+ background-color: #2196F3;
+ color: #f9f9f9;
+
+ .sm-section-content
+
+ .sm-login-subsection
+ display: flex;
+ justify-content: space-between;
+ align-self: stretch;
+
+ .sm-main-content
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+
+ .sm-main-header
+ display: flex;
+ flex-flow: row;
+ justify-content: space-around;
+
+ .sm-logo
+ padding: 30px;
+ width: 260px;
+
+ .sm-description
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+
+ p
+ text-align: center;
+ font-size: 20px;
+
+ .sm-landing-section-2
+ border-color: #fff;
+ padding: 40px 0;
+
+ .sm-section-content
+ font-size: blue;
+
+ .sm-desc-point
+ display: flex;
+ flex-flow: row nowrap;
+ justify-content: center;
+
+ margin: 40px 0;
+
+ .sm-desc-point-content
+ flex: 0 0 400px;
+
+ li
+ margin: 10px 0;
+
+ .sm-desc-point-icon
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ padding: 0 60px;
+
+ img
+ width: 300px;
+ padding: 0 10px;
+
+ img.sm-big-image-icon
+ width: 600px;
diff --git a/ui/imports/ui/components/link-type/link-type.html b/ui/imports/ui/components/link-type/link-type.html
new file mode 100644
index 0000000..b2a81dd
--- /dev/null
+++ b/ui/imports/ui/components/link-type/link-type.html
@@ -0,0 +1,88 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="LinkType">
+ <div class="os-link-type cards white">
+ {{#if notificationsExists}}
+ <div class="sm-notification-panel alert alert-danger">
+ {{#each note in notifications }}
+ <div>{{ note }}</div>
+ {{/each }}
+ </div>
+ {{/if}}
+
+ <h3>{{ getState 'pageHeader' }}</h3>
+ <div class="sm-form-container">
+ <form role="form" class="sm-item-form form-horizontal">
+
+ <div class="sm-field-group-id cl-field-group">
+ <label class="cl-field-label">Id</label>
+ <input name="id"
+ disabled="disabled"
+ value="{{ getModelField '_id' }}"
+ class="sm-input-id cl-input" type="text" placeholder="Id" />
+ <div class="cl-field-id">Id</div>
+ </div>
+
+ <div class="sm-field-group-desc cl-field-group">
+ <label class="cl-field-label">Description</label>
+ <input name="desc"
+ {{ getAttrDisabled }}
+ value="{{ getModelField 'description' }}"
+ class="sm-input-desc cl-input" type="text" placeholder="Description" />
+ <div class="cl-field-desc">Description</div>
+ </div>
+
+ <div class="sm-field-group-endpoint-a cl-field-group">
+ <label class="cl-field-label">Endpoint A - Object Type</label>
+ <select name="endPointA" class="sm-input-endpoint-a cl-input"
+ {{ getAttrDisabled }} >
+ {{#each objectType in objectTypesList }}
+ <option value="{{ objectType.value }}"
+ {{ getAttrSelected objectType.value (getModelField 'endPointA') }}
+ >{{ objectType.label }}</option>
+ {{/each }}
+ </select>
+ <div class="cl-field-desc">Endpoint A</div>
+ </div>
+
+ <div class="sm-field-group-endpoint-b cl-field-group">
+ <label class="cl-field-label">Endpoint B - Object Type</label>
+ <select name="endPointB" class="sm-input-endpoint-b cl-input"
+ {{ getAttrDisabled }} >
+ {{#each objectType in objectTypesList }}
+ <option value="{{ objectType.value }}"
+ {{ getAttrSelected objectType.value (getModelField 'endPointB') }}
+ >{{ objectType.label }}</option>
+ {{/each }}
+ </select>
+ <div class="cl-field-desc">Endpoint B</div>
+ </div>
+
+ {{#if isUpdateableAction }}
+ <button type="submit"
+ class="js-submit-button mdl-button mdl-js-button mdl-button--raised
+ mdl-js-ripple-effect mdl-button--colored"
+ >{{ actionLabel }}</button>
+ {{/if }}
+
+ </form>
+
+ {{#if (getState 'isMessage') }}
+ <div class="js-message-panel alert {{#if (getState 'isError')}}alert-danger{{/if}}
+ {{#if (getState 'isSuccess')}}alert-success{{/if}}"
+ role="alert">
+ {{ getState 'message' }}
+ </div>
+ {{/if }}
+
+ </div>
+ </div>
+</template>
diff --git a/ui/imports/ui/components/link-type/link-type.js b/ui/imports/ui/components/link-type/link-type.js
new file mode 100644
index 0000000..c14209a
--- /dev/null
+++ b/ui/imports/ui/components/link-type/link-type.js
@@ -0,0 +1,328 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: LinkType
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import * as R from 'ramda';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { LinkTypes } from '/imports/api/link-types/link-types';
+//import { Environments } from '/imports/api/environments/environments';
+import { Constants } from '/imports/api/constants/constants';
+import { insert, remove, update } from '/imports/api/link-types/methods';
+import { parseReqId } from '/imports/lib/utilities';
+
+
+import './link-type.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.LinkType.onCreated(function() {
+ let instance = this;
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ id: null,
+ //env: null,
+ action: 'insert',
+ isError: false,
+ isSuccess: false,
+ isMessage: false,
+ message: null,
+ disabled: false,
+ notifications: {},
+ model: {},
+ pageHeader: 'Link Type'
+ });
+
+ instance.autorun(function () {
+ let controller = Iron.controller();
+ let params = controller.getParams();
+ let query = params.query;
+
+ new SimpleSchema({
+ action: { type: String, allowedValues: ['insert', 'view', 'remove', 'update'] },
+ //env: { type: String, optional: true },
+ id: { type: String, optional: true }
+ }).validate(query);
+
+ switch (query.action) {
+ case 'insert':
+ initInsertView(instance, query);
+ break;
+
+ case 'view':
+ initViewView(instance, query);
+ break;
+
+ case 'update':
+ initUpdateView(instance, query);
+ break;
+
+ case 'remove':
+ initRemoveView(instance, query);
+ break;
+
+ default:
+ throw 'unimplemented action';
+ }
+ });
+});
+
+/*
+Template.LinkType.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.LinkType.events({
+ 'submit .sm-item-form': function(event, instance) {
+ event.preventDefault();
+
+ let _id = instance.state.get('id');
+ //let env = instance.$('.sm-input-env')[0].value;
+ let desc = instance.$('.sm-input-desc')[0].value;
+ let endPointA = instance.$('.sm-input-endpoint-a')[0].value;
+ let endPointB = instance.$('.sm-input-endpoint-b')[0].value;
+
+ submitItem(instance,
+ _id,
+ //env,
+ desc,
+ endPointA,
+ endPointB);
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.LinkType.helpers({
+ isUpdateableAction() {
+ let instance = Template.instance();
+ let action = instance.state.get('action');
+
+ return R.contains(action, ['insert', 'update', 'remove']);
+ },
+
+ getState: function (key) {
+ let instance = Template.instance();
+ return instance.state.get(key);
+ },
+
+ /*
+ envsList: function () {
+ return Environments.find({});
+ },
+ */
+
+ objectTypesList: function () {
+ return R.ifElse(R.isNil, R.always([]), R.prop('data')
+ )(Constants.findOne({ name: 'object_types_for_links' }));
+ },
+
+ getAttrDisabled: function () {
+ let instance = Template.instance();
+ let result = {};
+ let action = instance.state.get('action');
+
+ if (R.contains(action, ['view', 'remove'])) {
+ result = R.assoc('disabled', true, result);
+ }
+
+ return result;
+ },
+
+ getModel: function () {
+ let instance = Template.instance();
+ return instance.state.get('model');
+ },
+
+ getModelField: function (fieldName) {
+ let instance = Template.instance();
+ return R.path([fieldName], instance.state.get('model'));
+ },
+
+ getAttrSelected: function (optionValue, modelValue) {
+ let result = {};
+
+ if (optionValue === modelValue) {
+ result = R.assoc('selected', 'selected', result);
+ }
+
+ return result;
+ },
+
+ actionLabel: function () {
+ let instance = Template.instance();
+ let action = instance.state.get('action');
+ return calcActionLabel(action);
+ }
+});
+
+function initInsertView(instance, query) {
+ instance.state.set('action', query.action);
+ //instance.state.set('env', query.env);
+ instance.state.set('model', LinkTypes.schema.clean({
+ //environment: instance.state.get('env')
+ }));
+
+ subscribeToOptionsData(instance);
+ instance.subscribe('constants');
+ //instance.subscribe('link_types?env', query.env);
+}
+
+function initViewView(instance, query) {
+ let reqId = parseReqId(query.id);
+
+ instance.state.set('action', query.action);
+ //instance.state.set('env', query.env);
+ instance.state.set('id', reqId);
+
+ subscribeToOptionsData(instance);
+ instance.subscribe('constants');
+ instance.subscribe('link_types?_id', reqId.id);
+
+ LinkTypes.find({ _id: reqId.id }).forEach((model) => {
+ instance.state.set('model', model);
+ });
+
+}
+
+function initUpdateView(instance, query) {
+ let reqId = parseReqId(query.id);
+
+ instance.state.set('action', query.action);
+ //instance.state.set('env', query.env);
+ instance.state.set('id', reqId);
+
+ subscribeToOptionsData(instance);
+ instance.subscribe('constants');
+ instance.subscribe('link_types?_id', reqId.id);
+
+ LinkTypes.find({ _id: reqId.id }).forEach((model) => {
+ instance.state.set('model', model);
+ });
+
+}
+
+function initRemoveView(instance, query) {
+ initViewView(instance, query);
+}
+
+function subscribeToOptionsData(_instance) {
+ //instance.subscribe('environments_config');
+}
+
+function submitItem(
+ instance,
+ id,
+ //env,
+ desc,
+ endPointA,
+ endPointB) {
+
+ let action = instance.state.get('action');
+
+ instance.state.set('isError', false);
+ instance.state.set('isSuccess', false);
+ instance.state.set('isMessage', false);
+ instance.state.set('message', null);
+
+ switch (action) {
+ case 'insert':
+ insert.call({
+ //environment: env,
+ description: desc,
+ endPointA: endPointA,
+ endPointB: endPointB,
+ }, processActionResult.bind(null, instance));
+ break;
+
+ case 'update':
+ update.call({
+ _id: id.id,
+ description: desc,
+ endPointA: endPointA,
+ endPointB: endPointB,
+ }, processActionResult.bind(null, instance));
+ break;
+
+ case 'remove':
+ remove.call({
+ _id: id.id
+ }, processActionResult.bind(null, instance));
+ break;
+
+ default:
+ // todo
+ break;
+ }
+}
+
+function processActionResult(instance, error) {
+ let action = instance.state.get('action');
+
+ if (error) {
+ instance.state.set('isError', true);
+ instance.state.set('isSuccess', false);
+ instance.state.set('isMessage', true);
+
+ if (typeof error === 'string') {
+ instance.state.set('message', error);
+ } else {
+ instance.state.set('message', error.message);
+ }
+
+ return;
+ }
+
+ instance.state.set('isError', false);
+ instance.state.set('isSuccess', true);
+ instance.state.set('isMessage', true);
+
+ switch (action) {
+ case 'insert':
+ instance.state.set('message', 'Record had been added successfully');
+ instance.state.set('disabled', true);
+ break;
+
+ case 'remove':
+ instance.state.set('message', 'Record had been removed successfully');
+ instance.state.set('disabled', true);
+ break;
+
+ case 'update':
+ instance.state.set('message', 'Record had been updated successfully');
+ break;
+ }
+
+ Router.go('/link-types-list');
+}
+
+function calcActionLabel(action) {
+ switch (action) {
+ case 'insert':
+ return 'Add';
+ case 'remove':
+ return 'Remove';
+ case 'update':
+ return 'Update';
+ default:
+ return 'Submit';
+ }
+}
diff --git a/ui/imports/ui/components/link-type/link-type.styl b/ui/imports/ui/components/link-type/link-type.styl
new file mode 100644
index 0000000..e9178e0
--- /dev/null
+++ b/ui/imports/ui/components/link-type/link-type.styl
@@ -0,0 +1,34 @@
+.os-link-type
+ margin: 20px;
+
+ .cl-field-group
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ padding: 5px 0;
+
+ .cl-field-label
+ width: 170px;
+ margin: 0 5px;
+
+ .cl-input
+ display: block;
+ width: 100%;
+ height: 34px;
+ padding: 6px 12px;
+ font-size: 14px;
+ line-height: 1.42857143;
+ color: #555;
+ background-color: #fff;
+ background-image: none;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ width: 400px;
+ margin: 0 5px;
+
+ .cl-field-desc
+ margin: 0 5px;
+
+ .js-message-panel
+ margin-top: 20px;
diff --git a/ui/imports/ui/components/link-types-list/link-types-list.html b/ui/imports/ui/components/link-types-list/link-types-list.html
new file mode 100644
index 0000000..575557d
--- /dev/null
+++ b/ui/imports/ui/components/link-types-list/link-types-list.html
@@ -0,0 +1,56 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="LinkTypesList">
+<div class="os-link-types-list cards white">
+ <h3>Link Types</h3>
+ <a class="sm-add-new-link"
+ href="{{pathFor route='link-type' query=(asHash action='insert') }}">
+ <i class="cl-action-icon fa fa-plus" area-hidden="true"></i> Create new link type
+ </a>
+ <table class="sm-link-types-table table">
+ <thead>
+ <tr>
+ <th>Description</th>
+ <th>Type</th>
+ <th>Endpoint A</th>
+ <th>Endpoint B</th>
+ <th>Actions</th>
+ </tr> </thead>
+ <tbody>
+ {{#each linkType in linkTypes }}
+ <tr>
+ <td>{{ linkType.description }}</td>
+ <td>{{ linkType.type }}</td>
+ <td>{{ linkType.endPointA }}</td>
+ <td>{{ linkType.endPointB }}</td>
+ <td>
+ <div class="sm-action-bar">
+ <a href="{{pathFor route='link-type'
+ query=(asHash id=(idToStr linkType._id) action='view') }}"
+ ><i class="cl-action-icon fa fa-eye" area-hidden="true"></i></a>
+
+ {{#if isAuthManageLinkTypes }}
+ <a href="{{pathFor route='link-type'
+ query=(asHash id=(idToStr linkType._id) action='update') }}"
+ ><i class="cl-action-icon fa fa-pencil" area-hidden="true"></i></a>
+
+ <a href="{{pathFor route='link-type'
+ query=(asHash id=(idToStr linkType._id) action='remove') }}"
+ ><i class="cl-action-icon fa fa-trash-o" area-hidden="true"></i></a>
+ {{/if }}
+ </div>
+ </td>
+ </tr>
+ {{/each }}
+ </tbody>
+ </table>
+</div>
+</template>
diff --git a/ui/imports/ui/components/link-types-list/link-types-list.js b/ui/imports/ui/components/link-types-list/link-types-list.js
new file mode 100644
index 0000000..5eab355
--- /dev/null
+++ b/ui/imports/ui/components/link-types-list/link-types-list.js
@@ -0,0 +1,87 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: LinkTypesList
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import * as R from 'ramda';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { LinkTypes } from '/imports/api/link-types/link-types';
+import { Roles } from 'meteor/alanning:roles';
+
+import './link-types-list.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.LinkTypesList.onCreated(function() {
+ var instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ env: null
+ });
+
+ instance.autorun(function () {
+
+
+ //let data = Template.currentData();
+
+ var controller = Iron.controller();
+ var params = controller.getParams();
+ var query = params.query;
+
+ new SimpleSchema({
+ env: { type: String, optional: true },
+ }).validate(query);
+
+ let env = query.env;
+ if (R.isNil(env)) {
+ instance.state.set('env', null);
+ } else {
+ instance.state.set('env', env);
+ }
+
+ instance.subscribe('link_types?env*', env);
+ });
+});
+
+/*
+Template.LinkTypesList.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.LinkTypesList.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.LinkTypesList.helpers({
+ linkTypes: function () {
+ //let instance = Template.instance();
+
+ //var env = instance.state.get('env');
+ //return Scans.find({ environment: env });
+ return LinkTypes.find({});
+ },
+
+ isAuthManageLinkTypes: function () {
+ return Roles.userIsInRole(Meteor.userId(), 'manage-link-types', Roles.GLOBAL_GROUP);
+ },
+}); // end - helpers
diff --git a/ui/imports/ui/components/link-types-list/link-types-list.styl b/ui/imports/ui/components/link-types-list/link-types-list.styl
new file mode 100644
index 0000000..acb0a81
--- /dev/null
+++ b/ui/imports/ui/components/link-types-list/link-types-list.styl
@@ -0,0 +1,23 @@
+.os-link-types-list
+ margin: 20px;
+
+ .cl-action-icon,
+ .card.fa.cl-action-icon
+ font-size: 16px !important;
+
+ .sm-link-types-table
+ th
+ color: spark-blue
+
+ .sm-action-bar
+ display: flex;
+
+ a
+ margin: 0px 5px;
+
+ .cl-action-icon
+ color: gray
+
+ .sm-add-new-link
+ color: spark-blue
+
diff --git a/ui/imports/ui/components/list-info-box/list-info-box.html b/ui/imports/ui/components/list-info-box/list-info-box.html
new file mode 100644
index 0000000..8fa552f
--- /dev/null
+++ b/ui/imports/ui/components/list-info-box/list-info-box.html
@@ -0,0 +1,60 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="ListInfoBox">
+<div class="os-list-info-box cards-450 white flex-box-1">
+ <div class="flex-box ">
+ <div class="flex-box-1">
+ {{> Icon type=icon.type name=icon.name }}
+ </div>
+ <div class="flex-box-2">
+ <h3>{{ header }}</h3>
+ <table class="table table-striped">
+ <tbody>
+ <tr>
+ <th>Last Scanning</th>
+ <td>{{ lastScanning }}</td>
+ </tr>
+ <tr>
+ <th>Number of {{ header }}:</th>
+ <td>{{ itemsCount }}</td>
+ </tr>
+ <tr>
+ <th>{{ header }}:</th>
+ <td>
+ <div class="dropdown">
+ <button class="btn btn-default dropdown-toggle"
+ type="button"
+ data-toggle="dropdown"
+ aria-haspopup="true"
+ aria-expanded="true"
+ >
+ Select from dropdown
+ <span class="caret"></span>
+ </button>
+
+ <ul class="sm-items-dropdown-menu dropdown-menu"
+ aria-labelledby="dropdownMenu1">
+ {{#each option in (options list listItemFormat) }}
+ <li>
+ <a data-value="{{ option.value }}"
+ class="os-list-item">{{option.label}}</a>
+ </li>
+ {{/each}}
+ </ul>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/list-info-box/list-info-box.js b/ui/imports/ui/components/list-info-box/list-info-box.js
new file mode 100644
index 0000000..3fe4542
--- /dev/null
+++ b/ui/imports/ui/components/list-info-box/list-info-box.js
@@ -0,0 +1,111 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: ListInfoBox
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import * as R from 'ramda';
+import { LocalCollection } from 'meteor/minimongo';
+import { Icon } from '/imports/lib/icon';
+
+import './list-info-box.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.ListInfoBox.onCreated(function() {
+ let instance = this;
+ instance.autorun(function () {
+ let data = Template.currentData();
+ new SimpleSchema({
+ header: { type: String },
+ list: { type: LocalCollection.Cursor, blackbox: true },
+ icon: { type: Icon, blackbox: true },
+ listItemFormat: {
+ type: {
+ getLabelFn: { type: Function },
+ getValueFn: { type: Function },
+ },
+ blackbox: true
+ },
+ onItemSelected: { type: Function },
+
+ }).validate(data);
+
+ });
+});
+
+/*
+Template.ListInfoBox.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.ListInfoBox.events({
+ 'click .os-list-item'(event) {
+ let instance = Template.instance();
+ let val = event.target.attributes['data-value'].value;
+ instance.data.onItemSelected(val);
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.ListInfoBox.helpers({
+ options: function (list, listItemFormat) {
+ //let instance = Template.instance();
+
+ let options = R.map((listItem) => {
+ return {
+ label: listItemFormat.getLabelFn(listItem),
+ value: listItemFormat.getValueFn(listItem)
+ };
+ }, list.fetch());
+
+ return options;
+ },
+
+ itemsCount: function () {
+ let instance = Template.instance();
+ return instance.data.list.count();
+ },
+
+ argsSelect: function (list, listItemFormat) {
+ let instance = Template.instance();
+
+ let options = R.map((listItem) => {
+ return {
+ label: listItemFormat.getLabelFn(listItem),
+ value: listItemFormat.getValueFn(listItem)
+ };
+ }, list.fetch());
+
+ return {
+ values: [],
+ options: options,
+ showNullOption: true,
+ nullOptionLabel: 'Select from dropdown',
+ setModel: function (val) {
+ instance.data.onItemSelected(val);
+ },
+ };
+ }
+});
+
+
diff --git a/ui/imports/ui/components/list-info-box/list-info-box.styl b/ui/imports/ui/components/list-info-box/list-info-box.styl
new file mode 100644
index 0000000..43c8d0a
--- /dev/null
+++ b/ui/imports/ui/components/list-info-box/list-info-box.styl
@@ -0,0 +1,4 @@
+.os-list-info-box
+ .sm-items-dropdown-menu
+ li
+ cursor: pointer
diff --git a/ui/imports/ui/components/loading/loading.html b/ui/imports/ui/components/loading/loading.html
new file mode 100644
index 0000000..6a082c8
--- /dev/null
+++ b/ui/imports/ui/components/loading/loading.html
@@ -0,0 +1,12 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="loading">
+</template>
diff --git a/ui/imports/ui/components/loading/loading.js b/ui/imports/ui/components/loading/loading.js
new file mode 100644
index 0000000..a83f6d3
--- /dev/null
+++ b/ui/imports/ui/components/loading/loading.js
@@ -0,0 +1,30 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import './loading.html';
+
+Template.loading.rendered = function () {
+ if ( ! Session.get('loadingSplash') ) {
+ this.loading = window.pleaseWait({
+ logo: '/cisco-logo-load.png',
+ // logo: '',
+ backgroundColor: '#2196F3',
+ loadingHtml: message + spinner
+ });
+ Session.set('loadingSplash', true); // just show loading splash once
+ }
+};
+
+Template.loading.destroyed = function () {
+ if ( this.loading ) {
+ this.loading.finish();
+ }
+};
+
+var message = '<p class="loading-message">Loading Calipso</p>';
+var spinner = '<div class="sk-spinner sk-spinner-rotating-plane"></div>';
diff --git a/ui/imports/ui/components/loading/loading.styl b/ui/imports/ui/components/loading/loading.styl
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ui/imports/ui/components/loading/loading.styl
diff --git a/ui/imports/ui/components/main/main.html b/ui/imports/ui/components/main/main.html
new file mode 100644
index 0000000..96f6875
--- /dev/null
+++ b/ui/imports/ui/components/main/main.html
@@ -0,0 +1,15 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="main">
+ {{> TopNavbarMenu }}
+ {{> yield}}
+ {{> MessagesModal }}
+</template>
diff --git a/ui/imports/ui/components/main/main.js b/ui/imports/ui/components/main/main.js
new file mode 100644
index 0000000..525c53e
--- /dev/null
+++ b/ui/imports/ui/components/main/main.js
@@ -0,0 +1,98 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import '/imports/ui/components/messages-modal/messages-modal';
+import './main.html';
+
+Template.mainPage.rendered = function(){
+ $(window).scroll(function(){
+ var windowWidth = $(this).width();
+ //var windowHeight = $(this).height();
+ var windowScrollTop = $(this).scrollTop();
+
+ // effect - No1
+ if(windowScrollTop>60){
+ $('.banner h2').css('display','none');
+ $('.banner .info').css('display','block');
+ }else{
+ $('.banner h2').css('display','block');
+ $('.banner .info').css('display','none');
+ }
+
+ // effect - No2
+ var firstAnimation = function(){
+ $('.clients .clients-info').each(
+ function(){
+ $(this).delay(500).animate(
+ {opacity:1,height:'180',width:'250'},2000);}
+ );
+ };
+
+ // effect - No3
+ var secondAnimation = function(){
+ $('.method:eq(0)').delay(1000).animate({opacity:1},'slow', function(){
+ $(this).find('h4').css('background-color','#B5C3D5');
+ });
+ $('.method:eq(1)').delay(2000).animate({opacity:1},'slow', function(){
+ $(this).find('h4').css('background-color','#B5C3D5');
+ });
+ $('.method:eq(2)').delay(3000).animate({opacity:1},'slow', function(){
+ $(this).find('h4').css('background-color','#B5C3D5');
+ });
+ $('.method:eq(3)').delay(4000).animate({opacity:1},'slow', function(){
+ $(this).find('h4').css('background-color','#B5C3D5');
+ });
+ };
+
+ // effect - No4
+ var thirdAnimation = function(){
+ $('.blogs').find('p').delay(1400).animate({opacity:1, left:0},'slow');
+ $('.blogs').find('img').delay(2000).animate({opacity:1, right:0},'slow');
+ $('.blogs').find('button').delay(2500).animate({opacity:1, bottom:0},'slow');
+ };
+
+
+ if(windowWidth<=549){
+ if(windowScrollTop>600){
+ $('.clients').css('background','tomato');
+ firstAnimation();
+ }
+ if(windowScrollTop>1750){
+ $('.process').css('background','tomato');
+ secondAnimation();
+ }
+ if(windowScrollTop>3500){
+ $('.blogs').css('background','tomato');
+ thirdAnimation();
+ }
+ }else if(windowWidth>549 && windowWidth<=991){
+ if(windowScrollTop>480){
+ $('.clients').css('background','tomato');
+ firstAnimation();
+ }if(windowScrollTop>1150){
+ $('.process').css('background','tomato');
+ secondAnimation();
+ }if(windowScrollTop>2200){
+ $('.blogs').css('background','tomato');
+ thirdAnimation();
+ }
+ }else{
+ if(windowScrollTop>450){
+ $('.clients').css('background','tomato');
+ firstAnimation();
+ }if(windowScrollTop>850){
+ $('.process').css('background','tomato');
+ secondAnimation();
+ }
+ if(windowScrollTop>1600){
+ $('.blogs').css('background','tomato');
+ thirdAnimation();
+ }
+ }
+ });
+};
diff --git a/ui/imports/ui/components/main/main.styl b/ui/imports/ui/components/main/main.styl
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ui/imports/ui/components/main/main.styl
diff --git a/ui/imports/ui/components/message/message.html b/ui/imports/ui/components/message/message.html
new file mode 100644
index 0000000..d720be1
--- /dev/null
+++ b/ui/imports/ui/components/message/message.html
@@ -0,0 +1,168 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="Message">
+<div class="os-message cards white">
+ {{#if notificationsExists}}
+ <div class="sm-notification-panel alert alert-danger">
+ {{#each note in notifications }}
+ <div>{{ note }}</div>
+ {{/each }}
+ </div>
+ {{/if}}
+
+ <h3>{{ getState 'pageHeader' }}</h3>
+
+ <div class="sm-form-container">
+ <form role="form" class="sm-item-form form-horizontal">
+
+ <div class="sm-field-group-id cl-field-group">
+ <label class="cl-field-label">Id</label>
+ <input name="id"
+ disabled="disabled"
+ value="{{ getModelField '_id' }}"
+ class="sm-input-id cl-input" type="text" placeholder="Id" />
+ <div class="cl-field-id">Id</div>
+ </div>
+
+ <div class="sm-field-group-env cl-field-group">
+ <label class="cl-field-label">Environment</label>
+ <select name="env" class="sm-input-env cl-input"
+ {{ getAttrDisabled }} >
+ <option value="" selected disabled hidden></option>
+ {{#each env in envsList }}
+ <option value="{{ env.name }}"
+ {{ getAttrSelected env.name (getModelField 'environment') }}
+ >{{ env.name }}</option>
+ {{/each }}
+ </select>
+ <div class="cl-field-desc">Environment</div>
+ </div>
+
+ <div class="sm-field-group-viewed cl-field-group">
+ <label class="cl-field-label">Viewed</label>
+ <input name="viewed"
+ {{ getAttrDisabled }}
+ value="{{ getModelField 'viewed' }}"
+ class="sm-input-viewed cl-input" type="text" placeholder="" />
+ <div class="cl-field-desc">Viewed</div>
+ </div>
+
+ <div class="sm-field-group-display-context cl-field-group">
+ <label class="cl-field-label">Display Context</label>
+ <input name="display_context"
+ {{ getAttrDisabled }}
+ value="{{ getModelField 'display_context' }}"
+ class="sm-input-viewed cl-input" type="text" placeholder="Display context" />
+ <div class="cl-field-desc">Display context</div>
+ </div>
+
+ <div class="sm-field-group-message cl-field-group">
+ <label class="cl-field-label">Message</label>
+ <textarea name="message"
+ {{ getAttrDisabled }}
+ class="sm-input-message cl-input"
+ rows="10"
+ >{{ asString (getModelField 'message') }}</textarea>
+ <div class="cl-field-desc">Message</div>
+ </div>
+
+ <div class="sm-field-group-source-system cl-field-group">
+ <label class="cl-field-label">Source System</label>
+ <select name="source-system" class="sm-input-source-system cl-input"
+ {{ getAttrDisabled }} >
+ <option value="" selected disabled hidden></option>
+ {{#each sourceSystem in sourceSystemsList }}
+ <option value="{{ sourceSystem.value }}"
+ {{ getAttrSelected sourceSystem.label (getModelField 'source_system') }}
+ >{{ sourceSystem.label }}</option>
+ {{/each }}
+ </select>
+ <div class="cl-field-desc">Source system</div>
+ </div>
+
+ <div class="sm-field-group-level cl-field-group">
+ <label class="cl-field-label">Level</label>
+ <input name="level"
+ {{ getAttrDisabled }}
+ value="{{ getModelField 'level' }}"
+ class="sm-input-level cl-input"
+ type="text"
+ placeholder="Level" />
+ <div class="cl-field-desc">Level</div>
+ </div>
+
+ <div class="sm-field-group-timestamp cl-field-group">
+ <label class="cl-field-label">Timestamp</label>
+ <input name="timestamp"
+ {{ getAttrDisabled }}
+ value="{{ getModelField 'timestamp' }}"
+ class="sm-input-level cl-input"
+ type="text"
+ placeholder="Timestamp" />
+ <div class="cl-field-desc">Timestamp</div>
+ </div>
+
+ <div class="sm-field-group-related-object-type cl-field-group">
+ <label class="cl-field-label">Related Object Type</label>
+ <input name="related_object_type"
+ {{ getAttrDisabled }}
+ value="{{ getModelField 'related_object_type' }}"
+ class="sm-input-related-object-type cl-input"
+ type="text"
+ placeholder="Related object type" />
+ <div class="cl-field-desc">Related object type</div>
+ </div>
+
+ <div class="sm-field-group-related-object cl-field-group">
+ <label class="cl-field-label">Related Object</label>
+ <input name="related_object"
+ {{ getAttrDisabled }}
+ value="{{ getModelField 'related_object' }}"
+ class="sm-input-related-object cl-input"
+ type="text"
+ placeholder="Related object" />
+ {{#if (getModelField 'related_object') }}
+ {{> InventoryPropertiesDisplay (argsInvPropDisplay (getModelField 'environment') (getModelField 'related_object')) }}.
+ {{/if }}
+ <div class="cl-field-desc">Related object</div>
+ </div>
+
+ <div class="sm-field-group-scanid cl-field-group">
+ <label class="cl-field-label">Scan ID</label>
+ <input name="scanid"
+ {{ getAttrDisabled }}
+ value="{{ getModelField 'scan_id' }}"
+ class="sm-input-level cl-input"
+ type="text"
+ placeholder="Scan ID" />
+ <div class="cl-field-desc">Scan ID</div>
+ </div>
+
+ {{#if isUpdateableAction }}
+ <button type="submit"
+ class="js-submit-button mdl-button mdl-js-button mdl-button--raised
+ mdl-js-ripple-effect mdl-button--colored"
+ >{{ actionLabel }}</button>
+ {{/if }}
+
+ </form>
+
+ {{#if (getState 'isMessage') }}
+ <div class="js-message-panel alert {{#if (getState 'isError')}}alert-danger{{/if}}
+ {{#if (getState 'isSuccess')}}alert-success{{/if}}"
+ role="alert">
+ {{ getState 'message' }}
+ </div>
+ {{/if }}
+
+ </div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/message/message.js b/ui/imports/ui/components/message/message.js
new file mode 100644
index 0000000..41ea53d
--- /dev/null
+++ b/ui/imports/ui/components/message/message.js
@@ -0,0 +1,257 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: Message
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import * as R from 'ramda';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { Messages } from '/imports/api/messages/messages';
+import { Constants } from '/imports/api/constants/constants';
+import { Environments } from '/imports/api/environments/environments';
+import { idToStr } from '/imports/lib/utilities';
+//import { insert, update, remove } from '/imports/api/clique-types/methods';
+import { parseReqId } from '/imports/lib/utilities';
+//import { store } from '/imports/ui/store/store';
+//import { setCurrentNode } from '/imports/ui/actions/navigation';
+
+import './message.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.Message.onCreated(function() {
+ let instance = this;
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ id: null,
+ action: 'insert',
+ isError: false,
+ isSuccess: false,
+ isMessage: false,
+ message: null,
+ disabled: false,
+ notifications: {},
+ model: {},
+ pageHeader: 'Message'
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+
+ new SimpleSchema({
+ action: { type: String, allowedValues: ['view'] },
+ id: { type: String, optional: true }
+ }).validate(data);
+
+ switch (data.action) {
+ /*
+ case 'insert':
+ initInsertView(instance, data);
+ break;
+ */
+
+ case 'view':
+ initViewView(instance, data);
+ break;
+
+ /*
+ case 'update':
+ initUpdateView(instance, data);
+ break;
+
+ case 'remove':
+ initRemoveView(instance, data);
+ break;
+ */
+
+ default:
+ throw 'unimplemented action';
+ }
+ });
+});
+
+/*
+Template.Message.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.Message.events({
+ 'click .sm-field-group-display-context': function (event, instance) {
+ event.preventDefault();
+
+ let model = instance.state.get('model');
+ let environment = Environments.findOne({ name: model.environment });
+ let nodeId = model.display_context;
+
+ Meteor.apply('inventoryFindNode?env&id', [
+ environment.name,
+ nodeId,
+ ], {
+ wait: false
+ }, function (err, resp) {
+ if (err) {
+ console.error(R.toString(err));
+ return;
+ }
+
+ if (R.isNil(resp.node)) {
+ console.error('error finding node related to message', R.toString(nodeId));
+ return;
+ }
+
+ Router.go('environment', {
+ _id: idToStr(environment._id)
+ }, {
+ query: {
+ selectedNodeId: idToStr(resp.node._id)
+ }
+ });
+
+ });
+
+ },
+
+});
+
+/*
+ * Helpers
+ */
+
+Template.Message.helpers({
+ isUpdateableAction() {
+ let instance = Template.instance();
+ let action = instance.state.get('action');
+
+ return R.contains(action, ['insert', 'update', 'remove']);
+ },
+
+ getState: function (key) {
+ let instance = Template.instance();
+ return instance.state.get(key);
+ },
+
+ envsList: function () {
+ return Environments.find({});
+ },
+
+ sourceSystemsList: function () {
+ return R.ifElse(R.isNil, R.always([]), R.prop('data')
+ )(Constants.findOne({ name: 'message_source_systems' }));
+ },
+
+ getAttrDisabled: function () {
+ let instance = Template.instance();
+ let result = {};
+ let action = instance.state.get('action');
+
+ if (R.contains(action, ['view', 'remove'])) {
+ result = R.assoc('disabled', true, result);
+ }
+
+ return result;
+ },
+
+ getModel: function () {
+ let instance = Template.instance();
+ return instance.state.get('model');
+ },
+
+ getModelField: function (fieldName) {
+ let instance = Template.instance();
+ return R.path([fieldName], instance.state.get('model'));
+ },
+
+ getAttrSelected: function (optionValue, modelValue) {
+ let result = {};
+
+ if (optionValue === modelValue) {
+ result = R.assoc('selected', 'selected', result);
+ }
+
+ return result;
+ },
+
+ getAttrSelectedMultiple: function (optionValue, modelValues) {
+ let result = {};
+
+ if (R.isNil(modelValues)) { return result; }
+
+ if (R.contains(optionValue, modelValues)) {
+ result = R.assoc('selected', 'selected', result);
+ }
+
+ return result;
+ },
+
+ actionLabel: function () {
+ let instance = Template.instance();
+ let action = instance.state.get('action');
+ return calcActionLabel(action);
+ },
+
+ asString: function (val) {
+ let str = JSON.stringify(val, null, 4);
+ return str;
+ },
+
+ argsInvPropDisplay: function (env, nodeId) {
+ return {
+ env: env,
+ nodeId: nodeId,
+ displayFn: (node) => {
+ if (R.isNil(node)) { return ''; }
+ return `${node.object_name} - ${node.type}`;
+ }
+ };
+ },
+}); // end - helpers
+
+
+function initViewView(instance, data) {
+ let reqId = parseReqId(data.id);
+
+ instance.state.set('action', data.action);
+ //instance.state.set('env', query.env);
+ instance.state.set('id', reqId);
+
+ subscribeToOptionsData(instance);
+ instance.subscribe('messages?_id', reqId.id);
+
+ Messages.find({ _id: reqId.id }).forEach((model) => {
+ instance.state.set('model', model);
+ });
+
+}
+
+function subscribeToOptionsData(instance) {
+ instance.subscribe('environments_config');
+ instance.subscribe('constants');
+}
+
+function calcActionLabel(action) {
+ switch (action) {
+ case 'insert':
+ return 'Add';
+ case 'remove':
+ return 'Remove';
+ case 'update':
+ return 'Update';
+ default:
+ return 'Submit';
+ }
+}
diff --git a/ui/imports/ui/components/message/message.styl b/ui/imports/ui/components/message/message.styl
new file mode 100644
index 0000000..6003eb1
--- /dev/null
+++ b/ui/imports/ui/components/message/message.styl
@@ -0,0 +1,41 @@
+.os-message
+ margin: 20px;
+
+ .cl-field-group
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ padding: 5px 0;
+
+ .cl-field-label
+ width: 120px;
+ margin: 0 5px;
+
+ .cl-input
+ display: block;
+ width: 100%;
+ min-height: 34px;
+ padding: 6px 12px;
+ font-size: 14px;
+ line-height: 1.42857143;
+ color: #555;
+ background-color: #fff;
+ background-image: none;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ width: 400px;
+ margin: 0 5px;
+
+ .cl-field-desc
+ margin: 0 5px;
+
+ input[disabled]
+ pointer-events: none
+
+ .js-message-panel
+ margin-top: 20px;
+
+ .sm-field-group-display-context
+ cursor: pointer;
+
diff --git a/ui/imports/ui/components/messages-info-box/messages-info-box.html b/ui/imports/ui/components/messages-info-box/messages-info-box.html
new file mode 100644
index 0000000..9c10ace
--- /dev/null
+++ b/ui/imports/ui/components/messages-info-box/messages-info-box.html
@@ -0,0 +1,27 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="MessagesInfoBox">
+ <div class="cards-flex-col-h120 white {{ colorClass }}">
+ <div class="flex-box">
+ <div class="flex-box-1">
+ <i class="material-icons">{{ icon }}</i>
+ </div>
+ <div class="flex-box-2">
+ <h5>{{ title }}</h5>
+ <p class="active">Total: {{ count }}</p>
+ {{#if lastScanTimestamp }}
+ <p>Timestamp: {{ lastScanTimestamp }}</p>
+ {{/if }}
+ <a class="sm-more-details-btn" href="#" data-toggle="modal" data-target="#error">More details</a>
+ </div>
+ </div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/messages-info-box/messages-info-box.js b/ui/imports/ui/components/messages-info-box/messages-info-box.js
new file mode 100644
index 0000000..69dace6
--- /dev/null
+++ b/ui/imports/ui/components/messages-info-box/messages-info-box.js
@@ -0,0 +1,66 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: MessagesInfoBox
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+
+import './messages-info-box.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.MessagesInfoBox.onCreated(function() {
+ var instance = this;
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+ new SimpleSchema({
+ title: { type: String },
+ count: { type: Number },
+ lastScanTimestamp: { type: String, optional: true },
+ icon: { type: String },
+ colorClass: { type: String },
+ onMoreDetailsReq: { type: Function },
+ }).validate(data);
+
+ });
+});
+
+/*
+Template.MessagesInfoBox.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.MessagesInfoBox.events({
+ 'click .sm-more-details-btn': function (event, instance) {
+ event.preventDefault();
+
+ let data = instance.data;
+ data.onMoreDetailsReq();
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.MessagesInfoBox.helpers({
+});
+
+
diff --git a/ui/imports/ui/components/messages-info-box/messages-info-box.styl b/ui/imports/ui/components/messages-info-box/messages-info-box.styl
new file mode 100644
index 0000000..755a04e
--- /dev/null
+++ b/ui/imports/ui/components/messages-info-box/messages-info-box.styl
@@ -0,0 +1,2 @@
+/* Set the component style here */
+// "MessagesInfoBox"
diff --git a/ui/imports/ui/components/messages-list/messages-list.html b/ui/imports/ui/components/messages-list/messages-list.html
new file mode 100644
index 0000000..646b2e9
--- /dev/null
+++ b/ui/imports/ui/components/messages-list/messages-list.html
@@ -0,0 +1,103 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="MessagesList">
+<div class="os-messages-list cards white">
+ <div class="sm-table-section">
+ <h3>Messages</h3>
+ <table class="sm-messages-table table">
+ <thead>
+ <tr>
+ <th><a class="sm-table-header"
+ data-is-sortable="true"
+ data-sort-field="environment"
+ >Environment<span><i class="{{ fieldSortClass 'environment' }}"></i></span></a></th>
+ <th><a class="sm-table-header"
+ data-is-sortable="true"
+ data-sort-field="viewed"
+ >Viewed<span><i class="{{ fieldSortClass 'viewed' }}"></i></span></a></th>
+ <th><a class="sm-table-header">Display Context</a></th>
+ <th><a class="sm-table-header">Message</a></th>
+ <th><a class="sm-table-header"
+ data-is-sortable="true"
+ data-sort-field="source_system"
+ >Source System<span><i class="{{ fieldSortClass 'source_system' }}"></i></span></a></th>
+ <th><a class="sm-table-header"
+ data-is-sortable="true"
+ data-sort-field="level"
+ >Level<span><i class="{{ fieldSortClass 'level' }}"></i></span></a></th>
+ <th><a class="sm-table-header"
+ data-is-sortable="true"
+ data-sort-field="timestamp"
+ >Timestamp<span><i class="{{ fieldSortClass 'timestamp' }}"></i></span></a></th>
+ <th><a class="sm-table-header"
+ data-is-sortable="true"
+ data-sort-field="related_object_type"
+ >Related Object Type<span><i class="{{ fieldSortClass 'related_object_type' }}"></i></span></a></th>
+ <th><a class="sm-table-header">Related Object</a></th>
+ <th><a class="sm-table-header"
+ data-is-sortable="true"
+ data-sort-field="scan_id"
+ >Scan ID<span><i class="{{ fieldSortClass 'scan_id' }}"></i></span></a></th>
+ <th><a class="sm-table-header">Actions</a></th>
+ </tr> </thead>
+ <tbody>
+ {{#each message in messages }}
+ <tr>
+ <td>{{ message.environment }}</td>
+ <td>{{ message.viewed }}</td>
+ <td>
+ <a class="cl-link sm-display-context-link"
+ data-env-name="{{ message.environment }}"
+ data-item-id="{{ message.display_context }}">Link to node</a>
+ </td>
+ <td>{{ message.message }}</td>
+ <td>{{ message.source_system }}</td>
+ <td>{{ message.level }}</td>
+ <td>{{ message.timestamp }}</td>
+ <td>{{ message.related_object_type }}</td>
+ <td>
+ {{#if message.related_object }}
+ {{> InventoryPropertiesDisplay (argsInvPropDisplay message.environment message.related_object) }}
+ {{/if }}
+ </td>
+ <td>
+ <a class="cl-link sm-scan-id-link"
+ data-scan-id="{{ toIsoFormatStr message.scan_id }}"
+ >{{ message.scan_id }}
+ </a>
+ </td>
+ <td>
+ <div class="sm-action-bar">
+ <a href="{{pathFor route='message'
+ query=(asHash id=(idToStr message._id) action='view') }}"
+ ><i class="cl-action-icon fa fa-eye" area-hidden="true"></i></a>
+
+ <!--a href="{{pathFor route='message'
+ query=(asHash id=(idToStr message._id) action='update') }}"
+ ><i class="cl-action-icon fa fa-pencil" area-hidden="true"></i></a-->
+
+ <!--a href="{{pathFor route='message'
+ query=(asHash id=(idToStr message._id) action='remove') }}"
+ ><i class="cl-action-icon fa fa-trash-o" area-hidden="true"></i></a-->
+ </div>
+ </td>
+ </tr>
+ {{/each }}
+ </tbody>
+ </table>
+ </div>
+
+ <div class="sm-pager-section">
+ {{> Pager (argsPager currentPage amountPerPage totalMessages) }}
+ </div>
+
+</div>
+</template>
diff --git a/ui/imports/ui/components/messages-list/messages-list.js b/ui/imports/ui/components/messages-list/messages-list.js
new file mode 100644
index 0000000..d0f2730
--- /dev/null
+++ b/ui/imports/ui/components/messages-list/messages-list.js
@@ -0,0 +1,291 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: MessagesList
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import * as R from 'ramda';
+import { Template } from 'meteor/templating';
+import { Counter } from 'meteor/natestrauser:publish-performant-counts';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+//import { Messages } from '/imports/api/messages/messages';
+import { Environments } from '/imports/api/environments/environments';
+import { idToStr } from '/imports/lib/utilities';
+
+import '/imports/ui/components/pager/pager';
+import '/imports/ui/components/inventory-properties-display/inventory-properties-display';
+
+import './messages-list.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.MessagesList.onCreated(function() {
+ var instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ env: null,
+ page: 1,
+ amountPerPage: 10,
+ sortField: null,
+ sortDirection: null,
+ messsages: [],
+ });
+
+ instance.autorun(function () {
+ //let data = Template.currentData();
+
+ var controller = Iron.controller();
+ var params = controller.getParams();
+ var query = params.query;
+
+ new SimpleSchema({
+ }).validate(query);
+
+ instance.subscribe('environments_config');
+ instance.subscribe('messages/count');
+ });
+
+ instance.autorun(function () {
+ let amountPerPage = instance.state.get('amountPerPage');
+ let page = instance.state.get('page');
+ let sortField = instance.state.get('sortField');
+ let sortDirection = instance.state.get('sortDirection');
+
+ Meteor.apply('messages/get?level&env&page&amountPerPage&sortField&sortDirection', [
+ null, null, page, amountPerPage, sortField, sortDirection
+ ], {
+ wait: false
+ }, function (err, res) {
+ if (err) {
+ console.error(R.toString(err));
+ return;
+ }
+
+ instance.state.set('messages', res);
+ });
+ });
+});
+
+/*
+Template.MessagesList.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.MessagesList.events({
+ 'click .sm-display-context-link': function (event, _instance) {
+ event.preventDefault();
+ let envName = event.target.dataset.envName;
+ let nodeId = event.target.dataset.itemId;
+
+ let environment = Environments.findOne({ name: envName });
+
+ Meteor.apply('inventoryFindNode?env&id', [
+ environment.name,
+ nodeId,
+ ], {
+ wait: false
+ }, function (err, resp) {
+ if (err) {
+ console.error(R.toString(err));
+ return;
+ }
+
+ if (R.isNil(resp.node)) {
+ console.error('error finding node related to message', R.toString(nodeId));
+ return;
+ }
+
+ Router.go('environment', {
+ _id: idToStr(environment._id)
+ }, {
+ query: {
+ selectedNodeId: idToStr(resp.node._id)
+ }
+ });
+
+ });
+
+ },
+
+ 'click .sm-scan-id-link': function (event, _instance) {
+ event.preventDefault();
+ let scanStartTimeStamp = moment(event.target.dataset.scanId).toDate();
+
+ Meteor.apply('scansFind?start-timestamp-before', [
+ scanStartTimeStamp
+ ], {
+ wait: false
+ }, function (err, resp) {
+ if (err) {
+ console.error(R.toString(err));
+ return;
+ }
+
+ if (R.isNil(resp.scan)) {
+ console.error('error finding scan related to message', R.toString(scanStartTimeStamp));
+ return;
+ }
+
+ Router.go('scanning-request', {
+ _id: idToStr(resp.scan._id)
+ }, {
+ query: {
+ env: idToStr(resp.environment._id),
+ id: idToStr(resp.scan._id),
+ action: 'view'
+ }
+ });
+
+ });
+ },
+
+ 'click .sm-table-header': function (event, instance) {
+ event.preventDefault();
+ let isSortable = event.target.dataset.isSortable;
+ if (! isSortable ) { return; }
+
+ let sortField = event.target.dataset.sortField;
+ let currentSortField = instance.state.get('sortField');
+ let currentSortDirection = instance.state.get('sortDirection');
+
+ if (sortField === currentSortField) {
+ let sortDirection = null;
+ if (currentSortDirection === null) {
+ sortDirection = -1;
+ } else if (currentSortDirection === -1) {
+ sortDirection = 1;
+ } else if (currentSortDirection === 1) {
+ sortField = null;
+ sortDirection = null;
+ } else {
+ sortField = null;
+ sortDirection = null;
+ }
+
+ instance.state.set('sortField', sortField);
+ instance.state.set('sortDirection', sortDirection);
+
+ } else {
+ instance.state.set('sortField', sortField);
+ instance.state.set('sortDirection', -1);
+ }
+ },
+});
+
+/*
+ * Helpers
+ */
+
+Template.MessagesList.helpers({
+ messages: function () {
+ let instance = Template.instance();
+ return instance.state.get('messages');
+ },
+
+ currentPage: function () {
+ let instance = Template.instance();
+ return instance.state.get('page');
+ },
+
+ amountPerPage: function () {
+ let instance = Template.instance();
+ return instance.state.get('amountPerPage');
+ },
+
+ totalMessages: function () {
+ return Counter.get(`messages/count`);
+ },
+
+ toIsoFormatStr: function (date) {
+ if (R.isNil(date)) {
+ return '';
+ }
+
+ let str = moment(date).format();
+ return str;
+ },
+
+ argsPager: function (currentPage, amountPerPage, totalMessages) {
+ let instance = Template.instance();
+ let totalPages = Math.ceil(totalMessages / amountPerPage);
+
+ return {
+ disableNext: currentPage * amountPerPage > totalMessages,
+ disablePrev: currentPage == 1,
+ totalPages: totalPages,
+ currentPage: currentPage,
+ onReqNext: function () {
+ console.log('next');
+ let page = (currentPage * amountPerPage > totalMessages) ? currentPage : currentPage + 1;
+ instance.state.set('page', page);
+ },
+ onReqPrev: function () {
+ console.log('prev');
+ let page = (currentPage == 1) ? currentPage : currentPage - 1;
+ instance.state.set('page', page);
+ },
+ onReqFirst: function () {
+ console.log('req first');
+ instance.state.set('page', 1);
+ },
+ onReqLast: function () {
+ console.log('req last');
+ instance.state.set('page', totalPages);
+ },
+ onReqPage: function (pageNumber) {
+ console.log('req page');
+ let page;
+ if (pageNumber <= 1) {
+ page = 1;
+ } else if (pageNumber > Math.ceil(totalMessages / amountPerPage)) {
+ page = totalPages;
+ } else {
+ page = pageNumber;
+ }
+
+ instance.state.set('page', page);
+ },
+ };
+ },
+
+ fieldSortClass: function (fieldName) {
+ let instance = Template.instance();
+ let classes = 'fa fa-sort';
+ if (fieldName === instance.state.get('sortField')) {
+ let sortDirection = instance.state.get('sortDirection');
+ if (sortDirection === -1) {
+ classes = 'fa fa-sort-desc';
+ } else if (sortDirection === 1) {
+ classes = 'fa fa-sort-asc';
+ }
+ }
+
+ return classes;
+ },
+
+ argsInvPropDisplay: function (env, nodeId) {
+ return {
+ env: env,
+ nodeId: nodeId,
+ displayFn: (node) => {
+ if (R.isNil(node)) { return ''; }
+ return `${node.object_name} - ${node.type}`;
+ }
+ };
+ },
+}); // end: helpers
diff --git a/ui/imports/ui/components/messages-list/messages-list.styl b/ui/imports/ui/components/messages-list/messages-list.styl
new file mode 100644
index 0000000..adc9500
--- /dev/null
+++ b/ui/imports/ui/components/messages-list/messages-list.styl
@@ -0,0 +1,37 @@
+.os-messages-list
+ display: flex;
+ flex-flow: column nowrap;
+ margin: 20px;
+
+ .cl-action-icon,
+ .card.fa.cl-action-icon
+ font-size: 16px !important;
+
+ .sm-messages-table
+ th
+ color: spark-blue
+
+ a
+ color: spark-blue;
+ cursor: pointer;
+ i.fa
+ padding: 0px 3px;
+ font-size: small;
+
+ .sm-action-bar
+ display: flex;
+
+ a
+ color: spark-blue;
+ margin: 0px 5px;
+ cursor: pointer;
+
+ .cl-action-icon
+ color: gray
+
+ .sm-pager-section
+ display: flex;
+ justify-content: center;
+
+ .cl-link
+ cursor: pointer
diff --git a/ui/imports/ui/components/messages-modal/messages-modal.html b/ui/imports/ui/components/messages-modal/messages-modal.html
new file mode 100644
index 0000000..292bc20
--- /dev/null
+++ b/ui/imports/ui/components/messages-modal/messages-modal.html
@@ -0,0 +1,78 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="MessagesModal">
+<div class="os-messages-modal modal fade"
+ id="messagesModalGlobal"
+ tabindex="-1"
+ role="dialog"
+ aria-labelledby="myModalLabel"
+ >
+ <div class="modal-dialog"
+ role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label
+ ="Close"><span aria-hidden="true">&times;</span></button>
+ <i class="material-icons">{{ iconType }}</i>
+ </div>
+
+ <div class="modal-body">
+ <div class="sm-general-table-section">
+
+ <h5 class="modal-title" id="myModalLabel"
+ >{{ listHeader }}</h5>
+ <table class="table table-striped sm-messages-table">
+ <thead class="sm-message-table-section">
+ <tr>
+ <th>Environment</th>
+ <th>Display Context</th>
+ <th>Source System</th>
+ <th>Timestamp</th>
+ <th>Related Object</th>
+ </tr>
+ </thead>
+ <tbody class="sm-message-table-section">
+ {{#each message in messages }}
+ <tr class="sm-message-row">
+ <td>{{ message.environment }}</td>
+ <td>
+ <a class="cl-link sm-display-context-link"
+ data-env-name="{{ message.environment }}"
+ data-item-id="{{ message.display_context }}">Link</a>
+ </td>
+ <td>{{ message.source_system }}</td>
+ <td>{{ message.timestamp }}</td>
+ <td>
+ {{#if message.related_object }}
+ {{> InventoryPropertiesDisplay (argsInvPropDisplay message.environment message.related_object) }}
+ {{/if }}
+ </td>
+ </tr>
+ {{/each}}
+ </tbody>
+ </table>
+ </div>
+
+ <div class="sm-pager-section">
+ {{> Pager (argsPager currentPage amountPerPage totalMessages) }}
+ </div>
+
+ </div>
+
+ <div class="modal-footer">
+ <button type="button"
+ class="mdl-button mdl-js-button"
+ data-dismiss="modal">Close</button>
+ </div>
+ </div>
+ </div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/messages-modal/messages-modal.js b/ui/imports/ui/components/messages-modal/messages-modal.js
new file mode 100644
index 0000000..59a6ec7
--- /dev/null
+++ b/ui/imports/ui/components/messages-modal/messages-modal.js
@@ -0,0 +1,285 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: MessagesModal
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { Counter } from 'meteor/natestrauser:publish-performant-counts';
+import * as R from 'ramda';
+//import { Messages } from '/imports/api/messages/messages';
+import { Environments } from '/imports/api/environments/environments';
+import { idToStr } from '/imports/lib/utilities';
+
+import '/imports/ui/components/pager/pager';
+
+import './messages-modal.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.MessagesModal.onCreated(function() {
+ let instance = this;
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ messageLevel: 'info',
+ iconType: null,
+ listHeader: null,
+ envName: null,
+ page: 1,
+ amountPerPage: 10,
+ messages: [],
+ });
+
+ instance.autorun(function () {
+
+ //let amountPerPage = instance.state.get('amountPerPage');
+ //let page = instance.state.get('page');
+ let envName = instance.state.get('envName');
+ let messageLevel = instance.state.get('messageLevel');
+ /*
+
+ instance.subscribe('messages?env&level&page&amount', envName, messageLevel, page, amountPerPage);
+ */
+
+ if (R.isNil(envName)) {
+ instance.subscribe('messages/count?level', messageLevel);
+ } else {
+ instance.subscribe('messages/count?level&env', messageLevel, envName);
+ }
+ });
+
+ instance.autorun(function () {
+ let level = instance.state.get('messageLevel');
+ let envName = instance.state.get('envName');
+ let page = instance.state.get('page');
+ let amountPerPage = instance.state.get('amountPerPage');
+
+ Meteor.apply('messages/get?level&env&page&amountPerPage&sortField&sortDirection', [
+ level, envName, page, amountPerPage, null, null
+ ], {
+ wait: false
+ }, function (err, res) {
+ if (err) {
+ console.error(R.toString(err));
+ return;
+ }
+
+ instance.state.set('messages', res);
+ });
+ });
+});
+
+/*
+Template.MessagesModal.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.MessagesModal.events({
+ 'show.bs.modal #messagesModalGlobal': function (event, instance) {
+ let data = event.relatedTarget.dataset;
+ setParams(data.messageLevel, data.envName, instance);
+ },
+
+ 'click .sm-display-context-link': function (event, instance) {
+ event.preventDefault();
+ let envName = event.target.dataset.envName;
+ let nodeId = event.target.dataset.itemId;
+
+ let environment = Environments.findOne({ name: envName });
+
+ Meteor.apply('inventoryFindNode?env&id', [
+ environment.name,
+ nodeId,
+ ], {
+ wait: false
+ }, function (err, resp) {
+ if (err) {
+ console.error(R.toString(err));
+ return;
+ }
+
+ if (R.isNil(resp.node)) {
+ console.error('error finding node related to message', R.toString(nodeId));
+ return;
+ }
+
+ Router.go('environment', {
+ _id: idToStr(environment._id)
+ }, {
+ query: {
+ selectedNodeId: idToStr(resp.node._id)
+ }
+ });
+
+ instance.$('#messagesModalGlobal').modal('hide');
+
+ });
+
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.MessagesModal.helpers({
+ iconType: function () {
+ let instance = Template.instance();
+ return instance.state.get('iconType');
+ },
+
+ listHeader: function () {
+ let instance = Template.instance();
+ return instance.state.get('listHeader');
+ },
+
+ envName: function () {
+ let instance = Template.instance();
+ return instance.state.get('envName');
+ },
+
+ messages: function () {
+ let instance = Template.instance();
+ return instance.state.get('messages');
+ },
+
+ currentPage: function () {
+ let instance = Template.instance();
+ return instance.state.get('page');
+ },
+
+ amountPerPage: function () {
+ let instance = Template.instance();
+ return instance.state.get('amountPerPage');
+ },
+
+ totalMessages: function () {
+ let instance = Template.instance();
+ let level = instance.state.get('messageLevel');
+ let env = instance.state.get('envName');
+
+ if (R.isNil(env)) {
+ return Counter.get(`messages/count?level=${level}`);
+ } else {
+ return Counter.get(`messages/count?level=${level}&env=${env}`);
+ }
+ },
+
+ argsPager: function (currentPage, amountPerPage, totalMessages) {
+ let instance = Template.instance();
+ let totalPages = Math.ceil(totalMessages / amountPerPage);
+
+ return {
+ disableNext: currentPage * amountPerPage > totalMessages,
+ disablePrev: currentPage == 1,
+ totalPages: totalPages,
+ currentPage: currentPage,
+ onReqNext: function () {
+ console.log('next');
+ let page = (currentPage * amountPerPage > totalMessages) ? currentPage : currentPage + 1;
+ instance.state.set('page', page);
+ },
+ onReqPrev: function () {
+ console.log('prev');
+ let page = (currentPage == 1) ? currentPage : currentPage - 1;
+ instance.state.set('page', page);
+ },
+ onReqFirst: function () {
+ console.log('req first');
+ instance.state.set('page', 1);
+ },
+ onReqLast: function () {
+ console.log('req last');
+ instance.state.set('page', totalPages);
+ },
+ onReqPage: function (pageNumber) {
+ console.log('req page');
+ let page;
+ if (pageNumber <= 1) {
+ page = 1;
+ } else if (pageNumber > Math.ceil(totalMessages / amountPerPage)) {
+ page = totalPages;
+ } else {
+ page = pageNumber;
+ }
+
+ instance.state.set('page', page);
+ },
+ };
+ },
+
+ argsInvPropDisplay: function (env, nodeId) {
+ return {
+ env: env,
+ nodeId: nodeId,
+ displayFn: (node) => {
+ if (R.isNil(node)) { return ''; }
+ return `${node.object_name} - ${node.type}`;
+ }
+ };
+ },
+}); // end: helpers
+
+function setParams(messageLevel, envName, instance) {
+ instance.state.set('messageLevel', messageLevel);
+ instance.state.set('iconType', calcIconType(messageLevel));
+ instance.state.set('listHeader', calcListHeader(messageLevel, envName));
+ instance.state.set('envName', envName);
+ instance.state.set('page', 1);
+}
+
+function calcIconType(messageLevel) {
+ switch (messageLevel) {
+ case 'notify':
+ return 'notifications';
+ case 'info':
+ return 'notifications';
+ case 'warning':
+ return 'warning';
+ case 'error':
+ return 'error';
+ default:
+ throw 'unimplemented message level for icon';
+ }
+}
+
+function calcListHeader(messageLevel, envName) {
+ let header;
+
+ switch (messageLevel) {
+ case 'notify':
+ header = 'List of notifications';
+ break;
+ case 'info':
+ header = 'List of info messages';
+ break;
+ case 'warning':
+ header = 'List of warnings';
+ break;
+ case 'error':
+ header = 'List of errors';
+ break;
+ default:
+ throw 'unimplemented message level for list header';
+ }
+
+ if (! R.isNil(envName)) {
+ header = header + ` for environment ${envName}.`;
+ }
+
+ return header;
+}
diff --git a/ui/imports/ui/components/messages-modal/messages-modal.styl b/ui/imports/ui/components/messages-modal/messages-modal.styl
new file mode 100644
index 0000000..ec12941
--- /dev/null
+++ b/ui/imports/ui/components/messages-modal/messages-modal.styl
@@ -0,0 +1,18 @@
+.os-messages-modal
+
+ .cl-link
+ cursor: pointer
+
+ .modal-dialog
+ display: flex;
+ flex-flow: column nowrap;
+
+ .sm-messages-table
+ table-layout: fixed;
+
+ .sm-message-row
+ word-break: break-all;
+
+ .sm-pager-section
+ display: flex;
+ justify-content: center;
diff --git a/ui/imports/ui/components/mt-input/mt-input.html b/ui/imports/ui/components/mt-input/mt-input.html
new file mode 100644
index 0000000..c7803f4
--- /dev/null
+++ b/ui/imports/ui/components/mt-input/mt-input.html
@@ -0,0 +1,7 @@
+<template name="MtInput">
+ <input class="input-field {{ classStr }}"
+ value="{{ inputValue }}"
+ type="{{ inputType }}"
+ {{ attrsInput inputType placeholder isDisabled }}
+ />
+</template>
diff --git a/ui/imports/ui/components/mt-input/mt-input.js b/ui/imports/ui/components/mt-input/mt-input.js
new file mode 100644
index 0000000..f8192ef
--- /dev/null
+++ b/ui/imports/ui/components/mt-input/mt-input.js
@@ -0,0 +1,114 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: MtInput
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import * as R from 'ramda';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+
+import './mt-input.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.MtInput.onCreated(function() {
+ let instance = this;
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+
+ //simple schema does not support input value type of: number or string together.
+ data = R.dissoc('inputValue', data);
+
+ instance.autorun(function () {
+ new SimpleSchema({
+ inputType: { type: String },
+ classStr: { type: String, optional: true },
+ placeholder: { type: String, optional: true },
+ isDisabled: { type: Boolean, optional: true },
+ onInput: { type: Object, blackbox: true },
+ }).validate(data);
+ });
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+
+ instance.onInput = function (value) {
+ R.when(R.pipe(R.isNil, R.not), x => x(value))(R.path(['onInput', 'fn'], data));
+ };
+ });
+});
+
+/*
+Template.MtInput.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.MtInput.events({
+ 'input .input-field': function (event, instance) {
+ if (event.target.type === 'checkbox') { return; }
+
+ let value = R.cond([
+ [R.equals('number'), R.always(event.target.valueAsNumber)],
+ [R.T, R.always(event.target.value)],
+ ])(event.target.type);
+
+ instance.onInput(value);
+ },
+
+ 'click .input-field': function (event, instance) {
+ if (event.target.type !== 'checkbox') { return; }
+
+ let element = instance.$('.input-field')[0];
+ instance.onInput(element.checked);
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.MtInput.helpers({
+ attrsInput: function (inputType, placeholder, isDisabled) {
+ let attrs = {};
+
+ if (hasPlaceholder(inputType, placeholder)) {
+ attrs = R.assoc('placeholder', placeholder, attrs);
+ }
+
+ if (isDisabled) {
+ attrs = R.assoc('disabled', 'disabled', attrs);
+ }
+
+ return attrs;
+ },
+
+}); // end: helpers
+
+function hasPlaceholder(inputType, placeholder) {
+ if (R.contains(inputType, ['checkbox', 'select'])) {
+ return false;
+ }
+
+ if (R.isNil(placeholder)) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/ui/imports/ui/components/mt-input/mt-input.styl b/ui/imports/ui/components/mt-input/mt-input.styl
new file mode 100644
index 0000000..3638a14
--- /dev/null
+++ b/ui/imports/ui/components/mt-input/mt-input.styl
@@ -0,0 +1,2 @@
+/* Set the component style here */
+// "MtInput"
diff --git a/ui/imports/ui/components/mt-radios/mt-radios.html b/ui/imports/ui/components/mt-radios/mt-radios.html
new file mode 100644
index 0000000..23fa3d8
--- /dev/null
+++ b/ui/imports/ui/components/mt-radios/mt-radios.html
@@ -0,0 +1,22 @@
+<template name="MtRadios">
+<div class="os-mt-radios">
+
+{{#each option in options }}
+<div class="radio">
+ <div class="cl-field-group">
+ <label>
+ <input type="radio"
+ class="cl-mt-radio-input {{ inputClasses }}"
+ name="radioName"
+ id="{{ option.value }}"
+ value="{{ option.value }}"
+ {{ attrsInput option.value selectedValue }}
+ >
+ {{ option.label }}
+ </label>
+ </div>
+</div>
+{{/each }}
+
+</div>
+</template>
diff --git a/ui/imports/ui/components/mt-radios/mt-radios.js b/ui/imports/ui/components/mt-radios/mt-radios.js
new file mode 100644
index 0000000..e2c3169
--- /dev/null
+++ b/ui/imports/ui/components/mt-radios/mt-radios.js
@@ -0,0 +1,70 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: MtRadios
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+import * as R from 'ramda';
+
+import './mt-radios.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.MtRadios.onCreated(function() {
+ let instance = this;
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+
+ instance.onInput = function (value) {
+ R.when(R.pipe(R.isNil, R.not), x => x(value))(R.path(['onInput', 'fn'], data));
+ };
+ });
+});
+
+/*
+Template.MtRadios.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.MtRadios.events({
+ 'click .cl-mt-radio-input': function (event, instance) {
+ event.preventDefault();
+ event.stopPropagation();
+
+ instance.onInput(event.target.value);
+ },
+});
+
+/*
+ * Helpers
+ */
+
+Template.MtRadios.helpers({
+ attrsInput: function (inputValue, selectedValue) {
+ let attrs = {};
+
+ if (inputValue === selectedValue) {
+ attrs = R.assoc('checked', 'checked', attrs);
+ }
+
+ return attrs;
+ },
+}); // end: helpers
+
+
diff --git a/ui/imports/ui/components/mt-radios/mt-radios.styl b/ui/imports/ui/components/mt-radios/mt-radios.styl
new file mode 100644
index 0000000..868d2c0
--- /dev/null
+++ b/ui/imports/ui/components/mt-radios/mt-radios.styl
@@ -0,0 +1,2 @@
+/* Set the component style here */
+// "MtRadios"
diff --git a/ui/imports/ui/components/mt-select/mt-select.html b/ui/imports/ui/components/mt-select/mt-select.html
new file mode 100644
index 0000000..cce8973
--- /dev/null
+++ b/ui/imports/ui/components/mt-select/mt-select.html
@@ -0,0 +1,13 @@
+<template name="MtSelect">
+ <select class="sm-mt-select {{ classStr }}"
+ {{ attrsSelect isDisabled size }}
+ >
+ {{#each option in options }}
+ <option class="cl-mt-select-option"
+ value="{{ option.value }}"
+ {{ attrOptSelected option.value selectedValue }}
+ >{{ option.label }}
+ </option>
+ {{/each }}
+ </select>
+</template>
diff --git a/ui/imports/ui/components/mt-select/mt-select.js b/ui/imports/ui/components/mt-select/mt-select.js
new file mode 100644
index 0000000..48a2141
--- /dev/null
+++ b/ui/imports/ui/components/mt-select/mt-select.js
@@ -0,0 +1,99 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: MtSelect
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import * as R from 'ramda';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+
+import './mt-select.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.MtSelect.onCreated(function() {
+ let instance = this;
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+
+ instance.autorun(function () {
+ new SimpleSchema({
+ classStr: { type: String, optional: true },
+ selectedValue: { type: String, optional: true },
+ isDisabled: { type: Boolean, optional: true },
+ options: { type: [Object], blackbox: true },
+ onInput: { type: Object, blackbox: true },
+ size: { type: Number, optional: true },
+ }).validate(data);
+ });
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+
+ instance.onInput = function (value) {
+ R.when(R.pipe(R.isNil, R.not), x => x(value))(R.path(['onInput', 'fn'], data));
+ };
+ });
+});
+
+/*
+Template.MtSelect.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.MtSelect.events({
+ 'change .sm-mt-select': function (event, instance) {
+ event.preventDefault();
+ event.stopPropagation();
+
+ let value = R.pipe(R.head, R.prop('value'))(event.target.selectedOptions);
+ instance.onInput(value);
+ },
+});
+
+/*
+ * Helpers
+ */
+
+Template.MtSelect.helpers({
+ attrsSelect: function (isDisabled, size) {
+ let attrs = {};
+ if (isDisabled) {
+ attrs = R.assoc('disabled', 'disabled', attrs);
+ }
+
+ if (size) {
+ attrs = R.assoc('size', size, attrs);
+ }
+
+ return attrs;
+ },
+
+ attrOptSelected: function (currentValue, selectedValue) {
+ let attrs = {};
+ if (currentValue === selectedValue) {
+ attrs = R.assoc('selected', 'selected', attrs);
+ }
+ return attrs;
+ },
+
+}); // helpers
+
+
diff --git a/ui/imports/ui/components/mt-select/mt-select.styl b/ui/imports/ui/components/mt-select/mt-select.styl
new file mode 100644
index 0000000..e0ff8ca
--- /dev/null
+++ b/ui/imports/ui/components/mt-select/mt-select.styl
@@ -0,0 +1,2 @@
+/* Set the component style here */
+// "MtSelect"
diff --git a/ui/imports/ui/components/network-graph-manager/network-graph-manager.html b/ui/imports/ui/components/network-graph-manager/network-graph-manager.html
new file mode 100644
index 0000000..845db7c
--- /dev/null
+++ b/ui/imports/ui/components/network-graph-manager/network-graph-manager.html
@@ -0,0 +1,5 @@
+<template name="NetworkGraphManager">
+ {{#if isReady }}
+ {{>NetworkGraph (argsNetworkGraph graphDataChanged) }}
+ {{/if }}
+</template>
diff --git a/ui/imports/ui/components/network-graph-manager/network-graph-manager.js b/ui/imports/ui/components/network-graph-manager/network-graph-manager.js
new file mode 100644
index 0000000..7022bcc
--- /dev/null
+++ b/ui/imports/ui/components/network-graph-manager/network-graph-manager.js
@@ -0,0 +1,282 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: NetworkGraphManager
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { Inventory } from '/imports/api/inventories/inventories';
+import { Cliques } from '/imports/api/cliques/cliques.js';
+import { Links } from '/imports/api/links/links.js';
+import * as R from 'ramda';
+import { store } from '/imports/ui/store/store';
+import { activateGraphTooltipWindow } from '/imports/ui/actions/graph-tooltip-window.actions';
+import { closeGraphTooltipWindow } from '/imports/ui/actions/graph-tooltip-window.actions';
+//import { activateVedgeInfoWindow } from '/imports/ui/actions/vedge-info-window.actions';
+
+import '/imports/ui/components/network-graph/network-graph';
+
+import './network-graph-manager.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.NetworkGraphManager.onCreated(function() {
+ let instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ id_path: null,
+ graphDataChanged: null,
+ isReady: false,
+ });
+ instance.simpleState = {
+ graphData: {
+ links: [],
+ nodes: [],
+ groups: [],
+ }
+ };
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+
+ new SimpleSchema({
+ id_path: { type: String },
+ }).validate(data);
+
+ instance.state.set('id_path', data.id_path);
+ });
+
+ instance.autorun(function () {
+ let id_path = instance.state.get('id_path');
+
+ instance.simpleState.graphData = generateGraphData();
+ instance.state.set('isReady', false);
+
+ instance.subscribe('attributes_for_hover_on_data');
+ subscribeToNodeAndRelatedData(id_path, instance, instance.simpleState);
+ });
+});
+
+/*
+Template.NetworkGraphManager.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.NetworkGraphManager.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.NetworkGraphManager.helpers({
+ graphDataChanged: function () {
+ let instance = Template.instance();
+ return instance.state.get('graphDataChanged');
+ },
+
+ argsNetworkGraph: function (_graphDataChanged) {
+ let instance = Template.instance();
+ let graphData = instance.simpleState.graphData;
+ let isDragging = false;
+
+ return {
+ graphData: graphData,
+ onNodeOver: function (nodeId, x, y) {
+ if (isDragging) {
+ return;
+ }
+
+ Meteor.apply('inventoryFindNode?DataAndAttrs', [ nodeId ],
+ { wait: false }, function (err, res) {
+ if (err) {
+ console.error(`error fetching attrs for node for showing: ${R.toString(err)}`);
+ return;
+ }
+
+ store.dispatch(
+ activateGraphTooltipWindow(res.nodeName, res.attributes, x, y));
+ });
+ },
+ onNodeOut: function (_nodeId) {
+ store.dispatch(closeGraphTooltipWindow());
+ },
+ onNodeClick: function (_nodeId) {
+ },
+ onDragStart: function () {
+ isDragging = true;
+ store.dispatch(closeGraphTooltipWindow());
+ },
+ onDragEnd: function () {
+ isDragging = false;
+ },
+ };
+ },
+
+ isReady: function () {
+ let instance = Template.instance();
+ return instance.state.get('isReady');
+ }
+}); // end: helpers
+
+function subscribeToNodeAndRelatedData(id_path, instance, simpleState) {
+ instance.subscribe('inventory?id_path', id_path);
+
+ // id_path: assumption - unique
+ Inventory.find({ id_path: id_path }).forEach((inventory) => {
+ if (! inventory.clique) {
+ return;
+ }
+
+ // focal point: assumption - unique per inventory node.
+ let mainNodeIdStr = inventory._id._str;
+ instance.subscribe('cliques?focal_point', mainNodeIdStr);
+
+ Cliques.find({ focal_point: new Mongo.ObjectID(mainNodeIdStr) }).forEach( function (cliqueItem) {
+
+ // Find links for focal point.
+ instance.subscribe('links?_id-in', cliqueItem.links);
+
+ Links.find({ _id: {$in: cliqueItem.links} }).forEach(function(link) {
+ simpleState.graphData = addLinkToGraph(link, simpleState.graphData);
+ instance.state.set('graphDataChanged', Date.now());
+
+ // Find nodes for link
+ let nodesIds = [ link['source'], link['target'] ];
+ instance.subscribe('inventory?_id-in', nodesIds);
+
+ Inventory.find({ _id: { $in: nodesIds } }).forEach(function (node) {
+ simpleState.graphData = addNodeToGraph(node, simpleState.graphData);
+ let isReady = calcIsReady(simpleState.graphData);
+ instance.state.set('graphDataChanged', Date.now());
+ instance.state.set('isReady', isReady);
+
+ // Find nodes attributes for links nodes.
+ instance.subscribe('attributes_for_hover_on_data?type', node.type);
+ });
+ });
+ });
+ });
+}
+
+function generateGraphData() {
+ return {
+ nodes: [],
+ links: [],
+ groups: [],
+ };
+}
+
+function addLinkToGraph(link, graphData) {
+ let newLink = {
+ sourceId: link.source,
+ targetId: link.target,
+ label: link.link_name,
+ _osid: link._id
+ };
+
+ let links = R.unionWith(R.eqBy(R.prop('_osid')), graphData.links, [newLink]);
+ links = expandLinks(links, graphData.nodes);
+
+ return R.merge(graphData, {
+ links: links
+ });
+}
+
+function expandLinks(links, nodes) {
+ return R.map((link) => {
+ let newLink = link;
+
+ let nodeSource = R.find(R.propEq('_osid', newLink.sourceId), nodes);
+ if (!R.isNil(nodeSource)) {
+ newLink = R.assoc('source', nodeSource, newLink);
+ }
+
+ let nodeTarget = R.find(R.propEq('_osid', newLink.targetId), nodes);
+ if (!R.isNil(nodeTarget)) {
+ newLink = R.assoc('target', nodeTarget, newLink);
+ }
+
+ return newLink;
+ }, links);
+}
+
+function addNodeToGraph(node, graphData) {
+ let newNode = {
+ _osid: node._id,
+ _osmeta: {
+ type: node.type,
+ nodeId: node._id,
+ },
+ width: 60,
+ height: 40,
+ name: node._id._str,
+ };
+
+ newNode = R.ifElse(R.isNil,
+ R.always(newNode),
+ R.assocPath(['_osmeta', 'host'], R.__, newNode)
+ )(node.host);
+
+ let nodes = R.unionWith(R.eqBy(R.prop('_osid')), graphData.nodes, [newNode]);
+ let links = expandLinks(graphData.links, nodes);
+ let groups = calcGroups(nodes);
+
+ return R.merge(graphData, {
+ nodes: nodes,
+ links: links,
+ groups: groups,
+ });
+}
+
+function calcIsReady(graphData) {
+ return R.all((link) => {
+ return (!(R.isNil(link.source) || R.isNil(link.target)));
+ }, graphData.links);
+}
+
+function calcGroups(nodes) {
+ return R.reduce((accGroups, node) => {
+ let host = R.path(['_osmeta', 'host'], node);
+ if (R.isNil(host)) {
+ return accGroups;
+ }
+
+ let groupIndex = R.findIndex(R.propEq('_osid', host), accGroups);
+ let group = null;
+ if (groupIndex < 0) {
+ let group = {
+ _osid: host,
+ leaves: [node],
+ isExpanded: true,
+ };
+ accGroups = R.append(group, accGroups);
+
+ } else {
+ let group = accGroups[groupIndex];
+ group = R.merge(group, {
+ leaves: R.append(node, group.leaves)
+ });
+ accGroups = R.update(groupIndex, group, accGroups);
+ }
+
+ node.parent = group;
+ return accGroups;
+ }, [], nodes);
+}
diff --git a/ui/imports/ui/components/network-graph-manager/network-graph-manager.styl b/ui/imports/ui/components/network-graph-manager/network-graph-manager.styl
new file mode 100644
index 0000000..1df8d2f
--- /dev/null
+++ b/ui/imports/ui/components/network-graph-manager/network-graph-manager.styl
@@ -0,0 +1,2 @@
+/* Set the component style here */
+// "NetworkGraphManager"
diff --git a/ui/imports/ui/components/network-graph/network-graph.html b/ui/imports/ui/components/network-graph/network-graph.html
new file mode 100644
index 0000000..e68141a
--- /dev/null
+++ b/ui/imports/ui/components/network-graph/network-graph.html
@@ -0,0 +1,7 @@
+<template name="NetworkGraph">
+<div class="os-network-graph">
+<div class="sm-graph">
+<svg></svg>
+</div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/network-graph/network-graph.js b/ui/imports/ui/components/network-graph/network-graph.js
new file mode 100644
index 0000000..49e41a8
--- /dev/null
+++ b/ui/imports/ui/components/network-graph/network-graph.js
@@ -0,0 +1,697 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: NetworkGraph
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import * as R from 'ramda';
+import * as cola from 'webcola';
+import { imagesForNodeType, defaultNodeTypeImage } from '/imports/lib/images-for-node-type';
+
+import './network-graph.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.NetworkGraph.onCreated(function() {
+ let instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ graphDataChanged: null,
+ });
+ instance.simpleState = {
+ graphData: null
+ };
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+
+ new SimpleSchema({
+ graphData: { type: Object, blackbox: true },
+ onNodeOver: { type: Function, optional: true },
+ onNodeOut: { type: Function, optional: true },
+ onNodeClick: { type: Function, optional: true },
+ onDragStart: { type: Function, optional: true },
+ onDragEnd: { type: Function, optional: true },
+ }).validate(data);
+
+ instance.simpleState.graphData = data.graphData;
+ instance.state.set('graphDataChanged', Date.now());
+ instance.onNodeOver = R.defaultTo(() => {}, data.onNodeOver);
+ instance.onNodeOut = R.defaultTo(() => {}, data.onNodeOut);
+ instance.onNodeClick = R.defaultTo(() => {}, data.onNodeClick);
+ instance.onDragStart = R.defaultTo(() => {}, data.onDragStart);
+ instance.onDragEnd = R.defaultTo(() => {}, data.onDragEnd);
+ });
+});
+
+Template.NetworkGraph.rendered = function() {
+ let instance = Template.instance();
+
+ instance.autorun(function () {
+ //let _graphDataChanged =
+ instance.state.get('graphDataChanged');
+ let graphEl = instance.$('.sm-graph')[0];
+
+ renderGraph(graphEl,
+ graphEl.clientWidth,
+ graphEl.clientHeight,
+ instance.simpleState.graphData,
+ genConfig(),
+ instance.onNodeOver,
+ instance.onNodeOut,
+ instance.onNodeClick,
+ instance.onDragStart,
+ instance.onDragEnd
+ );
+ });
+};
+
+/*
+ * Events
+ */
+
+Template.NetworkGraph.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.NetworkGraph.helpers({
+}); // end: helpers
+
+
+function genConfig() {
+ let outline = false;
+ let tocolor = 'fill';
+ let towhite = 'stroke';
+ if (outline) {
+ tocolor = 'stroke';
+ towhite = 'fill';
+ }
+
+ return {
+ initialLinkLabelsFontSize: 18,
+ tocolor: tocolor,
+ towhite: towhite,
+ text_center: false,
+ outline: outline,
+ min_score: 0,
+ max_score: 1,
+ highlight_color: 'blue',
+ highlight_trans: 0.1,
+ default_node_color: '#ccc',
+ //var default_node_color: 'rgb(3,190,100)',
+ default_link_color: '#888',
+ nominal_base_node_size: 8,
+ nominal_text_size: 10,
+ max_text_size: 24,
+ nominal_stroke: 1.5,
+ max_stroke: 4.5,
+ max_base_node_size: 36,
+ min_zoom: 0.3,
+ max_zoom: 5,
+ };
+}
+
+function renderGraph(
+ mainElement,
+ w,
+ h,
+ graph,
+ config,
+ onNodeOver,
+ onNodeOut,
+ onNodeClick,
+ onDragStart,
+ onDragEnd
+) {
+
+ let force = genForceCola(cola, d3, w, h);
+ let drag = force.drag()
+ .on('start', function (_d) {
+ onDragStart();
+ })
+ .on('end', function (_d) {
+ onDragEnd();
+ })
+ ;
+
+ let svg = d3.select(mainElement).select('svg');
+ svg.remove();
+ svg = genSvg(d3, mainElement);
+
+ let zoom = genZoomBehavior(d3, config);
+ svg.call(zoom);
+
+ let mainEl = svg.append('g');
+ let groupsEl = mainEl.append('g').attr('class', 'groups-container');
+ let linksEl = mainEl.append('g').attr('class', 'links-container');
+ let nodesEl = mainEl.append('g').attr('class', 'nodes-container');
+
+ renderView(force, {
+ graph: graph,
+ viewGraph: {
+ nodes: [],
+ links: [],
+ groups: []
+ },
+ },
+ mainEl,
+ groupsEl,
+ nodesEl,
+ linksEl,
+ drag, zoom, config,
+ onNodeOver, onNodeOut, onNodeClick);
+}
+
+ // d3.select(window).on('resize', resize);
+
+function genSvg(d3, mainElement) {
+ let svg = d3.select(mainElement).append('svg');
+
+ svg.style('cursor', 'move')
+ .attr('width', '100%')
+ .attr('height', '100%')
+ .attr('pointer-events', 'all');
+
+ return svg;
+}
+
+function genSvgLinks(g, links, nominal_stroke, default_link_color, initialLinkLabelsFontSize) {
+ let svgLinks = g.selectAll('.link-group')
+ .data(links, (d) => d._osid);
+
+ let svgLinksEnter = svgLinks
+ .enter()
+ .append('g')
+ .attr('class', 'link-group')
+ .attr('data-link-id', function (d) {
+ return d._osid;
+ })
+ ;
+
+ //let svgLinksExit =
+ svgLinks
+ .exit().remove();
+
+ let svgLinkLines = svgLinksEnter
+ .append('line')
+ .attr('class', 'link-line')
+ .style('stroke-width', nominal_stroke)
+ .style('stroke',
+ function(_d) {
+ return default_link_color;
+ });
+
+ let svgLinkLabels = svgLinksEnter
+ .append('text')
+ .text(function(d) {
+ return d.label;
+ })
+ .attr('class', 'link-label')
+ .attr('x', function(d) { return (d.source.x + (d.target.x - d.source.x) * 0.5); })
+ .attr('y', function(d) { return (d.source.y + (d.target.y - d.source.y) * 0.5); })
+ .attr('dy', '.25em')
+ .attr('text-anchor', 'right')
+ .attr('font-size', initialLinkLabelsFontSize)
+ ;
+
+ return {svgLinks, svgLinkLines, svgLinkLabels};
+}
+
+function genSvgNodes(g, nodes, drag, onNodeOver, onNodeOut, onNodeClick, onGroupNodeClick) {
+ let svgNodes = g.selectAll('.node')
+ .data(nodes, (d) => d._osid);
+
+ let svgNodesEnter = svgNodes
+ .enter()
+ .append('g')
+ .attr('class', 'node')
+ .attr('data-node-id', (d) => d._osid)
+ .call(drag);
+
+ //let svgNodesExit =
+ svgNodes
+ .exit().remove();
+
+ let imageLength = 36;
+ let svgImages = svgNodesEnter.append('image')
+ .attr('class', 'node-image')
+ .attr('xlink:href', function(d) {
+ return `/${calcImageForNodeType(d._osmeta.type)}`;
+ })
+ .attr('x', -(Math.floor(imageLength / 2)))
+ .attr('y', -(Math.floor(imageLength / 2)))
+ .attr('width', imageLength)
+ .attr('height', imageLength)
+ .on('mouseover', function (d) {
+ onNodeOver(d._osmeta.nodeId, d3.event.pageX, d3.event.pageY);
+ })
+ .on('mouseout', function (d) {
+ onNodeOut(d._osmeta.nodeId);
+ })
+ .on('click', function (d) {
+ if (R.path(['_osmeta', 'type'], d) === 'view_group') {
+ onGroupNodeClick(d._osmeta.nodeId);
+ }
+ onNodeClick(d._osmeta.nodeId);
+ })
+ ;
+
+ return {svgNodes, svgImages};
+ //return [svgNodes];
+}
+
+function calcImageForNodeType(nodeType) {
+ return R.defaultTo(defaultNodeTypeImage, R.prop(nodeType, imagesForNodeType));
+}
+
+function genZoomBehavior(d3, config) {
+ let zoom = d3.zoom().scaleExtent([config.min_zoom, config.max_zoom]);
+
+ return zoom;
+}
+
+/*
+function genForceD3(d3, w, h) {
+ let force = d3.layout.force()
+ .linkDistance(60)
+ .charge(-300)
+ .size([w,h]);
+
+ return force;
+}
+*/
+
+function genForceCola(cola, d3, w, h) {
+ let force = cola.d3adaptor(d3)
+ .convergenceThreshold(0.1)
+ // .convergenceThreshold(1e-9)
+ .linkDistance(120)
+ .size([w,h]);
+
+ return force;
+}
+
+function activateForce(force, nodes, links, groups) {
+ force
+ .nodes(nodes)
+ .links(links)
+ .groups(groups)
+ //.symmetricDiffLinkLengths(25)
+ .handleDisconnected(true)
+ .avoidOverlaps(true)
+ .start(50, 100, 200);
+ //.start();
+}
+
+/*
+function resize() {
+ let width = mainElement.clientWidth;
+ let height = mainElement.clientHeight;
+
+ svg.attr('width', '100%') //width)
+ .attr('height', '100%'); //height);
+
+ force.size([
+ force.size()[0] + (width - w) / zoom.scale(),
+ force.size()[1] + (height - h) / zoom.scale()
+ ]).resume();
+
+ w = width;
+ h = height;
+}
+*/
+
+function renderView(force,
+ state,
+ mainEl,
+ groupsEl,
+ nodesEl,
+ linksEl,
+ drag,
+ zoom,
+ config,
+ onNodeOver,
+ onNodeOut,
+ onNodeClick) {
+
+ state.viewGraph = calcViewGraph(state.graph, state.viewGraph);
+
+ activateForce(force, state.viewGraph.nodes, state.viewGraph.links, state.viewGraph.groups);
+
+ zoom.on('zoom', zoomFn);
+
+ genSvgGroups(groupsEl, state.viewGraph.groups, drag, onRenderViewReq);
+
+ genSvgLinks(
+ linksEl, state.viewGraph.links,
+ config.nominal_stroke,
+ config.default_link_color,
+ config.initialLinkLabelsFontSize
+ );
+
+ genSvgNodes(
+ nodesEl, state.viewGraph.nodes, drag, onNodeOver, onNodeOut, onNodeClick,
+ function onGroupNodeClick(groupId) {
+ let group = R.find(R.propEq('_osid', groupId), state.graph.groups);
+ group.isExpanded = true;
+
+ state.viewGraph = renderView(force, state,
+ mainEl, groupsEl, nodesEl, linksEl,
+ drag, zoom, config,
+ onNodeOver, onNodeOut, onNodeClick);
+ });
+
+ force.on('tick', tickFn);
+
+ function onRenderViewReq() {
+ state.viewGraph = renderView(force, state,
+ mainEl, groupsEl, nodesEl, linksEl,
+ drag, zoom, config,
+ onNodeOver, onNodeOut, onNodeClick);
+ }
+
+ function tickFn() {
+ let svgGroups = mainEl.selectAll('.group');
+ svgGroups
+ .attr('x', function (d) {
+ return R.path(['bounds', 'x'], d);
+ })
+ .attr('y', function (d) {
+ return R.path(['bounds', 'y'], d);
+ })
+ .attr('width', function (d) {
+ if (d.bounds) { return d.bounds.width(); }
+ })
+ .attr('height', function (d) {
+ if (d.bounds) { return d.bounds.height(); }
+ });
+
+ let svgNodes = mainEl.selectAll('.node');
+ svgNodes.attr('transform', function(d) {
+ return 'translate(' + d.x + ',' + d.y + ')';
+ });
+
+ let svgLinkLines = mainEl.selectAll('.link-group').selectAll('.link-line');
+ svgLinkLines
+ .attr('x1', function(d) {
+ return d.source.x;
+ })
+ .attr('y1', function(d) { return d.source.y; })
+ .attr('x2', function(d) { return d.target.x; })
+ .attr('y2', function(d) { return d.target.y; });
+
+ let svgLinkLabels = mainEl.selectAll('.link-group').selectAll('.link-label');
+ svgLinkLabels
+ .attr('x', function(d) {
+ return (d.source.x + (d.target.x - d.source.x) * 0.5);
+ })
+ .attr('y', function(d) {
+ return (d.source.y + (d.target.y - d.source.y) * 0.5);
+ });
+
+ }
+
+ function zoomFn() {
+ mainEl.attr('transform', d3.event.transform);
+
+ let trn = d3.event.transform;
+
+ let maxZoomAllowedForNodes = 1.8;
+ let imageInitialLength = 36;
+ let imageLength;
+
+ if (trn.k > maxZoomAllowedForNodes) {
+ imageLength = (imageInitialLength / trn.k) * maxZoomAllowedForNodes;
+ } else {
+ imageLength = imageInitialLength;
+ }
+
+ let svgImages = mainEl.selectAll('.node-image');
+ svgImages
+ .attr('x', -(Math.floor(imageLength / 2)))
+ .attr('y', -(Math.floor(imageLength / 2)))
+ .attr('width', imageLength)
+ .attr('height', imageLength)
+ ;
+
+ let labelsFontSize;
+
+ if (trn.k > maxZoomAllowedForNodes) {
+ labelsFontSize = (config.initialLinkLabelsFontSize / trn.k) * maxZoomAllowedForNodes;
+ } else {
+ labelsFontSize = config.initialLinkLabelsFontSize;
+ }
+
+ let svgLinkLabels = mainEl.selectAll('.link-group').selectAll('.link-label');
+ svgLinkLabels
+ .attr('font-size', labelsFontSize);
+ }
+
+ return state.viewGraph;
+}
+
+function genSvgGroups(g, groups, drag, onRenderViewReq) {
+ let svgGroups = g.selectAll('.group')
+ .data(groups, (d) => d._osid);
+
+ //let rects =
+ svgGroups.enter()
+ .append('rect')
+ .attr('rx', 8)
+ .attr('ry', 8)
+ .attr('class', 'group')
+ .attr('data-group-id', (d) => d._osid)
+ .style('fill', function (_d, _i) { return 'lightblue'; })
+ .call(drag)
+ .on('click', function (d) {
+ console.log('click', d);
+ d.isExpanded = !d.isExpanded;
+ onRenderViewReq();
+ });
+
+ svgGroups.exit()
+ .remove();
+
+ return svgGroups;
+}
+function calcViewGraph(graph, prevViewGraph) {
+ let {groups, rejectedGroups} = calcGroupsAndRejectedGroups(graph.groups);
+ let newClosedGroupNodes = calcClosedGroupsNodes(rejectedGroups, prevViewGraph.nodes);
+ let {nodes, rejectedNodes} = calcNodesAndRejectedNodes(graph.nodes, graph.groups);
+ nodes = R.concat(newClosedGroupNodes, nodes);
+
+ let {links, rejectedSourceLinks, rejectedTargetLinks, rejectedBothLinks} =
+ calcLinksAndRejectedLinks(graph.links, rejectedNodes);
+
+ let newLinksForRejectedSource =
+ calcNewLinksForRejectedSource(rejectedSourceLinks, nodes, prevViewGraph.links);
+
+ let newLinksForRejectedTarget =
+ calcNewLinksForRejectedTarget(rejectedTargetLinks, nodes, prevViewGraph.links);
+
+ let newLinksForRejectedBoth =
+ calcNewLinksForRejectedBoth(rejectedBothLinks, nodes, prevViewGraph.links);
+
+ links = R.pipe(
+ R.concat(newLinksForRejectedSource),
+ R.concat(newLinksForRejectedTarget),
+ R.concat(newLinksForRejectedBoth)
+ )(links);
+
+ return {
+ nodes,
+ links,
+ groups
+ };
+}
+
+function calcGroupsAndRejectedGroups(originalGroups) {
+ let rejectedGroups = R.filter(R.propEq('isExpanded', false), originalGroups);
+ let groups = R.reject(R.propEq('isExpanded', false), originalGroups);
+
+ return { groups, rejectedGroups };
+}
+
+function calcClosedGroupsNodes(rejectedGroups, prevViewNodes) {
+ return R.reduce((acc, group) => {
+ let nodeId = `${group._osid}-group-node`;
+ let prevNode = R.find(R.propEq('_osid', nodeId), prevViewNodes);
+ if (prevNode) {
+ return R.append(prevNode, acc);
+ }
+
+ return R.append({
+ _osid: nodeId,
+ _osmeta: {
+ type: 'view_group',
+ nodeId: group._osid,
+ },
+ width: 60,
+ height: 40,
+ name: group._osid
+ }, acc);
+ }, [], rejectedGroups);
+}
+
+function calcNodesAndRejectedNodes(originalNodes, originalGroups) {
+ let rejectedNodes = [];
+ let nodes = R.reject((node) => {
+ let host = R.path(['_osmeta', 'host'], node);
+ if (R.isNil(host)) { return false; }
+
+ let group = R.find(R.propEq('_osid', host), originalGroups);
+ if (R.isNil(group)) { return false; }
+
+ if (group.isExpanded) { return false; }
+
+ rejectedNodes = R.append(node, rejectedNodes);
+ return true;
+ }, originalNodes);
+
+ return { nodes, rejectedNodes };
+}
+
+function calcLinksAndRejectedLinks(originalLinks, rejectedNodes) {
+ return R.reduce((acc, link) => {
+ let sourceRejected = R.contains(link.source, rejectedNodes);
+ let targetRejected = R.contains(link.target, rejectedNodes);
+
+ if (sourceRejected && targetRejected) {
+ acc = R.assoc('rejectedBothLinks', R.append(link, acc.rejectedBothLinks), acc);
+ return acc;
+ }
+
+ if (sourceRejected) {
+ acc = R.assoc('rejectedSourceLinks', R.append(link, acc.rejectedSourceLinks), acc);
+ return acc;
+ }
+
+ if (targetRejected) {
+ acc = R.assoc('rejectedTargetLinks', R.append(link, acc.rejectedTargetLinks), acc);
+ return acc;
+ }
+
+ acc = R.assoc('links', R.append(link, acc.links), acc);
+ return acc;
+ },
+ {links: [], rejectedSourceLinks: [], rejectedTargetLinks: [], rejectedBothLinks: [] },
+ originalLinks);
+}
+
+function calcNewLinksForRejectedSource(rejectedSourceLinks, nodes, prevLinks) {
+ let newLinksForRejectedSource = R.reduce((acc, link) => {
+ let host = R.path(['_osmeta', 'host'], link.source);
+ let groupNodeId = `${host}-group-node`;
+ let newSource = R.find(R.propEq('_osid', groupNodeId), nodes);
+ if (R.isNil(newSource)) {
+ throw 'error in new links for rejected source function';
+ }
+
+ let newLinkId = `${newSource._osid}:${link.target._osid}:rejected-source`;
+
+ let existingLink = R.find(R.propEq('_osid', newLinkId), acc);
+ if (existingLink) {
+ return acc;
+ }
+
+ let prevExistingLink = R.find(R.propEq('_osid', newLinkId), prevLinks);
+ if (prevExistingLink) {
+ return R.append(prevExistingLink, acc);
+ }
+
+ return R.append({
+ source: newSource ,
+ target: link.target,
+ label: link.label,
+ _osid: newLinkId
+ }, acc);
+ }, [], rejectedSourceLinks);
+
+ return newLinksForRejectedSource;
+}
+
+function calcNewLinksForRejectedTarget(rejectedLinks, nodes, prevLinks) {
+ let newLinks = R.reduce((acc, link) => {
+ let host = R.path(['_osmeta', 'host'], link.target);
+ let groupNodeId = `${host}-group-node`;
+ let newTarget = R.find(R.propEq('_osid', groupNodeId), nodes);
+ if (R.isNil(newTarget)) {
+ throw 'error in new links for rejected target function';
+ }
+
+ let newLinkId = `${link.source._osid}:${newTarget._osid}:rejected-target`;
+
+ let existingLink = R.find(R.propEq('_osid', newLinkId), acc);
+ if (existingLink) {
+ return acc;
+ }
+
+ let prevExistingLink = R.find(R.propEq('_osid', newLinkId), prevLinks);
+ if (prevExistingLink) {
+ return R.append(prevExistingLink, acc);
+ }
+
+ return R.append({
+ source: link.source ,
+ target: newTarget,
+ label: link.label,
+ _osid: newLinkId
+ }, acc);
+ }, [], rejectedLinks);
+
+ return newLinks;
+}
+
+function calcNewLinksForRejectedBoth(rejectedLinks, nodes, prevLinks) {
+ let newLinks = R.reduce((acc, link) => {
+ let targetHost = R.path(['_osmeta', 'host'], link.target);
+ let sourceHost = R.path(['_osmeta', 'host'], link.source);
+ let groupSourceNodeId = `${sourceHost}-group-node`;
+ let groupTargetNodeId = `${targetHost}-group-node`;
+
+ if (targetHost === sourceHost) {
+ return acc;
+ }
+
+ let newLinkId = `${sourceHost}:${targetHost}:groups-link`;
+ let existingNewLink = R.find(R.propEq('_osid', newLinkId), acc);
+ if (existingNewLink) {
+ return acc;
+ }
+
+ let prevExistingLink = R.find(R.propEq('_osid', newLinkId), prevLinks);
+ if (prevExistingLink) {
+ return R.append(prevExistingLink, acc);
+ }
+
+ let newSource = R.find(R.propEq('_osid', groupSourceNodeId), nodes);
+ let newTarget = R.find(R.propEq('_osid', groupTargetNodeId), nodes);
+
+ let newLink = {
+ source: newSource,
+ target: newTarget,
+ label: 'hosts link',
+ _osid: newLinkId
+ };
+
+ return R.append(newLink, acc);
+ }, [], rejectedLinks);
+
+ return newLinks;
+}
diff --git a/ui/imports/ui/components/network-graph/network-graph.styl b/ui/imports/ui/components/network-graph/network-graph.styl
new file mode 100644
index 0000000..114cc96
--- /dev/null
+++ b/ui/imports/ui/components/network-graph/network-graph.styl
@@ -0,0 +1,20 @@
+.os-network-graph
+ width: 100%;
+ height: 100%;
+
+ .sm-graph
+ width: 100%;
+ height: 100%;
+
+ .group {
+ stroke: #fff;
+ stroke-width: 1.5px;
+ cursor: move;
+ opacity: 0.7;
+ }
+
+
+ .link-group
+ text
+ font: bold sans-serif;
+ fill: rgba(8, 8, 8, 0.73);
diff --git a/ui/imports/ui/components/network-info-box/network-info-box.html b/ui/imports/ui/components/network-info-box/network-info-box.html
new file mode 100644
index 0000000..b9e07f2
--- /dev/null
+++ b/ui/imports/ui/components/network-info-box/network-info-box.html
@@ -0,0 +1,38 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="NetworkInfoBox">
+ <div class="cards-450 white flex-box-1">
+ <div class="flex-box ">
+ <div class="flex-box-1">
+ <i class="material-icons">device_hub</i>
+ </div>
+ <div class="flex-box-2">
+ <h3>Network name:<br> {{ network.name }}</h3>
+ <table class="table table-striped">
+ <tbody>
+ <tr>
+ <th>Last Scanning</th>
+ <td></td>
+ </tr>
+ <tr>
+ <th>Ports count</th>
+ <td>{{ portsCount }}</td>
+ </tr>
+ <tr>
+ <th>Instaces</th>
+ <td></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/ui/imports/ui/components/network-info-box/network-info-box.js b/ui/imports/ui/components/network-info-box/network-info-box.js
new file mode 100644
index 0000000..8843c5c
--- /dev/null
+++ b/ui/imports/ui/components/network-info-box/network-info-box.js
@@ -0,0 +1,69 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: NetworkInfoBox
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { regexEscape } from '/imports/lib/regex-utils';
+import { Inventory } from '/imports/api/inventories/inventories';
+
+import './network-info-box.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.NetworkInfoBox.onCreated(function() {
+ var instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ portsCount: 0
+ });
+
+ instance.autorun(function () {
+ let network = instance.data.network;
+ instance.subscribe('inventory?id_path_like&type', network.id_path, 'port');
+
+ let idPathExp = new RegExp(regexEscape(network.id));
+ instance.state.set('portsCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'port'
+ }).count());
+ });
+
+});
+
+/*
+Template.NetworkInfoBox.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.NetworkInfoBox.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.NetworkInfoBox.helpers({
+ portsCount: function () {
+ let instance = Template.instance();
+ return instance.state.get('portsCount');
+ }
+});
+
+
diff --git a/ui/imports/ui/components/network-info-box/network-info-box.styl b/ui/imports/ui/components/network-info-box/network-info-box.styl
new file mode 100644
index 0000000..5228d20
--- /dev/null
+++ b/ui/imports/ui/components/network-info-box/network-info-box.styl
@@ -0,0 +1,2 @@
+/* Set the component style here */
+// "NetworkInfoBox"
diff --git a/ui/imports/ui/components/new-scanning/new-scanning.html b/ui/imports/ui/components/new-scanning/new-scanning.html
new file mode 100644
index 0000000..acd65bc
--- /dev/null
+++ b/ui/imports/ui/components/new-scanning/new-scanning.html
@@ -0,0 +1,53 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="NewScanning">
+<div class="os-new-scanning">
+ <div class="sm-content cards white">
+ <ul class="nav nav-tabs" role="tablist">
+ <li role="presentation"
+ class="active">
+ <a href="#newImmediateScan"
+ aria-controls=""
+ role="tab"
+ data-toggle="tab"
+ id=""
+ data-is-disabled=""
+ class="sm-tab-link"
+ >Run a Scan Now</a>
+ </li>
+ <li role="presentation">
+ <a href="#newScheduledScan"
+ aria-controls="#newScheduledScan"
+ role="tab"
+ data-toggle="tab"
+ id="link-scheduled-schen"
+ data-is-disabled="false"
+ class="sm-tab-link"
+ >Schedule a Scan</a>
+ </li>
+ </ul>
+
+ <!-- Tab panes -->
+ <div class="tab-content">
+ <div role="tabpanel"
+ class="tab-pane fade active in"
+ id="newImmediateScan">
+ {{>ScanningRequest (argsScanningRequest env) }}
+ </div>
+ <div role="tabpanel"
+ class="tab-pane fade"
+ id="newScheduledScan">
+ {{>ScheduledScan (argsScheduledScan env) }}
+ </div>
+ </div>
+ </div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/new-scanning/new-scanning.js b/ui/imports/ui/components/new-scanning/new-scanning.js
new file mode 100644
index 0000000..1995ded
--- /dev/null
+++ b/ui/imports/ui/components/new-scanning/new-scanning.js
@@ -0,0 +1,73 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: NewScanning
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { ReactiveDict } from 'meteor/reactive-dict';
+
+import './new-scanning.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.NewScanning.onCreated(function() {
+ let instance = this;
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ env: null,
+ });
+
+ instance.autorun(function (env) {
+ let data = Template.currentData();
+ new SimpleSchema({
+ env: { type: String, optional: true },
+ }).validate(data);
+
+ instance.state.set('env', env);
+ });
+});
+
+/*
+Template.NewScanning.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.NewScanning.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.NewScanning.helpers({
+ argsScanningRequest: function (env) {
+ return {
+ action: 'insert',
+ env: env,
+ };
+ },
+
+ argsScheduledScan: function (env) {
+ return {
+ action: 'insert',
+ env: env,
+ };
+ },
+}); // end: helpers
+
+
diff --git a/ui/imports/ui/components/new-scanning/new-scanning.styl b/ui/imports/ui/components/new-scanning/new-scanning.styl
new file mode 100644
index 0000000..e7c83fe
--- /dev/null
+++ b/ui/imports/ui/components/new-scanning/new-scanning.styl
@@ -0,0 +1,7 @@
+.os-new-scanning
+ display: flex;
+ flex-flow: row nowrap;
+ padding: 20px;
+
+ .sm-content
+ flex: 1;
diff --git a/ui/imports/ui/components/pager/pager.html b/ui/imports/ui/components/pager/pager.html
new file mode 100644
index 0000000..2e14bbd
--- /dev/null
+++ b/ui/imports/ui/components/pager/pager.html
@@ -0,0 +1,42 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="Pager">
+<div class="os-pager">
+ <nav aria-label="Page navigation">
+ <ul class="pagination">
+ <li class="{{#if disablePrev}}disabled{{/if}}">
+ <a class="sm-first-button" aria-label="First">
+ <span aria-hidden="true">first</span>
+ </a>
+ </li>
+ <li class="{{#if disablePrev}}disabled{{/if}}">
+ <a class="sm-prev-button" aria-label="Previous">
+ <span aria-hidden="true">previous</span>
+ </a>
+ </li>
+ {{#each pageButton in pagesButtons }}
+ <li class="{{#if (isCurrentPage pageButton.number currentPage)}}active{{/if}}"><a class="sm-page-button" data-page-number="{{ pageButton.number }}"
+ >{{ pageButton.label }}</a></li>
+ {{/each }}
+ <li class="{{#if disableNext}}disabled{{/if}}">
+ <a class="sm-next-button" aria-label="Next">
+ <span aria-hidden="true">next</span>
+ </a>
+ </li>
+ <li class="{{#if disableNext}}disabled{{/if}}">
+ <a class="sm-last-button" aria-label="Next">
+ <span aria-hidden="true">last</span>
+ </a>
+ </li>
+ </ul>
+ </nav>
+</div>
+</template>
diff --git a/ui/imports/ui/components/pager/pager.js b/ui/imports/ui/components/pager/pager.js
new file mode 100644
index 0000000..19b2789
--- /dev/null
+++ b/ui/imports/ui/components/pager/pager.js
@@ -0,0 +1,123 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: Pager
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import * as R from 'ramda';
+import { Template } from 'meteor/templating';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { ReactiveDict } from 'meteor/reactive-dict';
+
+import './pager.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.Pager.onCreated(function() {
+ var instance = this;
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ pagesButtons: [{ label: '1', number: 1 }],
+ currentPage: 1,
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+ new SimpleSchema({
+ disableNext: { type: Boolean },
+ disablePrev: { type: Boolean },
+ totalPages: { type: Number },
+ currentPage: { type: Number },
+ onReqNext: { type: Function },
+ onReqPrev: { type: Function },
+ onReqPage: { type: Function },
+ onReqFirst: { type: Function },
+ onReqLast: { type: Function },
+ }).validate(data);
+
+ instance.state.set('totalPages', data.totalPages);
+ instance.state.set('currentPage', data.currentPage);
+ });
+
+ instance.autorun(function () {
+ let numOfPagesInPager = 5;
+ let totalPages = instance.state.get('totalPages');
+ let currentPage = instance.state.get('currentPage');
+ let first = R.ifElse((x) => x < 1, R.always(1), R.identity)(currentPage - numOfPagesInPager + 1);
+ let last = R.ifElse((x) => x > totalPages, R.always(totalPages + 1), R.identity)(
+ first + numOfPagesInPager);
+
+ let pagesButtons = R.map((pageNumber) => {
+ return {
+ label: R.toString(pageNumber), number: pageNumber
+ };
+ }, R.range(first, last));
+
+ instance.state.set('pagesButtons', pagesButtons);
+ });
+});
+
+/*
+Template.Pager.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.Pager.events({
+ 'click .sm-prev-button': function (_event, _instance) {
+ let data = Template.currentData();
+ data.onReqPrev();
+ },
+
+ 'click .sm-next-button': function (_event, _instance) {
+ let data = Template.currentData();
+ data.onReqNext();
+ },
+
+ 'click .sm-first-button': function (_event, _instance) {
+ let data = Template.currentData();
+ data.onReqFirst();
+ },
+
+ 'click .sm-last-button': function (_event, _instance) {
+ let data = Template.currentData();
+ data.onReqLast();
+ },
+
+ 'click .sm-page-button': function (event, _instance) {
+ let data = Template.currentData();
+ let pageNumber = parseInt(event.target.dataset.pageNumber);
+ data.onReqPage(pageNumber);
+ },
+
+
+});
+
+/*
+ * Helpers
+ */
+
+Template.Pager.helpers({
+ pagesButtons: function () {
+ let instance = Template.instance();
+ return instance.state.get('pagesButtons');
+ },
+
+ isCurrentPage: function (pageNum, currentPage) {
+ return pageNum === currentPage;
+ },
+}); // end: helpers
+
+
diff --git a/ui/imports/ui/components/pager/pager.styl b/ui/imports/ui/components/pager/pager.styl
new file mode 100644
index 0000000..3843ccd
--- /dev/null
+++ b/ui/imports/ui/components/pager/pager.styl
@@ -0,0 +1,4 @@
+.os-pager
+ .cl-disabled
+ color: gray;
+
diff --git a/ui/imports/ui/components/project-dashboard/project-dashboard.html b/ui/imports/ui/components/project-dashboard/project-dashboard.html
new file mode 100644
index 0000000..fd8365e
--- /dev/null
+++ b/ui/imports/ui/components/project-dashboard/project-dashboard.html
@@ -0,0 +1,38 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="ProjectDashboard">
+
+ <div class="os-project-dashboard flex-box justify-content-between">
+ <div class="flex-box-3 main-layout-no-nav">
+
+ <div class="flex">
+ <div class="flex-box-1 cards white title">
+ <h4>Project name: {{ project.name }}</h4>
+ </div>
+ </div>
+
+ <div class="sm-info-boxes">
+ {{#each infoBox in infoBoxes }}
+ {{> DataCubic (genArgsInfoBox infoBox) }}
+ {{/each }}
+ </div>
+
+ <div class="sm-list-info-boxes">
+ {{#each network in networks }}
+ <div class="sm-item">
+ {{> NetworkInfoBox (argsNetworkInfoBox network) }}
+ </div>
+ {{/each }}
+ </div>
+
+ </div>
+ </div>
+</template>
diff --git a/ui/imports/ui/components/project-dashboard/project-dashboard.js b/ui/imports/ui/components/project-dashboard/project-dashboard.js
new file mode 100644
index 0000000..6600dc5
--- /dev/null
+++ b/ui/imports/ui/components/project-dashboard/project-dashboard.js
@@ -0,0 +1,149 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: ProjectDashboard
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import * as R from 'ramda';
+
+
+import { Inventory } from '/imports/api/inventories/inventories';
+import { store } from '/imports/ui/store/store';
+import { Icon } from '/imports/lib/icon';
+import { regexEscape } from '/imports/lib/regex-utils';
+
+import '/imports/ui/components/accordion-nav-menu/accordion-nav-menu';
+
+import '/imports/ui/components/network-info-box/network-info-box';
+
+import './project-dashboard.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.ProjectDashboard.onCreated(function() {
+ var instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ _id: null,
+ id_path: null,
+ infoBoxes: [{
+ header: ['components', 'projectDashboard', 'infoBoxes', 'networks', 'header'],
+ dataSource: 'networksCount',
+ icon: { type: 'material', name: 'device_hub' },
+ theme: 'dark'
+ }, {
+ header: ['components', 'projectDashboard', 'infoBoxes', 'ports', 'header'],
+ dataSource: 'portsCount',
+ icon: { type: 'material', name: 'settings_input_hdmi' },
+ theme: 'dark'
+ }],
+ networksCount: 0,
+ portsCount: 0,
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+
+ new SimpleSchema({
+ _id: { type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } } },
+ onNodeSelected: { type: Function },
+ }).validate(data);
+
+ instance.state.set('_id', data._id);
+ });
+
+ instance.autorun(function () {
+ let _id = instance.state.get('_id');
+
+ instance.subscribe('inventory?_id', _id);
+
+ Inventory.find({ _id: _id }).forEach((project) => {
+ instance.state.set('id_path', project.id_path);
+
+ instance.subscribe('inventory?id_path', project.id_path);
+ instance.subscribe('inventory?id_path_start&type', project.id_path, 'network');
+ instance.subscribe('inventory?id_path_start&type', project.id_path, 'port');
+
+ let idPathExp = new RegExp(`^${regexEscape(project.id_path)}`);
+
+ instance.state.set('networksCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'network'
+ }).count());
+
+ instance.state.set('portsCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'port'
+ }).count());
+ });
+ });
+});
+
+/*
+Template.ProjectDashboard.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.ProjectDashboard.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.ProjectDashboard.helpers({
+ project: function () {
+ let instance = Template.instance();
+ let _id = instance.state.get('_id');
+ return Inventory.findOne({ _id: _id });
+ },
+
+ infoBoxes: function () {
+ let instance = Template.instance();
+ return instance.state.get('infoBoxes');
+ },
+
+ networks: function () {
+ let instance = Template.instance();
+ let project_id_path = instance.state.get('id_path');
+ let idPathExp = new RegExp(`^${regexEscape(project_id_path)}`);
+ return Inventory.find({
+ id_path: idPathExp,
+ type: 'network'
+ });
+ },
+
+ genArgsInfoBox: function (infoBox) {
+ let instance = Template.instance();
+
+ return {
+ header: R.path(infoBox.header, store.getState().api.i18n),
+ dataInfo: instance.state.get(infoBox.dataSource).toString(),
+ icon: new Icon(infoBox.icon),
+ theme: infoBox.theme
+ };
+ },
+
+ argsNetworkInfoBox: function (network) {
+ return {
+ network: network
+ };
+ }
+});
diff --git a/ui/imports/ui/components/project-dashboard/project-dashboard.styl b/ui/imports/ui/components/project-dashboard/project-dashboard.styl
new file mode 100644
index 0000000..01e2a67
--- /dev/null
+++ b/ui/imports/ui/components/project-dashboard/project-dashboard.styl
@@ -0,0 +1,14 @@
+/* Set the component style here */
+.os-project-dashboard
+ .sm-info-boxes
+ display: flex
+ flex-flow: row wrap;
+ justify-content: space-around
+
+ .sm-list-info-boxes
+ display: flex;
+ flex-flow: row wrap
+ justify-content: space-around
+
+ >.sm-item
+ margin: 10px;
diff --git a/ui/imports/ui/components/region-dashboard/region-dashboard.html b/ui/imports/ui/components/region-dashboard/region-dashboard.html
new file mode 100644
index 0000000..93967a5
--- /dev/null
+++ b/ui/imports/ui/components/region-dashboard/region-dashboard.html
@@ -0,0 +1,38 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="RegionDashboard">
+ <div class="os-region-dashboard flex-box justify-content-between">
+ <div class="flex-box-3 main-layout-no-nav">
+
+ <div class="flex">
+ <div class="flex-box-1 cards white title">
+ <h4>Region name: {{ region.name }}</h4>
+ </div>
+ </div>
+
+ <div class="sm-info-boxes">
+ {{#each infoBox in infoBoxes }}
+ {{> DataCubic (argsInfoBox infoBox) }}
+ {{/each }}
+ </div>
+
+ <!-- flex box for regions and projects -->
+ <div class="sm-list-info-boxes">
+ {{#each listInfoBox in listInfoBoxes }}
+ <div class="sm-item">
+ {{> ListInfoBox (argsListInfoBox listInfoBox) }}
+ </div>
+ {{/each }}
+ </div>
+
+ </div>
+ </div>
+</template>
diff --git a/ui/imports/ui/components/region-dashboard/region-dashboard.js b/ui/imports/ui/components/region-dashboard/region-dashboard.js
new file mode 100644
index 0000000..23d90da
--- /dev/null
+++ b/ui/imports/ui/components/region-dashboard/region-dashboard.js
@@ -0,0 +1,220 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: RegionDashboard
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import * as R from 'ramda';
+import { Inventory } from '/imports/api/inventories/inventories';
+import { store } from '/imports/ui/store/store';
+import { Icon } from '/imports/lib/icon';
+import { regexEscape } from '/imports/lib/regex-utils';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+//import { setCurrentNode } from '/imports/ui/actions/navigation';
+
+import '/imports/ui/components/accordion-nav-menu/accordion-nav-menu';
+import '/imports/ui/components/data-cubic/data-cubic';
+import '/imports/ui/components/list-info-box/list-info-box';
+
+import './region-dashboard.html';
+
+let infoBoxes = [{
+ header: ['components', 'regionDashboard', 'infoBoxes', 'instances', 'header'],
+ dataSource: 'instancesCount',
+ icon: { type: 'fa', name: 'desktop' },
+ theme: 'dark'
+}, {
+ header: ['components', 'regionDashboard', 'infoBoxes', 'vServices', 'header'],
+ dataSource: 'vServicesCount',
+ icon: { type: 'fa', name: 'object-group' },
+ theme: 'dark'
+}, {
+ header: ['components', 'regionDashboard', 'infoBoxes', 'hosts', 'header'],
+ dataSource: 'hostsCount',
+ icon: { type: 'fa', name: 'server' },
+ theme: 'dark'
+}, {
+ header: ['components', 'regionDashboard', 'infoBoxes', 'vConnectors', 'header'],
+ dataSource: 'vConnectorsCount',
+ icon: { type: 'fa', name: 'compress' },
+ theme: 'dark'
+}];
+
+let listInfoBoxes = [{
+ header: ['components', 'regionDashboard', 'listInfoBoxes', 'availabilityZones', 'header'],
+ listName: 'availabilityZones',
+ listItemFormat: {
+ getLabelFn: (item) => { return item.name; },
+ getValueFn: (item) => { return item._id._str; },
+ },
+ icon: { type: 'material', name: 'developer_board' },
+}, {
+ header: ['components', 'regionDashboard', 'listInfoBoxes', 'aggregates', 'header'],
+ listName: 'aggregates',
+ listItemFormat: {
+ getLabelFn: (item) => { return item.name; },
+ getValueFn: (item) => { return item._id._str; },
+ },
+ icon: { type: 'material', name: 'storage' },
+}];
+
+/*
+ * Lifecycles
+ */
+
+Template.RegionDashboard.onCreated(function() {
+ var instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ _id: null,
+ id_path: null,
+ instancesCount: 0,
+ vServicesCount: 0,
+ hostsCount: 0,
+ vConnectors: 0,
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+ new SimpleSchema({
+ _id: { type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } } },
+ onNodeSelected: { type: Function },
+ }).validate(data);
+
+ instance.state.set('_id', data._id);
+ });
+
+ instance.autorun(function () {
+ let _id = instance.state.get('_id');
+
+ instance.subscribe('inventory?_id', _id);
+ Inventory.find({ _id: _id }).forEach((region) => {
+ instance.state.set('id_path', region.id_path);
+
+ instance.subscribe('inventory?id_path', region.id_path);
+ instance.subscribe('inventory?id_path_start&type', region.id_path, 'instance');
+ instance.subscribe('inventory?id_path_start&type', region.id_path, 'vservice');
+ instance.subscribe('inventory?id_path_start&type', region.id_path, 'host');
+ instance.subscribe('inventory?id_path_start&type', region.id_path, 'vconnector');
+ instance.subscribe('inventory?id_path_start&type', region.id_path, 'availability_zone');
+ instance.subscribe('inventory?id_path_start&type', region.id_path, 'aggregate');
+
+ let idPathExp = new RegExp(`^${regexEscape(region.id_path)}`);
+
+ instance.state.set('instancesCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'instance'
+ }).count());
+
+ instance.state.set('vServicesCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'vservice'
+ }).count());
+
+ instance.state.set('hostsCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'host'
+ }).count());
+
+ instance.state.set('vConnectorsCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'vconnector'
+ }).count());
+ });
+
+ });
+
+});
+
+/*
+Template.RegionDashboard.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.RegionDashboard.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.RegionDashboard.helpers({
+ region: function () {
+ let instance = Template.instance();
+ let _id = instance.state.get('_id');
+
+ return Inventory.findOne({ _id: _id });
+ },
+
+ infoBoxes: function () {
+ return infoBoxes;
+ },
+
+ listInfoBoxes: function () {
+ return listInfoBoxes;
+ },
+
+ argsInfoBox: function (infoBox) {
+ let instance = Template.instance();
+
+ return {
+ header: R.path(infoBox.header, store.getState().api.i18n),
+ dataInfo: instance.state.get(infoBox.dataSource).toString(),
+ icon: new Icon(infoBox.icon),
+ theme: infoBox.theme
+ };
+ },
+
+ argsListInfoBox: function (listInfoBox) {
+ let instance = Template.instance();
+ let data = Template.currentData();
+ let region_id_path = instance.state.get('id_path');
+
+ return {
+ header: R.path(listInfoBox.header, store.getState().api.i18n),
+ list: getList(listInfoBox.listName, region_id_path),
+ //dataInfo: instance.state.get(infoBox.dataSource).toString(),
+ icon: new Icon(listInfoBox.icon),
+ //theme: infoBox.theme
+ listItemFormat: listInfoBox.listItemFormat,
+ onItemSelected: function (itemKey) {
+ data.onNodeSelected(new Mongo.ObjectID(itemKey));
+ }
+ };
+ },
+});
+
+function getList(listName, parentIdPath) {
+ let idPathExp = new RegExp(`^${regexEscape(parentIdPath)}`);
+
+ switch (listName) {
+ case 'availabilityZones':
+ return Inventory.find({
+ id_path: idPathExp,
+ type: 'availability_zone'
+ });
+
+ case 'aggregates':
+ return Inventory.find({
+ id_path: idPathExp,
+ type: 'aggregate'
+ });
+
+ default:
+ throw 'unknowned list type';
+ }
+}
diff --git a/ui/imports/ui/components/region-dashboard/region-dashboard.styl b/ui/imports/ui/components/region-dashboard/region-dashboard.styl
new file mode 100644
index 0000000..044760f
--- /dev/null
+++ b/ui/imports/ui/components/region-dashboard/region-dashboard.styl
@@ -0,0 +1,14 @@
+/* Set the component style here */
+.os-region-dashboard
+ .sm-info-boxes
+ display: flex
+ flex-flow: row wrap;
+ justify-content: space-around
+
+ .sm-list-info-boxes
+ display: flex;
+ flex-flow: row wrap
+ justify-content: space-around
+
+ >.sm-item
+ margin: 10px;
diff --git a/ui/imports/ui/components/scanning-request/scanning-request.html b/ui/imports/ui/components/scanning-request/scanning-request.html
new file mode 100644
index 0000000..9f0e044
--- /dev/null
+++ b/ui/imports/ui/components/scanning-request/scanning-request.html
@@ -0,0 +1,74 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="ScanningRequest">
+ <div class="os-scanning-request cards white">
+ {{#if (getState 'beforeInsert') }}
+ {{#if notificationsExists}}
+ <div class="sm-notification-panel alert alert-danger">
+ {{#each note in notifications }}
+ <div>{{ note }}</div>
+ {{/each }}
+ </div>
+ {{/if}}
+ {{/if}}
+
+ <h3>{{ pageHeader }}</h3>
+ <div class="sm-form-container">
+ <form role="form" class="form-horizontal">
+
+ {{#each commandOption in commandOptions }}
+
+ <div class="form-group">
+ <label class="col-sm-2 control-label">{{ commandOption.info.label }}</label>
+ <div class="col-sm-3">
+ {{#if (isCommandOptionSelectType commandOption)}}
+ {{> SelectModel(createSelectArgs
+ values=(getModelKeyValue commandOption.name)
+ key=commandOption.name
+ options=(calcCommandSelectOptions commandOption)
+ setModel=(calcSetModelFn commandOption.name)
+ disabled=(isCommandDisabled commandOption.info.disabled)
+ )
+ }}
+ {{else}}
+ {{> InputModel(createInputArgs
+ value=(getModelKeyValue commandOption.name)
+ key=commandOption.name
+ type=(calcInputType commandOption.info)
+ disabled=(isCommandDisabled commandOption.info.disabled)
+ )
+ }}
+ {{/if}}
+ </div>
+ </div>
+
+ {{/each }}
+
+ {{#if isUpdateableAction }}
+ <button type="button"
+ class="js-submit-button mdl-button mdl-js-button mdl-button--raised
+ mdl-js-ripple-effect mdl-button--colored"
+ >Submit</button>
+ {{/if }}
+
+ </form>
+
+ {{#if (getState 'isMessage') }}
+ <div class="js-message-panel alert {{#if (getState 'isError')}}alert-danger{{/if}}
+ {{#if (getState 'isSuccess')}}alert-success{{/if}}"
+ role="alert">
+ {{ getState 'message' }}
+ </div>
+ {{/if }}
+
+ </div>
+ </div>
+</template>
diff --git a/ui/imports/ui/components/scanning-request/scanning-request.js b/ui/imports/ui/components/scanning-request/scanning-request.js
new file mode 100644
index 0000000..6181150
--- /dev/null
+++ b/ui/imports/ui/components/scanning-request/scanning-request.js
@@ -0,0 +1,371 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: ScanningRequest
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import * as R from 'ramda';
+
+import { Constants } from '/imports/api/constants/constants';
+//import { createInputArgs } from '/imports/ui/lib/input-model';
+import { createSelectArgs } from '/imports/ui/lib/select-model';
+import { Scans } from '/imports/api/scans/scans';
+
+import '/imports/ui/components/input-model/input-model';
+import '/imports/ui/components/select-model/select-model';
+
+import {
+ insert,
+} from '/imports/api/scans/methods';
+
+import './scanning-request.html';
+
+const noteTypeScanExists = {
+ type: 'scanExists',
+ message: 'There is already a scan in progess in the system. Please wait until it ends.'
+};
+
+/*
+ * Lifecycles
+ */
+
+Template.ScanningRequest.onCreated(function() {
+ let instance = this;
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ env: null,
+ action: 'insert',
+ isError: false,
+ isSuccess: false,
+ isMessage: false,
+ message: null,
+ disabled: false,
+ notifications: {},
+ model: {},
+ beforeInsert: true
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+
+ new SimpleSchema({
+ action: { type: String, allowedValues: ['insert', 'view', 'update'] },
+ env: { type: String, optional: true },
+ _id: {
+ type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } },
+ optional: true
+ },
+ }).validate(data);
+
+ switch (data.action) {
+ case 'insert':
+ initInsertView(instance, data);
+ break;
+
+ case 'view':
+ initViewView(instance, data);
+ break;
+
+ default:
+ throw 'unimplemented action';
+ }
+ });
+});
+
+/*
+Template.ScanningRequest.rendered = function() {
+};
+*/
+
+/*
+* Events
+*/
+
+Template.ScanningRequest.events({
+ 'click .js-submit-button': function(event, instance) {
+ submitItem(instance);
+ }
+});
+
+/*
+* Helpers
+*/
+
+Template.ScanningRequest.helpers({
+ getState: function (key) {
+ let instance = Template.instance();
+ return instance.state.get(key);
+ },
+
+ notifications: function () {
+ let instance = Template.instance();
+ let notifications = instance.state.get('notifications');
+ let notesExpaned = R.pipe(
+ R.map((noteType) => {
+ switch(noteType) {
+ case noteTypeScanExists.type:
+ return noteTypeScanExists.message;
+ default:
+ return '';
+ }
+ }),
+ R.values()
+ )(notifications);
+
+ return notesExpaned;
+ },
+
+ notificationsExists: function () {
+ let instance = Template.instance();
+ return R.keys(instance.state.get('notifications')).length > 0;
+ },
+
+ model: function () {
+ let instance = Template.instance();
+ return instance.state.get('model');
+ },
+
+ createInputArgs: function (params) {
+ let instance = Template.instance();
+
+ return {
+ value: params.hash.value,
+ type: params.hash.type,
+ classes: params.hash.classes,
+ placeholder: params.hash.placeholder,
+ disabled: params.hash.disabled,
+ setModel: function (value) {
+ let key = params.hash.key;
+ let model = instance.state.get('model');
+ let newModel = model;
+
+ if(R.indexOf(key, Scans.scansOnlyFields) >= 0) {
+ newModel = setRadioValues(Scans.scansOnlyFields, key, value, model);
+ }else {
+ newModel = R.assoc(key, value, newModel);
+ }
+
+ instance.state.set('model', newModel);
+ },
+ };
+ },
+
+ createSelectArgs: createSelectArgs,
+
+ calcSetModelFn: function (key) {
+ let instance = Template.instance();
+ let intf = {
+ fn: (values) => {
+ let model = instance.state.get('model');
+ let newModel = R.assoc(key, values, model);
+ instance.state.set('model', newModel);
+ },
+ sample: 'text'
+ };
+
+ return intf;
+ },
+
+ getFieldDesc: function (key) {
+ //let instance = Template.instance();
+ return Scans.schemaRelated[key].description;
+ },
+
+ commandOptions: function () {
+ let array = [];
+
+ R.mapObjIndexed((value, key) => {
+ array = R.append({
+ name: key,
+ info: value
+ }, array);
+ }, Scans.schemaRelated);
+
+ return array;
+ },
+
+ getModelKeyValue: function (key) {
+ let instance = Template.instance();
+ return R.path([key], instance.state.get('model'));
+ },
+
+ calcInputType: function(fieldInfo) {
+ if (fieldInfo.type == Boolean) {
+ return 'checkbox';
+ }
+
+ if (fieldInfo.type == String) {
+ return 'textbox';
+ }
+
+ return 'textbox';
+ },
+
+ isCommandOptionSelectType(commandOption) {
+ return (R.path(['info', 'subtype'], commandOption) === 'select');
+ },
+
+ calcCommandSelectOptions(commandOption) {
+ let item = Constants.findOne({ name: R.path(['info', 'options'], commandOption) });
+ if (R.isNil(item)) { return []; }
+ return item.data;
+ },
+
+ pageHeader() {
+ let instance = Template.instance();
+ let action = instance.state.get('action');
+
+ switch (action) {
+ case 'insert':
+ return 'New Scanning Request';
+
+ case 'view':
+ return 'Scan Information';
+
+ default:
+ return '';
+ }
+ },
+
+ isUpdateableAction() {
+ let instance = Template.instance();
+ let action = instance.state.get('action');
+
+ return R.contains(action, ['insert', 'update']);
+ },
+
+ isCommandDisabled(isSpecificCommandDisabled) {
+ let instance = Template.instance();
+ let action = instance.state.get('action');
+
+ return isSpecificCommandDisabled || (action === 'view');
+ }
+});
+
+function submitItem(instance) {
+ let action = instance.state.get('action');
+ let model = instance.state.get('model');
+
+ instance.state.set('isError', false);
+ instance.state.set('isSuccess', false);
+ instance.state.set('isMessage', false);
+ instance.state.set('message', null);
+
+ switch (action) {
+ case 'insert':
+ insert.call({
+ environment: model.environment,
+ inventory: model.inventory,
+ object_id: model.object_id,
+ log_level: model.log_level,
+ clear: model.clear,
+ scan_only_inventory: model.scan_only_inventory,
+ scan_only_links: model.scan_only_links,
+ scan_only_cliques: model.scan_only_cliques,
+ }, processActionResult.bind(null, instance));
+ break;
+ default:
+ // todo
+ break;
+ }
+}
+
+function setRadioValues(radioFields, key, value, modal) {
+ let newModal = modal;
+ let currentRadioFields = R.filter(f => modal[f], radioFields);
+
+ for(let field of currentRadioFields) {
+ newModal = R.assoc(field, false, newModal);
+ }
+
+ newModal = R.assoc(key, value, newModal);
+ return newModal;
+}
+
+function processActionResult(instance, error) {
+ let action = instance.state.get('action');
+
+ if (error) {
+ instance.state.set('isError', true);
+ instance.state.set('isSuccess', false);
+ instance.state.set('isMessage', true);
+
+ if (typeof error === 'string') {
+ instance.state.set('message', error);
+ } else {
+ instance.state.set('message', error.message);
+ }
+
+ } else {
+ instance.state.set('isError', false);
+ instance.state.set('isSuccess', true);
+ instance.state.set('isMessage', true);
+
+ if (action === 'insert') {
+ instance.state.set('message', 'Record had been added successfully');
+ instance.state.set('disabled', true);
+ instance.state.set('beforeInsert', false);
+ } else if (action === 'update') {
+ instance.state.set('message', 'Record had been updated successfully');
+ }
+ }
+}
+
+function initInsertView(instance, data) {
+ instance.state.set('action', data.action);
+ instance.state.set('env', data.env);
+ instance.state.set('model', Scans.schema.clean({
+ environment: instance.state.get('env')
+ }));
+
+ instance.subscribe('constants');
+ instance.subscribe('scans?env', data.env);
+
+ updateNotificationSameScanExistsForInsert(instance, data.env);
+
+ // todo
+}
+
+function updateNotificationSameScanExistsForInsert(instance, env) {
+ let notifications = instance.state.get('notifications');
+ if (Scans.find({
+ environment: env,
+ status: {
+ $in: ['pending', 'running']
+ } }).count() > 0) {
+
+ instance.state.set('notifications', R.assoc(
+ noteTypeScanExists.type,
+ noteTypeScanExists.type,
+ notifications
+ ));
+ } else {
+ instance.state.set('notifications', R.dissoc(
+ noteTypeScanExists.type,
+ notifications
+ ));
+ }
+}
+
+function initViewView(instance, data) {
+ instance.state.set('action', data.action);
+ instance.state.set('env', data.env);
+ instance.state.set('id', data._id);
+
+ instance.subscribe('constants');
+ instance.subscribe('scans?id', data._id);
+
+ let model = Scans.findOne({ _id: data._id });
+ instance.state.set('model', model);
+ // todo
+}
diff --git a/ui/imports/ui/components/scanning-request/scanning-request.styl b/ui/imports/ui/components/scanning-request/scanning-request.styl
new file mode 100644
index 0000000..53f197f
--- /dev/null
+++ b/ui/imports/ui/components/scanning-request/scanning-request.styl
@@ -0,0 +1,7 @@
+.os-scanning-request
+ margin: 20px;
+
+ .sm-form-container
+ .js-message-panel
+ margin: 20px 0;
+
diff --git a/ui/imports/ui/components/scans-list/scans-list.html b/ui/imports/ui/components/scans-list/scans-list.html
new file mode 100644
index 0000000..f8998dd
--- /dev/null
+++ b/ui/imports/ui/components/scans-list/scans-list.html
@@ -0,0 +1,88 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="ScansList">
+<div class="os-scans-list cards white">
+ <div class="sm-table-section">
+ <h3>Scans</h3>
+ <table class="sm-scans-table table">
+ <thead>
+ <tr>
+ <th>
+ <a class="sm-table-header"
+ data-is-sortable="true"
+ data-sort-field="status"
+ >Status<span><i class="{{ fieldSortClass 'status' }}"></i></span>
+ </a>
+ </th>
+ <th>Log Level</th>
+ <th>Clear</th>
+ <th>Scan Only Iventory</th>
+ <th>Scan Only Links</th>
+ <th>Scan Only Cliques</th>
+ <th>Scan Completed</th>
+ <th>
+ <a class="sm-table-header"
+ data-is-sortable="true"
+ data-sort-field="submit_timestamp"
+ >Submit Timestamp<span><i class="{{ fieldSortClass 'submit_timestamp' }}"></i></span>
+ </a>
+ </th>
+ <th>
+ <a class="sm-table-header"
+ data-is-sortable="true"
+ data-sort-field="start_timestamp"
+ >Start Timestamp<span><i class="{{ fieldSortClass 'start_timestamp' }}"></i></span>
+ </a>
+ </th>
+ <th>
+ <a class="sm-table-header"
+ data-is-sortable="true"
+ data-sort-field="end_timestamp"
+ >End Timestamp<span><i class="{{ fieldSortClass 'end_timestamp' }}"></i></span>
+ </a>
+ </th>
+ <th>Environment</th>
+ <th>Inventory</th>
+ <th>Object ID</th>
+ <th>Action</th>
+ </tr> </thead>
+ <tbody>
+ {{#each scan in scans }}
+ <tr>
+ <td>{{ scan.status }}</td>
+ <td>{{ scan.log_level }}</td>
+ <td>{{ scan.clear }}</td>
+ <td>{{ scan.scan_only_inventory }}</td>
+ <td>{{ scan.scan_only_links }}</td>
+ <td>{{ scan.scan_only_cliques }}</td>
+ <td>{{ scan.scan_completed }}</td>
+ <td>{{ scan.submit_timestamp }}</td>
+ <td>{{ scan.start_timestamp }}</td>
+ <td>{{ scan.end_timestamp }}</td>
+ <td>{{ scan.environment }}</td>
+ <td>{{ scan.inventory }}</td>
+ <td>{{ scan.object_id }}</td>
+ <td><a href="{{pathFor route='scanning-request'
+ query=(asHash env=scan.environment _id=(idToStr scan._id) action='view') }}"
+ ><i class="cl-action-icon fa fa-eye" area-hidden="true"></i></a>
+ </td>
+ </tr>
+ {{/each }}
+ </tbody>
+ </table>
+ </div>
+
+ <div class="sm-pager-section">
+ {{> Pager (argsPager currentPage amountPerPage totalItems) }}
+ </div>
+
+</div>
+</template>
diff --git a/ui/imports/ui/components/scans-list/scans-list.js b/ui/imports/ui/components/scans-list/scans-list.js
new file mode 100644
index 0000000..b40026e
--- /dev/null
+++ b/ui/imports/ui/components/scans-list/scans-list.js
@@ -0,0 +1,224 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: ScansList
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import * as R from 'ramda';
+import { Counts } from 'meteor/tmeasday:publish-counts';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { Scans,
+ subsScansEnvPageAmountSorted,
+ subsScansEnvPageAmountSortedCounter,
+} from '/imports/api/scans/scans';
+
+import '/imports/ui/components/pager/pager';
+
+import './scans-list.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.ScansList.onCreated(function() {
+ var instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ env: null,
+ page: 1,
+ amountPerPage: 10,
+ sortField: 'start_timestamp',
+ sortDirection: -1,
+ });
+
+ instance.autorun(function () {
+ //let data = Template.currentData();
+
+ var controller = Iron.controller();
+ var params = controller.getParams();
+ var query = params.query;
+
+ new SimpleSchema({
+ env: { type: String, optional: true },
+ }).validate(query);
+
+ let env = query.env;
+ if (R.isNil(env)) {
+ instance.state.set('env', null);
+ } else {
+ instance.state.set('env', env);
+ }
+
+ });
+
+ instance.autorun(function () {
+ let env = instance.state.get('env');
+ let amountPerPage = instance.state.get('amountPerPage');
+ let page = instance.state.get('page');
+ let sortField = instance.state.get('sortField');
+ let sortDirection = instance.state.get('sortDirection');
+
+ instance.subscribe(subsScansEnvPageAmountSorted,
+ env, page, amountPerPage, sortField, sortDirection);
+
+ });
+
+});
+
+/*
+Template.ScansList.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.ScansList.events({
+ 'click .sm-table-header': function (event, instance) {
+ event.preventDefault();
+ let isSortable = event.target.dataset.isSortable;
+ if (! isSortable ) { return; }
+
+ let sortField = event.target.dataset.sortField;
+ let currentSortField = instance.state.get('sortField');
+ let currentSortDirection = instance.state.get('sortDirection');
+
+ if (sortField === currentSortField) {
+ let sortDirection = null;
+ if (currentSortDirection === null) {
+ sortDirection = -1;
+ } else if (currentSortDirection === -1) {
+ sortDirection = 1;
+ } else if (currentSortDirection === 1) {
+ sortField = null;
+ sortDirection = null;
+ } else {
+ sortField = null;
+ sortDirection = null;
+ }
+
+ instance.state.set('sortField', sortField);
+ instance.state.set('sortDirection', sortDirection);
+
+ } else {
+ instance.state.set('sortField', sortField);
+ instance.state.set('sortDirection', -1);
+ }
+ },
+});
+
+/*
+ * Helpers
+ */
+
+Template.ScansList.helpers({
+ scans: function () {
+ let instance = Template.instance();
+ let page = instance.state.get('page');
+ let amountPerPage = instance.state.get('amountPerPage');
+ let sortField = instance.state.get('sortField');
+ let sortDirection = instance.state.get('sortDirection');
+
+ let skip = (page - 1) * amountPerPage;
+ let sortParams = {};
+ sortParams = R.ifElse(R.isNil, R.always(sortParams),
+ R.assoc(R.__, sortDirection, sortParams))(sortField);
+
+ let qParams = {
+ limit: amountPerPage,
+ skip: skip,
+ sort: sortParams,
+ };
+
+ return Scans.find({}, qParams);
+ },
+
+ currentPage: function () {
+ let instance = Template.instance();
+ return instance.state.get('page');
+ },
+
+ amountPerPage: function () {
+ let instance = Template.instance();
+ return instance.state.get('amountPerPage');
+ },
+
+ totalItems: function () {
+ //let instance = Template.instance();
+ //let page = instance.state.get('page');
+ //let amountPerPage = instance.state.get('amountPerPage');
+ let counterName = subsScansEnvPageAmountSortedCounter;
+
+ return Counts.get(counterName);
+ },
+
+ argsPager: function (currentPage, amountPerPage, totalItems) {
+ let instance = Template.instance();
+ let totalPages = Math.ceil(totalItems / amountPerPage);
+
+ return {
+ disableNext: currentPage * amountPerPage > totalItems,
+ disablePrev: currentPage == 1,
+ totalPages: totalPages,
+ currentPage: currentPage,
+ onReqNext: function () {
+ console.log('next');
+ let page = (currentPage * amountPerPage > totalItems) ? currentPage : currentPage + 1;
+ instance.state.set('page', page);
+ },
+ onReqPrev: function () {
+ console.log('prev');
+ let page = (currentPage == 1) ? currentPage : currentPage - 1;
+ instance.state.set('page', page);
+ },
+ onReqFirst: function () {
+ console.log('req first');
+ instance.state.set('page', 1);
+ },
+ onReqLast: function () {
+ console.log('req last');
+ instance.state.set('page', totalPages);
+ },
+ onReqPage: function (pageNumber) {
+ console.log('req page');
+ let page;
+ if (pageNumber <= 1) {
+ page = 1;
+ } else if (pageNumber > Math.ceil(totalItems / amountPerPage)) {
+ page = totalPages;
+ } else {
+ page = pageNumber;
+ }
+
+ instance.state.set('page', page);
+ },
+ };
+ },
+
+ fieldSortClass: function (fieldName) {
+ let instance = Template.instance();
+ let classes = 'fa fa-sort';
+ if (fieldName === instance.state.get('sortField')) {
+ let sortDirection = instance.state.get('sortDirection');
+ if (sortDirection === -1) {
+ classes = 'fa fa-sort-desc';
+ } else if (sortDirection === 1) {
+ classes = 'fa fa-sort-asc';
+ }
+ }
+
+ return classes;
+ },
+});
+
diff --git a/ui/imports/ui/components/scans-list/scans-list.styl b/ui/imports/ui/components/scans-list/scans-list.styl
new file mode 100644
index 0000000..327ff4b
--- /dev/null
+++ b/ui/imports/ui/components/scans-list/scans-list.styl
@@ -0,0 +1,33 @@
+.os-scans-list
+ display: flex;
+ flex-flow: column nowrap;
+ margin: 20px;
+
+ .cl-action-icon,
+ .card.fa.cl-action-icon
+ font-size: 16px !important;
+
+ .sm-scans-table
+ th
+ color: spark-blue
+
+ a
+ color: spark-blue;
+ cursor: pointer;
+ i.fa
+ padding: 0px 3px;
+ font-size: small;
+
+ .sm-action-bar
+ display: flex;
+
+ a
+ margin: 0px 5px;
+
+ .cl-action-icon
+ color: gray
+
+ .sm-pager-section
+ display: flex;
+ justify-content: center;
+
diff --git a/ui/imports/ui/components/scheduled-scan/scheduled-scan.html b/ui/imports/ui/components/scheduled-scan/scheduled-scan.html
new file mode 100644
index 0000000..c5c5c72
--- /dev/null
+++ b/ui/imports/ui/components/scheduled-scan/scheduled-scan.html
@@ -0,0 +1,116 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="ScheduledScan">
+ <div class="os-scheduled-scan cards white">
+
+ <h3>{{ getState 'pageHeader' }}</h3>
+ <div class="sm-form-container">
+ <form role="form" class="sm-item-form form-horizontal">
+
+ <div class="sm-field-group-id cl-field-group">
+ <label class="cl-field-label">Id</label>
+ <input name="id"
+ disabled="disabled"
+ value="{{ modelField '_id' }}"
+ class="sm-input-id cl-input" type="text" placeholder="Id" />
+ </div>
+
+ <div class="cl-field-group">
+ <label class="cl-field-label">Environment</label>
+ {{>MtSelect (argsSelect classStr="cl-input"
+ options=envsAsOptions
+ selectedValue=(modelField 'environment')
+ onInput=onInputEnvFn
+ disabled=isGenDisabled
+ )
+ }}
+ </div>
+
+ <div class="cl-field-group">
+ <label class="cl-field-label">Scan specific object</label>
+ {{>MtInput (argsInput classStr="cl-input"
+ placeholder="Object Id"
+ inputValue=(modelField 'object_id')
+ inputType="text" onInput=onInputObjectIdFn
+ disabled=isGenDisabled
+ ) }}
+ </div>
+
+ <div class="cl-field-group">
+ <label class="cl-field-label">Log level</label>
+ {{>MtSelect (argsSelect classStr="cl-input"
+ options=logLevelsAsOptions
+ selectedValue=(modelField 'log_level')
+ onInput=onInputLogLevelFn
+ disabled=isGenDisabled
+ ) }}
+ </div>
+
+ <div class="cl-field-group">
+ <label class="cl-field-label">Clear data</label>
+ {{>MtInput (argsInput classStr="cl-input"
+ inputValue=(modelField 'clear')
+ inputType="checkbox"
+ onInput=onInputClearFn
+ disabled=isGenDisabled
+ ) }}
+ </div>
+
+ <div class="cl-field-group">
+ <label class="cl-field-label">What to scan</label>
+ {{>MtSelect (argsSelect classStr="cl-input"
+ options=scanOnlyFieldOptions
+ selectedValue=scanOnlyFieldsSelectedValue
+ onInput=scanOnlyFieldInputFn
+ disabled=isGenDisabled
+ ) }}
+ </div>
+
+ <div class="cl-field-group">
+ <label class="cl-field-label">Frequency</label>
+ {{>MtSelect (argsSelect classStr="cl-input"
+ options=freqsAsOptions
+ selectedValue=(modelField 'freq')
+ onInput=onInputFreqFn
+ disabled=isGenDisabled
+ ) }}
+ </div>
+
+ <div class="cl-field-group">
+ <label class="cl-field-label">Recurrence</label>
+ <div class="cl-info-data">{{ getRecurrenceText (getState 'model') }}</div>
+ </div>
+
+ <div class="cl-field-group">
+ <label class="cl-field-label">Next run</label>
+ <div class="cl-info-data">{{ getNextRunText (getState 'model') }}</div>
+ </div>
+
+ {{#if isUpdateableAction }}
+ <button type="submit"
+ class="js-submit-button mdl-button mdl-js-button mdl-button--raised
+ mdl-js-ripple-effect mdl-button--colored"
+ >{{ actionLabel }}</button>
+ {{/if }}
+
+ </form>
+
+ {{#if (getState 'isMessage') }}
+ <div class="js-message-panel alert {{#if (getState 'isError')}}alert-danger{{/if}}
+ {{#if (getState 'isSuccess')}}alert-success{{/if}}"
+ role="alert">
+ {{ getState 'message' }}
+ </div>
+ {{/if }}
+
+ </div>
+ </div>
+</template>
diff --git a/ui/imports/ui/components/scheduled-scan/scheduled-scan.js b/ui/imports/ui/components/scheduled-scan/scheduled-scan.js
new file mode 100644
index 0000000..3bcc591
--- /dev/null
+++ b/ui/imports/ui/components/scheduled-scan/scheduled-scan.js
@@ -0,0 +1,511 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: ScheduledScan
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import * as R from 'ramda';
+import { RRule } from 'rrule';
+import { ScheduledScans,
+ scansOnlyFields,
+ subsScheduledScansId,
+} from '/imports/api/scheduled-scans/scheduled-scans';
+import { Environments } from '/imports/api/environments/environments';
+import { Constants } from '/imports/api/constants/constants';
+import { insert, remove, update } from '/imports/api/scheduled-scans/methods';
+
+import '/imports/ui/components/mt-select/mt-select';
+import '/imports/ui/components/mt-input/mt-input';
+import '/imports/ui/components/mt-radios/mt-radios';
+
+import './scheduled-scan.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.ScheduledScan.onCreated(function() {
+ var instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ action: null,
+ _id: null,
+ model: null,
+ isError: false,
+ isSuccess: false,
+ isMessage: false,
+ message: null,
+ envsAsOptions: [],
+ logLevelsAsOptions: [],
+ pageHeader: 'Schedule a Scan',
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+ new SimpleSchema({
+ _id: {
+ type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } },
+ optional: true
+ },
+ action: { type: String },
+ env: { type: String, optional: true },
+ }).validate(data);
+
+ instance.state.set('action', data.action);
+ R.when(R.pipe(R.isNil, R.not), x => instance.state.set('_id', x))(data._id);
+ R.when(R.pipe(R.isNil, R.not), x => instance.state.set('env', x))(data.env);
+ });
+
+ instance.autorun(function () {
+ let currentOptions = instance.state.get('envsAsOptions');
+ instance.subscribe('environments_config');
+ let tempOptions = [];
+
+ let addToOptionsDebounced = _.debounce(() => {
+ if (currentOptions.length === tempOptions.length) {
+ let result = R.intersectionWith(R.eqBy(R.prop('value')), tempOptions, currentOptions);
+ if (result.length === currentOptions.length) {
+ return;
+ }
+ }
+
+ instance.state.set('envsAsOptions', tempOptions);
+ }, 250);
+
+ Environments.find({}).forEach((env) => {
+ let option = envToOption(env);
+ tempOptions = R.unionWith(R.eqBy(R.prop('value')), [option], tempOptions);
+ addToOptionsDebounced();
+ });
+ });
+
+ instance.autorun(function () {
+ let currentOptions = instance.state.get('logLevelsAsOptions');
+ instance.subscribe('constants');
+
+ let tempOptions = [];
+
+ let addToOptionsDebounced = _.debounce(() => {
+ if (currentOptions.length === tempOptions.length) {
+ let result = R.intersectionWith(R.eqBy(R.prop('value')), tempOptions, currentOptions);
+ if (result.length === currentOptions.length) {
+ return;
+ }
+ }
+
+ instance.state.set('logLevelsAsOptions', tempOptions);
+ }, 250);
+
+ Constants.find({ name: 'log_levels' }).forEach((logLevelsRec) => {
+ let logLevels = logLevelsRec.data;
+ R.map((logLevel) => {
+ let option = logLevelToOption(logLevel);
+ tempOptions = R.unionWith(R.eqBy(R.prop('value')), [option], tempOptions);
+ addToOptionsDebounced();
+ }, logLevels);
+ });
+
+ });
+
+ instance.autorun(function () {
+ let action = instance.state.get('action');
+ let _id = instance.state.get('_id');
+ let env = instance.state.get('env');
+
+ R.cond([
+ [R.equals('insert'), _x => initInsertView(instance, env)],
+ [R.equals('update'), _x => initUpdateView(instance, _id)],
+ [R.equals('view'), _x => initViewView(instance, _id)],
+ [R.equals('remove'), _x => initRemoveView(instance, _id)],
+ [R.T, x => { throw `unimplemented action: ${R.toString(x)}`; }]
+ ])(action);
+ });
+});
+
+/*
+Template.ScheduledScan.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.ScheduledScan.events({
+ 'submit .sm-item-form': function(event, instance) {
+ event.preventDefault();
+ let model = instance.state.get('model');
+
+ submitItem(instance, model);
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.ScheduledScan.helpers({
+ isUpdateableAction() {
+ let instance = Template.instance();
+ let action = instance.state.get('action');
+
+ return R.contains(action, ['insert', 'update', 'remove']);
+ },
+
+ actionLabel: function () {
+ let instance = Template.instance();
+ let action = instance.state.get('action');
+ return calcActionLabel(action);
+ },
+
+ asJson: function (val) {
+ return JSON.stringify(val);
+ },
+
+ getState: function (key) {
+ let instance = Template.instance();
+ return instance.state.get(key);
+ },
+
+ modelField: function (fieldName) {
+ let instance = Template.instance();
+ return R.path([fieldName], instance.state.get('model'));
+ },
+
+ envsAsOptions: function () {
+ let instance = Template.instance();
+ return instance.state.get('envsAsOptions');
+ },
+
+ onInputInventoryFn: function () {
+ let instance = Template.instance();
+ return { fn: createSetModelFn(instance, 'inventory') };
+ },
+
+ onInputObjectIdFn: function () {
+ let instance = Template.instance();
+ return { fn: createSetModelFn(instance, 'object_id') };
+ },
+
+ onInputClearFn: function () {
+ let instance = Template.instance();
+ return { fn: createSetModelFn(instance, 'clear') };
+ },
+
+ onInputEnvFn: function () {
+ let instance = Template.instance();
+ return { fn: createSetModelFn(instance, 'environment') };
+ },
+
+ onInputLogLevelFn: function () {
+ let instance = Template.instance();
+ return { fn: createSetModelFn(instance, 'log_level') };
+ },
+
+ onInputFreqFn: function () {
+ let instance = Template.instance();
+ return { fn: createSetModelFn(instance, 'freq') };
+ },
+
+ argsSelect: function (args) {
+ //let instance = Template.instance();
+ let classStr = args.hash.classStr;
+ let options = args.hash.options;
+ let selectedValue = args.hash.selectedValue;
+ let onInput = args.hash.onInput;
+ let disabled = args.hash.disabled;
+
+ return {
+ classStr: classStr,
+ selectedValue: selectedValue,
+ isDisabled: disabled,
+ options: options,
+ onInput: onInput,
+ };
+ },
+
+ scanOnlyFieldOptions: function () {
+ return [
+ { label: 'Full scan', value: '_full_scan' },
+ { label: 'Scan only inventory', value: 'scan_only_inventory' },
+ { label: 'Scan only links', value: 'scan_only_links' },
+ { label: 'Scan only cliques', value: 'scan_only_cliques' },
+ ];
+ },
+
+ scanOnlyFieldInputFn: function () {
+ let instance = Template.instance();
+
+ return {
+ fn: function (newFieldName) {
+ let model = instance.state.get('model');
+ model = R.reduce((acc, fieldName) => {
+ return R.assoc(fieldName, false, acc);
+ }, model, scansOnlyFields);
+
+ if (newFieldName === '_full_scan') {
+ console.log('full scan selected. all scan_only_ fields are reset');
+ } else {
+ model = R.assoc(newFieldName, true, model);
+ }
+ instance.state.set('model', model);
+ }
+ };
+ },
+
+ scanOnlyFieldsSelectedValue: function () {
+ let instance = Template.instance();
+ let model = instance.state.get('model');
+ if (R.isNil(model)) { return null; }
+
+ let selectedValue = R.find((fieldName) => {
+ return R.prop(fieldName, model) === true;
+ }, scansOnlyFields);
+
+ if (R.isNil(selectedValue)) {
+ selectedValue = '_full_scan';
+ }
+ return selectedValue;
+ },
+
+ argsRadios: function (options, onInputFn, selectedValue) {
+ return {
+ inputClasses: 'cl-input',
+ options: options,
+ selectedValue: selectedValue,
+ onInput: onInputFn,
+ };
+ },
+
+ freqsAsOptions: function () {
+ return [
+ { label: 'Yearly', value: 'YEARLY' },
+ { label: 'Monthly', value: 'MONTHLY' },
+ { label: 'Weekly', value: 'WEEKLY' },
+ { label: 'Daily', value: 'DAILY' },
+ { label: 'Hourly', value: 'HOURLY' },
+ ];
+ },
+
+ argsInput: function (args) {
+ let classStr = args.hash.classStr;
+ let placeholder = args.hash.placeholder;
+ let inputValue = args.hash.inputValue;
+ let inputType = args.hash.inputType;
+ let onInput = args.hash.onInput;
+ let disabled = args.hash.disabled;
+
+ return {
+ inputValue: inputValue,
+ inputType: inputType,
+ classStr: classStr,
+ placeholder: placeholder,
+ isDisabled: disabled,
+ onInput: onInput,
+ };
+ },
+
+ getEnvsAsOptions: function () {
+ let instance = Template.instance();
+ return instance.state.get('envsAsOptions');
+ },
+
+ logLevelsAsOptions: function () {
+ let instance = Template.instance();
+ return instance.state.get('logLevelsAsOptions');
+ },
+
+ isGenDisabled: function () {
+ let instance = Template.instance();
+ let action = instance.state.get('action');
+ if (R.contains(action, ['view', 'remove'])) {
+ return true;
+ }
+
+ return false;
+ },
+
+ getRecurrenceText: function (model) {
+ if (R.isNil(model)) { return ''; }
+
+ let rule = new RRule({
+ freq: RRule[model.freq]
+ });
+
+ return rule.toText();
+ },
+
+ getNextRunText: function (model) {
+ if (R.isNil(model)) { return ''; }
+ if (R.isNil(model.scheduled_timestamp)) { return ''; }
+
+ let next = moment(model.scheduled_timestamp);
+ return next.fromNow();
+ },
+}); // end: helpers
+
+
+function initInsertView(instance, env) {
+ instance.state.set('model', ScheduledScans.schema.clean({
+ environment: env,
+ }));
+
+ subscribeToOptionsData(instance);
+}
+
+function initExistingItemView(instance, _id) {
+ subscribeToOptionsData(instance);
+ instance.subscribe(subsScheduledScansId, _id);
+
+ ScheduledScans.find({ _id: _id }).forEach((model) => {
+ instance.state.set('model', model);
+ });
+}
+
+function initViewView(instance, _id) {
+ initExistingItemView(instance, _id);
+}
+
+function initUpdateView(instance, _id) {
+ initExistingItemView(instance, _id);
+}
+
+function initRemoveView(instance, _id) {
+ initExistingItemView(instance, _id);
+}
+
+function subscribeToOptionsData(_instance) {
+
+}
+
+function envToOption(env) {
+ return { value: env.name, label: env.name };
+}
+
+function logLevelToOption(logLevel) {
+ return { value: logLevel.value, label: logLevel.label };
+}
+
+function createSetModelFn(instance, fieldName) {
+ return function (value) {
+ let model = instance.state.get('model');
+ model = R.assoc(fieldName, value, model);
+ instance.state.set('model', model);
+ };
+}
+
+function calcActionLabel(action) {
+ switch (action) {
+ case 'insert':
+ return 'Add';
+ case 'remove':
+ return 'Remove';
+ case 'update':
+ return 'Update';
+ default:
+ return 'Submit';
+ }
+}
+
+function submitItem(
+ instance,
+ model
+) {
+
+ let action = instance.state.get('action');
+
+ instance.state.set('isError', false);
+ instance.state.set('isSuccess', false);
+ instance.state.set('isMessage', false);
+ instance.state.set('message', null);
+
+ switch (action) {
+ case 'insert':
+ insert.call({
+ environment: model.environment,
+ object_id: model.object_id,
+ log_level: model.log_level,
+ clear: model.clear,
+ scan_only_inventory: model.scan_only_inventory,
+ scan_only_links: model.scan_only_links,
+ scan_only_cliques: model.scan_only_cliques,
+ freq: model.freq,
+ }, processActionResult.bind(null, instance));
+ break;
+
+ case 'update':
+ update.call({
+ _id: model._id,
+ environment: model.environment,
+ object_id: model.object_id,
+ log_level: model.log_level,
+ clear: model.clear,
+ scan_only_inventory: model.scan_only_inventory,
+ scan_only_links: model.scan_only_links,
+ scan_only_cliques: model.scan_only_cliques,
+ freq: model.freq,
+ }, processActionResult.bind(null, instance));
+ break;
+
+ case 'remove':
+ remove.call({
+ _id: model._id,
+ }, processActionResult.bind(null, instance));
+ break;
+
+ default:
+ // todo
+ break;
+ }
+}
+
+function processActionResult(instance, error) {
+ let action = instance.state.get('action');
+
+ if (error) {
+ instance.state.set('isError', true);
+ instance.state.set('isSuccess', false);
+ instance.state.set('isMessage', true);
+
+ if (typeof error === 'string') {
+ instance.state.set('message', error);
+ } else {
+ instance.state.set('message', error.message);
+ }
+
+ return;
+ }
+
+ instance.state.set('isError', false);
+ instance.state.set('isSuccess', true);
+ instance.state.set('isMessage', true);
+
+ switch (action) {
+ case 'insert':
+ instance.state.set('message', 'Record had been added successfully');
+ instance.state.set('disabled', true);
+ break;
+
+ case 'remove':
+ instance.state.set('message', 'Record had been removed successfully');
+ instance.state.set('disabled', true);
+ break;
+
+ case 'update':
+ instance.state.set('message', 'Record had been updated successfully');
+ break;
+ }
+
+ //Router.go('/link-types-list');
+}
diff --git a/ui/imports/ui/components/scheduled-scan/scheduled-scan.styl b/ui/imports/ui/components/scheduled-scan/scheduled-scan.styl
new file mode 100644
index 0000000..ac64dd3
--- /dev/null
+++ b/ui/imports/ui/components/scheduled-scan/scheduled-scan.styl
@@ -0,0 +1,34 @@
+.os-scheduled-scan
+ margin: 20px;
+
+ .cl-field-group
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ padding: 5px 0;
+
+ .cl-field-label
+ width: 170px;
+ margin: 0 5px;
+
+ .cl-input
+ display: block;
+ width: 100%;
+ height: 34px;
+ padding: 6px 12px;
+ font-size: 14px;
+ line-height: 1.42857143;
+ color: #555;
+ background-color: #fff;
+ background-image: none;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ width: 400px;
+ margin: 0 5px;
+
+ .cl-field-desc
+ margin: 0 5px;
+
+ .js-message-panel
+ margin-top: 20px;
diff --git a/ui/imports/ui/components/scheduled-scans-list/scheduled-scans-list.html b/ui/imports/ui/components/scheduled-scans-list/scheduled-scans-list.html
new file mode 100644
index 0000000..4b141e7
--- /dev/null
+++ b/ui/imports/ui/components/scheduled-scans-list/scheduled-scans-list.html
@@ -0,0 +1,66 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="ScheduledScansList">
+<div class="os-scheduled-scans-list cards white">
+ <div class="sm-table-section">
+ <h3>Scheduled Scans</h3>
+ <table class="sm-scheduled-scans-table table">
+ <thead>
+ <tr>
+ <th>Log Level</th>
+ <th>Clear</th>
+ <th>Scan Only Iventory</th>
+ <th>Scan Only Links</th>
+ <th>Scan Only Cliques</th>
+ <th>Environment</th>
+ <th>Inventory</th>
+ <th>Object ID</th>
+ <th>Frequency</th>
+ <th>Submit Timestamp</th>
+ <th>Action</th>
+ </tr> </thead>
+ <tbody>
+ {{#each scan in scheduledScans }}
+ <tr>
+ <td>{{ scan.log_level }}</td>
+ <td>{{ scan.clear }}</td>
+ <td>{{ scan.scan_only_inventory }}</td>
+ <td>{{ scan.scan_only_links }}</td>
+ <td>{{ scan.scan_only_cliques }}</td>
+ <td>{{ scan.environment }}</td>
+ <td>{{ scan.inventory }}</td>
+ <td>{{ scan.object_id }}</td>
+ <td>{{ scan.frequency }}</td>
+ <td>{{ scan.submit_timestamp }}</td>
+ <td>
+ <a href="{{pathFor route='scheduled-scan'
+ query=(asHash _id=(idToStr scan._id) action='view') }}"
+ ><i class="cl-action-icon fa fa-eye" area-hidden="true"></i></a>
+ <a href="{{pathFor route='scheduled-scan'
+ query=(asHash _id=(idToStr scan._id) action='update') }}"
+ ><i class="cl-action-icon fa fa-pencil" area-hidden="true"></i></a>
+
+ <a href="{{pathFor route='scheduled-scan'
+ query=(asHash _id=(idToStr scan._id) action='remove') }}"
+ ><i class="cl-action-icon fa fa-trash-o" area-hidden="true"></i></a>
+ </td>
+ </tr>
+ {{/each }}
+ </tbody>
+ </table>
+ </div>
+
+ <div class="sm-pager-section">
+ {{> Pager (argsPager currentPage amountPerPage totalItems) }}
+ </div>
+
+</div>
+</template>
diff --git a/ui/imports/ui/components/scheduled-scans-list/scheduled-scans-list.js b/ui/imports/ui/components/scheduled-scans-list/scheduled-scans-list.js
new file mode 100644
index 0000000..7aa76e9
--- /dev/null
+++ b/ui/imports/ui/components/scheduled-scans-list/scheduled-scans-list.js
@@ -0,0 +1,160 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: ScheduledScansList
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import * as R from 'ramda';
+import { Counts } from 'meteor/tmeasday:publish-counts';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { ScheduledScans,
+ subsScheduledScansPageAmountSorted,
+ subsScheduledScansPageAmountSortedCounter,
+} from '/imports/api/scheduled-scans/scheduled-scans';
+
+import '/imports/ui/components/pager/pager';
+
+import './scheduled-scans-list.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.ScheduledScansList.onCreated(function() {
+ var instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ env: null,
+ page: 1,
+ amountPerPage: 10,
+ sortField: null,
+ sortDirection: -1,
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+
+ new SimpleSchema({
+ }).validate(data);
+ });
+
+ instance.autorun(function () {
+ let amountPerPage = instance.state.get('amountPerPage');
+ let page = instance.state.get('page');
+ let sortField = instance.state.get('sortField');
+ let sortDirection = instance.state.get('sortDirection');
+
+ instance.subscribe(subsScheduledScansPageAmountSorted,
+ page, amountPerPage, sortField, sortDirection);
+ });
+});
+
+/*
+Template.ScheduledScansList.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.ScheduledScansList.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.ScheduledScansList.helpers({
+ scheduledScans: function () {
+ let instance = Template.instance();
+ let page = instance.state.get('page');
+ let amountPerPage = instance.state.get('amountPerPage');
+ let sortField = instance.state.get('sortField');
+ let sortDirection = instance.state.get('sortDirection');
+
+ let skip = (page - 1) * amountPerPage;
+ let sortParams = {};
+ sortParams = R.ifElse(R.isNil, R.always(sortParams),
+ R.assoc(R.__, sortDirection, sortParams))(sortField);
+
+ let qParams = {
+ limit: amountPerPage,
+ skip: skip,
+ sort: sortParams,
+ };
+
+ return ScheduledScans.find({}, qParams);
+ },
+
+ currentPage: function () {
+ let instance = Template.instance();
+ return instance.state.get('page');
+ },
+
+ amountPerPage: function () {
+ let instance = Template.instance();
+ return instance.state.get('amountPerPage');
+ },
+
+ totalItems: function () {
+ let counterName = subsScheduledScansPageAmountSortedCounter;
+
+ return Counts.get(counterName);
+ },
+
+ argsPager: function (currentPage, amountPerPage, totalItems) {
+ let instance = Template.instance();
+ let totalPages = Math.ceil(totalItems / amountPerPage);
+
+ return {
+ disableNext: currentPage * amountPerPage > totalItems,
+ disablePrev: currentPage == 1,
+ totalPages: totalPages,
+ currentPage: currentPage,
+ onReqNext: function () {
+ console.log('next');
+ let page = (currentPage * amountPerPage > totalItems) ? currentPage : currentPage + 1;
+ instance.state.set('page', page);
+ },
+ onReqPrev: function () {
+ console.log('prev');
+ let page = (currentPage == 1) ? currentPage : currentPage - 1;
+ instance.state.set('page', page);
+ },
+ onReqFirst: function () {
+ console.log('req first');
+ instance.state.set('page', 1);
+ },
+ onReqLast: function () {
+ console.log('req last');
+ instance.state.set('page', totalPages);
+ },
+ onReqPage: function (pageNumber) {
+ console.log('req page');
+ let page;
+ if (pageNumber <= 1) {
+ page = 1;
+ } else if (pageNumber > Math.ceil(totalItems / amountPerPage)) {
+ page = totalPages;
+ } else {
+ page = pageNumber;
+ }
+
+ instance.state.set('page', page);
+ },
+ };
+ },
+}); // end: helpers
+
+
diff --git a/ui/imports/ui/components/scheduled-scans-list/scheduled-scans-list.styl b/ui/imports/ui/components/scheduled-scans-list/scheduled-scans-list.styl
new file mode 100644
index 0000000..bd2f7c0
--- /dev/null
+++ b/ui/imports/ui/components/scheduled-scans-list/scheduled-scans-list.styl
@@ -0,0 +1,33 @@
+.os-scheduled-scans-list
+ display: flex;
+ flex-flow: column nowrap;
+ margin: 20px;
+
+ .cl-action-icon,
+ .card.fa.cl-action-icon
+ font-size: 16px !important;
+
+ .sm-scheduled-scans-table
+ th
+ color: spark-blue
+
+ a
+ color: spark-blue;
+ cursor: pointer;
+ i.fa
+ padding: 0px 3px;
+ font-size: small;
+
+ .sm-action-bar
+ display: flex;
+
+ a
+ margin: 0px 5px;
+
+ .cl-action-icon
+ color: gray
+
+ .sm-pager-section
+ display: flex;
+ justify-content: center;
+
diff --git a/ui/imports/ui/components/search-auto-complete-list/search-auto-complete-list.html b/ui/imports/ui/components/search-auto-complete-list/search-auto-complete-list.html
new file mode 100644
index 0000000..bd5e0e2
--- /dev/null
+++ b/ui/imports/ui/components/search-auto-complete-list/search-auto-complete-list.html
@@ -0,0 +1,22 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="SearchAutoCompleteList">
+<div class="os-search-auto-complete-list">
+ {{#if isOpen}}
+ <div class="sm-backdrop"></div>
+ {{/if }}
+ <ul class="sm-search-auto-complete-list {{#if isOpen }}cl-open{{/if}}">
+ {{#each resultItem in searchResults }}
+ {{>AutoSearchResultLine (createAutoSearchResultLineArgs resultItem) }}
+ {{/each }}
+ </ul>
+</div>
+</template>
diff --git a/ui/imports/ui/components/search-auto-complete-list/search-auto-complete-list.js b/ui/imports/ui/components/search-auto-complete-list/search-auto-complete-list.js
new file mode 100644
index 0000000..cfc706b
--- /dev/null
+++ b/ui/imports/ui/components/search-auto-complete-list/search-auto-complete-list.js
@@ -0,0 +1,167 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: SearchAutoCompleteList
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import * as R from 'ramda';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { ReactiveVar } from 'meteor/reactive-var';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { EJSON } from 'meteor/ejson';
+import { _idFieldDef } from '/imports/lib/simple-schema-utils';
+
+//import { store } from '/imports/ui/store/store';
+
+import '../auto-search-result-line/auto-search-result-line';
+
+import './search-auto-complete-list.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.SearchAutoCompleteList.onCreated(function() {
+ let instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ isOpen: false,
+ envId: null,
+ searchTerm: null,
+ results: [],
+ });
+
+ instance.currentData = new ReactiveVar(null, EJSON.equals);
+ instance.autorun((function(_this) {
+ return function(_computation) {
+ return _this.currentData.set(Template.currentData());
+ };
+ })(instance));
+
+ instance.autorun(function () {
+ let data = instance.currentData.get();
+
+ new SimpleSchema({
+ isOpen: { type: Boolean },
+ envId: R.merge(_idFieldDef, { optional: true }),
+ searchTerm: { type: String, optional: true },
+ onResultSelected: { type: Function },
+ onCloseReq: { type: Function },
+ }).validate(data);
+
+ instance.state.set('isOpen', data.isOpen);
+ instance.state.set('envId', data.envId);
+ instance.state.set('searchTerm', data.searchTerm);
+
+ instance.onCloseReq = R.defaultTo(() => console.log('close requested'), data.onCloseReq);
+ });
+
+ instance.opCounter = 0;
+
+ instance.autorun(function () {
+ let envId = instance.state.get('envId');
+ let searchTerm = instance.state.get('searchTerm');
+ performSearch(searchTerm, envId,
+ function getLastOpCounter() {
+ return instance.opCounter;
+ },
+ function setLastOpCounter(opCounter) {
+ instance.opCounter = opCounter;
+ }
+ ).then(function (results) {
+ instance.state.set('results', results);
+ });
+ });
+
+});
+
+/*
+Template.SearchAutoCompleteList.rendered = function() {
+};
+*/
+
+Template.SearchAutoCompleteList.onDestroyed(() => {
+});
+
+/*
+ * Events
+ */
+
+Template.SearchAutoCompleteList.events({
+ 'click .sm-backdrop': function (event, instance) {
+ instance.onCloseReq();
+ }
+}); // end - events
+
+/*
+ * Helpers
+ */
+
+Template.SearchAutoCompleteList.helpers({
+ searchResults: function () {
+ let instance = Template.instance();
+ return instance.state.get('results');
+ },
+
+ createAutoSearchResultLineArgs: function (resultItem) {
+ let instance = Template.instance();
+
+ return {
+ namePath: resultItem.name_path,
+ objectName: resultItem.object_name,
+ objectType: resultItem.type,
+ environment: resultItem.environment,
+ onClick() {
+ instance.data.onResultSelected(resultItem);
+ }
+ };
+ },
+
+}); // end - helpers
+
+function performSearch(
+ searchTerm,
+ envId,
+ getLastOpCounterFn,
+ setLastOpCounterFn
+) {
+ return new Promise((resolve, reject) => {
+ let results = [];
+ let opCounter = getLastOpCounterFn() + 1;
+ setLastOpCounterFn(opCounter);
+
+ Meteor.apply('inventorySearch', [
+ searchTerm, envId, opCounter,
+ ], {
+ wait: false
+ }, function (err, res) {
+ if (err) {
+ console.error(R.toString(err));
+ reject(err);
+ return;
+ }
+
+ let currentOpCounter = getLastOpCounterFn();
+ if (res.opCounter !== currentOpCounter) {
+ reject('stale search result');
+ return;
+ }
+
+ R.forEach((resultItem) => {
+ results = R.append(resultItem, results);
+ }, res.searchResults);
+
+ resolve(results);
+ return;
+ });
+ });
+}
diff --git a/ui/imports/ui/components/search-auto-complete-list/search-auto-complete.styl b/ui/imports/ui/components/search-auto-complete-list/search-auto-complete.styl
new file mode 100644
index 0000000..4bd2998
--- /dev/null
+++ b/ui/imports/ui/components/search-auto-complete-list/search-auto-complete.styl
@@ -0,0 +1,28 @@
+.os-search-auto-complete-list
+
+ .sm-search-auto-complete-list
+ display: none
+ list-style: none;
+ -webkit-padding-start: 0px;
+ position: relative;
+ z-index: 4;
+
+ .sm-search-auto-complete-list.cl-open
+ display: block
+ background: rgba(255, 255, 255, 0.98);
+ color: black;
+ border: 1px solid #d0d0d0;
+ border-radius: 0 0 2px 2px;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+
+ .sm-search-auto-complete-list.cl-open:empty
+ display: none;
+
+ .sm-backdrop
+ width: 100vw;
+ height: 100vh;
+ //background-color: rgba(82, 192, 245, 0.58);
+ position: fixed;
+ z-index: 3;
+ top: 0;
+ left: 0;
diff --git a/ui/imports/ui/components/select-model/select-model.html b/ui/imports/ui/components/select-model/select-model.html
new file mode 100644
index 0000000..71b9c81
--- /dev/null
+++ b/ui/imports/ui/components/select-model/select-model.html
@@ -0,0 +1,23 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="SelectModel">
+ <select class="js-select form-control"
+ multiple="{{ multi }}"
+ {{ markIfDisabled }}
+ >
+ {{#if showNullOption }}<option value="">{{ nullOptionLabel }}</option>{{/if}}
+ {{#each option in options }}
+ <option value="{{ option.value }}"
+ selected="{{ isSelected option.value }}"
+ >{{ option.label }}</option>
+ {{/each }}
+ </select>
+</template>
diff --git a/ui/imports/ui/components/select-model/select-model.js b/ui/imports/ui/components/select-model/select-model.js
new file mode 100644
index 0000000..01fca9c
--- /dev/null
+++ b/ui/imports/ui/components/select-model/select-model.js
@@ -0,0 +1,79 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: SelectModel
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import * as R from 'ramda';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+
+import './select-model.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.SelectModel.onCreated(function() {
+});
+
+/*
+Template.SelectModel.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.SelectModel.events({
+ 'change .js-select': function (event) {
+ event.stopPropagation();
+ event.preventDefault();
+
+ let instance = Template.instance();
+ // Extract string values from select element's attribute.
+ let elementSelectedValues = R.map(function (optionEl) {
+ return optionEl.value;
+ }, event.target.selectedOptions);
+
+ let selectedValues = instance.data.multi ? elementSelectedValues :
+ elementSelectedValues[0];
+
+ if (instance.data.setModel) {
+ instance.data.setModel(selectedValues);
+ }
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.SelectModel.helpers({
+ isSelected: function (optionValue) {
+ let instance = Template.instance();
+ let selectedValues = instance.data.values;
+
+ if (R.isNil(selectedValues)) { return false; }
+ return R.contains(optionValue, selectedValues);
+ },
+ markIfDisabled: function () {
+ let instance = Template.instance();
+ let attrs = {};
+ if (instance.data.disabled) {
+ attrs = R.assoc('disabled', true, attrs);
+ }
+
+ return attrs;
+ }
+});
+
+
diff --git a/ui/imports/ui/components/selectable-ordered-input/selectable-ordered-input.html b/ui/imports/ui/components/selectable-ordered-input/selectable-ordered-input.html
new file mode 100644
index 0000000..6b81a8f
--- /dev/null
+++ b/ui/imports/ui/components/selectable-ordered-input/selectable-ordered-input.html
@@ -0,0 +1,29 @@
+<template name="SelectableOrderedInput">
+<div class="os-selectable-ordered-input">
+ <div class="sm-choices-section">
+ {{>MtSelect (argsChoicesSelect currentChoices selectedChoiceValue) }}
+ </div>
+
+ <div class="sm-choices-product-actions-bar">
+ <button class="cl-action-btn sm-add-choice-btn">
+ <i class="fa fa-arrow-right" aria-hidden="true"></i>
+ </button>
+ <button class="cl-action-btn sm-remove-choice-btn">
+ <i class="fa fa-arrow-left" aria-hidden="true"></i>
+ </button>
+ </div>
+
+ <div class="sm-product-section">
+ {{>MtSelect (argsProductSelect currentProduct selectedProductOptValue) }}
+ </div>
+
+ <div class="sm-product-actions-bar">
+ <button class="cl-action-btn sm-move-up-product-btn">
+ <i class="fa fa-arrow-up" aria-hidden="true"></i>
+ </button>
+ <button class="cl-action-btn sm-move-down-product-btn">
+ <i class="fa fa-arrow-down" aria-hidden="true"></i>
+ </button>
+ </div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/selectable-ordered-input/selectable-ordered-input.js b/ui/imports/ui/components/selectable-ordered-input/selectable-ordered-input.js
new file mode 100644
index 0000000..30b740f
--- /dev/null
+++ b/ui/imports/ui/components/selectable-ordered-input/selectable-ordered-input.js
@@ -0,0 +1,243 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: SelectableOrderedInput
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import * as R from 'ramda';
+
+import './selectable-ordered-input.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.SelectableOrderedInput.onCreated(function() {
+ let instance = this;
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ choices: [],
+ currentChoices: [],
+ currentProduct: [],
+ selectedChoice: null,
+ selectedProductOption: null,
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+
+ new SimpleSchema({
+ choices: { type: [Object], blackbox: true },
+ product: { type: [Object], blackbox: true },
+ onProductChange: { type: Function },
+ }).validate(data);
+
+ instance.state.set('choices', data.choices);
+ instance.state.set('product', data.product);
+ instance.onProductChange = R.defaultTo((_x) => {}, data.onProductChange);
+ });
+
+ instance.autorun(function () {
+ let choices = instance.state.get('choices');
+ let product = instance.state.get('product');
+
+ let currentChoices = R.differenceWith((choice, product) => choice.value === product.value,
+ choices, product);
+
+ instance.state.set('currentChoices', currentChoices);
+ instance.state.set('currentProduct', product);
+ instance.state.set('selectedChoice', null);
+ instance.state.set('selectedProductOption', null);
+ });
+
+ let lastCurrentProduct = null;
+ instance.autorun(function () {
+ let currentProduct = instance.state.get('currentProduct');
+ if (R.isNil(lastCurrentProduct)) {
+ lastCurrentProduct = currentProduct;
+ return;
+ }
+
+ if (R.equals(lastCurrentProduct, currentProduct)) {
+ return;
+ }
+
+ lastCurrentProduct = currentProduct;
+ instance.onProductChange(currentProduct);
+ });
+
+});
+
+/*
+Template.SelectableOrderedInput.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.SelectableOrderedInput.events({
+ 'click .sm-add-choice-btn': function (event, instance) {
+ event.preventDefault();
+
+ moveSelectedChoiceToProduct(instance);
+ },
+
+ 'click .sm-remove-choice-btn': function (event, instance) {
+ event.preventDefault();
+
+ moveSelectedProductOptionToChoices(instance);
+ },
+
+ 'click .sm-move-up-product-btn': function (event, instance) {
+ event.preventDefault();
+
+ moveSelectedProductOptionUp(instance);
+ },
+
+ 'click .sm-move-down-product-btn': function (event, instance) {
+ event.preventDefault();
+
+ moveSelectedProductOptionDown(instance);
+ },
+});
+
+/*
+ * Helpers
+ */
+
+Template.SelectableOrderedInput.helpers({
+ argsChoicesSelect: function (choices, selectedValue) {
+ let instance = Template.instance();
+
+ return {
+ classStr: 'cl-input',
+ //isDisabled:,
+ selectedValue: selectedValue,
+ size: 7,
+ options: choices,
+ onInput: {
+ fn: function (choice) {
+ let choices = instance.state.get('currentChoices');
+ let fullChoice = R.find(R.propEq('value', choice), choices);
+ instance.state.set('selectedChoice', fullChoice);
+ }
+ },
+ };
+ },
+
+ currentChoices: function () {
+ let instance = Template.instance();
+ return instance.state.get('currentChoices');
+ },
+
+ argsProductSelect: function (currentProduct, selectedProductOptValue) {
+ let instance = Template.instance();
+
+ return {
+ classStr: 'cl-input',
+ //isDisabled:,
+ selectedValue: selectedProductOptValue,
+ size: 7,
+ options: currentProduct,
+ onInput: {
+ fn: function (productOption) {
+ let product = instance.state.get('currentProduct');
+ let fullProductOption = R.find(R.propEq('value', productOption), product);
+ instance.state.set('selectedProductOption', fullProductOption);
+ }
+ },
+ };
+ },
+
+ currentProduct: function () {
+ let instance = Template.instance();
+ return instance.state.get('currentProduct');
+ },
+
+ selectedChoiceValue: function () {
+ let instance = Template.instance();
+ return R.path(['value'], instance.state.get('selectedChoice'));
+ },
+
+ selectedProductOptValue: function () {
+ let instance = Template.instance();
+ return R.path(['value'], instance.state.get('selectedProductOption'));
+ },
+}); // end: helpers
+
+function moveSelectedChoiceToProduct(instance) {
+ let selectedChoice = instance.state.get('selectedChoice');
+ if (R.isNil(selectedChoice)) { return; }
+
+ // remove selected choice from choices.
+ let choices = instance.state.get('currentChoices');
+ choices = R.reject(R.propEq('value', selectedChoice.value), choices);
+ instance.state.set('currentChoices', choices);
+
+ // add selected choice to product.
+ let product = instance.state.get('currentProduct');
+ product = R.append(selectedChoice, product);
+ instance.state.set('currentProduct', product);
+
+ // clear selected choice
+ instance.state.set('selectedChoice', null);
+}
+
+function moveSelectedProductOptionToChoices(instance) {
+ let selectedProductOption = instance.state.get('selectedProductOption');
+ if (R.isNil(selectedProductOption)) { return; }
+
+ // remove selected option from product
+ let product = instance.state.get('currentProduct');
+ product = R.reject(R.propEq('value', selectedProductOption.value), product);
+ instance.state.set('currentProduct', product);
+
+ // add selected option to choices
+ let choices = instance.state.get('currentChoices');
+ choices = R.append(selectedProductOption, choices);
+ instance.state.set('currentChoices', choices);
+
+ // clear selection product option
+ instance.state.set('selectedProductOption', null);
+}
+
+function moveSelectedProductOptionUp(instance) {
+ // get selected product option. exit if null.
+ let selectedProductOption = instance.state.get('selectedProductOption');
+ if (R.isNil(selectedProductOption)) { return; }
+
+ // move product option up.
+ let product = instance.state.get('currentProduct');
+ let index = R.findIndex(R.propEq('value', selectedProductOption.value), product);
+ if (index === 0) { return; }
+ product = R.remove(index, 1, product);
+ product = R.insert(index - 1, selectedProductOption, product);
+ instance.state.set('currentProduct', product);
+}
+
+function moveSelectedProductOptionDown(instance) {
+ // get selected product option. exit if null.
+ let selectedProductOption = instance.state.get('selectedProductOption');
+ if (R.isNil(selectedProductOption)) { return; }
+
+ // move product option down.
+ let product = instance.state.get('currentProduct');
+ let index = R.findIndex(R.propEq('value', selectedProductOption.value), product);
+ if (index === product.length - 1) { return; }
+
+ product = R.remove(index, 1, product);
+ product = R.insert(index + 1, selectedProductOption, product);
+ instance.state.set('currentProduct', product);
+}
diff --git a/ui/imports/ui/components/selectable-ordered-input/selectable-ordered-input.styl b/ui/imports/ui/components/selectable-ordered-input/selectable-ordered-input.styl
new file mode 100644
index 0000000..e18358b
--- /dev/null
+++ b/ui/imports/ui/components/selectable-ordered-input/selectable-ordered-input.styl
@@ -0,0 +1,30 @@
+.os-selectable-ordered-input
+ display: flex;
+ flex-flow: row nowrap;
+
+ .cl-action-btn
+ width: 33px;
+ height: 29px;
+ font-size: 5px;
+ margin: 3px;
+
+ .sm-choices-section
+ flex: 1;
+
+ .sm-product-section
+ flex: 1;
+
+ .sm-choices-product-actions-bar
+ flex: 0;
+ display: flex;
+ flex-flow: column nowrap;
+ justify-content: center;
+
+ padding: 0 6px;
+
+ .sm-product-actions-bar
+ flex: 0;
+ display: flex;
+ flex-flow: column nowrap;
+ justify-content: center;
+ padding: 0 6px;
diff --git a/ui/imports/ui/components/time-selection-widget/time-selection-widget.html b/ui/imports/ui/components/time-selection-widget/time-selection-widget.html
new file mode 100644
index 0000000..ba808c7
--- /dev/null
+++ b/ui/imports/ui/components/time-selection-widget/time-selection-widget.html
@@ -0,0 +1,33 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="TimeSelectionWidget">
+ <form class="os-time-selection-widget form-inline">
+ <div class="form-group">
+ <input type="number" class="form-control sm-year-input" placeholder="Year">
+ </div>
+ <span>-</span>
+ <div class="form-group">
+ <input type="number" class="form-control sm-month-input" placeholder="Month">
+ </div>
+ <span>-</span>
+ <div class="form-group">
+ <input type="number" class="form-control sm-day-input" placeholder="Day">
+ </div>
+ <span> </span>
+ <div class="form-group">
+ <input type="number" class="form-control sm-hour-input" placeholder="Hour">
+ </div>
+ <span>:</span>
+ <div class="form-group">
+ <input type="number" class="form-control sm-minute-input" placeholder="Minute">
+ </div>
+ </form>
+</template>
diff --git a/ui/imports/ui/components/time-selection-widget/time-selection-widget.js b/ui/imports/ui/components/time-selection-widget/time-selection-widget.js
new file mode 100644
index 0000000..f61ac50
--- /dev/null
+++ b/ui/imports/ui/components/time-selection-widget/time-selection-widget.js
@@ -0,0 +1,45 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: TimeSelectionWidget
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+
+import './time-selection-widget.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.TimeSelectionWidget.onCreated(function() {
+});
+
+/*
+Template.TimeSelectionWidget.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.TimeSelectionWidget.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.TimeSelectionWidget.helpers({
+});
+
+
diff --git a/ui/imports/ui/components/time-selection-widget/time-selection-widget.styl b/ui/imports/ui/components/time-selection-widget/time-selection-widget.styl
new file mode 100644
index 0000000..c10e7af
--- /dev/null
+++ b/ui/imports/ui/components/time-selection-widget/time-selection-widget.styl
@@ -0,0 +1,5 @@
+.os-time-selection-widget
+ width: 450px;
+
+ input.form-control
+ width: 70px;
diff --git a/ui/imports/ui/components/top-navbar-menu/top-navbar-menu.html b/ui/imports/ui/components/top-navbar-menu/top-navbar-menu.html
new file mode 100644
index 0000000..0b87521
--- /dev/null
+++ b/ui/imports/ui/components/top-navbar-menu/top-navbar-menu.html
@@ -0,0 +1,59 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="TopNavbarMenu">
+
+ <nav class="os-topnavbarmenu navbar navbar-inverse navbar-custom navbar-fixed-top">
+ <div class="container-fluid">
+ <div class="sm-navbar-header navbar-header">
+ <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+
+ <div class="sm-navbar-brand-container navbar-brand navbar-custom">
+ <img class="cl-item sm-cisco-logo" src="/logo-cisco.svg" >
+ <img class="cl-item sm-calipso-logo" src="/images/calipso-logo.jpg" >
+ <p class="cl-item sm-project-label" href="/">Project Calipso </p>
+ </div>
+ </div>
+ <div id="navbar" class="navbar-collapse collapse">
+ <div class="navbar-form navbar-right">
+ <div class="search">
+ <div class="search-input-group">
+ <span class="fa fa-search"></span>
+ <input id="search" placeholder="Search ...">
+ </div>
+ <div class="search-auto-complete">
+ {{>SearchAutoCompleteList (argsSearch envId searchTerm) }}
+ </div>
+ </div>
+ </div>
+ <ul class="nav navbar-nav navbar-right">
+ <li class="dropdown os-nav-link">
+ {{> envForm argsEnvForm }}
+ </li>
+ <li><a class="sm-dashboard-link os-nav-link">
+ <i class="material-icons">home</i>
+ Dashboard
+ </a></li>
+ <li><a class="sm-get-started-link os-nav-link">Get started</a></li>
+ <li><a class="sm-login-buttons-link os-nav-link" href="#"
+ >{{> loginButtons}}</a></li>
+ </ul>
+ </div>
+ </div>
+ </nav>
+
+ {{> alarmIcons}}
+
+</template>
diff --git a/ui/imports/ui/components/top-navbar-menu/top-navbar-menu.js b/ui/imports/ui/components/top-navbar-menu/top-navbar-menu.js
new file mode 100644
index 0000000..6968060
--- /dev/null
+++ b/ui/imports/ui/components/top-navbar-menu/top-navbar-menu.js
@@ -0,0 +1,129 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: TopNavbarMenu
+ */
+
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+//import * as R from 'ramda';
+
+import { store } from '/imports/ui/store/store';
+//import { setSearchTerm } from '/imports/ui/actions/search-interested-parties';
+//import { notifySearchAutoCompleteTermChanged } from '/imports/ui/actions/search-interested-parties';
+import { idToStr } from '/imports/lib/utilities';
+import factory from 'reactive-redux';
+
+import '/imports/ui/components/search-auto-complete-list/search-auto-complete-list';
+import '/imports/ui/components/get-started/get-started';
+import '/imports/ui/components/env-form/env-form';
+import '/imports/ui/components/alarm-icons/alarm-icons';
+
+import './top-navbar-menu.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.TopNavbarMenu.onCreated(function () {
+ let instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ isAutoCompleteOpen: false,
+ searchTerm: null
+ });
+
+ const mainEnvIdSelector = (state) => (state.components.mainApp.selectedEnvironment._id);
+ instance.rdxMainEnvId = factory(mainEnvIdSelector, store);
+
+ instance.tempSearchTerm = null;
+ instance.searchDebounced = _.debounce(function () {
+ instance.state.set('searchTerm', instance.tempSearchTerm);
+ instance.state.set('isAutoCompleteOpen', true);
+ }, 250);
+});
+
+Template.TopNavbarMenu.onDestroyed(function () {
+ //let instance = this;
+});
+
+Template.TopNavbarMenu.events = {
+ 'keyup #search': function (event) {
+ let instance = Template.instance();
+ let searchTerm = instance.$(event.target).val();
+
+ instance.tempSearchTerm = searchTerm;
+ instance.searchDebounced();
+ },
+
+ 'click .os-nav-link': function () {
+ let instance = Template.instance();
+ instance.state.set('isAutoCompleteOpen', false);
+ },
+
+ 'click .sm-dashboard-link': function () {
+ Router.go('Dashboard');
+ },
+
+ 'click .sm-get-started-link': function () {
+ Router.go('getstarted');
+ }
+};
+
+Template.TopNavbarMenu.helpers({
+ envId: function () {
+ let instance = Template.instance();
+ return instance.rdxMainEnvId.get();
+ },
+
+ searchTerm: function () {
+ let instance = Template.instance();
+ return instance.state.get('searchTerm');
+ },
+
+ argsSearch: function (envId, searchTerm) {
+ let instance = Template.instance();
+
+ return {
+ isOpen: instance.state.get('isAutoCompleteOpen'),
+ envId: envId,
+ searchTerm: searchTerm,
+ onResultSelected(node) {
+ instance.state.set('isAutoCompleteOpen', false);
+
+ let searchInput = instance.$('input#search');
+ searchInput.val(node.name_path);
+
+ Router.go('environment', { _id: idToStr(node._envId) }, {
+ query: { selectedNodeId: idToStr(node._id) }
+ });
+ },
+ onCloseReq() {
+ instance.state.set('isAutoCompleteOpen', false);
+
+ let searchInput = instance.$('input#search');
+ searchInput.val(null);
+ },
+ };
+ },
+
+ argsEnvForm: function () {
+ let instance = Template.instance();
+ let selectedEnvironment = instance.state.get('selectedEnvironment');
+
+ return {
+ selectedEnvironment: selectedEnvironment,
+ onEnvSelected: function (env) {
+ Router.go('environment', { _id: idToStr(env._id) }, { });
+ }
+ };
+ }
+
+}); // end: helpers
diff --git a/ui/imports/ui/components/top-navbar-menu/top-navbar-menu.styl b/ui/imports/ui/components/top-navbar-menu/top-navbar-menu.styl
new file mode 100644
index 0000000..0a49678
--- /dev/null
+++ b/ui/imports/ui/components/top-navbar-menu/top-navbar-menu.styl
@@ -0,0 +1,23 @@
+.os-topnavbarmenu
+ .os-nav-link
+ cursor: pointer;
+
+ .sm-dashboard-link
+ display: flex;
+
+ .sm-navbar-header
+ .sm-navbar-brand-container
+ display: flex;
+
+ .sm-cisco-logo
+ width: 40px;
+
+ .sm-calipso-logo
+ width: 40px;
+
+ .sm-project-label
+ color: white;
+ font-size: medium;
+
+ .cl-item
+ margin: 0px 10px
diff --git a/ui/imports/ui/components/tree-node/tree-node.html b/ui/imports/ui/components/tree-node/tree-node.html
new file mode 100644
index 0000000..87f5f1e
--- /dev/null
+++ b/ui/imports/ui/components/tree-node/tree-node.html
@@ -0,0 +1,58 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="TreeNode">
+<div class="os-tree-node" style="background-color: {{ calcColor level }};">
+
+ {{#if showDetailsLine }}
+ <div class="sm-details-line">
+ <div class="sm-node-desc">
+ <div class="sm-type-icon">
+ {{#if childDetected }}
+ <i class="material-icons">class</i>
+ {{else }}
+ <i class="material-icons">description</i>
+ {{/if }}
+ </div>
+ <div class="sm-space-a"></div>
+ <div class="sm-node-name">{{ node.object_name }}</div>
+ </div>
+
+ <div class="sm-actions-segment">
+ {{#if linkDetected }}
+ <div class="sm-link-icon"
+ data-toggle="tooltip"
+ data-placement="right"
+ title="Link to {{ linkRefName }} under zone."
+ >
+ <i class="fa fa-arrow-right" aria-hidden="true"></i>
+ </div>
+ {{/if }}
+
+ {{#if childDetected }}
+ <div class="sm-node-open-button">
+ {{#if isOpen }}
+ <i class="fa fa-minus" aria-hidden="true"></i>
+ {{else }}
+ <i class="fa fa-plus" aria-hidden="true"></i>
+ {{/if }}
+ </div>
+ {{/if }}
+ </div>
+ </div>
+ {{/if }}
+
+ <div class="sm-children-list {{#if isOpen }} cl-opened {{else }} cl-closed {{/if }}">
+ {{#each child in children }}
+ {{>TreeNode (argsChild child node) }}
+ {{/each }}
+ </div>
+</div>
+</template>
diff --git a/ui/imports/ui/components/tree-node/tree-node.js b/ui/imports/ui/components/tree-node/tree-node.js
new file mode 100644
index 0000000..7a79314
--- /dev/null
+++ b/ui/imports/ui/components/tree-node/tree-node.js
@@ -0,0 +1,419 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: TreeNode
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { EJSON } from 'meteor/ejson';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+import { ReactiveVar } from 'meteor/reactive-var';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { InventoryTreeNodeBehavior } from '/imports/ui/lib/inventory-tree-node-behavior';
+import * as R from 'ramda';
+import { calcColorMem } from '/imports/lib/utilities';
+import 'jquery.scrollto';
+
+import './tree-node.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.TreeNode.onCreated(function() {
+ let instance = this;
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ node: null,
+ openState: 'closed',
+ orderDataSubscribe: { counter: 0, data: { node: null, forOpen: false } },
+ needOpenCloseAnimation: { counter: 0, data: { type: 'opening', node: null } },
+ positionNeeded: false,
+ scrollToNodeIsNeeded: false,
+ });
+
+ //console.log('tree-node - on create', R.path(['data', 'node', '_id', '_str'], instance));
+
+ //let oldData = null;
+
+ createAttachedFns(instance);
+
+ instance.currentData = new ReactiveVar(null, EJSON.equals);
+
+ instance.autorun((function(_this) {
+ return function(_computation) {
+ return _this.currentData.set(Template.currentData());
+ };
+ })(instance));
+
+ instance.autorun(function () {
+ //let data = Template.currentData();
+ let data = instance.currentData.get();
+ //let data = instance.data;
+
+ new SimpleSchema({
+ behavior: {
+ type: { isOpenDefault: { type: Boolean } },
+ blackbox: true
+ },
+ showDetailsLine: { type: Boolean },
+ openState: { type: String },
+ node: { type: Object, blackbox: true },
+ children: { type: [Object], blackbox: true },
+ childDetected: { type: Boolean },
+ needChildDetection: { type: Boolean },
+ linkDetected: { type: Boolean },
+ level: { type: Number },
+ positionNeeded: { type: Boolean },
+ scrollToNodeIsNeeded: { type: Boolean },
+ onResetChildren: { type: Function },
+ onChildRead: { type: Function },
+ onChildrenRead: { type: Function },
+ onStartOpenReq: { type: Function },
+ onOpeningDone: { type: Function },
+ onStartCloseReq: { type: Function },
+ onClosingDone: { type: Function },
+ onChildDetected: { type: Function },
+ onNodeSelected: { type: Function },
+ onPositionRetrieved: { type: Function },
+ onScrollToNodePerformed: { type: Function },
+ onOpenLinkReq: { type: Function },
+ onResetNeedChildDetection: { type: Function },
+ }).validate(data);
+
+ instance.state.set('openState', data.openState);
+ instance.state.set('node', data.node);
+ instance.state.set('positionNeeded', data.positionNeeded);
+ instance.state.set('scrollToNodeIsNeeded', data.scrollToNodeIsNeeded);
+ instance.state.set('needChildDetection', data.needChildDetection);
+
+ //console.log('tree-node - main autorun - ' + data.node._id._str);
+
+ /*
+ R.forEach((keyName) => {
+ if (R.isNil(oldData)) { return; }
+
+ if (! R.equals(R.prop(keyName, data), R.prop(keyName, oldData)) ) {
+ console.log('tree-node - main autorun - prop change: ' + keyName);
+ //R.path([keyName], data), R.path([keyName], oldData));
+ }
+ }, R.keys(data));
+
+ if (oldData !== data) { console.log('tree-node - main autorn - data ob change'); }
+
+ oldData = data;
+ */
+
+ });
+
+ instance.autorun(function () {
+ let node = instance.state.get('node');
+ let openState = instance.state.get('openState');
+
+ switch (openState) {
+ case 'start_open':
+ issueOrder(instance, 'orderDataSubscribe', { node: node, forOpen: true });
+ setTimeout(() => {
+ instance.data.onOpeningDone([node._id._str], node);
+ }, 400);
+ break;
+ case 'opened':
+ issueOrder(instance, 'needOpenCloseAnimation', { type: 'opening', node: node});
+ break;
+ case 'start_close':
+ issueOrder(instance, 'needOpenCloseAnimation', { type: 'closing', node: node });
+ setTimeout(() => {
+ instance.data.onClosingDone([node._id._str]);
+ }, 200);
+ break;
+ case 'closed':
+ issueOrder(instance, 'orderDataSubscribe', { node: node, forOpen: false });
+ break;
+ }
+ });
+
+ instance.autorun(() => {
+ let order = instance.state.get('orderDataSubscribe');
+ if (order.counter == 0) { return; }
+
+ instance.data.onResetChildren(R.append(R.path(['_id', '_str'], order.data.node), []));
+ // console.log('reset children in autoron order data sub: ' + order.data.node._id._str);
+
+ if (order.data.forOpen) {
+ instance.data.behavior.subscribeGetChildrenFn(instance, order.data.node);
+
+ let children = [];
+ let onChildReadThrottle = _.throttle(() => {
+ instance.data.onChildrenRead([ order.data.node._id._str ], children);
+ children = [];
+ }, 200);
+
+ instance.data.behavior.getChildrenFn(order.data.node).forEach((child) => {
+ // todo: aggregate the collection into threshold and then dispatch.
+ // debounce/throttle
+ // https://lodash.com/docs#debounce
+
+ //instance.data.onChildRead(
+ // [order.data.node._id._str, child._id._str], child);
+
+ children = R.append(child, children);
+ onChildReadThrottle();
+ });
+ }
+ });
+
+ instance.autorun(() => {
+ //let needChildDetection =
+ instance.state.get('needChildDetection');
+ let data = instance.data;
+
+ instance.data.behavior.subscribeGetFirstChildFn(instance, data.node);
+ // todo: let childDetectedSubmited = false;
+ instance.data.behavior.getChildrenFn(data.node).forEach((_child) => {
+ instance.data.onChildDetected([data.node._id._str]);
+ });
+
+ instance.data.onResetNeedChildDetection([data.node._id._str]);
+ });
+
+ instance.autorun(function () {
+ let positionNeeded = instance.state.get('positionNeeded');
+
+ if (positionNeeded) {
+ let el = instance.$('>.os-tree-node')[0];
+ let rect = el.getBoundingClientRect();
+ instance.data.onPositionRetrieved([instance.data.node._id._str], rect);
+ }
+ });
+
+ instance.autorun(function () {
+ let scrollToNodeIsNeeded = instance.state.get('scrollToNodeIsNeeded');
+
+ if (scrollToNodeIsNeeded) {
+ let el = instance.$('>.os-tree-node')[0];
+ let rect = el.getBoundingClientRect();
+ if (rect.top < 0) {
+ //window.scroll(0, el.offsetTop);
+ $(window).scrollTo(el, 50);
+ instance.data.onScrollToNodePerformed([instance.data.node._id._str]);
+ return;
+ }
+
+ let childrenCont = instance.$('>.os-tree-node > .sm-children-list')[0];
+ let childrenRect = childrenCont.getBoundingClientRect();
+ if (childrenRect.bottom > window.innerHeight) {
+ let scrollPos = childrenRect.bottom - window.innerHeight;
+ scrollPos = window.scrollY + scrollPos;
+ if ((window.scrollY + rect.top) < scrollPos) {
+ scrollPos = window.scrollY + rect.top;
+ }
+ $(window).scrollTo(scrollPos, 50);
+ }
+
+ instance.data.onScrollToNodePerformed([instance.data.node._id._str]);
+ }
+ });
+
+});
+
+Template.TreeNode.rendered = function() {
+ let instance = Template.instance();
+ // Detect change in isOpen.
+ instance.autorun(() => {
+ let order = instance.state.get('needOpenCloseAnimation');
+ if (order.counter == 0) { return; }
+
+ let $childrenList;
+
+ switch(order.data.type) {
+ case 'opening':
+ // The children list element is not present on first isOpen change render. We
+ // need to wait out of loop inorder to let the render first render to list then
+ // we animate the opening/closing action.
+
+ //$childrenList = instance.$('>.sm-children-list');
+ $childrenList = instance.$(instance.firstNode).children('.sm-children-list');
+ $childrenList.slideDown(200);
+ break;
+
+ case 'closing':
+ //$childrenList = instance.$('>.sm-children-list');
+ $childrenList = instance.$(instance.firstNode).children('.sm-children-list');
+ $childrenList.slideUp(200);
+ break;
+ }
+
+ });
+};
+
+/*
+ * Events
+ */
+
+Template.TreeNode.events({
+ 'click .sm-details-line': function (event, _instance) {
+ event.preventDefault();
+ event.stopPropagation();
+
+ let data = Template.currentData();
+
+ if (R.pathEq(['type'], 'host_ref')(data.node)) {
+ data.onOpenLinkReq(data.node.environment, data.node.name);
+
+ } else {
+ switch(data.openState) {
+ case 'opened':
+ R.when(R.pipe(R.isNil, R.not),
+ (fn) => fn([data.node._id._str])
+ )(data.onStartCloseReq);
+ break;
+
+ case 'closed':
+ R.when(R.pipe(R.isNil, R.not),
+ (fn) => fn([data.node._id._str])
+ )(data.onStartOpenReq);
+ break;
+ }
+
+ data.onNodeSelected(data.node);
+ }
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.TreeNode.helpers({
+ argsChild: function (child, _node) {
+ let instance = Template.instance();
+ //let data = Template.currentData();
+
+ return {
+ behavior: InventoryTreeNodeBehavior,
+ showDetailsLine: true,
+ openState: child.openState,
+ node: child.nodeInfo,
+ children: child.children,
+ childDetected: child.childDetected,
+ needChildDetection: child.needChildDetection,
+ linkDetected: child.linkDetected,
+ level: child.level,
+ positionNeeded: child.positionNeeded,
+ scrollToNodeIsNeeded: child.scrollToNodeIsNeeded,
+ onChildRead: instance._fns.onChildRead,
+ onChildrenRead: instance._fns.onChildrenRead,
+ onResetChildren: instance._fns.onResetChildren,
+ onStartOpenReq: instance._fns.onStartOpenReq,
+ onOpeningDone: instance._fns.onOpeningDone,
+ onStartCloseReq: instance._fns.onStartCloseReq,
+ onClosingDone: instance._fns.onClosingDone,
+ onChildDetected: instance._fns.onChildDetected,
+ onNodeSelected: instance._fns.onNodeSelected,
+ onPositionRetrieved: instance._fns.onPositionRetrieved,
+ onScrollToNodePerformed: instance._fns.onScrollToNodePerformed,
+ onOpenLinkReq: instance._fns.onOpenLinkReq,
+ onResetNeedChildDetection: instance._fns.onResetNeedChildDetection,
+ };
+ },
+
+ isOpen: function () {
+ let instance = Template.instance();
+ return R.equals('opened', instance.state.get('openState'));
+ },
+
+ calcColor: function (level) {
+ return calcColorMem(level);
+ },
+
+ linkRefName: function () {
+ let instance = Template.instance();
+ let node = instance.state.get('node');
+
+ if (R.isNil(node)) { return ''; }
+ if (R.propEq('type', 'host_ref', node)) {
+ return node.name;
+ }
+
+ return '';
+ }
+}); // end: helpers
+
+function issueOrder(instance, name, data) {
+ let val = JSON.parse(instance.state.keys[name]);
+ val = R.merge(val, {
+ counter: val.counter + 1,
+ data: data
+ });
+
+ instance.state.set(name, val);
+}
+
+function createAttachedFns(instance) {
+
+ instance._fns = {
+ onChildRead: function (reqPath, nodeInfo) {
+ instance.data.onChildRead(
+ R.prepend(instance.data.node._id._str, reqPath), nodeInfo);
+ },
+ onChildrenRead: function (reqPath, childrenInfo) {
+ instance.data.onChildrenRead(
+ R.prepend(instance.data.node._id._str, reqPath), childrenInfo);
+ },
+ onResetChildren: function (reqPath) {
+ instance.data.onResetChildren(
+ R.prepend(instance.data.node._id._str, reqPath));
+ },
+ onStartOpenReq: (reqPath) => {
+ instance.data.onStartOpenReq(
+ R.prepend(instance.data.node._id._str, reqPath));
+ },
+ onOpeningDone: (reqPath, nodeInfo) => {
+ instance.data.onOpeningDone(
+ R.prepend(instance.data.node._id._str, reqPath), nodeInfo);
+ },
+ onStartCloseReq: (reqPath) => {
+ instance.data.onStartCloseReq(
+ R.prepend(instance.data.node._id._str, reqPath));
+ },
+ onClosingDone: (reqPath) => {
+ instance.data.onClosingDone(
+ R.prepend(instance.data.node._id._str, reqPath));
+ },
+ onChildDetected: (reqPath) => {
+ instance.data.onChildDetected(
+ R.prepend(instance.data.node._id._str, reqPath));
+ },
+ onNodeSelected: (nodeInfo) => {
+ instance.data.onNodeSelected(nodeInfo);
+ },
+ onPositionRetrieved: (reqPath, rect) => {
+ instance.data.onPositionRetrieved(
+ R.prepend(instance.data.node._id._str, reqPath),
+ rect
+ );
+ },
+ onScrollToNodePerformed: (reqPath) => {
+ instance.data.onScrollToNodePerformed(
+ R.prepend(instance.data.node._id._str, reqPath)
+ );
+ },
+
+ onOpenLinkReq: (envName, nodeName) => {
+ instance.data.onOpenLinkReq(envName, nodeName);
+ },
+
+ onResetNeedChildDetection: (reqPath) => {
+ instance.data.onResetNeedChildDetection(
+ R.prepend(instance.data.node._id._str, reqPath)
+ );
+ }
+ };
+}
diff --git a/ui/imports/ui/components/tree-node/tree-node.styl b/ui/imports/ui/components/tree-node/tree-node.styl
new file mode 100644
index 0000000..f0b4922
--- /dev/null
+++ b/ui/imports/ui/components/tree-node/tree-node.styl
@@ -0,0 +1,53 @@
+.os-tree-node
+ display: flex;
+ flex-flow: column nowrap;
+ background-color: #0b7ad1;
+
+ .sm-details-line
+ display: flex;
+ flex-flow: row nowrap;
+ justify-content: space-between;
+
+ padding: 14px 22px;
+ padding-left: 44px;
+ cursor: pointer;
+
+ &:hover
+ background-color: #0a6ebd;
+
+ .sm-node-desc
+ display: flex;
+ flex-flow: row nowrap;
+
+ font-size: 11px;
+ line-height: 18px;
+ color: white;
+
+ .sm-type-icon
+ i
+ font-size: 16px;
+
+ .sm-space-a
+ width: 10px;
+
+ //.sm-node-name
+
+ .sm-actions-segment
+ display: flex;
+
+ .fa
+ font-size: 10px;
+ width: 10px;
+ color: white;
+ margin: 0 3px;
+
+ .sm-children-list
+ display: none
+ //height: 0
+
+/*
+ &.cl-opened
+ display: block
+ &.cl-closed
+ display: none
+*/
diff --git a/ui/imports/ui/components/user-list/user-list.html b/ui/imports/ui/components/user-list/user-list.html
new file mode 100644
index 0000000..0f8c082
--- /dev/null
+++ b/ui/imports/ui/components/user-list/user-list.html
@@ -0,0 +1,53 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="UserList">
+<div class="os-user-list cards white">
+ <h3>User List</h3>
+ <a class="sm-add-new-link"
+ href="{{pathFor route='user' query=(asHash action='insert') }}">
+ <i class="cl-action-icon fa fa-plus" area-hidden="true"></i> Create new user
+ </a>
+ <table class="sm-user-list-table table">
+ <thead>
+ <tr>
+ <th>User Name</th>
+ <th>Emails</th>
+ <th>Profile</th>
+ <th>Roles</th>
+ </tr> </thead>
+ <tbody>
+ {{#each user in userList }}
+ <tr>
+ <td>{{ user.username }}</td>
+ <td>{{ toString user.emails }}</td>
+ <td>{{ toString user.profile }}</td>
+ <td>{{ toString user.roles }}</td>
+ <td>
+ <div class="sm-action-bar">
+ <a href="{{pathFor route='user'
+ query=(asHash id=(idToStr user._id) action='view') }}"
+ ><i class="cl-action-icon fa fa-eye" area-hidden="true"></i></a>
+
+ <a href="{{pathFor route='user'
+ query=(asHash id=(idToStr user._id) action='update') }}"
+ ><i class="cl-action-icon fa fa-pencil" area-hidden="true"></i></a>
+
+ <a href="{{pathFor route='user'
+ query=(asHash id=(idToStr user._id) action='remove') }}"
+ ><i class="cl-action-icon fa fa-trash-o" area-hidden="true"></i></a>
+ </div>
+ </td>
+ </tr>
+ {{/each }}
+ </tbody>
+ </table>
+</div>
+</template>
diff --git a/ui/imports/ui/components/user-list/user-list.js b/ui/imports/ui/components/user-list/user-list.js
new file mode 100644
index 0000000..8deb24d
--- /dev/null
+++ b/ui/imports/ui/components/user-list/user-list.js
@@ -0,0 +1,74 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: UserList
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+//import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import * as R from 'ramda';
+
+import './user-list.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.UserList.onCreated(function() {
+ var instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ });
+
+ instance.autorun(function () {
+ //let data = Template.currentData();
+
+ /*
+ var controller = Iron.controller();
+ var params = controller.getParams();
+ var query = params.query;
+
+ new SimpleSchema({
+ }).validate(query);
+ */
+
+ instance.subscribe('users');
+ });
+});
+
+/*
+Template.UserList.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.UserList.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.UserList.helpers({
+ userList: function () {
+ return Meteor.users.find({});
+ },
+
+ toString: function (val) {
+ return R.toString(val);
+ }
+});
+
+
diff --git a/ui/imports/ui/components/user-list/user-list.styl b/ui/imports/ui/components/user-list/user-list.styl
new file mode 100644
index 0000000..5bb3d41
--- /dev/null
+++ b/ui/imports/ui/components/user-list/user-list.styl
@@ -0,0 +1,22 @@
+.os-user-list
+ margin: 20px;
+
+ .cl-action-icon,
+ .card.fa.cl-action-icon
+ font-size: 16px !important;
+
+ .sm-user-list-table
+ th
+ color: spark-blue
+
+ .sm-action-bar
+ display: flex;
+
+ a
+ margin: 0px 5px;
+
+ .cl-action-icon
+ color: gray
+
+ .sm-add-new-link
+ color: spark-blue
diff --git a/ui/imports/ui/components/user/user.html b/ui/imports/ui/components/user/user.html
new file mode 100644
index 0000000..e7ca9b8
--- /dev/null
+++ b/ui/imports/ui/components/user/user.html
@@ -0,0 +1,111 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="User">
+ <div class="os-user cards white">
+ {{#if notificationsExists}}
+ <div class="sm-notification-panel alert alert-danger">
+ {{#each note in notifications }}
+ <div>{{ note }}</div>
+ {{/each }}
+ </div>
+ {{/if}}
+
+ <h3>{{ getState 'pageHeader' }}</h3>
+ <div class="sm-form-container">
+ <form role="form" class="sm-item-form form-horizontal">
+
+ <div class="sm-field-group-id cl-field-group">
+ <label class="cl-field-label">Id</label>
+ <input name="id"
+ disabled="disabled"
+ value="{{ getModelField '_id' }}"
+ class="sm-input-id cl-input" type="text" placeholder="Id" />
+ <div class="cl-field-id">Id</div>
+ </div>
+
+ <div class="sm-field-group-username cl-field-group">
+ <label class="cl-field-label">User name</label>
+ <input name="username"
+ {{ getAttrDisabled }}
+ value="{{ getModelField 'username' }}"
+ class="sm-input-username cl-input"
+ type="text"
+ placeholder="User name"
+ autocomplete="new-user"
+ />
+ <div class="cl-field-desc">User name</div>
+ </div>
+
+ <div class="sm-field-group-password cl-field-group">
+ <label class="cl-field-label">Password</label>
+ <input name="password"
+ {{ getAttrDisabled }}
+ value="{{ getModelField 'password' }}"
+ class="sm-input-password cl-input"
+ type="password"
+ placeholder=""
+ autocomplete="new-password"
+ />
+ <div class="cl-field-desc">Password</div>
+ </div>
+
+ <div class="sm-field-group-view-env-roles cl-field-group">
+ <label class="cl-field-label">Allowed environments : viewing</label>
+ <select name="viewEnvs"
+ class="sm-input-view-envs cl-input"
+ multiple
+ size="3"
+ {{ getAttrDisabled }} >
+ {{#each env in envs }}
+ <option value="{{ env.name }}"
+ {{ getAttrSelectedMultiple env.name viewEnvs }}
+ >{{ env.name }}</option>
+ {{/each }}
+ </select>
+ <div class="cl-field-desc">View role for environments</div>
+ </div>
+
+ <div class="sm-field-group-edit-env-roles cl-field-group">
+ <label class="cl-field-label">Allowed environments : editing</label>
+ <select name="editEnvs"
+ class="sm-input-edit-envs cl-input"
+ multiple
+ size="3"
+ {{ getAttrDisabled }} >
+ {{#each env in envs }}
+ <option value="{{ env.name }}"
+ {{ getAttrSelectedMultiple env.name editEnvs }}
+ >{{ env.name }}</option>
+ {{/each }}
+ </select>
+ <div class="cl-field-desc">Edit/Delete role for environments</div>
+ </div>
+
+ {{#if isUpdateableAction }}
+ <button type="submit"
+ class="js-submit-button mdl-button mdl-js-button mdl-button--raised
+ mdl-js-ripple-effect mdl-button--colored"
+ >{{ actionLabel }}</button>
+ {{/if }}
+
+ </form>
+
+ {{#if (getState 'isMessage') }}
+ <div class="js-message-panel alert {{#if (getState 'isError')}}alert-danger{{/if}}
+ {{#if (getState 'isSuccess')}}alert-success{{/if}}"
+ role="alert">
+ {{ getState 'message' }}
+ </div>
+ {{/if }}
+
+ </div>
+ </div>
+</template>
diff --git a/ui/imports/ui/components/user/user.js b/ui/imports/ui/components/user/user.js
new file mode 100644
index 0000000..80e4870
--- /dev/null
+++ b/ui/imports/ui/components/user/user.js
@@ -0,0 +1,366 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: User
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { parseReqId } from '/imports/lib/utilities';
+import * as R from 'ramda';
+import { remove, insert, update } from '/imports/api/accounts/methods';
+import { Environments } from '/imports/api/environments/environments';
+
+import './user.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.User.onCreated(function() {
+ let instance = this;
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ id: null,
+ //env: null,
+ action: 'insert',
+ isError: false,
+ isSuccess: false,
+ isMessage: false,
+ message: null,
+ disabled: false,
+ notifications: {},
+ model: {},
+ pageHeader: 'User',
+ viewEnvs: [],
+ editEnvs: [],
+ });
+
+ instance.autorun(function () {
+ let controller = Iron.controller();
+ let params = controller.getParams();
+ let query = params.query;
+
+ new SimpleSchema({
+ action: { type: String, allowedValues: ['insert', 'view', 'remove', 'update'] },
+ //env: { type: String, optional: true },
+ id: { type: String, optional: true }
+ }).validate(query);
+
+ switch (query.action) {
+ case 'insert':
+ initInsertView(instance, query);
+ break;
+
+ case 'view':
+ initViewView(instance, query);
+ break;
+
+ case 'update':
+ initUpdateView(instance, query);
+ break;
+
+ case 'remove':
+ initRemoveView(instance, query);
+ break;
+
+ default:
+ throw 'unimplemented action';
+ }
+ });
+});
+
+/*
+Template.User.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.User.events({
+ 'submit .sm-item-form': function(event, instance) {
+ event.preventDefault();
+
+ let _id = instance.state.get('id');
+ let username = instance.$('.sm-input-username')[0].value;
+ let password = instance.$('.sm-input-password')[0].value;
+ let viewEnvs = R.map(R.prop('value'),
+ instance.$('.sm-input-view-envs')[0].selectedOptions);
+ let editEnvs = R.map(R.prop('value'),
+ instance.$('.sm-input-edit-envs')[0].selectedOptions);
+
+ submitItem(instance,
+ _id,
+ username,
+ password,
+ viewEnvs,
+ editEnvs
+ );
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.User.helpers({
+ isUpdateableAction() {
+ let instance = Template.instance();
+ let action = instance.state.get('action');
+
+ return R.contains(action, ['insert', 'update', 'remove']);
+ },
+
+ getState: function (key) {
+ let instance = Template.instance();
+ return instance.state.get(key);
+ },
+
+ getAttrDisabled: function () {
+ let instance = Template.instance();
+ let result = {};
+ let action = instance.state.get('action');
+
+ if (R.contains(action, ['view', 'remove'])) {
+ result = R.assoc('disabled', true, result);
+ }
+
+ return result;
+ },
+
+ getModelField: function (fieldName) {
+ let instance = Template.instance();
+ return R.path([fieldName], instance.state.get('model'));
+ },
+
+ actionLabel: function () {
+ let instance = Template.instance();
+ let action = instance.state.get('action');
+ return calcActionLabel(action);
+ },
+
+ viewEnvs: function () {
+ let instance = Template.instance();
+ return instance.state.get('viewEnvs');
+ },
+
+ editEnvs: function () {
+ let instance = Template.instance();
+ return instance.state.get('editEnvs');
+ },
+
+ envs: function () {
+ return Environments.find({});
+ },
+
+ getAttrSelected: function (optionValue, modelValue) {
+ let result = {};
+
+ if (optionValue === modelValue) {
+ result = R.assoc('selected', 'selected', result);
+ }
+
+ return result;
+ },
+
+ getAttrSelectedMultiple: function (optionValue, modelValues) {
+ let result = {};
+
+ if (R.isNil(modelValues)) { return result; }
+
+ if (R.contains(optionValue, modelValues)) {
+ result = R.assoc('selected', 'selected', result);
+ }
+
+ return result;
+ },
+}); // end: helpers
+
+
+function initInsertView(instance, query) {
+ instance.state.set('action', query.action);
+ //instance.state.set('env', query.env);
+
+ instance.state.set('model',
+ {
+ username: '',
+ password: ''
+ }
+ /*.schema.clean({
+ //environment: instance.state.get('env')
+ })
+ */
+ );
+
+ subscribeToOptionsData(instance);
+ //instance.subscribe('constants');
+ //instance.subscribe('link_types?env', query.env);
+}
+
+function initViewView(instance, query) {
+ let reqId = parseReqId(query.id);
+
+ instance.state.set('action', query.action);
+ //instance.state.set('env', query.env);
+ instance.state.set('id', reqId);
+
+ subscribeToOptionsData(instance);
+ subscribeToModel(instance, reqId.id);
+ //instance.subscribe('constants');
+ //instance.subscribe('link_types?_id', reqId.id);
+
+}
+
+function initUpdateView(instance, query) {
+ let reqId = parseReqId(query.id);
+
+ instance.state.set('action', query.action);
+ //instance.state.set('env', query.env);
+ instance.state.set('id', reqId);
+
+ subscribeToOptionsData(instance);
+ subscribeToModel(instance, reqId.id);
+ //instance.subscribe('constants');
+ //instance.subscribe('link_types?_id', reqId.id);
+}
+
+function initRemoveView(instance, query) {
+ initViewView(instance, query);
+}
+
+function subscribeToOptionsData(instance) {
+ instance.subscribe('constants');
+ instance.subscribe('environments_config');
+}
+
+function subscribeToModel(instance, id) {
+ instance.subscribe('users');
+
+ Meteor.users.find({ _id: id }).forEach((model) => {
+ instance.state.set('model', {
+ _id: model._id,
+ username: model.username,
+ password: '******'
+ });
+
+ instance.subscribe('environments.view-env&userId', model._id);
+ instance.subscribe('environments.edit-env&userId', model._id);
+
+ let viewEnvsList = [];
+ Environments.find({ 'auth.view-env': { $in: [ model._id ] }}).forEach((viewEnv) => {
+ viewEnvsList = R.union(viewEnvsList, [ viewEnv.name ]);
+ instance.state.set('viewEnvs', viewEnvsList);
+ });
+
+ let editEnvsList = [];
+ Environments.find({ 'auth.edit-env': { $in: [ model._id ] }}).forEach((editEnv) => {
+ editEnvsList = R.union(editEnvsList, [ editEnv.name ]);
+ instance.state.set('editEnvs', editEnvsList);
+ });
+ });
+}
+
+function submitItem(
+ instance,
+ id,
+ username,
+ password,
+ viewEnvs,
+ editEnvs
+ ){
+
+ let action = instance.state.get('action');
+
+ instance.state.set('isError', false);
+ instance.state.set('isSuccess', false);
+ instance.state.set('isMessage', false);
+ instance.state.set('message', null);
+
+ switch (action) {
+ case 'insert':
+ insert.call({
+ username: username,
+ password: password,
+ viewEnvs: viewEnvs,
+ editEnvs: editEnvs,
+ }, processActionResult.bind(null, instance));
+ break;
+
+ case 'update':
+ update.call({
+ _id: id.id,
+ //password: password,
+ viewEnvs: viewEnvs,
+ editEnvs: editEnvs,
+ }, processActionResult.bind(null, instance));
+ break;
+
+ case 'remove':
+ remove.call({
+ _id: id.id
+ }, processActionResult.bind(null, instance));
+ break;
+
+ default:
+ // todo
+ break;
+ }
+}
+
+function processActionResult(instance, error) {
+ let action = instance.state.get('action');
+
+ if (error) {
+ instance.state.set('isError', true);
+ instance.state.set('isSuccess', false);
+ instance.state.set('isMessage', true);
+
+ if (typeof error === 'string') {
+ instance.state.set('message', error);
+ } else {
+ instance.state.set('message', error.message);
+ }
+
+ } else {
+ instance.state.set('isError', false);
+ instance.state.set('isSuccess', true);
+ instance.state.set('isMessage', true);
+
+ switch (action) {
+ case 'insert':
+ instance.state.set('message', 'Record had been added successfully');
+ instance.state.set('disabled', true);
+ break;
+
+ case 'remove':
+ instance.state.set('message', 'Record had been removed successfully');
+ instance.state.set('disabled', true);
+ break;
+
+ case 'update':
+ instance.state.set('message', 'Record had been updated successfully');
+ break;
+ }
+ }
+}
+
+function calcActionLabel(action) {
+ switch (action) {
+ case 'insert':
+ return 'Add';
+ case 'remove':
+ return 'Remove';
+ default:
+ return 'Submit';
+ }
+}
diff --git a/ui/imports/ui/components/user/user.styl b/ui/imports/ui/components/user/user.styl
new file mode 100644
index 0000000..434cc64
--- /dev/null
+++ b/ui/imports/ui/components/user/user.styl
@@ -0,0 +1,34 @@
+.os-user
+ margin: 20px;
+
+ .cl-field-group
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ padding: 5px 0;
+
+ .cl-field-label
+ width: 170px;
+ margin: 0 5px;
+
+ .cl-input
+ display: block;
+ width: 100%;
+ min-height: 34px;
+ padding: 6px 12px;
+ font-size: 14px;
+ line-height: 1.42857143;
+ color: #555;
+ background-color: #fff;
+ background-image: none;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ width: 400px;
+ margin: 0 5px;
+
+ .cl-field-desc
+ margin: 0 5px;
+
+ .js-message-panel
+ margin-top: 20px;
diff --git a/ui/imports/ui/components/vedge-info-window/vedge-info-window.html b/ui/imports/ui/components/vedge-info-window/vedge-info-window.html
new file mode 100644
index 0000000..3670d71
--- /dev/null
+++ b/ui/imports/ui/components/vedge-info-window/vedge-info-window.html
@@ -0,0 +1,114 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="VedgeInfoWindow">
+<div class="os-vedge-info-window {{#if isShow}}cl-visible{{/if}}"
+ style="top: {{ top }}px; left: {{ left }}px;">
+
+ <div class="sm-header">
+ <button type="button" class="sm-close-button close"
+ data-dismiss="modal" aria-label="Close">
+ <span aria-hidden="true">x</span>
+ </button>
+ <h4 class="modal-title">Object Information: {{ name }}</h4>
+ </div>
+
+ <div class="sm-body">
+ {{#if showMessage }}
+ <div class="sm-message-panel alert alert-{{ messageType }}">
+ <div class="sm-message-text">{{ message }}</div>
+ </div>
+ {{/if }}
+
+ <form class="sm-form-graph-filters">
+ <div class="sm-form-group form-group">
+ <label for="">Flow types</label>
+ <select class="sm-flow-type-select cl-input">
+ <option value="">Choose a flow type</option>
+ {{#each flowType in flowTypes }}
+ <option value="{{ flowType.name }}">{{ flowType.name }}</option>
+ {{/each }}
+ </select>
+ </div>
+
+ {{#if (is selectedFlowType 'L2') }}
+ <div class="sm-f2-input-group">
+ <div class="sm-form-group form-group">
+ <label for="">Source: Mac address</label>
+ <select class="sm-source-mac-address-select cl-input">
+ <option value="">Choose an address </option>
+ {{#each address in srcMacAddresses }}
+ <option value="{{ address }}">{{ address }}</option>
+ {{/each }}
+ </select>
+ </div>
+
+ <div class="sm-form-group form-group">
+ <label for="">Destination: Mac address</label>
+ <select class="sm-destination-mac-address-select cl-input">
+ <option value="">Choose an address </option>
+ {{#each address in dstMacAddresses }}
+ <option value="{{ address }}">{{ address }}</option>
+ {{/each }}
+ </select>
+ </div>
+ </div>
+ {{/if }}
+
+ {{#if (is selectedFlowType 'L3') }}
+ <div class="sm-f3-input-group">
+ <select class="sm-source-ip-address cl-input">
+ <option value="">Choose an address </option>
+ {{#each address in srcIPv4Addresses }}
+ <option value="{{ address }}">{{ address }}</option>
+ {{/each }}
+ </select>
+
+ <select class="sm-destination-ip-address cl-input">
+ {{#each address in dstIPv4Addresses }}
+ <option value="{{ address }}">{{ address }}</option>
+ {{/each }}
+ </select>
+ </div>
+ {{/if }}
+
+ <div class="checkbox">
+ <label>
+ <input type="checkbox" class="sm-simulate-graph">Simulate graph
+ </label>
+ </div>
+
+ <div class="form-group">
+ <label for="">Y-Scale</label>
+ <input type="number" class="form-control sm-y-scale-input">
+ </div>
+
+ <div class="form-group">
+ <label for="">Start time</label>
+ <div class="sm-start-datetime-group input-group date">
+ <input class="sm-start-datetime form-control" type="text"/>
+ <span class="input-group-addon"
+ ><i class="glyphicon glyphicon-calendar"></i></span>
+ </div>
+ </div>
+ </form>
+ </div><!-- end: body -->
+
+ {{#if isShowGraph }}
+ <div class="sm-graph-container">
+ {{>FlowGraph argsFlowGraph }}
+ </div>
+ {{/if }}
+
+ <div class="sm-footer">
+ </div>
+
+</div>
+</template>
diff --git a/ui/imports/ui/components/vedge-info-window/vedge-info-window.js b/ui/imports/ui/components/vedge-info-window/vedge-info-window.js
new file mode 100644
index 0000000..a278f3a
--- /dev/null
+++ b/ui/imports/ui/components/vedge-info-window/vedge-info-window.js
@@ -0,0 +1,380 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: VedgeInfoWindow
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+//import { VedgeFlows } from '/imports/api/vedge_flows/vedge_flows';
+import * as R from 'ramda';
+//import * as moment from 'moment';
+
+import '/imports/ui/components/flow-graph/flow-graph';
+import '/imports/ui/components/time-selection-widget/time-selection-widget';
+import './vedge-info-window.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.VedgeInfoWindow.onCreated(function() {
+ let instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ showMessage: false,
+ messageType: null,
+ message: null,
+ environment: null,
+ object_id: null,
+ flowTypes: [],
+ name: null,
+ srcMacAddresses: [],
+ dstMacAddresses: [],
+ srcIPv4Addresses: [],
+ dstIPv4Addresses: [],
+ selectedFlowType: null,
+ selectedSrcMacAddress: null,
+ selectedDstMacAddress: null,
+ selectedSrcIPv4Address: null,
+ selectedDstIPv4Address: null,
+ simulateGraph: false,
+ show: false,
+ yScale: 5000000,
+ startDateTime: null
+ });
+
+ instance.autorun(() => {
+ new SimpleSchema({
+ environment: { type: String },
+ object_id: { type: String },
+ name: { type: String },
+ left: { type: Number },
+ top: { type: Number },
+ show: { type: Boolean },
+ onCloseRequested: { type: Function }
+ }).validate(Template.currentData());
+
+ instance.state.set('show', Template.currentData().show);
+ instance.state.set('environment', Template.currentData().environment);
+ instance.state.set('object_id', Template.currentData().object_id);
+ });
+
+ instance.autorun(() => {
+ let environment = instance.state.get('environment');
+ let object_id = instance.state.get('object_id');
+ let flowType = instance.state.get('selectedFlowType');
+
+ Meteor.call('statistics.flowTypes?env&object_id&type', {
+ env: environment,
+ object_id: object_id,
+ type: 'vedge_flows'
+ }, (err, res) => {
+ if (! R.isNil(err)) {
+ showMessage(instance, 'danger',
+ 'error in query for: flowTypes' + '\n' + err);
+ return;
+ }
+
+ let flowTypes = R.pipe(
+ R.map(R.prop('flowType')),
+ R.map((name) => { return { name: name }; })
+ )(res);
+ instance.state.set('flowTypes', flowTypes);
+ });
+
+ switch (flowType) {
+ case 'L2':
+ fetchL2Addressess(
+ environment,
+ object_id,
+ flowType,
+ instance
+ );
+ break;
+
+ case 'L3':
+ fetchL3Addressess(
+ environment,
+ object_id,
+ flowType,
+ instance
+ );
+ break;
+
+
+ default:
+ break;
+ }
+ });
+});
+
+Template.VedgeInfoWindow.rendered = function() {
+ this.$('.sm-start-datetime-group').datetimepicker({
+ format: 'YYYY-MM-DD HH:mm'
+ });
+};
+
+/*
+ * Events
+ */
+
+Template.VedgeInfoWindow.events({
+ 'click .sm-close-button': function (event, instance) {
+ event.stopPropagation();
+ instance.data.onCloseRequested();
+ },
+
+ 'change .sm-flow-type-select': function (event, instance) {
+ event.stopPropagation();
+ event.preventDefault();
+
+ let selections = R.map(function (optionEl) {
+ return optionEl.value;
+ }, event.target.selectedOptions);
+
+ instance.state.set('selectedFlowType', selections[0]);
+ },
+
+ 'change .sm-source-mac-address-select': function (event, instance) {
+ event.stopPropagation();
+ event.preventDefault();
+
+ let selections = R.map(function (optionEl) {
+ return optionEl.value;
+ }, event.target.selectedOptions);
+
+ instance.state.set('selectedSrcMacAddress', selections[0]);
+ },
+
+ 'change .sm-destination-mac-address-select': function (event, instance) {
+ event.stopPropagation();
+ event.preventDefault();
+
+ let selections = R.map(function (optionEl) {
+ return optionEl.value;
+ }, event.target.selectedOptions);
+
+ instance.state.set('selectedDstMacAddress', selections[0]);
+ },
+
+ 'click .sm-simulate-graph': function (event, instance) {
+ let element = instance.$('.sm-simulate-graph')[0];
+ instance.state.set('simulateGraph', element.checked);
+ },
+
+ 'input .sm-y-scale-input': function (event, instance) {
+ let element = instance.$('.sm-y-scale-input')[0];
+ let val = R.ifElse(isNaN, R.always(5000000), Number)(element.value);
+ instance.state.set('yScale', val);
+ },
+
+ 'dp.change .sm-start-datetime-group': function (event, instance) {
+ let element = instance.$('.sm-start-datetime')[0];
+ //let startDateTime = moment(element.value);
+ instance.state.set('startDateTime', element.value);
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.VedgeInfoWindow.helpers({
+ flowTypes: function () {
+ let instance = Template.instance();
+ return instance.state.get('flowTypes');
+ },
+
+ srcMacAddresses: function () {
+ let instance = Template.instance();
+ return instance.state.get('srcMacAddresses');
+ },
+
+ dstMacAddresses: function () {
+ let instance = Template.instance();
+ return instance.state.get('dstMacAddresses');
+ },
+
+ srcIPv4Addresses: function () {
+ let instance = Template.instance();
+ return instance.state.get('srcIPv4Addresses');
+ },
+
+ dstIPv4Addresses: function () {
+ let instance = Template.instance();
+ return instance.state.get('dstIPv4Addresses');
+ },
+
+ selectedFlowType: function () {
+ let instance = Template.instance();
+ return instance.state.get('selectedFlowType');
+ },
+
+ is: function (src, trg) {
+ return src === trg;
+ },
+
+ isShow: function () {
+ let instance = Template.instance();
+ return instance.state.get('show');
+ },
+
+ isShowGraph: function () {
+ let instance = Template.instance();
+
+ let show = instance.state.get('show');
+ if (! show) { return false; }
+
+ let info = {
+ env: instance.state.get('environment'),
+ object_id: instance.state.get('object_id'),
+ flowType: instance.state.get('selectedFlowType'),
+ sourceMacAddress: instance.state.get('selectedSrcMacAddress'),
+ destinationMacAddress: instance.state.get('selectedDstMacAddress'),
+ sourceIPv4Address: instance.state.get('selectedSrcIPv4Address'),
+ destinationIPv4Address: instance.state.get('selectedDstIPv4Address')
+ };
+
+ if (R.any(R.either(R.isNil, R.isEmpty))([info.env, info.object_id, info.flowType])) {
+ return false;
+ }
+
+ let sourceDestVals = R.cond([
+ [R.equals('L2'), R.always([info.sourceMacAddress, info.destinationMacAddress])],
+ [R.equals('L3'), R.always([info.sourceIPv4Address, info.destinationIPv4Address])]
+ ])(info.flowType);
+
+ if (R.any(R.either(R.isNil, R.isEmpty))(sourceDestVals)) {
+ return false;
+ }
+
+ return true;
+ },
+
+ argsFlowGraph: function () {
+ let instance = Template.instance();
+
+ return {
+ env: instance.state.get('environment'),
+ object_id: instance.state.get('object_id'),
+ type: 'vedge_flows',
+ flowType: instance.state.get('selectedFlowType'),
+ sourceMacAddress: instance.state.get('selectedSrcMacAddress'),
+ destinationMacAddress: instance.state.get('selectedDstMacAddress'),
+ sourceIPv4Address: instance.state.get('selectedSrcIPv4Address'),
+ destinationIPv4Address: instance.state.get('selectedDstIPv4Address'),
+ simulateGraph: instance.state.get('simulateGraph'),
+ yScale: instance.state.get('yScale'),
+ startDateTime: instance.state.get('startDateTime')
+ };
+ },
+
+ showMessage: function () {
+ let instance = Template.instance();
+ return instance.state.get('showMessage');
+ },
+
+ message: function () {
+ let instance = Template.instance();
+ return instance.state.get('message');
+ },
+
+ messageType: function () {
+ let instance = Template.instance();
+ return instance.state.get('messageType');
+ },
+});
+
+function showMessage(instance, type, message) {
+ instance.state.set('showMessage', true);
+ instance.state.set('messageType', type);
+ instance.state.set('message', message);
+}
+
+function fetchL2Addressess(
+ environment,
+ id,
+ flowType,
+ instance) {
+
+ Meteor.call('statistics.srcMacAddresses?env&object_id&type&flowType', {
+ env: environment,
+ object_id: id,
+ type: 'vedge_flows',
+ flowType: flowType
+ }, (err, res) => {
+ if (!R.isNil(err)) {
+ showMessage(instance, 'danger', 'error in query for: srcMacAddresses');
+ return;
+ }
+
+ let addresses = R.map((address) => { return address.sourceMacAddress; } )(res);
+ instance.state.set('srcMacAddresses', addresses);
+ });
+
+ Meteor.call('statistics.dstMacAddresses?env&object_id&type&flowType', {
+ env: environment,
+ object_id: id,
+ type: 'vedge_flows',
+ flowType: flowType
+ }, (err, res) => {
+ if (!R.isNil(err)) {
+ showMessage(instance, 'danger',
+ `error in query for: dstMacAddresses
+ message: ${err.message}` );
+ return;
+ }
+
+ let addresses = R.map((address) => {
+ return address.destinationMacAddress;
+ })(res);
+ instance.state.set('dstMacAddresses', addresses);
+ });
+}
+
+function fetchL3Addressess(
+ environment,
+ id,
+ flowType,
+ instance) {
+
+ Meteor.call('statistics.srcIPv4Addresses?env&object_id&type&flowType', {
+ env: environment,
+ object_id: id,
+ type: 'vedge_flows',
+ flowType: flowType
+ }, (err, res) => {
+ if (!R.isNil(err)) {
+ showMessage(instance, 'danger', 'error in query for: src ip addresses');
+ return;
+ }
+
+ let addresses = R.map((address) => { return address.sourceIPv4Address; } )(res);
+ instance.state.set('srcIPv4Addresses', addresses);
+ });
+
+ Meteor.call('statistics.dstIPv4Addresses?env&object_id&type&flowType', {
+ env: environment,
+ object_id: id,
+ type: 'vedge_flows',
+ flowType: flowType
+ }, (err, res) => {
+ if (!R.isNil(err)) {
+ showMessage(instance, 'danger', 'error in query for: dst ip addresses');
+ return;
+ }
+
+ let addresses = R.map((address) => { return address.destinationIPv4Address; } )(res);
+ instance.state.set('dstIPv4Addresses', addresses);
+ });
+}
diff --git a/ui/imports/ui/components/vedge-info-window/vedge-info-window.styl b/ui/imports/ui/components/vedge-info-window/vedge-info-window.styl
new file mode 100644
index 0000000..6a5bda1
--- /dev/null
+++ b/ui/imports/ui/components/vedge-info-window/vedge-info-window.styl
@@ -0,0 +1,43 @@
+/* Set the component style here */
+.os-vedge-info-window
+ visibility: hidden;
+ opacity: 0
+
+ position: absolute;
+ padding: 20px;
+ width: 0;
+ height: 0;
+
+ text-align: left;
+ font: normal 18px sans-serif;
+ color white
+ background: dk-gray1;
+
+ border: 2px solid stark-blue
+
+ transition: opacity 0.5s linear
+
+ .cl-input
+ color: dk-gray1
+ padding: 7px 5px;
+
+ .sm-body
+ margin: 20px 0;
+
+ .sm-form-group
+ display: flex;
+ flex-flow: column nowrap;
+
+.os-vedge-info-window.cl-visible
+ visibility: visible
+ min-width: 500px;
+ min-height: 400px;
+ z-index: 3;
+ opacity: 0.9
+ transition: visibility 0s, opacity 0.2s linear
+ width: initial;
+ height: initial;
+
+ .sm-start-datetime-group
+ width: 500px;
+ color: dk-gray1;
diff --git a/ui/imports/ui/components/zone-dashboard/zone-dashboard.html b/ui/imports/ui/components/zone-dashboard/zone-dashboard.html
new file mode 100644
index 0000000..9b80945
--- /dev/null
+++ b/ui/imports/ui/components/zone-dashboard/zone-dashboard.html
@@ -0,0 +1,35 @@
+<!--
+########################################################################################
+# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others #
+# #
+# All rights reserved. This program and the accompanying materials #
+# are made available under the terms of the Apache License, Version 2.0 #
+# which accompanies this distribution, and is available at #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+########################################################################################
+ -->
+<template name="ZoneDashboard">
+ <div class="os-zone-dashboard flex-box justify-content-between">
+ <div class="flex-box-3 main-layout-no-nav">
+
+ <div class="flex">
+ <div class="flex-box-1 cards white title">
+ <h4>Zone name: {{ zone.name }}</h4>
+ </div>
+ </div>
+
+ <div class="sm-info-boxes">
+ {{#each infoBox in infoBoxes }}
+ {{> DataCubic (argsInfoBox infoBox) }}
+ {{/each }}
+ </div>
+
+ <div class="sm-list-info-boxes">
+ {{#each listInfoBox in listInfoBoxes }}
+ {{> ListInfoBox (argsListInfoBox listInfoBox) }}
+ {{/each }}
+ </div>
+
+ </div>
+ </div>
+</template>
diff --git a/ui/imports/ui/components/zone-dashboard/zone-dashboard.js b/ui/imports/ui/components/zone-dashboard/zone-dashboard.js
new file mode 100644
index 0000000..7ad01e7
--- /dev/null
+++ b/ui/imports/ui/components/zone-dashboard/zone-dashboard.js
@@ -0,0 +1,214 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: ZoneDashboard
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { Inventory } from '/imports/api/inventories/inventories';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { regexEscape } from '/imports/lib/regex-utils';
+import * as R from 'ramda';
+import { store } from '/imports/ui/store/store';
+import { Icon } from '/imports/lib/icon';
+
+//import '/imports/ui/components/accordionNavMenu/accordionNavMenu';
+import '/imports/ui/components/data-cubic/data-cubic';
+import '/imports/ui/components/list-info-box/list-info-box';
+
+import './zone-dashboard.html';
+let infoBoxes = [{
+ header: ['components', 'zoneDashboard', 'infoBoxes', 'instances', 'header'],
+ dataSource: 'instancesCount',
+ icon: { type: 'fa', name: 'desktop' },
+ theme: 'dark'
+}, {
+ header: ['components', 'zoneDashboard', 'infoBoxes', 'vServices', 'header'],
+ dataSource: 'vServicesCount',
+ icon: { type: 'fa', name: 'object-group' },
+ theme: 'dark'
+}, {
+ header: ['components', 'zoneDashboard', 'infoBoxes', 'hosts', 'header'],
+ dataSource: 'hostsCount',
+ icon: { type: 'fa', name: 'server' },
+ theme: 'dark'
+}, {
+ header: ['components', 'zoneDashboard', 'infoBoxes', 'vConnectors', 'header'],
+ dataSource: 'vConnectorsCount',
+ icon: { type: 'fa', name: 'compress' },
+ theme: 'dark'
+}, {
+ header: ['components', 'zoneDashboard', 'infoBoxes', 'vEdges', 'header'],
+ dataSource: 'vEdgesCount',
+ icon: { type: 'fa', name: 'external-link' },
+ theme: 'dark'
+}];
+
+let listInfoBoxes = [{
+ header: ['components', 'zoneDashboard', 'listInfoBoxes', 'hosts', 'header'],
+ listName: 'hosts',
+ listItemFormat: {
+ getLabelFn: (item) => { return item.name; },
+ getValueFn: (item) => { return item._id._str; },
+ },
+ icon: { type: 'material', name: 'developer_board' },
+}];
+
+/*
+ * Lifecycles
+ */
+
+Template.ZoneDashboard.onCreated(function() {
+ var instance = this;
+
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ _id: null,
+ id_path: null,
+ instancesCount: 0,
+ vServicesCount: 0,
+ hostsCount: 0,
+ vConnectors: 0,
+ vEdges: 0,
+ });
+
+ instance.autorun(function () {
+ let data = Template.currentData();
+
+ new SimpleSchema({
+ _id: { type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } } },
+ onNodeSelected: { type: Function },
+ }).validate(data);
+
+ instance.state.set('_id', data._id);
+ });
+
+ instance.autorun(function () {
+ let _id = instance.state.get('_id');
+
+ instance.subscribe('inventory?_id', _id);
+ Inventory.find({ _id: _id }).forEach((zone) => {
+ instance.state.set('id_path', zone.id_path);
+
+ instance.subscribe('inventory?id_path', zone.id_path);
+ instance.subscribe('inventory?id_path_start&type', zone.id_path, 'instance');
+ instance.subscribe('inventory?id_path_start&type', zone.id_path, 'vservice');
+ instance.subscribe('inventory?id_path_start&type', zone.id_path, 'host');
+ instance.subscribe('inventory?id_path_start&type', zone.id_path, 'vconnector');
+ instance.subscribe('inventory?id_path_start&type', zone.id_path, 'vedge');
+
+ let idPathExp = new RegExp(`^${regexEscape(zone.id_path)}`);
+
+ instance.state.set('instancesCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'instance'
+ }).count());
+
+ instance.state.set('vServicesCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'vservice'
+ }).count());
+
+ instance.state.set('hostsCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'host'
+ }).count());
+
+ instance.state.set('vConnectorsCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'vconnector'
+ }).count());
+
+ instance.state.set('vEdgesCount', Inventory.find({
+ id_path: idPathExp,
+ type: 'vedge'
+ }).count());
+ });
+ });
+});
+
+/*
+Template.ZoneDashboard.rendered = function() {
+};
+*/
+
+/*
+ * Events
+ */
+
+Template.ZoneDashboard.events({
+});
+
+/*
+ * Helpers
+ */
+
+Template.ZoneDashboard.helpers({
+ zone: function () {
+ let instance = Template.instance();
+ let _id = instance.state.get('_id');
+
+ return Inventory.findOne({ _id: _id });
+ },
+
+ infoBoxes: function () {
+ return infoBoxes;
+ },
+
+ listInfoBoxes: function () {
+ return listInfoBoxes;
+ },
+
+ argsInfoBox: function (infoBox) {
+ let instance = Template.instance();
+
+ return {
+ header: R.path(infoBox.header, store.getState().api.i18n),
+ dataInfo: instance.state.get(infoBox.dataSource).toString(),
+ icon: new Icon(infoBox.icon),
+ theme: infoBox.theme
+ };
+ },
+
+ argsListInfoBox: function (listInfoBox) {
+ let instance = Template.instance();
+ let data = Template.currentData();
+ let zone_id_path = instance.state.get('id_path');
+
+ return {
+ header: R.path(listInfoBox.header, store.getState().api.i18n),
+ list: getList(listInfoBox.listName, zone_id_path),
+ //dataInfo: instance.state.get(infoBox.dataSource).toString(),
+ icon: new Icon(listInfoBox.icon),
+ //theme: infoBox.theme
+ listItemFormat: listInfoBox.listItemFormat,
+ onItemSelected: function (itemKey) {
+ data.onNodeSelected(new Mongo.ObjectID(itemKey));
+ }
+ };
+ }
+});
+
+
+function getList(listName, parentIdPath) {
+ let idPathExp = new RegExp(`^${regexEscape(parentIdPath)}`);
+
+ switch (listName) {
+ case 'hosts':
+ return Inventory.find({
+ id_path: idPathExp,
+ type: 'host'
+ });
+
+ default:
+ throw 'unknowned list type';
+ }
+}
diff --git a/ui/imports/ui/components/zone-dashboard/zone-dashboard.styl b/ui/imports/ui/components/zone-dashboard/zone-dashboard.styl
new file mode 100644
index 0000000..6910abb
--- /dev/null
+++ b/ui/imports/ui/components/zone-dashboard/zone-dashboard.styl
@@ -0,0 +1,10 @@
+.os-zone-dashboard
+ .sm-info-boxes
+ display: flex
+ flex-flow: row wrap;
+ justify-content: space-around
+
+ .sm-list-info-boxes
+ display: flex;
+ flex-flow: row wrap
+ justify-content: space-around
diff --git a/ui/imports/ui/index.styl b/ui/imports/ui/index.styl
new file mode 100644
index 0000000..281bbf0
--- /dev/null
+++ b/ui/imports/ui/index.styl
@@ -0,0 +1 @@
+@import 'components/*'
diff --git a/ui/imports/ui/lib/environment-tree-node-behavior.js b/ui/imports/ui/lib/environment-tree-node-behavior.js
new file mode 100644
index 0000000..86286a4
--- /dev/null
+++ b/ui/imports/ui/lib/environment-tree-node-behavior.js
@@ -0,0 +1,46 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { Inventory } from '/imports/api/inventories/inventories';
+
+export let EnvironmentTreeNodeBehavior = {
+ subscribeGetChildrenFn: function (instance, env) {
+ instance.subscribe('inventory.children',
+ env.name, env.type, null, env.name);
+ },
+
+ subscribeGetFirstChildFn: function (instance, env) {
+ instance.subscribe('inventory.first-child',
+ env.name, env.type, null, env.name);
+ },
+
+ getChildrenFn: function (env) {
+ let query = {
+ $or: [{
+ parent_id: env.name,
+ parent_type: env.type,
+ environment: env.name,
+ show_in_tree: true
+ }]
+ };
+
+ return Inventory.find(query);
+ },
+
+ hasChildrenFn: function (env) {
+ let query = {
+ $or: [
+ {
+ parent_id: env.name
+ }
+ ]
+ };
+
+ return Inventory.find(query).count() > 0;
+ }
+};
diff --git a/ui/imports/ui/lib/input-model.js b/ui/imports/ui/lib/input-model.js
new file mode 100644
index 0000000..5a5be84
--- /dev/null
+++ b/ui/imports/ui/lib/input-model.js
@@ -0,0 +1,31 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * file: input-model.js
+ */
+
+import * as R from 'ramda';
+
+export const createInputArgs = function (params) {
+ let instance = Template.instance();
+
+ return {
+ value: params.hash.value,
+ type: params.hash.type,
+ placeholder: params.hash.placeholder,
+ disabled: params.hash.disabled,
+ setModel: function (value) {
+ let mainModel = instance.data.model;
+ let newMainModel = R.assoc(params.hash.key, value, mainModel);
+ if (instance.data.setModel) {
+ instance.data.setModel(newMainModel);
+ }
+ },
+ };
+};
diff --git a/ui/imports/ui/lib/inventory-tree-node-behavior.js b/ui/imports/ui/lib/inventory-tree-node-behavior.js
new file mode 100644
index 0000000..ecf9c60
--- /dev/null
+++ b/ui/imports/ui/lib/inventory-tree-node-behavior.js
@@ -0,0 +1,66 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { Inventory } from '/imports/api/inventories/inventories';
+//import * as R from 'ramda';
+
+export let InventoryTreeNodeBehavior = {
+ subscribeGetChildrenFn: function (instance, parent) {
+ instance.subscribe('inventory.children',
+ parent.id, parent.type, parent.name, parent.environment);
+ },
+
+ subscribeGetFirstChildFn: function (instance, parent) {
+ instance.subscribe('inventory.first-child',
+ parent.id, parent.type, parent.name, parent.environment);
+ },
+
+ getChildrenFn: function (parent) {
+ let query = {
+ $or: [{
+ parent_id: parent.id,
+ parent_type: parent.type,
+ environment: parent.environment,
+ show_in_tree: true
+ }]
+ };
+
+ /*
+ if (R.equals('host_ref', parent.type)) {
+ let realParent = Inventory.findOne({
+ name: parent.name,
+ environment: parent.environment,
+ type: 'host'
+ });
+
+ if (! R.isNil(realParent)) {
+ query = R.merge(query, {
+ $or: R.append({
+ environment: parent.environment,
+ parent_id: realParent.id
+ }, query.$or)
+ });
+ }
+ }
+ */
+
+ return Inventory.find(query);
+ },
+
+ hasChildrenFn: function (parent) {
+ let query = {
+ $or: [
+ {
+ parent_id: parent._id
+ }
+ ]
+ };
+
+ return Inventory.find(query).count() > 0;
+ }
+};
diff --git a/ui/imports/ui/lib/select-model.js b/ui/imports/ui/lib/select-model.js
new file mode 100644
index 0000000..da553b5
--- /dev/null
+++ b/ui/imports/ui/lib/select-model.js
@@ -0,0 +1,31 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import * as R from 'ramda';
+
+export const createSelectArgs = function (params) {
+ let instance = Template.instance();
+
+ return {
+ values: params.hash.values,
+ type: params.hash.type,
+ placeholder: params.hash.placeholder,
+ options: params.hash.options,
+ multi: params.hash.multi ? params.hash.multi : false,
+ disabled: params.hash.disabled,
+ setModel: params.hash.setModel ? params.hash.setModel.fn :
+ function (values) {
+ let model = instance.data.model;
+ let newModel = R.assoc(params.hash.key, values, model);
+ if (instance.data.setModel) {
+ instance.data.setModel(newModel);
+ }
+ },
+ showNullOption: R.isNil(params.hash.showNullOption) ? false : params.hash.showNullOption
+ };
+};
diff --git a/ui/imports/ui/reducers/environment-panel.reducer.js b/ui/imports/ui/reducers/environment-panel.reducer.js
new file mode 100644
index 0000000..bac0e72
--- /dev/null
+++ b/ui/imports/ui/reducers/environment-panel.reducer.js
@@ -0,0 +1,194 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import * as R from 'ramda';
+
+import * as actions from '/imports/ui/actions/environment-panel.actions';
+import { reducer as treeNode } from './tree-node.reducer';
+import {
+ updateTreeNodeInfo,
+ addUpdateChildrenTreeNode,
+ resetTreeNodeChildren,
+ startOpenTreeNode,
+ endOpenTreeNode,
+ startCloseTreeNode,
+ endCloseTreeNode,
+ setChildDetectedTreeNode,
+ setPositionReportIsNeededAsOn,
+ reportNodePositionRetrieved,
+ setScrollToNodeIsNeededAsOn,
+ reportScrollToNodePerformed,
+ resetNeedChildDetection,
+}
+ from '/imports/ui/actions/tree-node.actions';
+
+const defaultState = {
+ _id: null,
+ envName: null,
+ isLoaded: false,
+ treeNode: treeNode(),
+ selectedNode: {
+ _id: null,
+ type: null
+ },
+ showType: 'dashboard'
+};
+
+let newState;
+
+export function reducer(state = defaultState, action) {
+ switch (action.type) {
+ case actions.SET_ENV_NAME:
+ return R.assoc('envName', action.payload.envName, state);
+
+ case actions.UPDATE_ENV_TREE_NODE:
+ return R.assoc('treeNode',
+ treeNode(state.treeNode,
+ updateTreeNodeInfo(action.payload.nodeInfo, 0)),
+ state);
+
+ case actions.ADD_UPDATE_CHILDREN_ENV_TREE_NODE:
+ return R.assoc('treeNode',
+ treeNode(state.treeNode,
+ addUpdateChildrenTreeNode(action.payload.nodePath, action.payload.childrenInfo, 0)),
+ state);
+
+ case actions.RESET_ENV_TREE_NODE_CHILDREN:
+ return R.assoc('treeNode',
+ treeNode(state.treeNode, resetTreeNodeChildren(action.payload.nodePath)),
+ state
+ );
+
+ case actions.START_OPEN_ENV_TREE_NODE:
+ return R.assoc('treeNode',
+ treeNode(state.treeNode, startOpenTreeNode(action.payload.nodePath)),
+ state
+ );
+
+ case actions.END_OPEN_ENV_TREE_NODE:
+ return R.assoc('treeNode',
+ treeNode(state.treeNode, endOpenTreeNode(action.payload.nodePath)),
+ state
+ );
+
+ case actions.START_CLOSE_ENV_TREE_NODE:
+ return R.assoc('treeNode',
+ treeNode(state.treeNode, startCloseTreeNode(action.payload.nodePath)),
+ state
+ );
+
+ case actions.END_CLOSE_ENV_TREE_NODE:
+ return R.assoc('treeNode',
+ treeNode(state.treeNode, endCloseTreeNode(action.payload.nodePath)),
+ state
+ );
+
+ case actions.SET_ENV_CHILD_DETECTED_TREE_NODE:
+ return R.assoc('treeNode',
+ treeNode(state.treeNode, setChildDetectedTreeNode(action.payload.nodePath)),
+ state
+ );
+
+ case actions.SET_ENV_SELECTED_NODE:
+ if (R.pathEq(['selectedNode', '_id'], action.payload.nodeId, state) &&
+ R.pathEq(['selectedNode', 'type'], action.payload.nodeType)
+ ) {
+ return state;
+ }
+
+ return R.merge(state, {
+ selectedNode: {
+ _id: action.payload.nodeId,
+ type: action.payload.nodeType
+ }
+ });
+
+ case actions.SET_ENV_SELECTED_NODE_INFO:
+ newState = R.merge(state, {
+ selectedNode: R.merge(state.selectedNode, {
+ type: action.payload.nodeInfo.type,
+ clique: action.payload.nodeInfo.clique,
+ id_path: action.payload.nodeInfo.id_path
+ })
+ });
+
+ if (! R.isNil(action.payload.nodeInfo.clique)) {
+ newState = R.assoc('showType', 'graph', newState);
+ }
+
+ return newState;
+
+ case actions.SET_ENV_SELECTED_NODE_AS_ENV:
+ return R.merge(state, {
+ selectedNode: {
+ _id: state._id,
+ type: 'environment'
+ }
+ });
+
+ case actions.SET_ENV_ENV_ID:
+ return R.assoc('_id', action.payload._id, state);
+
+ case actions.SET_ENV_AS_LOADED:
+ return R.assoc('isLoaded', true, state);
+
+ case actions.SET_ENV_AS_NOT_LOADED:
+ return R.assoc('isLoaded', false, state);
+
+ case actions.SET_SHOW_DASHBOARD:
+ return R.assoc('showType', 'dashboard', state);
+
+ case actions.SET_SHOW_GRAPH:
+ return R.assoc('showType', 'graph', state);
+
+ case actions.TOGGLE_ENV_SHOW:
+ return R.pipe(
+ R.ifElse(R.equals('dashboard'),
+ R.always('graph'),
+ R.always('dashboard')),
+ R.assoc('showType', R.__, state)
+ )(state.showType);
+
+ case actions.SET_ENV_POSITION_REPORT_IS_NEEDED_AS_ON:
+ return R.assoc('treeNode',
+ treeNode(state.treeNode, setPositionReportIsNeededAsOn(action.payload.nodePath)),
+ state
+ );
+
+ case actions.REPORT_ENV_NODE_POSITION_RETRIEVED:
+ return R.assoc('treeNode',
+ treeNode(state.treeNode, reportNodePositionRetrieved(
+ action.payload.nodePath, action.payload.rect)),
+ state
+ );
+
+ case actions.SET_ENV_SCROLL_TO_NODE_IS_NEEDED_AS_ON:
+ return R.assoc('treeNode',
+ treeNode(state.treeNode, setScrollToNodeIsNeededAsOn(
+ action.payload.nodePath)),
+ state
+ );
+
+ case actions.REPORT_ENV_SCROLL_TO_NODE_PERFORMED:
+ return R.assoc('treeNode',
+ treeNode(state.treeNode, reportScrollToNodePerformed(
+ action.payload.nodePath)),
+ state
+ );
+
+ case actions.RESET_ENV_NEED_CHILD_DETECTION:
+ return R.assoc('treeNode',
+ treeNode(state.treeNode, resetNeedChildDetection(
+ action.payload.nodePath)),
+ state
+ );
+
+ default:
+ return state;
+ }
+}
diff --git a/ui/imports/ui/reducers/graph-tooltip-window.reducer.js b/ui/imports/ui/reducers/graph-tooltip-window.reducer.js
new file mode 100644
index 0000000..67f96f1
--- /dev/null
+++ b/ui/imports/ui/reducers/graph-tooltip-window.reducer.js
@@ -0,0 +1,48 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import * as R from 'ramda';
+
+import * as actions from '/imports/ui/actions/graph-tooltip-window.actions';
+
+const defaultState = {
+ label: '',
+ title: '',
+ left: 0,
+ top: 0,
+ show: false
+};
+
+export function reducer(state = defaultState, action) {
+ let attrsStr;
+ switch (action.type) {
+ case actions.ACTIVATE_GRAPH_TOOLTIP_WINDOW:
+ attrsStr = JSON.stringify(action.payload.attributes, null, 4)
+ .toString()
+ .replace(/\,/g,'<BR>')
+ .replace(/\[/g,'')
+ .replace(/\]/g,'')
+ .replace(/\{/g,'')
+ .replace(/\}/g,'')
+ .replace(/"/g,'');
+
+ return R.merge(state, {
+ label: action.payload.label,
+ title: attrsStr,
+ left: action.payload.left,
+ top: action.payload.top - 28,
+ show: true
+ });
+
+ case actions.CLOSE_GRAPH_TOOLTIP_WINDOW:
+ return R.assoc('show', false, state);
+
+ default:
+ return state;
+ }
+}
diff --git a/ui/imports/ui/reducers/i18n.reducer.js b/ui/imports/ui/reducers/i18n.reducer.js
new file mode 100644
index 0000000..8771aad
--- /dev/null
+++ b/ui/imports/ui/reducers/i18n.reducer.js
@@ -0,0 +1,180 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+//import * as R from 'ramda';
+
+const defaultState = {
+ apis: {
+
+ },
+ collections: {
+ environments: {
+ fields: {
+ eventBasedScan: {
+ header: 'Event based scan',
+ desc: 'Update the inventory in real-time whenever a user makes a change to the OpenStack environment'
+ }
+ }
+ }
+ },
+ components: {
+ environment: {
+ noGraphForLeafMsg: 'No clique for this focal_point',
+ briefInfos: {
+ lastScanning: {
+ header: 'Last scanning'
+ },
+ vConnectorsNum: {
+ header: 'Number of vConnectors'
+ },
+ hostsNum: {
+ header: 'Number of hosts'
+ },
+ vServicesNum: {
+ header: 'Number of vServices'
+ },
+ instancesNum: {
+ header: 'Number of instances'
+ }
+ },
+ listInfoBoxes: {
+ regions: {
+ header: 'Regions'
+ },
+ projects: {
+ header: 'Projects'
+ }
+ }
+ },
+ projectDashboard: {
+ infoBoxes: {
+ networks: {
+ header: 'Number of networks'
+ },
+ ports: {
+ header: 'Number of ports'
+ }
+ }
+ },
+
+ regionDashboard: {
+ infoBoxes: {
+ instances: {
+ header: 'Number of instances'
+ },
+ vServices: {
+ header: 'Number of vServices'
+ },
+ hosts: {
+ header: 'Number of hosts'
+ },
+ vConnectors: {
+ header: 'Number of vConnectors'
+ }
+ },
+ listInfoBoxes: {
+ availabilityZones: {
+ header: 'Availability zones'
+ },
+ aggregates: {
+ header: 'Aggregates'
+ }
+ }
+ },
+
+ zoneDashboard: {
+ infoBoxes: {
+ instances: {
+ header: 'Number of instances'
+ },
+ vServices: {
+ header: 'Number of vServices'
+ },
+ hosts: {
+ header: 'Number of hosts'
+ },
+ vConnectors: {
+ header: 'Number of vConnectors'
+ },
+ vEdges: {
+ header: 'Number of vEdges'
+ }
+ },
+ listInfoBoxes: {
+ hosts: {
+ header: 'Hosts'
+ },
+ }
+ },
+
+ aggregateDashboard: {
+ infoBoxes: {
+ instances: {
+ header: 'Number of instances'
+ },
+ vServices: {
+ header: 'Number of vServices'
+ },
+ hosts: {
+ header: 'Number of hosts'
+ },
+ vConnectors: {
+ header: 'Number of vConnectors'
+ },
+ vEdges: {
+ header: 'Number of vEdges'
+ }
+ },
+ listInfoBoxes: {
+ hosts: {
+ header: 'Hosts'
+ },
+ }
+ },
+
+ hostDashboard: {
+ infoBoxes: {
+ instances: {
+ header: 'Number of instances'
+ },
+ vServices: {
+ header: 'Number of vServices'
+ },
+ vConnectors: {
+ header: 'Number of vConnectors'
+ },
+ networkAgents: {
+ header: 'Number of agents'
+ },
+ pnics: {
+ header: 'Number of pnics'
+ },
+ vEdges: {
+ header: 'Number of vEdges'
+ },
+ ports: {
+ header: 'Number of ports'
+ }
+ },
+ },
+
+ generalFolderNodeDashboard: {
+ mainCubic: {
+ header: 'Number of children'
+ }
+ }
+ }
+};
+
+export function reducer(state = defaultState, action) {
+ switch (action.type) {
+
+ default:
+ return state;
+ }
+}
diff --git a/ui/imports/ui/reducers/index.js b/ui/imports/ui/reducers/index.js
new file mode 100644
index 0000000..6ee909d
--- /dev/null
+++ b/ui/imports/ui/reducers/index.js
@@ -0,0 +1,33 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { combineReducers } from 'redux';
+
+import { navigation } from './navigation';
+import { searchInterestedParties } from './search-interested-parties';
+import { reducer as environmentPanel } from './environment-panel.reducer';
+import { reducer as i18n } from './i18n.reducer';
+import { reducer as graphTooltipWindow } from './graph-tooltip-window.reducer';
+import { reducer as vedgeInfoWindow } from './vedge-info-window.reducer';
+import { reducer as mainApp } from './main-app.reducer';
+
+const calipsoApp = combineReducers({
+ api: combineReducers({
+ navigation,
+ searchInterestedParties,
+ i18n
+ }),
+ components: combineReducers({
+ mainApp: mainApp,
+ environmentPanel,
+ graphTooltipWindow,
+ vedgeInfoWindow
+ })
+});
+
+export default calipsoApp;
diff --git a/ui/imports/ui/reducers/main-app.reducer.js b/ui/imports/ui/reducers/main-app.reducer.js
new file mode 100644
index 0000000..abc4574
--- /dev/null
+++ b/ui/imports/ui/reducers/main-app.reducer.js
@@ -0,0 +1,28 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import * as R from 'ramda';
+
+import * as actions from '/imports/ui/actions/main-app.actions';
+
+const defaultState = {
+ selectedEnvironment: {},
+};
+
+export function reducer(state = defaultState, action) {
+ switch (action.type) {
+ case actions.SET_MAIN_APP_SELECTED_ENVIRONMENT:
+ return R.assoc('selectedEnvironment', {
+ _id: action.payload._id,
+ name: action.payload.name
+ }, state);
+
+ default:
+ return state;
+ }
+}
diff --git a/ui/imports/ui/reducers/navigation.js b/ui/imports/ui/reducers/navigation.js
new file mode 100644
index 0000000..de78ee5
--- /dev/null
+++ b/ui/imports/ui/reducers/navigation.js
@@ -0,0 +1,99 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import * as R from 'ramda';
+
+import * as actions from '/imports/ui/actions/navigation';
+
+const defaultState = { current: [], lastActionable: [] };
+
+function reducer(state = defaultState, action) {
+ let lastActionable = null;
+
+ switch (action.type) {
+ case actions.SET_CURRENT_NODE:
+ lastActionable = isActionable(action.payload.nodeChain) ? action.payload.nodeChain :
+ state.lastActionable;
+
+ return R.merge(state, {
+ current: action.payload.nodeChain,
+ lastActionable: lastActionable
+ });
+
+ case actions.SET_CURRENT_NODE_FROM_TREE_CONTROL:
+ lastActionable = isActionable(action.payload.nodeChain) ? action.payload.nodeChain :
+ state.lastActionable;
+
+ if (contains(action.payload.nodeChain, state.current)) {
+ let equalLastIndex = findEqualLastIndex(action.payload.nodeChain, state.current);
+ return R.merge(state, {
+ current: R.slice(0, equalLastIndex, action.payload.nodeChain),
+ lastActionable: lastActionable
+ });
+ } else {
+ return R.merge(state, {
+ current: action.payload.nodeChain,
+ lastActionable: lastActionable
+ });
+ }
+
+ default:
+ return state;
+ }
+}
+
+function contains(subArray, array) {
+ let equalLastIndex = findEqualLastIndex(subArray, array);
+
+ if (subArray.length <= array.length &&
+ equalLastIndex >= 0 &&
+ subArray.length === equalLastIndex + 1) {
+
+ return true;
+ }
+
+ return false;
+}
+
+function findEqualLastIndex (arrayA, arrayB) {
+ let indexResult = -1;
+
+ for (let i = 0; (i < arrayA.length) && (i < arrayB.length); i++) {
+ if (equalsNodes(arrayA[i], arrayB[i])) {
+ indexResult = i;
+ } else {
+ break;
+ }
+ }
+
+ return indexResult;
+}
+
+function equalsNodes(nodeA, nodeB) {
+ if (nodeA.fullIdPath !== nodeB.fullIdPath) { return false; }
+ if (nodeA.fullNamePath !== nodeB.fullNamePath) { return false; }
+
+ return true;
+}
+
+function isActionable(nodeChain) {
+ let last = R.last(nodeChain);
+
+ if (R.isNil(last)) { return false; }
+ if (R.isNil(last.item)) { return false; }
+
+ if (! R.isNil(last.item.clique)) { return true; }
+
+ if (last.item.id === 'aggregate-WebEx-RTP-SSD-Aggregate-node-24') {
+ return true;
+ }
+
+ return false;
+}
+
+export const navigation = reducer;
diff --git a/ui/imports/ui/reducers/search-interested-parties.js b/ui/imports/ui/reducers/search-interested-parties.js
new file mode 100644
index 0000000..26220c2
--- /dev/null
+++ b/ui/imports/ui/reducers/search-interested-parties.js
@@ -0,0 +1,76 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import * as R from 'ramda';
+
+import * as actions from '/imports/ui/actions/search-interested-parties';
+
+const defaultState = {
+ listeners: [],
+ searchTerm: null,
+ searchAutoCompleteTerm: null,
+ searchAutoCompleteFutureId: null
+};
+
+function reducer(state = defaultState, action) {
+ let newListeners;
+
+ switch (action.type) {
+ case actions.ADD_SEARCH_INTERESTED_PARTY:
+ newListeners = R.unionWith(
+ R.eqBy(R.prop('action')),
+ state.listeners,
+ [{ action: action.payload.listener }]);
+ return R.assoc('listeners', newListeners, state);
+
+ case actions.REMOVE_SEARCH_INTERESTED_PARTY:
+ newListeners = R.differenceWith(
+ R.eqBy(R.prop('action')),
+ state.listeners,
+ [{ action:action.payload.listener }]);
+ return R.assoc('listeners', newListeners, state);
+
+ case actions.SET_SEARCH_TERM:
+ asyncCall(() => {
+ notifyListeners(action.payload.searchTerm, state.listeners);
+ });
+ return R.assoc('searchTerm', action.payload.searchTerm, state);
+
+ case actions.SET_SEARCH_AUTO_COMPLETE_TERM:
+ return R.assoc('searchAutoCompleteTerm', action.payload.searchTerm, state);
+
+ case actions.RESET_SEARCH_AUTO_COMPLETE_FUTURE:
+ if (! R.isNil(state.searchAutoCompleteFutureId)) {
+ clearTimeout(state.searchAutoCompleteFutureId);
+ }
+ return R.assoc('searchAutoCompleteFutureId', null, state);
+
+ case actions.SET_SEARCH_AUTO_COMPLETE_FUTURE:
+ if (! R.isNil(state.searchAutoCompleteFutureId)) {
+ clearTimeout(state.searchAutoCompleteFutureId);
+ }
+ return R.assoc('searchAutoCompleteFutureId', action.payload.futureId, state);
+
+ default:
+ return state;
+ }
+}
+
+function asyncCall(fnObject) {
+ setTimeout(() => {
+ fnObject.call(null);
+ }, 0);
+}
+
+function notifyListeners(searchTerm, listeners) {
+ R.forEach((listenerItem) => {
+ listenerItem.action.call(null, searchTerm);
+ }, listeners);
+}
+
+export const searchInterestedParties = reducer;
diff --git a/ui/imports/ui/reducers/tree-node.reducer.js b/ui/imports/ui/reducers/tree-node.reducer.js
new file mode 100644
index 0000000..0a6ec73
--- /dev/null
+++ b/ui/imports/ui/reducers/tree-node.reducer.js
@@ -0,0 +1,232 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+//import { Mongo } from 'meteor/mongo';
+import * as R from 'ramda';
+
+import * as actions from '/imports/ui/actions/tree-node.actions';
+
+const defaultState = {
+ _id: null,
+ nodeInfo: {},
+ openState: 'closed', // opened, start_close, closed, start_open
+ children: [],
+ childDetected: false,
+ needChildDetection: true,
+ linkDetected: false,
+ level: 1,
+ positionNeeded: false,
+ position: null,
+ scrollToNodeIsNeeded: false
+};
+
+export function reducer(state = defaultState, action) {
+ let nodeId;
+ let rest;
+ //let child;
+ //let index;
+
+ if (R.isNil(action)) { return defaultState; }
+
+ switch (action.type) {
+
+ case actions.UPDATE_TREE_NODE_INFO:
+ return R.merge(state, {
+ _id: action.payload.nodeInfo._id._str,
+ nodeInfo: action.payload.nodeInfo,
+ openState: 'closed',
+ children: [],
+ childDetected: false,
+ needChildDetection: true,
+ linkDetected: R.propEq('type', 'host_ref', action.payload.nodeInfo),
+ level: action.payload.level,
+ });
+
+ case actions.ADD_UPDATE_CHILDREN_TREE_NODE:
+ nodeId = R.head(action.payload.nodePath);
+ rest = R.tail(action.payload.nodePath);
+
+ if (R.isNil(nodeId)) {
+ let actionChildren = R.map((childInfo) => {
+ let existingChild = R.find(
+ R.pathEq(['nodeInfo', '_id', '_str'], childInfo._id._str), state.children);
+
+ return reducer(existingChild,
+ actions.updateTreeNodeInfo(childInfo, action.payload.level + 1));
+ }, action.payload.childrenInfo);
+
+ let allChildren = R.unionWith(R.eqBy(R.path(['nodeInfo', '_id', '_str'])),
+ actionChildren, state.children);
+
+ /*
+ R.forEach((actionChild) => {
+ let index = R.findIndex(R.pathEq(['nodeInfo', '_id', '_str'], actionChild._id._str),state.children);
+ if (index < 0) {
+ state.children.push(actionChild);
+ } else {
+ state.children[index] = actionChild;
+ }
+ }, actionChildren);
+ let allChildren = state.children;
+ */
+
+ return R.merge(state, {
+ children: allChildren,
+ childDetected: R.length(allChildren) > 0
+ });
+
+ /*
+ state.childDetected = R.length(allChildren) > 0;
+ return state;
+ */
+ }
+
+ return reduceActionOnChild(state,
+ actions.addUpdateChildrenTreeNode(
+ rest, action.payload.childrenInfo, action.payload.level + 1),
+ nodeId);
+
+ case actions.RESET_TREE_NODE_CHILDREN:
+ nodeId = R.head(action.payload.nodePath);
+ rest = R.tail(action.payload.nodePath);
+
+ if (R.isNil(nodeId)) {
+ return R.merge(state, {
+ children: [],
+ childDetected: false,
+ needChildDetection: true,
+ });
+ }
+
+ return reduceActionOnChild(state, actions.resetTreeNodeChildren(rest), nodeId);
+
+ case actions.START_OPEN_TREE_NODE:
+ nodeId = R.head(action.payload.nodePath);
+ rest = R.tail(action.payload.nodePath);
+
+ if (R.isNil(nodeId)) {
+ return R.assoc('openState', 'start_open', state);
+ }
+
+ return reduceActionOnChild(state, actions.startOpenTreeNode(rest), nodeId);
+
+ case actions.END_OPEN_TREE_NODE:
+ nodeId = R.head(action.payload.nodePath);
+ rest = R.tail(action.payload.nodePath);
+
+ if (R.isNil(nodeId)) {
+ return R.assoc('openState', 'opened', state);
+ }
+
+ return reduceActionOnChild(state, actions.endOpenTreeNode(rest), nodeId);
+
+ case actions.START_CLOSE_TREE_NODE:
+ nodeId = R.head(action.payload.nodePath);
+ rest = R.tail(action.payload.nodePath);
+
+ if (R.isNil(nodeId)) {
+ return R.assoc('openState', 'start_close', state);
+ }
+
+ return reduceActionOnChild(state, actions.startCloseTreeNode(rest), nodeId);
+
+ case actions.END_CLOSE_TREE_NODE:
+ nodeId = R.head(action.payload.nodePath);
+ rest = R.tail(action.payload.nodePath);
+
+ if (R.isNil(nodeId)) {
+ return R.assoc('openState', 'closed', state);
+ }
+
+ return reduceActionOnChild(state, actions.endCloseTreeNode(rest), nodeId);
+
+ case actions.SET_CHILD_DETECTED_TREE_NODE:
+ nodeId = R.head(action.payload.nodePath);
+ rest = R.tail(action.payload.nodePath);
+
+ if (R.isNil(nodeId)) {
+ return R.assoc('childDetected', true, state);
+ }
+
+ return reduceActionOnChild(state, actions.setChildDetectedTreeNode(rest), nodeId);
+
+ case actions.SET_POSITION_REPORT_IS_NEEDED_AS_ON:
+ nodeId = R.head(action.payload.nodePath);
+ rest = R.tail(action.payload.nodePath);
+
+ if (R.isNil(nodeId)) {
+ return R.assoc('positionNeeded', true, state);
+ }
+
+ return reduceActionOnChild(state, actions.setPositionReportIsNeededAsOn(rest), nodeId);
+
+ case actions.REPORT_NODE_POSITION_RETRIEVED:
+ nodeId = R.head(action.payload.nodePath);
+ rest = R.tail(action.payload.nodePath);
+
+ if (R.isNil(nodeId)) {
+ return R.merge(state, {
+ position: {
+ top: action.payload.rect.top,
+ bottom: action.payload.rect.bottom,
+ height: action.payload.rect.height,
+ },
+ positionNeeded: false
+ });
+ }
+
+ return reduceActionOnChild(state,
+ actions.reportNodePositionRetrieved(rest, action.payload.rect), nodeId);
+
+ case actions.SET_SCROLL_TO_NODE_IS_NEEDED_AS_ON:
+ nodeId = R.head(action.payload.nodePath);
+ rest = R.tail(action.payload.nodePath);
+
+ if (R.isNil(nodeId)) {
+ return R.assoc('scrollToNodeIsNeeded', true, state);
+ }
+
+ return reduceActionOnChild(state, actions.setScrollToNodeIsNeededAsOn(rest), nodeId);
+
+ case actions.REPORT_SCROLL_TO_NODE_PERFORMED:
+ nodeId = R.head(action.payload.nodePath);
+ rest = R.tail(action.payload.nodePath);
+
+ if (R.isNil(nodeId)) {
+ return R.assoc('scrollToNodeIsNeeded', false, state);
+ }
+
+ return reduceActionOnChild(state, actions.reportScrollToNodePerformed(rest), nodeId);
+
+ case actions.RESET_NEED_CHILD_DETECTION:
+ nodeId = R.head(action.payload.nodePath);
+ rest = R.tail(action.payload.nodePath);
+
+ if (R.isNil(nodeId)) {
+ return R.assoc('needChildDetection', false, state);
+ }
+
+ return reduceActionOnChild(state, actions.resetNeedChildDetection(rest), nodeId);
+
+
+ default:
+ return state;
+ }
+}
+
+function reduceActionOnChild(state, action, nodeId) {
+ let index = R.findIndex(R.pathEq(['nodeInfo', '_id', '_str'], nodeId), state.children);
+ if (index < 0) throw 'error in reduce action on child';
+ let child = state.children[index];
+
+ return R.assoc('children',
+ R.update(index,
+ reducer(child, action),
+ state.children),
+ state);
+}
diff --git a/ui/imports/ui/reducers/vedge-info-window.reducer.js b/ui/imports/ui/reducers/vedge-info-window.reducer.js
new file mode 100644
index 0000000..d1be629
--- /dev/null
+++ b/ui/imports/ui/reducers/vedge-info-window.reducer.js
@@ -0,0 +1,50 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import * as R from 'ramda';
+
+import * as actions from '/imports/ui/actions/vedge-info-window.actions';
+
+const defaultState = {
+ node: null,
+ left: 0,
+ top: 0,
+ show: false
+};
+
+export function reducer(state = defaultState, action) {
+ let newState;
+
+ switch (action.type) {
+ case actions.ACTIVATE_VEDGE_INFO_WINDOW:
+ newState = R.merge(state, {
+ node: R.pick([
+ '_id',
+ 'id',
+ 'id_path',
+ 'name',
+ 'name_path',
+ 'environment'
+ ], action.payload.node),
+ left: action.payload.left,
+ top: action.payload.top - 28,
+ show: true
+ });
+ return newState;
+
+ case actions.CLOSE_VEDGE_INFO_WINDOW:
+ return R.merge(state, {
+ show: false,
+ top: 0,
+ left: 0
+ });
+
+ default:
+ return state;
+ }
+}
diff --git a/ui/imports/ui/store/index.js b/ui/imports/ui/store/index.js
new file mode 100644
index 0000000..fc5e2f5
--- /dev/null
+++ b/ui/imports/ui/store/index.js
@@ -0,0 +1,11 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { Store } from './store';
+
+export { Store };
diff --git a/ui/imports/ui/store/store.js b/ui/imports/ui/store/store.js
new file mode 100644
index 0000000..76da6a9
--- /dev/null
+++ b/ui/imports/ui/store/store.js
@@ -0,0 +1,25 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { createStore, applyMiddleware, compose } from 'redux';
+import thunk from 'redux-thunk';
+import calipsoApp from '/imports/ui/reducers/index';
+
+const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
+const store = createStore(
+ calipsoApp,
+ composeEnhancers(
+ applyMiddleware(
+ thunk
+ )
+ )
+);
+
+export {
+ store
+};