aboutsummaryrefslogtreecommitdiffstats
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
parentb70483739d1f6f4f0d31987ed2e4d1e30d71d579 (diff)
adding calipso ui
Change-Id: Ifa6f63daebb07f45580f747341960e898fdb00c4 Signed-off-by: Koren Lev <korenlev@gmail.com>
-rw-r--r--ui/Dockerfile2
-rw-r--r--ui/app.styl34
-rwxr-xr-xui/build-deploy22
-rwxr-xr-xui/build.sh20
-rw-r--r--ui/client/accounts-config.js11
-rw-r--r--ui/client/css/accordionNavMenu.styl259
-rw-r--r--ui/client/css/components.styl187
-rw-r--r--ui/client/css/d3graph.styl47
-rw-r--r--ui/client/css/flex.styl23
-rw-r--r--ui/client/css/header.styl118
-rw-r--r--ui/client/css/landing.css122
-rw-r--r--ui/client/css/loading.styl19
-rw-r--r--ui/client/css/modals.styl10
-rw-r--r--ui/client/css/plugins.styl4
-rw-r--r--ui/client/css/wizard.css28
-rw-r--r--ui/client/lib/jquery.multilevelpushmenu.css120
-rw-r--r--ui/client/lib/jquery.multilevelpushmenu.js1148
-rw-r--r--ui/client/main.html18
-rw-r--r--ui/client/main.js10
-rw-r--r--ui/client/main.styl85
-rw-r--r--ui/client/style/d3plusgraph.css14
-rw-r--r--ui/client/style/landingpage.css138
-rw-r--r--ui/client/style/multilevelorig.css43
-rw-r--r--ui/client/style/threeTest.css65
-rw-r--r--ui/client/style/threeTest2.css41
-rw-r--r--ui/client/templates/envdialog.html131
-rw-r--r--ui/client/templates/header.html13
-rw-r--r--ui/client/templates/mainPage.html207
-rw-r--r--ui/contributors.md6
-rw-r--r--ui/doc/tasks/refactor-tree-node-to-component.md13
-rw-r--r--ui/imports/api/accounts/methods.js196
-rw-r--r--ui/imports/api/accounts/server/publications.js29
-rw-r--r--ui/imports/api/attributes_for_hover_on_data/attributes_for_hover_on_data.js12
-rw-r--r--ui/imports/api/attributes_for_hover_on_data/methods.js8
-rw-r--r--ui/imports/api/attributes_for_hover_on_data/server/publications.js25
-rw-r--r--ui/imports/api/clique-constraints/clique-constraints.js48
-rw-r--r--ui/imports/api/clique-constraints/methods.js99
-rw-r--r--ui/imports/api/clique-constraints/server/publications.js30
-rw-r--r--ui/imports/api/clique-types/clique-types.js107
-rw-r--r--ui/imports/api/clique-types/methods.js108
-rw-r--r--ui/imports/api/clique-types/server/publications.js34
-rw-r--r--ui/imports/api/cliques/cliques.js12
-rw-r--r--ui/imports/api/cliques/methods.js8
-rw-r--r--ui/imports/api/cliques/server/publications.js33
-rw-r--r--ui/imports/api/constants/constants.js22
-rw-r--r--ui/imports/api/constants/data/distributions.js64
-rw-r--r--ui/imports/api/constants/data/env-types.js15
-rw-r--r--ui/imports/api/constants/data/environment-monitoring-types.js12
-rw-r--r--ui/imports/api/constants/data/environment-provision-types.js21
-rw-r--r--ui/imports/api/constants/data/log-levels.js27
-rw-r--r--ui/imports/api/constants/data/mechanism-drivers.js24
-rw-r--r--ui/imports/api/constants/data/message-source-systems.js15
-rw-r--r--ui/imports/api/constants/data/network-plugins.js15
-rw-r--r--ui/imports/api/constants/data/object-types-for-links.js39
-rw-r--r--ui/imports/api/constants/data/scans-statuses.js30
-rw-r--r--ui/imports/api/constants/data/type-drivers.js24
-rw-r--r--ui/imports/api/constants/server/publications.js16
-rw-r--r--ui/imports/api/environments/configuration-groups/aci-configuration.js29
-rw-r--r--ui/imports/api/environments/configuration-groups/amqp-configuration.js29
-rw-r--r--ui/imports/api/environments/configuration-groups/cli-configuration.js69
-rw-r--r--ui/imports/api/environments/configuration-groups/monitoring-configuration.js119
-rw-r--r--ui/imports/api/environments/configuration-groups/mysql-configuration.js33
-rw-r--r--ui/imports/api/environments/configuration-groups/nfv-provider-configuration.js25
-rw-r--r--ui/imports/api/environments/configuration-groups/open-stack-configuration.js30
-rw-r--r--ui/imports/api/environments/environments.js457
-rw-r--r--ui/imports/api/environments/methods.js154
-rw-r--r--ui/imports/api/environments/server/publications.js102
-rw-r--r--ui/imports/api/inventories/inventories.js11
-rw-r--r--ui/imports/api/inventories/server/methods.js151
-rw-r--r--ui/imports/api/inventories/server/publications.js250
-rw-r--r--ui/imports/api/link-types/link-types.js86
-rw-r--r--ui/imports/api/link-types/methods.js114
-rw-r--r--ui/imports/api/link-types/server/publications.js46
-rw-r--r--ui/imports/api/links/links.js11
-rw-r--r--ui/imports/api/links/methods.js8
-rw-r--r--ui/imports/api/links/server/publications.js32
-rw-r--r--ui/imports/api/messages/messages.js125
-rw-r--r--ui/imports/api/messages/methods.js8
-rw-r--r--ui/imports/api/messages/server/methods.js49
-rw-r--r--ui/imports/api/messages/server/publications.js98
-rw-r--r--ui/imports/api/migrations/migrations.js20
-rw-r--r--ui/imports/api/scans/methods.js55
-rw-r--r--ui/imports/api/scans/scans.js159
-rw-r--r--ui/imports/api/scans/server/methods.js44
-rw-r--r--ui/imports/api/scans/server/publications.js82
-rw-r--r--ui/imports/api/scheduled-scans/methods.js131
-rw-r--r--ui/imports/api/scheduled-scans/scheduled-scans.js91
-rw-r--r--ui/imports/api/scheduled-scans/server/methods.js27
-rw-r--r--ui/imports/api/scheduled-scans/server/publications.js60
-rw-r--r--ui/imports/api/simple-schema.init.js13
-rw-r--r--ui/imports/api/statistics/helpers.js64
-rw-r--r--ui/imports/api/statistics/methods.js159
-rw-r--r--ui/imports/api/statistics/server/publications.js52
-rw-r--r--ui/imports/api/statistics/statistics.js14
-rw-r--r--ui/imports/api/supported_environments/methods.js8
-rw-r--r--ui/imports/api/supported_environments/server/publications.js17
-rw-r--r--ui/imports/api/supported_environments/supported_environments.js49
-rw-r--r--ui/imports/index.styl1
-rw-r--r--ui/imports/lib/d3-graph.js573
-rw-r--r--ui/imports/lib/d3three.js789
-rw-r--r--ui/imports/lib/general-regex.js15
-rw-r--r--ui/imports/lib/icon.js14
-rw-r--r--ui/imports/lib/images-for-node-type.js22
-rw-r--r--ui/imports/lib/regex-utils.js11
-rw-r--r--ui/imports/lib/simple-schema-utils.js15
-rw-r--r--ui/imports/lib/utilities.js54
-rw-r--r--ui/imports/startup/both/config.js9
-rw-r--r--ui/imports/startup/both/configs/accounts.js9
-rw-r--r--ui/imports/startup/both/index.js10
-rw-r--r--ui/imports/startup/client/index.js38
-rw-r--r--ui/imports/startup/client/template-helpers.js32
-rw-r--r--ui/imports/startup/server/config.js9
-rw-r--r--ui/imports/startup/server/configs/accounts.js16
-rw-r--r--ui/imports/startup/server/index.js13
-rw-r--r--ui/imports/startup/server/register-api.js56
-rw-r--r--ui/imports/startup/server/seeds.js10
-rw-r--r--ui/imports/startup/server/seeds/constants.js68
-rw-r--r--ui/imports/startup/server/seeds/users.js51
-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
-rw-r--r--ui/lib/router.js363
-rw-r--r--ui/osdna.js9
-rwxr-xr-xui/package-source.sh23
-rw-r--r--ui/package.json26
-rw-r--r--ui/public/cisco-favicon.icobin0 -> 38692 bytes
-rw-r--r--ui/public/cisco-logo-load.pngbin0 -> 4214 bytes
-rw-r--r--ui/public/dna-718905_600.pngbin0 -> 17620 bytes
-rw-r--r--ui/public/get-started/dashboard-notifications.pngbin0 -> 10832 bytes
-rw-r--r--ui/public/get-started/env-dashboard.pngbin0 -> 46839 bytes
-rw-r--r--ui/public/get-started/env-delete-action.pngbin0 -> 81457 bytes
-rw-r--r--ui/public/get-started/env-diting-os-db-credentails.pngbin0 -> 65797 bytes
-rw-r--r--ui/public/get-started/env-edit-action.pngbin0 -> 81503 bytes
-rw-r--r--ui/public/get-started/env-editing-amqp-credentials.pngbin0 -> 64211 bytes
-rw-r--r--ui/public/get-started/env-editing-master-host-credentials.pngbin0 -> 71760 bytes
-rw-r--r--ui/public/get-started/env-editing-monitoring.pngbin0 -> 117008 bytes
-rw-r--r--ui/public/get-started/env-editing-os-end-point.pngbin0 -> 45236 bytes
-rw-r--r--ui/public/get-started/env-editing-provision.pngbin0 -> 9517 bytes
-rw-r--r--ui/public/get-started/env-editing.pngbin0 -> 75945 bytes
-rw-r--r--ui/public/get-started/environments-pick.pngbin0 -> 92013 bytes
-rw-r--r--ui/public/get-started/main-screen-explained.pngbin0 -> 195772 bytes
-rw-r--r--ui/public/get-started/navbar-explained.pngbin0 -> 181291 bytes
-rw-r--r--ui/public/get-started/new-environment-action.pngbin0 -> 72093 bytes
-rw-r--r--ui/public/get-started/scan-action.pngbin0 -> 81481 bytes
-rw-r--r--ui/public/get-started/scan-request-screen.pngbin0 -> 25126 bytes
-rw-r--r--ui/public/get-started/selecting-dashboard.pngbin0 -> 91003 bytes
-rw-r--r--ui/public/get-started/selecting-env.pngbin0 -> 7762 bytes
-rw-r--r--ui/public/get-started/setting-action.pngbin0 -> 34477 bytes
-rw-r--r--ui/public/get-started/setting-scans-action.pngbin0 -> 125195 bytes
-rw-r--r--ui/public/ic_cloud_queue_black_48dp_2x.pngbin0 -> 9845 bytes
-rw-r--r--ui/public/ic_computer_black_48dp_2x.pngbin0 -> 5171 bytes
-rw-r--r--ui/public/ic_dns_black_48dp_2x.pngbin0 -> 8044 bytes
-rw-r--r--ui/public/ic_gamepad_black_48dp_2x.pngbin0 -> 5647 bytes
-rw-r--r--ui/public/ic_keyboard_return_black_48dp_2x.pngbin0 -> 5438 bytes
-rw-r--r--ui/public/ic_lens_black_48dp_2x.pngbin0 -> 8523 bytes
-rw-r--r--ui/public/ic_settings_input_composite_black_48dp_2x.pngbin0 -> 5389 bytes
-rw-r--r--ui/public/ic_settings_input_hdmi_black_48dp_2x.pngbin0 -> 5881 bytes
-rw-r--r--ui/public/ic_storage_black_48dp_2x.pngbin0 -> 4804 bytes
-rw-r--r--ui/public/images/calipso-logo.jpgbin0 -> 2895 bytes
-rw-r--r--ui/public/instance.pngbin0 -> 1191 bytes
-rw-r--r--ui/public/landing/calipso-logo.pngbin0 -> 22270 bytes
-rw-r--r--ui/public/landing/docker.pngbin0 -> 11495 bytes
-rw-r--r--ui/public/landing/eye.pngbin0 -> 33084 bytes
-rw-r--r--ui/public/landing/first.pngbin0 -> 47856 bytes
-rw-r--r--ui/public/landing/graph.pngbin0 -> 69443 bytes
-rw-r--r--ui/public/landing/main-screen.pngbin0 -> 48337 bytes
-rw-r--r--ui/public/landing/openstack.pngbin0 -> 8286 bytes
-rw-r--r--ui/public/logo-cisco-png.pngbin0 -> 23417 bytes
-rw-r--r--ui/public/logo-cisco.svg37
-rw-r--r--ui/public/logo_microscope_3.pngbin0 -> 17774 bytes
-rw-r--r--ui/public/network.pngbin0 -> 2506 bytes
-rw-r--r--ui/public/particle.pngbin0 -> 2924 bytes
-rw-r--r--ui/public/pnic.jpgbin0 -> 37828 bytes
-rw-r--r--ui/public/vconnector.pngbin0 -> 3464 bytes
-rw-r--r--ui/public/vnic.pngbin0 -> 4520 bytes
-rw-r--r--ui/public/vservice.pngbin0 -> 4431 bytes
-rw-r--r--ui/public/wiki/addNewEnv.pngbin0 -> 105144 bytes
-rw-r--r--ui/public/wiki/addNewEnv1.pngbin0 -> 160919 bytes
-rw-r--r--ui/public/wiki/wizard1.pngbin0 -> 46714 bytes
-rw-r--r--ui/public/wiki/wizard2.pngbin0 -> 53414 bytes
-rw-r--r--ui/public/wiki/wizard3.pngbin0 -> 62210 bytes
-rw-r--r--ui/public/wiki/wizard4.pngbin0 -> 54438 bytes
-rw-r--r--ui/readme.md43
-rwxr-xr-xui/run2
-rwxr-xr-xui/run-dev.sh10
-rw-r--r--ui/server/main.js10
416 files changed, 29904 insertions, 0 deletions
diff --git a/ui/Dockerfile b/ui/Dockerfile
new file mode 100644
index 0000000..1e3ba52
--- /dev/null
+++ b/ui/Dockerfile
@@ -0,0 +1,2 @@
+FROM meteorhacks/meteord:onbuild
+#FROM jshimko/meteor-launchpad:latest
diff --git a/ui/app.styl b/ui/app.styl
new file mode 100644
index 0000000..bdf9492
--- /dev/null
+++ b/ui/app.styl
@@ -0,0 +1,34 @@
+// Colors
+black = #222
+grey = #D8D8D8
+purp = #524763
+dark-purp = #373142
+red = #E54B4B
+teal = #82D8D8
+dark-teal = #66A6A6
+green = #CBE56C
+blue = #16528E
+bright-blue = #0082C8
+maroon = #6A2140
+light-red = #CA5765
+pink = #CF256D
+orange = #E8846B
+
+// cisco brand Colors
+brand-blue = #2196F3
+spark-blue = #0A9AD7
+spark-green = #73BE46
+spark-grey = #D1D2D1
+dk-blue = #303f9f
+light-grey = #efefef
+dk-gray1 = #58585b
+// cisco brand status Colors
+status-blue = #64bbe3
+status-turq = #14a792
+status-green = #6cc04a
+status-yellow = #ffcc00
+status-orange = #ff7300
+status-red = #cf2030
+// cisco accepteble colors
+
+@import './imports/ui/components/index';
diff --git a/ui/build-deploy b/ui/build-deploy
new file mode 100755
index 0000000..1f3713e
--- /dev/null
+++ b/ui/build-deploy
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+SECONDS=0
+OS_TARGET_PATH=".."
+OS_TARGET_NAME=osdna-meteor-frontend-$(date +%Y-%m-%d-%s).tar.gz
+OS_NAME=${PWD##*/}
+OS_USER_DEV="eyal"
+DEV_HOST="korlev-osdna-be.cisco.com"
+
+meteor build $OS_TARGET_PATH/ --architecture=os.linux.x86_64
+mv $OS_TARGET_PATH/$OS_NAME.tar.gz $OS_TARGET_PATH/$OS_TARGET_NAME
+
+duration=$SECONDS
+echo "$(($duration / 60)) minutes and $(($duration % 60)) seconds elapsed."
+
+echo "deploy to dev"
+SECONDS=0
+echo "target $OS_TARGET_PATH/$OS_TARGET_NAME"
+echo "user $OS_USER_DEV"
+echo "dev $DEV_HOST"
+scp $OS_TARGET_PATH/$OS_TARGET_NAME $OS_USER_DEV@$DEV_HOST\:\~
+echo "$(($duration / 60)) minutes and $(($duration % 60)) seconds elapsed."
diff --git a/ui/build.sh b/ui/build.sh
new file mode 100755
index 0000000..4e33f30
--- /dev/null
+++ b/ui/build.sh
@@ -0,0 +1,20 @@
+########################################################################################
+# 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 #
+########################################################################################
+#!/usr/bin/env bash
+
+SECONDS=0
+OS_TARGET_PATH=".."
+OS_TARGET_NAME=osdna-meteor-frontend-$(date +%Y-%m-%d-%s).tar.gz
+OS_NAME=${PWD##*/}
+
+meteor build $OS_TARGET_PATH/ --architecture=os.linux.x86_64
+mv $OS_TARGET_PATH/$OS_NAME.tar.gz $OS_TARGET_PATH/$OS_TARGET_NAME
+
+duration=$SECONDS
+echo "$(($duration / 60)) minutes and $(($duration % 60)) seconds elapsed."
diff --git a/ui/client/accounts-config.js b/ui/client/accounts-config.js
new file mode 100644
index 0000000..68901ea
--- /dev/null
+++ b/ui/client/accounts-config.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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+Accounts.ui.config({
+ passwordSignupFields: 'USERNAME_ONLY',
+}); \ No newline at end of file
diff --git a/ui/client/css/accordionNavMenu.styl b/ui/client/css/accordionNavMenu.styl
new file mode 100644
index 0000000..b5ba1d5
--- /dev/null
+++ b/ui/client/css/accordionNavMenu.styl
@@ -0,0 +1,259 @@
+// for WIKI
+
+.os-accordion-nav-menu
+ .sm-menu-items-list
+ >.os-accordion-tree-node
+ >a
+ display none
+
+.left-nav-content-wiki {
+ width: 300px;
+ height: 100vh
+ background brand-blue
+ top 90px
+ position fixed
+}
+
+
+.left-nav-menu
+ float: left;
+ min-width: 100px;
+ outline: 0;
+ position: relative;
+ //margin-top -40px
+ shadow-level(1)
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ outline: 0;
+ .menu-footer, .menu-header
+ background: brand-blue;
+ color: #f0f0f0;
+ float: left;
+ font-weight: normal;
+ line-height: 50px;
+ font-size: 0.8em;
+ width: 100%;
+ border-bottom 1px solid black
+ .menu-footer
+ text-align: center;
+ .menu-header
+ height: 39px;
+ p
+ font-size 1.7em
+ padding 6px
+ margin 0px
+
+
+ i
+ cursor pointer
+ position absolute
+ top -5px
+ right 0
+ display block
+ color white
+ padding-right 5px
+ trans()
+ &:hover
+ color spark-blue
+ opacity 0.8
+
+ ul
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ li
+ display: block;
+ float: left;
+ position: relative;
+ width: 100%;
+ .menu-label
+ background: #f0f0f0;
+ border-radius: 100%;
+ color: #555555;
+ font-size: 11px;
+ font-weight: 800;
+ line-height: 18px;
+ min-width: 20px;
+ padding: 1px 2px 1px 1px;
+ position: absolute;
+ right: 18px;
+ text-align: center;
+ top: 14px;
+ a
+ background: brand-blue;
+ color: #f0f0f0;
+ float: left;
+ font-size: 13px;
+ overflow: hidden;
+ padding: 14px 22px;
+ position: relative;
+ text-decoration: none;
+ white-space: nowrap;
+ width: 100%;
+ trans()
+ &:hover
+ background: @background - 20%;
+ i
+ float: left;
+ font-size: 16px;
+ line-height: 18px;
+ text-align: left;
+ width: 34px;
+
+.left-nav-menu ul li .menu-label {
+ background: #f0f0f0;
+ border-radius: 100%;
+ color: #555555;
+ font-size: 11px;
+ font-weight: 800;
+ line-height: 18px;
+ min-width: 20px;
+ padding: 1px 2px 1px 1px;
+ position: absolute;
+ right: 18px;
+ text-align: center;
+ top: 14px;
+}
+
+.left-nav-menu
+ ul
+ .submenu
+ display: none;
+ position: static;
+ width: 100%;
+ .submenu-indicator
+ line-height: 16px
+ li
+ clear: both;
+ width: 100%;
+ ul.submenu
+ display: none;
+ position: static;
+ width: 100%;
+ overflow: hidden;
+ a
+ background: brand-blue - 10%
+ border-left: solid 6px transparent;
+ border-top: none;
+ float: left;
+ font-size: 11px;
+ position: relative;
+ width: 100%;
+ &:hover
+ background @background - 20%
+
+
+.left-nav-menu ul .submenu li .menu-label {
+ background: #f0f0f0;
+ border-radius: 100%;
+ color: #555555;
+ font-size: 11px;
+ font-weight: 800;
+ line-height: 18px;
+ min-width: 20px;
+ padding: 1px 2px 1px 1px;
+ position: absolute;
+ right: 18px;
+ text-align: center;
+ top: 12px;
+ top: 14px;
+}
+
+.left-nav-menu ul .submenu li a
+ padding-left 40px
+ background brand-blue - 20%
+
+.left-nav-menu ul .submenu > li > ul.submenu > li > a
+ padding-left: 40px
+ background brand-blue - 30%
+
+.left-nav-menu ul .submenu > li > ul.submenu > li > ul.submenu > li > a
+ padding-left: 40px;
+ background brand-blue - 40%
+.left-nav-menu ul .submenu > li > ul.submenu > li > ul.submenu > li > ul.submenu > li > a
+ padding-left: 50px;
+ background brand-blue - 50%
+.left-nav-menu ul .submenu > li > ul.submenu > li > ul.submenu > li > ul.submenu > li > ul.submenu > li > a
+ padding-left: 60px;
+ background brand-blue - 60%
+.left-nav-menu ul .submenu > li > ul.submenu > li > ul.submenu > li > ul.submenu > li > ul.submenu > li > ul.submenu > li > a
+ padding-left: 70px;
+ background brand-blue - 70%
+.left-nav-menu ul .submenu > li > ul.submenu > li > ul.submenu > li > ul.submenu > li > ul.submenu > li > ul.submenu > li > ul.submenu > li > ul.submenu > li > a
+ padding-left: 80px;
+ background brand-blue - 80%
+
+.left-nav-menu .sm-open-close-indicator
+ float: right;
+
+ i.fa
+ font-size: 10px;
+ width: 10px;
+
+.left-nav-menu .submenu-indicator {
+ -moz-transition: "transform .3s linear";
+ -o-transition: "transform .3s linear";
+ -webkit-transition: "transform .3s linear";
+ transition: "transform .3s linear";
+ float: right;
+ font-size: 20px;
+ line-height: 19px;
+ position: absolute;
+ right: 22px;
+}
+
+.left-nav-menu .submenu-indicator-minus > .submenu-indicator {
+ -moz-transform: rotate(45deg);
+ -ms-transform: rotate(45deg);
+ -o-transform: rotate(45deg);
+ -webkit-transform: rotate(45deg);
+ transform: rotate(45deg);
+}
+
+.left-nav-menu > ul > li.active > a {
+ background: #3b424d;
+ color: #ffffff;
+}
+
+
+.left-nav-menu > ul > li > a { border-bottom: solid 1px #3b424d; }
+
+.ink {
+ -moz-transform: scale(0);
+ -ms-transform: scale(0);
+ -o-transform: scale(0);
+ -webkit-transform: scale(0);
+ background: rgba(255, 255, 255, 0.3);
+ border-radius: 100%;
+ display: block;
+ position: absolute;
+ transform: scale(0);
+}
+
+.animate-ink {
+ -moz-animation: ripple .3s linear;
+ -ms-animation: ripple .3s linear;
+ -o-animation: ripple .3s linear;
+ -webkit-animation: ripple .3s linear;
+ animation: ripple .3s linear;
+}
+
+// @-moz-keyframes 'ripple' {
+// 100% {
+// opacity: 0;
+// transform: scale(2.5);
+// }
+// }
+// @-webkit-keyframes 'ripple' {
+// 100% {
+// opacity: 0;
+// transform: scale(2.5);
+// }
+// }
+// @keyframes 'ripple' {
+// 100% {
+// opacity: 0;
+// transform: scale(2.5);
+// }
+// }
diff --git a/ui/client/css/components.styl b/ui/client/css/components.styl
new file mode 100644
index 0000000..a4b2f04
--- /dev/null
+++ b/ui/client/css/components.styl
@@ -0,0 +1,187 @@
+.main-layout
+ margin 30px 30px 30px 330px
+
+.main-layout-no-nav
+ margin 0px
+
+.site-sidenav
+ width: 300px;
+ min-width: 300px;
+ max-width: 300px;
+ //height: 100vh
+ height: initial;
+ align-self: stretch;
+ min-height: 95vh;
+ background brand-blue
+ trans()
+
+.site-sidenav-collapse
+ width: 30px
+ min-width: 30px
+ max-width: 30px
+ min-height 100vh
+ background brand-blue
+ color white
+ margin-top -40px
+ trans()
+
+.cards
+ color #FFF
+ padding 20px
+ margin-bottom 30px
+ .material-icons
+ padding-top 20px
+ shadow-level(1)
+ trans()
+ &:hover
+ shadow-level(2)
+ &.not-active
+ shadow-level (0)
+ &.blue
+ background-color spark-blue
+ &.green
+ background-color status-green
+ &.grey
+ background-color dk-gray1
+ &.light-grey
+ background-color light-grey
+ &.dk-blue
+ background-color dk-blue
+ &.orange
+ background-color status-orange
+ &.red
+ background-color status-red
+ &.white
+ background #FFF
+ color #000
+ &.blue-text
+ color spark-blue
+ &.green-text
+ .fa, .material-icons
+ color status-green
+ h5
+ border-bottom 3px solid status-green
+ p.active
+ padding-top 3px
+ font-size 1.2em
+ color status-green
+ &.orange-text
+ .fa, .material-icons
+ color status-orange
+ h5
+ border-bottom 3px solid status-orange
+ p.active
+ padding-top 3px
+ font-size 1.2em
+ color status-orange
+ &.red-text
+ .fa, .material-icons
+ color status-red
+ h5
+ border-bottom 3px solid status-red
+ p.active
+ padding-top 3px
+ font-size 1.2em
+ color status-red
+ h3
+ color spark-blue
+ font-size 2em
+ border-bottom 3px solid spark-blue
+ p.text
+ color #000
+ .fa, .material-icons
+ font-size 4em
+ &.title
+ h4
+ margin 0
+ text-align center
+ color spark-blue
+
+.cards-w250
+ @extend .cards
+ padding 20px 5px
+ max-height 100px
+ width: 250px
+ max-width: 250px
+ min-width: 250px
+ text-align center
+ .fa, .material-icons
+ font-size 3em
+ p
+ margin 0
+ span
+ font-size 1.3em
+ font-weight bold
+
+.cards-w300
+ @extend .cards
+ max-height 100px
+ width: 300px
+ max-width: 300px
+ min-width: 300px;
+ p
+ font-size 1.1em
+ span
+ font-size 1.3em
+ font-weight bold
+
+.cards-w300-h60
+ @extend .cards-w300
+ font-size 0.9em
+ height 60px
+ .fa, .material-icons
+ font-size 2em
+ &:hover
+ shadow-level(1)
+
+.cards-flex-col-h120
+ @extend .cards
+ height: 120px
+ max-height 120px
+ min-height 120px
+ h5
+ padding 0px
+ margin 0px
+ a
+ color spark-blue
+ p
+ font-size 1em
+ padding 0px
+ margin 0px
+ span
+ font-size 1.3em
+ font-weight bold
+
+.cards-450
+ @extend .cards
+ width: 520px
+ max-width: 520px
+ min-width: 520px
+ p
+ font-size 1.1em
+ span
+ font-size 1.3em
+ font-weight bold
+ .material-icons
+ color spark-blue
+ font-size 5em
+ padding 40px 30px
+
+.cards-flex-col-h500
+ @extend .cards
+ margin-right 30px
+ height 500px
+ max-height 500px
+ min-height 500px
+ p
+ font-size 1.1em
+ span
+ font-size 1.3em
+ font-weight bold
+ .material-icons
+ color spark-blue
+ font-size 8em
+ padding 40px
+
+.margin-top-40
+ margin-top 40px
diff --git a/ui/client/css/d3graph.styl b/ui/client/css/d3graph.styl
new file mode 100644
index 0000000..6d53c45
--- /dev/null
+++ b/ui/client/css/d3graph.styl
@@ -0,0 +1,47 @@
+
+.node circle {
+ fill: steelblue;
+}
+.node text {
+ font: bold 14px sans-serif;
+ fill: rgb(16, 15, 15);
+ text-shadow: 0 0px 0 #fff;
+ /*font-size: 0.8em;*/
+}
+.link {
+ /*
+ fill: #1313AB;
+ stroke: #1313AB;
+ */
+}
+.different-groups{
+ stroke: #999;
+}
+.default {
+ stroke: #fbb;
+ stroke-width: 4px;
+}
+.outline {
+ stroke: #844;
+ stroke-width: 6px;
+}
+
+div.tooltip {
+ position: absolute;
+ text-align: left;
+ opacity 1
+ font: bold 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;
+}
+.background {
+ stroke: white;
+ stroke-width: 1px;
+ fill: white;
+}
diff --git a/ui/client/css/flex.styl b/ui/client/css/flex.styl
new file mode 100644
index 0000000..e780d78
--- /dev/null
+++ b/ui/client/css/flex.styl
@@ -0,0 +1,23 @@
+// flex grid system
+.flex-box
+ list-style none
+ display flex
+ flex-wrap wrap
+ // border 2px solid red
+ .flex-box-1
+ flex 1
+ // border 2px solid green
+ .flex-box-2
+ flex 2
+ .flex-box-3
+ flex 3
+ .flex-box-4
+ flex 4
+.flex-col
+ flex-direction column
+.justify-content-around
+ justify-content space-around
+.justify-content-between
+ justify-content space-between
+.justify-content-start
+ justify-content flex-start \ No newline at end of file
diff --git a/ui/client/css/header.styl b/ui/client/css/header.styl
new file mode 100644
index 0000000..ebc3ff6
--- /dev/null
+++ b/ui/client/css/header.styl
@@ -0,0 +1,118 @@
+.os-alarm-icons
+ display: flex;
+ flex-flow: row;
+ justify-content: flex-end;
+
+ padding-top 50px
+ //background-color dk-gray1
+ background-color: brand-blue
+ border-bottom: solid 1px black;
+
+ .breadcrumb
+ background-color dk-gray1
+
+ .alarm-icons
+ padding-top 15px
+ padding-right 10px
+ color #FFF
+ //text-align right
+
+ .material-icons:hover
+ cursor pointer
+ color spark-grey
+ trans()
+
+ .dropdown
+ display inline
+ trans()
+
+ .dropdown-menu-right
+ right 25px
+
+ .dropdown-header
+ font-size 1.4em
+ color dk-gray1
+
+ >a
+ font-size: 20px;
+ color dk-gray1
+ line-height: 1.42857143;
+ padding-left: 4px;
+
+img.navbar-brand.navbar-custom {
+ padding-left: 40px;
+}
+
+p.navbar-brand.navbar-custom{
+ margin: 0px;
+ padding-left: 95px;
+ color: #FFF;
+}
+
+nav.navbar-inverse.navbar-custom
+ background-color: brand-blue
+ color: #FFF;
+ a
+ color: #FFF;
+ .login-close-text
+ color: #555;
+ #login-name-link:hover
+ color: rgb(63,81,181);
+ &:hover
+ color: rgb(63,81,181);
+ trans()
+ a.droplist
+ color: brand-blue
+ padding: 10px 0px 0px 10px
+
+ .search
+ position: relative;
+ color: white
+ input
+ height: 30px;
+ background: brand-blue
+ // text-indent: 25px;
+ padding-left: 20px;
+ border 0px
+ border-bottom 2px solid spark-grey
+ width 175px
+ &:hover, &:active, &:focus
+ border-bottom 2px solid white
+ ::-webkit-input-placeholder
+ color: white
+ .fa-search
+ position: absolute;
+ top: 7px;
+
+ .search-auto-complete
+ position: absolute;
+ right: 0;
+ top: 34px
+ z-index: 1000;
+
+.navbar-inverse .navbar-nav > .open > a,
+.navbar-inverse .navbar-nav > .open > a:hover,
+.navbar-inverse .navbar-nav > .open > a:focus {
+ color: brand-blue
+ background-color: #fff;
+}
+
+/* Login form style */
+.sign-in #login-sign-in-link, #login-name-link, #login-name-link.login-link-text {
+ color: #FFF;
+ text-decoration: none;
+}
+
+.login-button#login-buttons-open-change-password,
+.login-button#login-buttons-password,
+.login-button#login-buttons-logout {
+ background-color: brand-blue
+ border: brand-blue
+}
+
+.login-button#login-buttons-open-change-password:hover,
+.login-button#login-buttons-password:hover,
+.login-button#login-buttons-logout:hover {
+ background-color: rgb(63,81,181);
+ border: rgb(63,81,181);
+}
diff --git a/ui/client/css/landing.css b/ui/client/css/landing.css
new file mode 100644
index 0000000..59c0806
--- /dev/null
+++ b/ui/client/css/landing.css
@@ -0,0 +1,122 @@
+/****************************************************************************************/
+/* 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 */
+/****************************************************************************************/
+/* ======= landging page =======*/
+
+a.mdl-button:hover{
+ text-decoration: none;
+}
+
+.mdl-button--raised.mdl-button--colored {
+ background-color: #2196F3;
+}
+
+.mdl-button--accent.mdl-button--accent.mdl-button--raised:hover {
+ background-color: rgb(63,81,181);
+
+}
+
+section.section-margin-50 {
+ margin: 50px 0;
+}
+
+.background-blue{
+ background-color: #2196F3;
+ color: #f9f9f9;
+}
+.background-white {
+ border-color: #FFF;
+}
+
+.background-grey {
+ background-color: #f9f9f9;
+}
+
+.home-fullscreen {
+ /* padding: 25px; */
+}
+
+.mtop-50{
+ margin-top: 100px;
+}
+
+.font20{
+ font-size: 20px;
+}
+
+.title-border-bottom{
+ border-bottom: 5px solid #2196F3;
+}
+.text-align-center{
+ text-align: center;
+}
+
+/*
+.home_mockups {
+ position: relative;
+ margin-top: 4rem;
+ margin-bottom: -1rem;
+ z-index: 3;
+}
+
+.home_mockups_1 {
+ position: relative;
+ animation-delay: 0.5s;
+}
+
+.home_mockups_2 {
+ animation-delay: 1s;
+}
+
+.home_mockups_2, .home_mockups_3 {
+ position: absolute;
+ margin: 0 auto;
+ top: 0;
+ right: 0;
+ left: 0;
+ bottom: 0;
+}
+*/
+
+.box {
+ margin: 50px;
+}
+
+.box-side{
+ margin: 10px 50px;
+}
+/* ======= End of section =========*/
+
+/*====== Main page =======*/
+.logo{
+ padding: 10px;
+}
+
+.view-100{
+ height: 100vh;
+}
+
+.user-bar{
+ background: #42A5F5;
+ color: #fff;
+ padding-left: 15px;
+}
+/* ======= End of section =======*/
+
+/*// Rules for sizing the icon.*/
+.material-icons.md-18 { font-size: 21px; }
+.material-icons.md-24 { font-size: 24px; }
+.material-icons.md-36 { font-size: 36px; }
+.material-icons.md-48 { font-size: 48px; }
+/*// Rules for using icons as black on a light background.*/
+.material-icons.md-dark { color: rgba(0, 0, 0, 0.54); }
+.material-icons.md-dark.md-inactive { color: rgba(0, 0, 0, 0.26); }
+/*// Rules for using icons as white on a dark background.*/
+.material-icons.md-light { color: rgba(255, 255, 255, 1); }
+.material-icons.md-light.md-inactive { color: rgba(255, 255, 255, 0.3); }
+/* ======= End of section ===========*/
diff --git a/ui/client/css/loading.styl b/ui/client/css/loading.styl
new file mode 100644
index 0000000..1545b58
--- /dev/null
+++ b/ui/client/css/loading.styl
@@ -0,0 +1,19 @@
+
+
+.loading-message {
+ color: white;
+ font-size: 2.5em;
+ font-weight: 300;
+ font-family: sans-serif;
+}
+
+.sk-spinner-rotating-plane.sk-spinner {
+ background-color: white;
+}
+
+.landing
+ .material-icons
+ font-size 64px
+
+.margin-50
+ margin 80px 0px \ No newline at end of file
diff --git a/ui/client/css/modals.styl b/ui/client/css/modals.styl
new file mode 100644
index 0000000..f0d4741
--- /dev/null
+++ b/ui/client/css/modals.styl
@@ -0,0 +1,10 @@
+.modal-header
+ .material-icons
+ color spark-blue
+
+.modal-body
+ a
+ color spark-blue
+ h5
+ color spark-blue
+ border-bottom 2px solid spark-blue
diff --git a/ui/client/css/plugins.styl b/ui/client/css/plugins.styl
new file mode 100644
index 0000000..cd602ba
--- /dev/null
+++ b/ui/client/css/plugins.styl
@@ -0,0 +1,4 @@
+.graph
+ width: 100%;
+ height: 400px;
+ border: 3px solid spark-blue; \ No newline at end of file
diff --git a/ui/client/css/wizard.css b/ui/client/css/wizard.css
new file mode 100644
index 0000000..ebac04f
--- /dev/null
+++ b/ui/client/css/wizard.css
@@ -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 */
+/****************************************************************************************/
+.btn-mgt-5 {
+ margin-top: 5px;
+}
+
+.nav > li > a {
+ color: #2196F3;
+}
+
+.form-horizontal {
+ margin-top: 25px;
+}
+
+.mdl-button {
+ color: #2196F3;
+}
+
+.mdl-button:hover {
+ background-color: rgb(63,81,181);
+ color: #FFF;
+}
diff --git a/ui/client/lib/jquery.multilevelpushmenu.css b/ui/client/lib/jquery.multilevelpushmenu.css
new file mode 100644
index 0000000..407cda9
--- /dev/null
+++ b/ui/client/lib/jquery.multilevelpushmenu.css
@@ -0,0 +1,120 @@
+/****************************************************************************************/
+/* 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 */
+/****************************************************************************************/
+.multilevelpushmenu_wrapper {
+ position: absolute;
+ overflow: hidden;
+ min-width: 100%;
+ min-height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+.multilevelpushmenu_wrapper .levelHolderClass {
+ position: absolute;
+ overflow: hidden;
+ top: 0;
+ background: #336ca6;
+ width: auto;
+ min-height: 100%;
+ font-family: 'Open Sans Condensed', sans-serif;
+ font-size: 1em;
+ zoom: 1;
+}
+
+.multilevelpushmenu_wrapper .ltr {
+ margin-left: -100%;
+ left: 0;
+ -moz-box-shadow: 5px 0 5px -5px #1f4164;
+ -webkit-box-shadow: 5px 0 5px -5px #1f4164;
+ box-shadow: 5px 0 5px -5px #1f4164;
+ filter: progid:DXImageTransform.Microsoft.Shadow(color=#1f4164,direction=90,strength=2);
+}
+
+.multilevelpushmenu_wrapper .rtl {
+ margin-right: -100%;
+ right: 0;
+ -moz-box-shadow: 5px 0 5px 5px #1f4164;
+ -webkit-box-shadow: 5px 0 5px 5px #1f4164;
+ box-shadow: 5px 0 5px 5px #1f4164;
+ filter: progid:DXImageTransform.Microsoft.Shadow(color=#1f4164,direction=270,strength=2);
+}
+
+.multilevelpushmenu_wrapper .multilevelpushmenu_inactive {
+ background: #2e6196;
+}
+
+.multilevelpushmenu_wrapper h2 {
+ font-size: 1.5em;
+ line-height: 1em;
+ font-weight: bold;
+ color: #1f4164;
+ padding: 0 .4em 0 .4em;
+}
+
+.multilevelpushmenu_wrapper ul {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.multilevelpushmenu_wrapper li {
+ cursor: pointer;
+ border-top: 1px solid #295685;
+ padding: .4em .4em .4em .4em;
+}
+
+.multilevelpushmenu_wrapper li:last-child {
+ border-bottom: 1px solid #295685;
+}
+
+.multilevelpushmenu_wrapper li:hover {
+ background-color: #295685;
+}
+
+.multilevelpushmenu_wrapper a {
+ display: block;
+ outline: none;
+ overflow: hidden;
+ font-size: 1.5em;
+ line-height: 1em;
+ padding: .2em .2em;
+ text-decoration: none;
+ color: #fff;
+}
+
+.multilevelpushmenu_wrapper a:hover {
+ color: #ffe;
+}
+
+.multilevelpushmenu_wrapper .backItemClass {
+ display: block;
+ padding: .4em .4em .4em .4em;
+ background: #2e6196;
+ border-top: 1px solid #295685;
+}
+
+.multilevelpushmenu_wrapper .floatRight {
+ float: right;
+}
+
+.multilevelpushmenu_wrapper .floatLeft {
+ float: left;
+}
+
+.multilevelpushmenu_wrapper .cursorPointer {
+ cursor: pointer;
+}
+
+.multilevelpushmenu_wrapper .iconSpacing_ltr {
+ padding: 0 .4em 0 0;
+}
+
+.multilevelpushmenu_wrapper .iconSpacing_rtl {
+ padding: 0 0 0 .4em;
+} \ No newline at end of file
diff --git a/ui/client/lib/jquery.multilevelpushmenu.js b/ui/client/lib/jquery.multilevelpushmenu.js
new file mode 100644
index 0000000..0b2a24d
--- /dev/null
+++ b/ui/client/lib/jquery.multilevelpushmenu.js
@@ -0,0 +1,1148 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * jquery.multilevelpushmenu.js v2.1.4
+ *
+ * Licensed under the MIT license.
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ * Copyright 2013-2014, Make IT d.o.o.
+ * http://multi-level-push-menu.make.rs
+ * https://github.com/adgsm/multi-level-push-menu
+ */
+(function ( $ ) {
+ $.fn.multilevelpushmenu = function( options ) {
+ "use strict";
+ var args = arguments,
+ returnValue = null;
+
+ this.each(function(){
+ var instance = this,
+ $this = $( this ),
+ $container = ( $this.context != undefined ) ? $this : $( 'body' ),
+ menu = ( options && options.menu != undefined ) ? options.menu : $this.find( 'nav' ),
+ clickEventType, dragEventType;
+
+ // Settings
+ var settings = $.extend({
+ container: $container,
+ containersToPush: null,
+ menuID: ( ( $container.prop( 'id' ) != undefined && $container.prop( 'id' ) != '' ) ? $container.prop( 'id' ) : this.nodeName.toLowerCase() ) + "_multilevelpushmenu",
+ wrapperClass: 'multilevelpushmenu_wrapper',
+ menuInactiveClass: 'multilevelpushmenu_inactive',
+ menu: menu,
+ menuWidth: 0,
+ menuHeight: 0,
+ collapsed: false,
+ fullCollapse: false,
+ direction: 'ltr',
+ backText: 'Back',
+ backItemClass: 'backItemClass',
+ backItemIcon: 'fa fa-angle-right',
+ groupIcon: 'fa fa-angle-left',
+ mode: 'overlap',
+ overlapWidth: 40,
+ preventItemClick: true,
+ preventGroupItemClick: true,
+ swipe: 'both',
+ durationSlideOut: 400,
+ durationSlideDown: 500,
+ durationTransition: 400,
+ onCollapseMenuStart: function() {},
+ onCollapseMenuEnd: function() {},
+ onExpandMenuStart: function() {},
+ onExpandMenuEnd: function() {},
+ onGroupItemClick: function() {},
+ onItemClick: function() {},
+ onTitleItemClick: function() {},
+ onBackItemClick: function() {},
+ onMenuReady: function() {},
+ onMenuSwipe: function() {}
+ }, options );
+
+ // Store a settings reference withint the element's data
+ if (!$.data(instance, 'plugin_multilevelpushmenu')) {
+ $.data(instance, 'plugin_multilevelpushmenu', settings);
+ instance.settings = $.data(instance, 'plugin_multilevelpushmenu');
+ }
+
+ // Exposed methods
+ var methods = {
+ // Initialize menu
+ init: function () {
+ return initialize.apply(this, Array.prototype.slice.call(arguments));
+ },
+ // Collapse menu
+ collapse: function () {
+ return collapseMenu.apply(this, Array.prototype.slice.call(arguments));
+ },
+ // Expand menu
+ expand: function () {
+ return expandMenu.apply(this, Array.prototype.slice.call(arguments));
+ },
+ // Menu expanded
+ menuexpanded: function () {
+ return menuExpanded.apply(this, Array.prototype.slice.call(arguments));
+ },
+ // Active menu
+ activemenu: function () {
+ return activeMenu.apply(this, Array.prototype.slice.call(arguments));
+ },
+ // Find menu(s) by title
+ findmenusbytitle: function () {
+ return findMenusByTitle.apply(this, Array.prototype.slice.call(arguments));
+ },
+ // Find item(s) by name
+ finditemsbyname: function () {
+ return findItemsByName.apply(this, Array.prototype.slice.call(arguments));
+ },
+ // Find path to root menu collection
+ pathtoroot: function () {
+ return pathToRoot.apply(this, Array.prototype.slice.call(arguments));
+ },
+ // Find shared path to root of two menus
+ comparepaths: function () {
+ return comparePaths.apply(this, Array.prototype.slice.call(arguments));
+ },
+ // Get/Set settings options
+ option: function () {
+ return manageOptions.apply(this, Array.prototype.slice.call(arguments));
+ },
+ // Add item(s)
+ additems: function () {
+ return addItems.apply(this, Array.prototype.slice.call(arguments));
+ },
+ // Remove item(s)
+ removeitems: function () {
+ return removeItems.apply(this, Array.prototype.slice.call(arguments));
+ },
+ // Size DOM elements
+ redraw: function () {
+ return sizeDOMelements.apply(this, Array.prototype.slice.call(arguments));
+ },
+ // Returns visible level holders
+ visiblemenus: function () {
+ return visibleLevelHolders.apply(this, Array.prototype.slice.call(arguments));
+ },
+ // Returns visible level holders
+ hiddenmenus: function () {
+ return hiddenLevelHolders.apply(this, Array.prototype.slice.call(arguments));
+ },
+ // Propagate event to underneath layer
+ propagateevent: function () {
+ return propagateEvent.apply(this, Array.prototype.slice.call(arguments));
+ }
+ };
+
+ // IE 8 and modern browsers, prevent event propagation
+ function stopEventPropagation( e ){
+ if ( e.stopPropagation && e.preventDefault ) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ else {
+ e.cancelBubble = true;
+ e.returnValue = false;
+ }
+ }
+
+ // propagate event to underneath layer
+ // http://jsfiddle.net/E9zTs/2/
+ function propagateEvent( $element , event ) {
+ if( $element == undefined || event == undefined ) return false;
+ $element.on( event , function ( e , ee ) {
+ $element.hide();
+ try {
+ if(!e.pageX || !e.pageY) return false;
+ ee = ee || {
+ pageX: e.pageX,
+ pageY: e.pageY
+ };
+ var next = document.elementFromPoint( ee.pageX , ee.pageY );
+ next = ( next.nodeType == 3 ) ? next.parentNode : next //Opera
+ $( next ).trigger( event , ee );
+ }
+ catch ( err ) {
+ $.error( 'Error while propagating event: ' + err.message );
+ }
+ finally {
+ $element.show();
+ }
+ });
+ }
+
+ // Create DOM structure if it does not already exist within the container (input: array)
+ function createDOMStructure() {
+ var $mainWrapper = $( "<nav />" )
+ .prop( { "id" : instance.settings.menuID, "className" : instance.settings.wrapperClass } )
+ .appendTo( instance.settings.container );
+ createNestedDOMStructure( instance.settings.menu, $mainWrapper );
+ }
+ function createNestedDOMStructure( menus, $wrapper ){
+ if( menus.level == undefined ) menus.level = 0;
+ $.each( menus, function(){
+ var $levelHolder = $( "<div />" )
+ .attr( { "class" : "levelHolderClass" + ( ( instance.settings.direction == 'rtl' ) ? " rtl" : " ltr" ), "data-level" : menus.level, "style" : ( ( instance.settings.direction == 'rtl' ) ? "margin-right: " : "margin-left: " ) + ( ( menus.level == 0 && !instance.settings.collapsed ) ? 0 : "-200%" ) } )
+ .appendTo( $wrapper ),
+ extWidth = ( isValidDim( instance.settings.menuWidth ) || ( isInt( instance.settings.menuWidth ) && instance.settings.menuWidth > 0 ) );
+ $levelHolder.bind( dragEventType , function(e){
+ holderSwipe( e, $levelHolder );
+ });
+ if( this.id != undefined ) $levelHolder.attr( { "id" : this.id } );
+ var $title = $( "<h2 />" )
+ .attr( { "style" : "text-align: " + ( ( instance.settings.direction == 'rtl' ) ? "right" : "left" ) } )
+ .text( this.title )
+ .appendTo( $levelHolder ),
+ $titleIcon = $( "<i />" )
+ .prop( { "class" : ( ( instance.settings.direction == 'rtl' ) ? "floatLeft" : "floatRight" ) + " cursorPointer " + this.icon } )
+ .prependTo( $title );
+ $titleIcon.bind( clickEventType , function(e){
+ titleIconClick(e, $levelHolder, menus);
+ });
+ if( menus.level > 0 ) createBackItem( $levelHolder );
+ var $itemGroup = $( "<ul />" )
+ .appendTo( $levelHolder );
+ $.each(this.items, function(){
+ createItem( this, $levelHolder , -1 );
+ })
+ });
+ }
+
+ // Update DOM structure if it already exists in container (input: HTML markup)
+ function updateDOMStructure() {
+ var $mainWrapper = ( instance.settings.container.find( 'nav' ).length > 0 ) ? instance.settings.container.find( 'nav' ) : instance.settings.menu;
+ if( $mainWrapper.length == 0 ) return false;
+ $mainWrapper.prop( { "id" : instance.settings.menuID, "className" : instance.settings.wrapperClass } );
+ updateNestedDOMStructure( $mainWrapper );
+ }
+ function updateNestedDOMStructure( $wrapper ){
+ if( $wrapper.level == undefined ) $wrapper.level = 0;
+ $.each( $wrapper, function(){
+ var $levelHolder = $( "<div />" )
+ .attr( { "class" : "levelHolderClass" + ( ( instance.settings.direction == 'rtl' ) ? " rtl" : " ltr" ), "data-level" : $wrapper.level, "style" : ( ( instance.settings.direction == 'rtl' ) ? "margin-right: " : "margin-left: " ) + ( ( $wrapper.level == 0 && !instance.settings.collapsed ) ? 0 : "-200%" ) } )
+ .appendTo( $wrapper ),
+ extWidth = ( isValidDim( instance.settings.menuWidth ) || ( isInt( instance.settings.menuWidth ) && instance.settings.menuWidth > 0 ) );
+ $levelHolder.bind( dragEventType , function(e){
+ holderSwipe( e, $levelHolder );
+ });
+ var $title = $wrapper.children( 'h2' );
+ $title.attr( { "style" : "text-align: " + ( ( instance.settings.direction == 'rtl' ) ? "right" : "left" ) } );
+ $title.appendTo( $levelHolder );
+ var $titleIcon = $title.children( 'i' );
+ $titleIcon.addClass( ( ( instance.settings.direction == 'rtl' ) ? "floatLeft" : "floatRight" ) + " cursorPointer" );
+ $titleIcon.bind( clickEventType , function(e){
+ titleIconClick(e, $levelHolder, $wrapper);
+ });
+ if( $wrapper.level > 0 ) createBackItem( $levelHolder );
+ var $itemGroup = $wrapper.children( 'ul' );
+ $itemGroup.appendTo( $levelHolder );
+ $.each($itemGroup.children( 'li' ), function(){
+ var $item = $( this );
+ $item.attr( { "style" : "text-align: " + ( ( instance.settings.direction == 'rtl' ) ? "right" : "left" ) } );
+ var $itemAnchor = $item.children( 'a' );
+ var $itemIcon = $itemAnchor.children( 'i' );
+ $itemIcon.addClass( ( ( instance.settings.direction == 'rtl' ) ? "floatLeft" : "floatRight" ) );
+ if($item.children( 'ul' ).length > 0) {
+ $itemAnchor.bind( clickEventType , function(e){
+ itemGroupAnchorClick( e, $levelHolder, $item );
+ });
+ createItemGroupIcon( $itemAnchor );
+ $item.level = $wrapper.level + 1;
+ updateNestedDOMStructure($item);
+ } else {
+ $itemAnchor.bind( clickEventType , function(e){
+ itemAnchorClick( e, $levelHolder, $item );
+ });
+ }
+ })
+ });
+ }
+
+ // Click event for title icon
+ function titleIconClick( e, $levelHolder, menus ) {
+ if( $(instance).find( 'div.levelHolderClass' ).is(':animated') ) return false;
+ instance.settings.onTitleItemClick.apply(this, Array.prototype.slice.call([e, $levelHolder, instance.settings]));
+ stopEventPropagation(e);
+ var instanceFC = ( instance.settings.direction == 'rtl' ) ?
+ parseInt( $levelHolder.css( 'margin-right' ) ) < 0
+ :
+ parseInt( $levelHolder.css( 'margin-left' ) ) < 0;
+ if( menus.level == 0 && instanceFC ) {
+ expandMenu();
+ }
+ else {
+ var $nextLevelHolders = instance.settings.container
+ .find( '#' + instance.settings.menuID + ' div.levelHolderClass' )
+ .filter(function(){
+ var retObjs = ( instance.settings.direction == 'rtl' ) ?
+ (($( this ).attr( 'data-level' ) > $levelHolder.attr( 'data-level' )) && ( parseInt( $( this ).css( 'margin-right' ) ) >= 0 ) )
+ :
+ (($( this ).attr( 'data-level' ) > $levelHolder.attr( 'data-level' )) && ( parseInt( $( this ).css( 'margin-left' ) ) >= 0 ) );
+ return retObjs;
+ }),
+ $prevLevelHolders = instance.settings.container
+ .find( '#' + instance.settings.menuID + ' div.levelHolderClass' )
+ .filter(function(){
+ var retObjs = ( instance.settings.direction == 'rtl' ) ?
+ (($( this ).attr( 'data-level' ) <= $levelHolder.attr( 'data-level' )) && ( parseInt( $( this ).css( 'margin-right' ) ) >= 0 ) )
+ :
+ (($( this ).attr( 'data-level' ) <= $levelHolder.attr( 'data-level' )) && ( parseInt( $( this ).css( 'margin-left' ) ) >= 0 ) );
+ return retObjs;
+ }),
+ collapseAll = ( $nextLevelHolders.length == 0 && $prevLevelHolders.length == 1 ) ? collapseMenu() : collapseMenu( parseInt( $levelHolder.attr( 'data-level' ) ) );
+ }
+ $levelHolder.css( 'visibility' , 'visible' );
+ $levelHolder.find( '.' + instance.settings.backItemClass ).css( 'visibility' , 'visible' );
+ $levelHolder.find( 'ul' ).css( 'visibility' , 'visible' );
+ $levelHolder.removeClass( instance.settings.menuInactiveClass );
+ }
+
+ // Create Back item DOM elements
+ function createBackItem( $levelHolder ) {
+ var $backItem = $( "<div />" )
+ .attr( { "class" : instance.settings.backItemClass } )
+ .appendTo( $levelHolder ),
+ $backItemAnchor = $( "<a />" )
+ .prop( { "href" : "#" } )
+ .text( instance.settings.backText )
+ .appendTo( $backItem ),
+ $backItemIcon = $( "<i />" )
+ .prop( { "class" : ( ( instance.settings.direction == 'rtl' ) ? "floatLeft " : "floatRight " ) + instance.settings.backItemIcon } )
+ .prependTo( $backItemAnchor );
+ $backItemAnchor.bind( clickEventType , function(e){
+ backItemAnchorClick(e, $levelHolder);
+ });
+ }
+
+ // Click event for back item
+ function backItemAnchorClick( e, $levelHolder ) {
+ if( $(instance).find( 'div.levelHolderClass' ).is(':animated') ) return false;
+ instance.settings.onBackItemClick.apply(this, Array.prototype.slice.call([e, $levelHolder, instance.settings]));
+ stopEventPropagation(e);
+ collapseMenu( parseInt( $levelHolder.attr( 'data-level' ) - 1 ) );
+ }
+
+ // Click event for group items
+ function itemGroupAnchorClick( e, $levelHolder, $item ) {
+ if( $(instance).find( 'div.levelHolderClass' ).is(':animated') ) return false;
+ instance.settings.onGroupItemClick.apply(this, Array.prototype.slice.call([e, $levelHolder, $item, instance.settings]));
+ expandMenu( $item.find( 'div:first' ) );
+ if( instance.settings.preventGroupItemClick ) stopEventPropagation(e);
+ }
+
+ // Create item group DOM element
+ function createItemGroupIcon( $itemAnchor ) {
+ var $itemGroupIcon = $( "<i />" )
+ .attr( { "class" : ( ( instance.settings.direction == 'rtl' ) ? " floatRight iconSpacing_rtl " : " floatLeft iconSpacing_ltr " ) + instance.settings.groupIcon } )
+ .prependTo( $itemAnchor );
+ }
+
+ // Create item DOM element
+ function createItem() {
+ var item = arguments[0],
+ $levelHolder = arguments[1],
+ position = arguments[2],
+ $itemGroup = $levelHolder.find( 'ul:first' ),
+ $item = $( "<li />" );
+ ( position < ( $itemGroup.children( 'li' ).length ) && position >= 0 ) ?
+ $item.insertBefore( $itemGroup.children( 'li' ).eq( position ) ) : $item.appendTo( $itemGroup );
+ $item.attr( { "style" : "text-align: " + ( ( instance.settings.direction == 'rtl' ) ? "right" : "left" ) } );
+ if( item.id != undefined ) $item.attr( { "id" : item.id } );
+ var $itemAnchor = $( "<a />" )
+ .prop( { "href" : item.link } )
+ .text( item.name )
+ .appendTo( $item ),
+ $itemIcon = $( "<i />" )
+ .prop( { "class" : ( ( instance.settings.direction == 'rtl' ) ? "floatLeft " : "floatRight " ) + item.icon } )
+ .prependTo( $itemAnchor );
+ if(item.items) {
+ $itemAnchor.bind( clickEventType , function(e){
+ itemGroupAnchorClick( e, $levelHolder, $item );
+ });
+ createItemGroupIcon( $itemAnchor );
+ item.items.level = parseInt( $levelHolder.attr( 'data-level' ), 10 ) + 1;
+ createNestedDOMStructure(item.items, $item);
+ } else {
+ $itemAnchor.bind( clickEventType , function(e){
+ itemAnchorClick( e, $levelHolder, $item );
+ });
+ }
+ }
+
+ // Click event for items
+ function itemAnchorClick( e, $levelHolder, $item ) {
+ instance.settings.onItemClick.apply(this, Array.prototype.slice.call([e, $levelHolder, $item, instance.settings]));
+ if( instance.settings.preventItemClick ) stopEventPropagation(e);
+ }
+
+ // Swipe/Drag event for holders
+ function holderSwipe( emd, $levelHolder ) {
+ var extRes = instance.settings.onMenuSwipe.apply(this, Array.prototype.slice.call([emd, $levelHolder, instance.settings]));
+ if( extRes == false ) return false;
+ if( $(instance).find( 'div.levelHolderClass' ).is(':animated') ) return false;
+ var level = ( $levelHolder.attr( 'data-level' ) > 0 ) ? $levelHolder.attr( 'data-level' ) - 1 : undefined;
+ if( emd.type == 'touchmove' && instance.settings.swipe != 'desktop' ) {
+ stopEventPropagation( emd );
+ emd = ( emd.touches ) ? emd : emd.originalEvent;
+ if( !emd.touches || emd.touches.length <= 0 ) return false;
+ var touch = emd.touches[0];
+ instance.settings.container.unbind( 'touchend' );
+ instance.settings.container.bind( 'touchend' , function( emm ) {
+ stopEventPropagation( emm );
+ $levelHolder.significance = 0;
+ $levelHolder.swipeStart = 0;
+ instance.settings.container.unbind( 'touchend' );
+ });
+ if ( $levelHolder.swipeStart != undefined && $levelHolder.swipeStart != 0 ) {
+ $levelHolder.significance = touch.pageX - $levelHolder.swipeStart;
+ }
+ else {
+ $levelHolder.significance = 0;
+ $levelHolder.swipeStart = touch.pageX;
+ return true;
+ }
+ if( Math.abs( $levelHolder.significance ) > instance.settings.overlapWidth*.3 ) {
+ if( instance.settings.direction == 'rtl' ) $levelHolder.significance *= ( -1 );
+ ( $levelHolder.significance > 0 ) ? expandMenu( ( level == undefined ) ? level : $levelHolder ) : collapseMenu( level );
+ $levelHolder.significance = 0;
+ $levelHolder.swipeStart = 0;
+ }
+ }
+ else if( instance.settings.swipe != 'touchscreen' ) {
+ stopEventPropagation( emd );
+ var significance = 0;
+ $levelHolder.unbind( 'mousemove' );
+ $levelHolder.bind( 'mousemove' , function( emm ){
+ significance = emm.clientX - emd.clientX;
+ if( Math.abs( significance ) > instance.settings.overlapWidth*.3 ) {
+ $levelHolder.unbind( 'mousemove' );
+ if( instance.settings.direction == 'rtl' ) significance *= ( -1 );
+ ( significance > 0 ) ? expandMenu( ( level == undefined ) ? level : $levelHolder ) : collapseMenu( level );
+ return true;
+ }
+ });
+ instance.settings.container.unbind( 'mouseup' );
+ instance.settings.container.bind( 'mouseup' , function(e){
+ stopEventPropagation( e );
+ $levelHolder.unbind( 'mousemove' );
+ instance.settings.container.unbind( 'mouseup' );
+ });
+ }
+ }
+
+ // Returns visible level holders
+ function visibleLevelHolders() {
+ var $visibleLevelHolders = instance.settings.container
+ .find( '#' + instance.settings.menuID + ' div.levelHolderClass' )
+ .filter(function(){
+ var retObjs = ( instance.settings.direction == 'rtl' ) ?
+ ( parseInt( $( this ).css( 'margin-right' ) ) >= 0 && $( this ).position().left < instance.settings.container.width() - instance.settings.overlapWidth )
+ :
+ ( parseInt( $( this ).css( 'margin-left' ) ) >= 0 && $( this ).position().left >= 0 );
+ return retObjs;
+ });
+ if( $visibleLevelHolders.length < 1 ) $visibleLevelHolders = false;
+ return $visibleLevelHolders;
+ }
+
+ // Returns hidden level holders
+ function hiddenLevelHolders() {
+ var $hiddenLevelHolders = instance.settings.container
+ .find( '#' + instance.settings.menuID + ' div.levelHolderClass' )
+ .filter(function(){
+ var retObjs = ( instance.settings.direction == 'rtl' ) ?
+ ( ( $( this ).position().left > instance.settings.container.width() || parseInt( $( this ).css( 'margin-right' ) ) < 0 ) )
+ :
+ ( ( $( this ).position().left < 0 || parseInt( $( this ).css( 'margin-left' ) ) < 0 ) );
+ return retObjs;
+ });
+ if( $hiddenLevelHolders.length < 1 ) $hiddenLevelHolders = false;
+ return $hiddenLevelHolders;
+ }
+
+ // Sizing DOM elements per creation/update
+ function sizeDOMelements() {
+ if( !instance.redraw ) {
+ instance.redraw = true;
+ var forceWidth = arguments[0],
+ forceHeight = arguments[1],
+ filter = arguments[2],
+ ieShadowFilterDistortion = ($('#' + instance.settings.menuID + ' div.levelHolderClass').first().css('filter').match(/DXImageTransform\.Microsoft\.Shadow/)) ? $('#' + instance.settings.menuID + ' div.levelHolderClass').first().get(0).filters.item("DXImageTransform.Microsoft.Shadow").strength : 0,
+ maxWidth = ( forceWidth == undefined ) ? Math.max.apply( null,
+ $('#' + instance.settings.menuID + ' div.levelHolderClass').map(function(){ return $(this).width(); }).get() ) - ieShadowFilterDistortion : forceWidth - ieShadowFilterDistortion,
+ maxLevel = Math.max.apply( null,
+ $('#' + instance.settings.menuID + ' div.levelHolderClass').map(function(){ return $(this).attr( 'data-level' ); }).get() ),
+ extWidth = ( isValidDim( instance.settings.menuWidth ) || ( isInt( instance.settings.menuWidth ) && instance.settings.menuWidth > 0 ) ),
+ extHeight = ( isValidDim( instance.settings.menuHeight ) || ( isInt( instance.settings.menuHeight ) && instance.settings.menuHeight > 0 ) ),
+ $objects = ( filter == undefined ) ? $('#' + instance.settings.menuID + ' div.levelHolderClass' ) : filter,
+ currWidth;
+ if ( !extWidth && instance.menuWidth != undefined ) maxWidth = instance.menuWidth;
+ ( extWidth && forceWidth == undefined ) ? $objects.width(instance.settings.menuWidth) : $objects.width( maxWidth );
+ if( extWidth ){
+ if( ( $objects.width() == 0 || ( isValidDim( instance.settings.menuWidth ) && instance.settings.menuWidth.indexOf( '%' ) != -1 ) ) && forceWidth == undefined ) {
+ $objects.css( 'min-width' , '' );
+ $objects.width( parseInt( instance.settings.container.parent().width() * parseInt( instance.settings.menuWidth )/100 ) )
+ };
+ maxWidth = $objects.width() - ieShadowFilterDistortion;
+ $objects.css( 'min-width' , $objects.width() - ieShadowFilterDistortion + 'px' );
+ }
+ var maxExtWidth = ( extWidth && forceWidth == undefined ) ? ( $objects.width() - ieShadowFilterDistortion + maxLevel * ( instance.settings.overlapWidth + ieShadowFilterDistortion ) ) : ( maxWidth + maxLevel * ( instance.settings.overlapWidth + ieShadowFilterDistortion ) ),
+ maxHeight = ( forceHeight == undefined ) ? Math.max.apply( null,
+ $('#' + instance.settings.menuID + ' div.levelHolderClass').map(function(){ return $(this).height(); }).get() ) : forceHeight;
+
+ instance.settings.container.css( 'min-height' , '' );
+ instance.settings.container.children( 'nav:first' ).css( 'min-height' , '' );
+ if( extHeight ) {
+ instance.settings.container.height( instance.settings.menuHeight );
+ instance.settings.container.css( 'min-height' , instance.settings.menuHeight );
+ instance.settings.container.children( 'nav:first' ).css( 'min-height' , instance.settings.menuHeight );
+ $('#' + instance.settings.menuID).height(instance.settings.menuHeight);
+ maxHeight = instance.settings.container.height();
+ }
+ else {
+ $('#' + instance.settings.menuID).height( maxHeight );
+ }
+ instance.settings.container.css( 'min-height' , maxHeight + 'px' );
+ instance.settings.container.children( 'nav:first' ).css( 'min-height' , maxHeight + 'px' );
+ instance.settings.container.width( maxExtWidth );
+ instance.settings.container.height( maxHeight );
+ var $baseLevelHolder = $('#' + instance.settings.menuID + ' div.levelHolderClass:first'),
+ $visibleLevelHolders = visibleLevelHolders(),
+ $hiddenLevelHolders = hiddenLevelHolders(),
+ $activeLevelHolder = activeMenu(),
+ activeLevel = ( $activeLevelHolder.length == 1 ) ? $activeLevelHolder.attr( 'data-level' ) : 0;
+ if( $visibleLevelHolders )
+ $visibleLevelHolders.each(function(){
+ if ( instance.settings.mode == 'overlap' )
+ $( this ).width( $( this ).width() + ( parseInt( activeLevel , 10) - parseInt( $( this ).attr( 'data-level' ) , 10) ) * ( instance.settings.overlapWidth + ieShadowFilterDistortion ) );
+ });
+ if( $hiddenLevelHolders )
+ $hiddenLevelHolders.each(function(){
+ ( instance.settings.direction == 'rtl' ) ?
+ $( this ).css( 'margin-right' , ( $( this ).attr( 'data-level' ) == $baseLevelHolder.attr( 'data-level' ) && !instance.settings.fullCollapse ) ? $( this ).width() * (-1) + instance.settings.overlapWidth : $( this ).width() * (-2) )
+ :
+ $( this ).css( 'margin-left' , ( $( this ).attr( 'data-level' ) == $baseLevelHolder.attr( 'data-level' ) && !instance.settings.fullCollapse ) ? $( this ).width() * (-1) + instance.settings.overlapWidth : $( this ).width() * (-2) );
+ });
+ currWidth = $baseLevelHolder.width() + parseInt( $baseLevelHolder.css( ( instance.settings.direction == 'rtl' ) ? 'margin-right' : 'margin-left' ) , 10 );
+ sizeElementWidth( instance.settings.container , currWidth );
+ instance.menuWidth = maxWidth;
+ instance.menuHeight = maxHeight;
+ instance.redraw = false;
+ }
+ }
+
+ // Simple/singe DOM element width sizing
+ function sizeElementWidth( $element , size ) {
+ if( $element == undefined || size == undefined ) return false;
+ $element.css( 'min-width' , '' );
+ $element.css( 'min-width' , size + 'px' );
+ $element.children( 'nav:first' ).css( 'min-width' , '' );
+ $element.children( 'nav:first' ).css( 'min-width' , size + 'px' );
+ $element.width( size );
+ }
+
+ // Hide wrappers in browsers that
+ // does not understand negative margin in %
+ // before DOM element got its dimensions
+ function fixLazyBrowsers() {
+ var $baseLevelHolder = $('#' + instance.settings.menuID + ' div.levelHolderClass:first'),
+ $hiddenLevelHolders = instance.settings.container
+ .find( '#' + instance.settings.menuID + ' div.levelHolderClass' )
+ .filter(function(){
+ var retObjs = ( instance.settings.direction == 'rtl' ) ?
+ ( ( $( this ).position().left > instance.settings.container.width() || parseInt( $( this ).css( 'margin-right' ) ) < 0 ) && $( this ).attr( 'data-level' ) > $baseLevelHolder.attr( 'data-level' ) )
+ :
+ ( ( $( this ).position().left < 0 || parseInt( $( this ).css( 'margin-left' ) ) < 0 ) && $( this ).attr( 'data-level' ) > $baseLevelHolder.attr( 'data-level' ) );
+ return retObjs;
+ });
+ $hiddenLevelHolders.each(function(){
+ if( instance.settings.direction == 'rtl' ){
+ $( this ).css( 'margin-right' , ( ( $( this ).attr( 'data-level' ) == $baseLevelHolder.attr( 'data-level' ) && !instance.settings.collapsed ) ? 0 : (-2) * $( this ).width() ) )
+ }
+ else {
+ $( this ).css( 'margin-left' , ( ( $( this ).attr( 'data-level' ) == $baseLevelHolder.attr( 'data-level' ) && !instance.settings.collapsed ) ? 0 : (-2) * $( this ).width() ) );
+ }
+ });
+ if( instance.settings.direction == 'rtl' ){
+ $baseLevelHolder.css( 'margin-right' , ( !instance.settings.collapsed ) ? 0 : (-2) * $baseLevelHolder.width() )
+ }
+ else {
+ $baseLevelHolder.css( 'margin-left' , ( !instance.settings.collapsed ) ? 0 : (-2) * $baseLevelHolder.width() );
+ }
+ }
+
+ // Is integer
+ function isInt( n ) {
+ return typeof n === 'number' && parseFloat( n ) == parseInt( n, 10 ) && !isNaN( n );
+ }
+
+ // Is Valid CSS dimension
+ function isValidDim( s ) {
+ return typeof s === 'string' && ( s.indexOf( '%' ) != -1 || s.indexOf( 'px' ) != -1 || s.indexOf( 'em' ) != -1 );
+ }
+
+ // Initialize menu level push menu
+ function initialize(){
+ var execute = ( options && options.menu != undefined ) ? createDOMStructure() : updateDOMStructure();
+ propagateEvent( instance.settings.container , clickEventType );
+ sizeDOMelements();
+ fixLazyBrowsers();
+ startMode( instance.settings.collapsed );
+ instance.settings.onMenuReady.apply(this, Array.prototype.slice.call([instance.settings]));
+ return $this;
+ }
+
+ // Initialize menu in collapsed/expanded mode
+ function startMode( mode ) {
+ if( mode ) {
+ var $baseLevelHolder = $('#' + instance.settings.menuID + ' div.levelHolderClass:first');
+ $baseLevelHolder.find( 'ul' ).hide();
+ $baseLevelHolder.addClass( instance.settings.menuInactiveClass );
+ if( instance.settings.direction == 'rtl' ) {
+ $baseLevelHolder.stop().animate({
+ marginRight: ( ( -1 ) * $baseLevelHolder.width() + ( ( instance.settings.fullCollapse ) ? 0 : instance.settings.overlapWidth ) )
+ }, instance.settings.durationSlideOut)
+ }
+ else {
+ $baseLevelHolder.stop().animate({
+ marginLeft: ( ( -1 ) * $baseLevelHolder.width() + ( ( instance.settings.fullCollapse ) ? 0 : instance.settings.overlapWidth ) )
+ }, instance.settings.durationSlideOut);
+ }
+ }
+ }
+
+ // Push container(s) of choice
+ function pushContainers( absMove ) {
+ if( instance.settings.containersToPush == null ) return false;
+ $.each( instance.settings.containersToPush, function() {
+ var lMr = parseInt( $( this ).css( 'margin-left' ) ),
+ lM = isInt( lMr ) ? lMr : 0,
+ rMr = parseInt( $( this ).css( 'margin-right' ) ),
+ rM = isInt( rMr ) ? rMr : 0;
+ $( this ).stop().animate({
+ marginLeft: lM + ( ( instance.settings.direction == 'rtl' ) ? (-1) : 1 ) * absMove,
+ marginRight: rM + ( ( instance.settings.direction == 'rtl' ) ? 1 : (-1) ) * absMove
+ }, instance.settings.durationSlideOut);
+ });
+ }
+
+ // Collapse menu
+ function collapseMenu() {
+ if( $(instance).find( 'div.levelHolderClass' ).is(':animated') ) return false;
+ instance.settings.onCollapseMenuStart.apply(this, Array.prototype.slice.call([instance.settings]));
+ var level = arguments[0],
+ callbacks = arguments[1],
+ collapingObjects = {},
+ ieShadowFilterDistortion,lwidth, lpush, lMarginLeft, lMarginLeftFC,
+ $baseLevelHolder = $('#' + instance.settings.menuID + ' div.levelHolderClass:first'),
+ collapseAll = ( level == undefined ) ? true : false,
+ currWidth;
+ collapingObjects[ 'collapsingEnded' ] = false;
+ if( typeof level == 'object' ) {
+ level = level.attr( 'data-level' );
+ }
+ else if( typeof level == 'string' ){
+ var $selectedLevelHolder = findMenusByTitle( level );
+ if( $selectedLevelHolder && $selectedLevelHolder.length == 1 ) {
+ level = $selectedLevelHolder.attr( 'data-level' );
+ }
+ else {
+ level = $baseLevelHolder.attr( 'data-level' );
+ }
+ }
+ else if( level == undefined || !isInt( level ) || level < 0 ) {
+ level = $baseLevelHolder.attr( 'data-level' );
+ }
+ if( callbacks == undefined && typeof callbacks != 'object' ) {
+ callbacks = [ { 'method' : instance.settings.onCollapseMenuEnd, 'args' : [instance.settings] } ];
+ } else {
+ $.merge(callbacks, [ { 'method' : instance.settings.onCollapseMenuEnd, 'args' : [instance.settings] } ]);
+ }
+ var $nextLevelHolders = instance.settings.container
+ .find( '#' + instance.settings.menuID + ' div.levelHolderClass' )
+ .filter(function(){
+ var retObjs = ( instance.settings.direction == 'rtl' ) ?
+ ($( this ).attr( 'data-level' ) > level) && (parseInt( $( this ).css( 'margin-right' ) ) >= 0 && $( this ).position().left < instance.settings.container.width() - instance.settings.overlapWidth )
+ :
+ ($( this ).attr( 'data-level' ) > level) && (parseInt( $( this ).css( 'margin-left' ) ) >= 0 && $( this ).position().left >= 0 );
+ return retObjs;
+ }),
+ $prevLevelHolders = instance.settings.container
+ .find( '#' + instance.settings.menuID + ' div.levelHolderClass' )
+ .filter(function(){
+ var retObjs = ( instance.settings.direction == 'rtl' ) ?
+ ($( this ).attr( 'data-level' ) <= level) && (parseInt( $( this ).css( 'margin-right' ) ) >= 0 && $( this ).position().left < instance.settings.container.width() - instance.settings.overlapWidth )
+ :
+ ($( this ).attr( 'data-level' ) <= level) && (parseInt( $( this ).css( 'margin-left' ) ) >= 0 && $( this ).position().left >= 0 );
+ return retObjs;
+ });
+ if( $prevLevelHolders.length > 0 ) {
+ collapingObjects[ 'prevAnimEnded' ] = false;
+ $nextLevelHolders.each(function( key, val ){
+ ieShadowFilterDistortion = ($( val ).css('filter').match(/DXImageTransform\.Microsoft\.Shadow/)) ? $( val ).get(0).filters.item("DXImageTransform.Microsoft.Shadow").strength : 0;
+ lwidth = ( instance.settings.mode == 'overlap' ) ? $( val ).width() - ( $nextLevelHolders.length + $prevLevelHolders.length - $( val ).attr( 'data-level' ) - 1) * ( instance.settings.overlapWidth + ieShadowFilterDistortion ) - ieShadowFilterDistortion : $( val ).width() - ieShadowFilterDistortion
+ if( instance.settings.direction == 'rtl' ) {
+ $( val ).stop().animate({
+ marginRight : ( (-1) * lwidth ),
+ width: lwidth
+ }, instance.settings.durationTransition);
+ }
+ else {
+ $( val ).stop().animate({
+ marginLeft : ( (-1) * lwidth ),
+ width: lwidth
+ }, instance.settings.durationTransition);
+ }
+ });
+ collapingObjects[ 'nextAnimEnded' ] = ( $nextLevelHolders.length > 0 ) ? false : true ;
+ $nextLevelHolders.last().queue(function(){
+ collapingObjects[ 'nextAnimEnded' ] = true;
+ animatedEventCallback( collapingObjects , callbacks );
+ });
+ $prevLevelHolders.each(function( key, val ){
+ ieShadowFilterDistortion = ($( val ).css('filter').match(/DXImageTransform\.Microsoft\.Shadow/)) ? $( val ).get(0).filters.item("DXImageTransform.Microsoft.Shadow").strength : 0;
+ var $makeLevelHolderVisible = $prevLevelHolders.filter(function(){
+ return $( this ).attr( 'data-level' ) == level;
+ });
+ $makeLevelHolderVisible.css( 'visibility' , 'visible' );
+ $makeLevelHolderVisible.find( '.' + instance.settings.backItemClass ).css( 'visibility' , 'visible' );
+ $makeLevelHolderVisible.find( 'ul' ).css( 'visibility' , 'visible' );
+ $makeLevelHolderVisible.removeClass( instance.settings.menuInactiveClass );
+ lwidth = ( instance.settings.mode == 'overlap' ) ? $( val ).width() - $nextLevelHolders.length * ( instance.settings.overlapWidth + ieShadowFilterDistortion ) - ieShadowFilterDistortion : $( val ).width() - ieShadowFilterDistortion;
+ if( instance.settings.direction == 'rtl' ) {
+ $( val ).stop().animate({
+ width: lwidth,
+ marginRight : ( $( val ).attr( 'data-level' ) == $baseLevelHolder.attr( 'data-level' ) && collapseAll ) ?
+ ( instance.settings.fullCollapse ) ?
+ ( -1 ) * $( val ).width()
+ :
+ ( ( -1 ) * $( val ).width() + ( ( instance.settings.mode == 'overlap' ) ? $nextLevelHolders.length + 1 : 1 ) * instance.settings.overlapWidth )
+ :
+ 0
+ }, instance.settings.durationSlideOut, function(){
+ if( $( val ).attr( 'data-level' ) == $baseLevelHolder.attr( 'data-level' ) && collapseAll ){
+ $baseLevelHolder.children( 'ul' ).first().hide(instance.settings.durationSlideDown, function(){
+ $baseLevelHolder.addClass( instance.settings.menuInactiveClass );
+ });
+ }
+ currWidth = $baseLevelHolder.width() + parseInt( $baseLevelHolder.css( 'margin-right' ) , 10 );
+ sizeElementWidth( instance.settings.container , currWidth );
+ });
+ }
+ else {
+ $( val ).stop().animate({
+ width: lwidth,
+ marginLeft : ( $( val ).attr( 'data-level' ) == $baseLevelHolder.attr( 'data-level' ) && collapseAll ) ?
+ ( instance.settings.fullCollapse ) ?
+ ( -1 ) * $( val ).width()
+ :
+ ( ( -1 ) * $( val ).width() + ( ( instance.settings.mode == 'overlap' ) ? $nextLevelHolders.length + 1 : 1 ) * instance.settings.overlapWidth )
+ :
+ 0
+ }, instance.settings.durationSlideOut, function(){
+ if( $( val ).attr( 'data-level' ) == $baseLevelHolder.attr( 'data-level' ) && collapseAll ){
+ $baseLevelHolder.children( 'ul' ).first().hide(instance.settings.durationSlideDown, function(){
+ $baseLevelHolder.addClass( instance.settings.menuInactiveClass );
+ });
+ }
+ currWidth = $baseLevelHolder.width() + parseInt( $baseLevelHolder.css( 'margin-left' ) , 10 );
+ sizeElementWidth( instance.settings.container , currWidth );
+ });
+ }
+ lpush = ( instance.settings.mode == 'overlap' ) ? ( (-1) * ( $nextLevelHolders.length * ( instance.settings.overlapWidth + ieShadowFilterDistortion ) ) ) : 0 ;
+ if( $( val ).attr( 'data-level' ) == $baseLevelHolder.attr( 'data-level' ) && collapseAll ){
+ var blpush = ( instance.settings.fullCollapse ) ? ( -1 ) * ( $baseLevelHolder.width() - ieShadowFilterDistortion ) : ( -1 ) * ( $baseLevelHolder.width() - ieShadowFilterDistortion ) + instance.settings.overlapWidth;
+ pushContainers( blpush );
+ }
+ else {
+ pushContainers( lpush );
+ }
+ });
+ $prevLevelHolders.last().queue(function(){
+ collapingObjects[ 'prevAnimEnded' ] = true;
+ animatedEventCallback( collapingObjects , callbacks );
+ });
+ }
+ collapingObjects[ 'collapsingEnded' ] = true;
+ animatedEventCallback( collapingObjects , callbacks );
+ return $this;
+ }
+
+ // Expand Menu helper
+ function expandMenuActions() {
+ if( $(instance).find( 'div.levelHolderClass' ).is(':animated') ) return false;
+ instance.settings.onExpandMenuStart.apply(this, Array.prototype.slice.call([instance.settings]));
+ var menuTitle = arguments[0],
+ callbacks = arguments[1],
+ ieShadowFilterDistortion, lwidth, lpush, blpush, currWidth,
+ expandingObjects = {},
+ $baseLevelHolder = $('#' + instance.settings.menuID + ' div.levelHolderClass:first'),
+ baseExpand = ( menuTitle == undefined ) ? true : false,
+ baseLevelHolderCollapsed = ( instance.settings.direction == 'rtl' ) ?
+ parseInt( $baseLevelHolder.css( 'margin-right' ), 10 ) < 0 || $baseLevelHolder.position().left >= instance.settings.container.width() - instance.settings.overlapWidth
+ :
+ parseInt( $baseLevelHolder.css( 'margin-left' ), 10 ) < 0 || $baseLevelHolder.position().left < 0;
+ expandingObjects[ 'expandingEnded' ] = false;
+ if( callbacks == undefined && typeof callbacks != 'object' ) {
+ callbacks = [ { 'method' : instance.settings.onExpandMenuEnd, 'args' : [instance.settings] } ];
+ } else {
+ $.merge(callbacks, [ { 'method' : instance.settings.onExpandMenuEnd, 'args' : [instance.settings] } ]);
+ }
+ if( baseExpand ) {
+ expandingObjects[ 'baseAnimEnded' ] = false;
+ $baseLevelHolder.removeClass( instance.settings.menuInactiveClass );
+ currWidth = $baseLevelHolder.width();
+ sizeElementWidth( instance.settings.container , currWidth );
+ if( instance.settings.direction == 'rtl' ) {
+ $baseLevelHolder.stop().animate({
+ marginRight: 0
+ }, instance.settings.durationSlideOut, function(){
+ $baseLevelHolder.children( 'ul' ).first().show(instance.settings.durationSlideDown , function(){
+ expandingObjects[ 'baseAnimEnded' ] = true;
+ animatedEventCallback( expandingObjects , callbacks );
+ });
+ });
+ }
+ else {
+ $baseLevelHolder.stop().animate({
+ marginLeft: 0
+ }, instance.settings.durationSlideOut, function(){
+ $baseLevelHolder.children( 'ul' ).first().show(instance.settings.durationSlideDown , function(){
+ expandingObjects[ 'baseAnimEnded' ] = true;
+ animatedEventCallback( expandingObjects , callbacks );
+ });
+ });
+ }
+ blpush = ( instance.settings.fullCollapse ) ? $baseLevelHolder.width() : $baseLevelHolder.width() - instance.settings.overlapWidth;
+ var pushbm = ( !menuExpanded( $baseLevelHolder ) ) ? pushContainers( blpush ) : null;
+ } else {
+ var $selectedLevelHolder;
+ if( typeof menuTitle == 'object' ) {
+ $selectedLevelHolder = menuTitle;
+ }
+ else if( typeof menuTitle == 'string' ){
+ $selectedLevelHolder = findMenusByTitle( menuTitle );
+ }
+ else {
+ $selectedLevelHolder = null;
+ $.error( 'Provided menu selector is not valid' );
+ }
+ if( $selectedLevelHolder && $selectedLevelHolder.length == 1 ) {
+ var $activeLevelHolder = activeMenu(),
+ activeLevel = ( $activeLevelHolder.length == 1 ) ? $activeLevelHolder.attr( 'data-level' ) : 0,
+ baseWidth = $selectedLevelHolder.width(),
+ setToOpenHolders = pathToRoot( $selectedLevelHolder );
+ expandingObjects[ 'setToOpenAnimEnded' ] = false;
+ if( setToOpenHolders ) {
+ var parentLevelHoldersLen = $( setToOpenHolders ).length - 1;
+ $baseLevelHolder.find( 'ul' ).each(function(){
+ $( this ).show(0);
+ });
+ $( setToOpenHolders ).find( 'ul' ).css( 'visibility' , 'hidden' );
+ $( setToOpenHolders ).find( 'div' ).css( 'visibility' , 'visible' );
+ $( setToOpenHolders ).find( '.' + instance.settings.backItemClass ).css( 'visibility' , 'hidden' );
+ $( setToOpenHolders ).each( function( key, val ) {
+ ieShadowFilterDistortion = ($( val ).css('filter').match(/DXImageTransform\.Microsoft\.Shadow/)) ? $( val ).get(0).filters.item("DXImageTransform.Microsoft.Shadow").strength : 0;
+ lwidth = baseWidth - ieShadowFilterDistortion + ( parentLevelHoldersLen - $( val ).attr( 'data-level' ) ) * ( instance.settings.overlapWidth + ieShadowFilterDistortion );
+ if(instance.settings.container.width() < lwidth && instance.settings.mode == 'overlap' )
+ sizeElementWidth( instance.settings.container , lwidth );
+ if( instance.settings.direction == 'rtl' ) {
+ $( val ).stop().animate({
+ marginRight: 0,
+ width: ( instance.settings.mode == 'overlap' ) ? lwidth : baseWidth - ieShadowFilterDistortion
+ }, instance.settings.durationTransition, function(){
+ $( val ).addClass( instance.settings.menuInactiveClass );
+ });
+ }
+ else {
+ $( val ).stop().animate({
+ marginLeft: 0,
+ width: ( instance.settings.mode == 'overlap' ) ? lwidth : baseWidth - ieShadowFilterDistortion
+ }, instance.settings.durationTransition, function(){
+ $( val ).addClass( instance.settings.menuInactiveClass );
+ });
+ }
+ });
+ $( setToOpenHolders ).last().queue(function(){
+ $( this ).removeClass( instance.settings.menuInactiveClass );
+ expandingObjects[ 'setToOpenAnimEnded' ] = true;
+ animatedEventCallback( expandingObjects , callbacks );
+ });
+ if( baseLevelHolderCollapsed ) {
+ blpush = ( instance.settings.fullCollapse ) ? $baseLevelHolder.width() : ( $baseLevelHolder.width() - instance.settings.overlapWidth );
+ pushContainers( blpush );
+ }
+ if( instance.settings.mode == 'overlap' ){
+ lpush = ( ( baseLevelHolderCollapsed ) ? ( baseWidth + ( parentLevelHoldersLen - ( ( instance.settings.fullCollapse ) ? 0 : 1 ) ) * ( instance.settings.overlapWidth + ieShadowFilterDistortion ) ) : ( ( parentLevelHoldersLen - activeLevel ) * ( instance.settings.overlapWidth + ieShadowFilterDistortion ) ) );
+ pushContainers( lpush );
+ }
+ $selectedLevelHolder.css( 'visibility' , 'visible' );
+ $selectedLevelHolder.find( '.' + instance.settings.backItemClass ).css( 'visibility' , 'visible' );
+ $selectedLevelHolder.find( 'ul' ).css( 'visibility' , 'visible' );
+ $selectedLevelHolder.removeClass( instance.settings.menuInactiveClass );
+ }
+ else {
+ $.error( 'Invalid menu object provided' );
+ }
+ }
+ else {
+ $.error( 'No or too many menus named ' + menuTitle );
+ }
+ }
+ expandingObjects[ 'expandingEnded' ] = true;
+ animatedEventCallback( expandingObjects , callbacks );
+ }
+
+ // Expand menu
+ function expandMenu() {
+ var menu = arguments[0],
+ $expandLevelHolder,
+ $activeLevelHolder = activeMenu(),
+ $sharedLevelHolders, collapseLevel, $searchRes;
+ if( typeof menu == 'object' ) {
+ $expandLevelHolder = menu;
+ }
+ else if( typeof menu == 'string' ){
+ $searchRes = findMenusByTitle( menu );
+ if($searchRes) {
+ $expandLevelHolder = $searchRes.eq( 0 );
+ }
+ else {
+ $.error( menu + ' menu level does not exist!' );
+ }
+ }
+ else {
+ $expandLevelHolder = $('#' + instance.settings.menuID + ' div.levelHolderClass:first');
+ }
+ $sharedLevelHolders = comparePaths( $expandLevelHolder , $activeLevelHolder, true );
+ collapseLevel = ( $sharedLevelHolders.length > 0 ) ? Math.max.apply( null,
+ $sharedLevelHolders.map(function(){ return $(this).attr( 'data-level' ); }).get() ) : 0;
+ if( collapseLevel < $activeLevelHolder.attr( 'data-level' ) ) {
+ collapseMenu( collapseLevel , [ { 'method' : expandMenuActions, 'args' : arguments } ] );
+ }
+ else {
+ expandMenuActions.apply( this, Array.prototype.slice.call( arguments ) );
+ }
+ return $this;
+ }
+
+ // Find menu(s) by Title text
+ function findMenusByTitle() {
+ var menuTitle = arguments[0],
+ response,
+ $selectedLevelHolders = instance.settings.container
+ .find( '#' + instance.settings.menuID + ' div.levelHolderClass' )
+ .filter(function(){
+ return ( ($( this ).children( 'h2' ).text() == menuTitle ) );
+ });
+ if( $selectedLevelHolders.length > 0 ) {
+ returnValue = $selectedLevelHolders;
+ response = returnValue;
+ }
+ else {
+ returnValue = false;
+ response = returnValue;
+ }
+ return response;
+ }
+
+ // Find item(s) by Name
+ function findItemsByName() {
+ var itemName = arguments[0],
+ response,
+ $selectedItems = instance.settings.container
+ .find( '#' + instance.settings.menuID + ' div.levelHolderClass li' )
+ .filter(function(){
+ return ( ($( this ).children( 'a' ).text() == itemName ) );
+ });
+ if( $selectedItems.length > 0 ) {
+ returnValue = $selectedItems;
+ response = returnValue;
+ }
+ else {
+ returnValue = false;
+ response = returnValue;
+ }
+ return response;
+ }
+
+ // Find pathToRoot for provided menu
+ function pathToRoot() {
+ var $selectedLevelHolder = arguments[0],
+ $parentLevelHolders, setToOpenHolders, response;
+ if( $selectedLevelHolder == undefined || $selectedLevelHolder.length != 1 ) {
+ returnValue = false;
+ return returnValue;
+ };
+ $parentLevelHolders = $selectedLevelHolder.parents( 'div.levelHolderClass' );
+ setToOpenHolders = $.merge( $parentLevelHolders.get().reverse(), $selectedLevelHolder.get() );
+ returnValue = setToOpenHolders;
+ return returnValue;
+ }
+
+ // Finds the same part of the path to root of two provided menus
+ function comparePaths() {
+ var $levelHolder0 = arguments[0],
+ $levelHolder1 = arguments[1],
+ mode = ( arguments[2] != undefined ) ? arguments[2] : false,
+ $parentLevelHolders0, $parentLevelHolders1, setParents0, setParents1, lPath, sPath, comparePath, response;
+ if( $levelHolder0 == undefined || $levelHolder1 == undefined ) {
+ returnValue = false;
+ return returnValue;
+ };
+ $parentLevelHolders0 = ( $levelHolder0.length == 1 ) ? $levelHolder0.parents( 'div.levelHolderClass' ) : null;
+ $parentLevelHolders1 = ( $levelHolder1.length == 1 ) ? $levelHolder1.parents( 'div.levelHolderClass' ) : null;
+ setParents0 = ( $parentLevelHolders0 != null ) ? $.merge( $parentLevelHolders0.get().reverse(), $levelHolder0.get() ) : [];
+ setParents1 = ( $parentLevelHolders1 != null ) ? $.merge( $parentLevelHolders1.get().reverse(), $levelHolder1.get() ) : [];
+ lPath = ( setParents0.length >= setParents1.length ) ? setParents0 : setParents1;
+ sPath = ( lPath === setParents0 ) ? setParents1 : setParents0;
+ comparePath = $( lPath ).filter(function() {
+ return ( mode ) ? ( $.inArray( this, sPath ) != -1 ) : ( $.inArray( this, sPath ) == -1 );
+ });
+ returnValue = comparePath;
+ return returnValue;
+ }
+
+ // Active menu
+ function activeMenu() {
+ var $activeLevelHolders = instance.settings.container
+ .find( '#' + instance.settings.menuID + ' div.levelHolderClass' )
+ .filter(function(){
+ var retObjs = ( instance.settings.direction == 'rtl' ) ?
+ ((parseInt( $( this ).css( 'margin-right' ) ) >= 0 && $( this ).position().left < instance.settings.container.width() - instance.settings.overlapWidth ) )
+ :
+ ((parseInt( $( this ).css( 'margin-left' ) ) >= 0 && $( this ).position().left >= 0 ) );
+ return retObjs;
+ }),
+ maxLevel = Math.max.apply( null,
+ $activeLevelHolders.map(function(){ return $(this).attr( 'data-level' ); }).get() ),
+ $activeLevelHolder = $activeLevelHolders.filter(function(){
+ return $( this ).attr( 'data-level' ) == maxLevel;
+ });
+ returnValue = $activeLevelHolder;
+ return returnValue;
+ }
+
+ // Menu expanded
+ function menuExpanded() {
+ var $levelHolder = arguments[0],
+ returnValue = false;
+ if( $levelHolder == undefined ) return returnValue;
+
+ var check = ( instance.settings.direction == 'rtl' ) ?
+ ( parseInt( $levelHolder.css( 'margin-right' ) ) >= 0 && $levelHolder.position().left < instance.settings.container.width() - instance.settings.overlapWidth )
+ :
+ ( parseInt( $levelHolder.css( 'margin-left' ) ) >= 0 && $levelHolder.position().left >= 0 );
+ return check;
+ }
+
+ // Add item(s)
+ function addItems() {
+ var items = arguments[0],
+ $levelHolder = arguments[1],
+ position = arguments[2];
+ if( $levelHolder == undefined || typeof items != 'object' || !$levelHolder ) return false;
+ if( items.level == undefined ) items.level = parseInt( $levelHolder.attr( 'data-level' ) , 10 );
+ if( position == undefined ) position = 0;
+ var $itemGroup = $levelHolder.find( 'ul:first' );
+ $.each(items, function() {
+ if( this.name != undefined )
+ createItem( this, $levelHolder, position );
+ });
+ sizeDOMelements( instance.menuWidth );
+ return $this;
+ }
+
+ // Remove item(s)
+ function removeItems() {
+ var $items = arguments[0];
+ if( $items == undefined || typeof $items != 'object' || $items.length == 0 ) return false;
+ $items.remove();
+ var $activeMenu = activeMenu();
+ if( $activeMenu.length == 1 ) {
+ $activeMenu.css( 'visibility' , 'visible' );
+ $activeMenu.find( '.' + instance.settings.backItemClass ).css( 'visibility' , 'visible' );
+ $activeMenu.find( 'ul' ).css( 'visibility' , 'visible' );
+ $activeMenu.removeClass( instance.settings.menuInactiveClass );
+ var widthDiff = $activeMenu.width() - instance.menuWidth;
+ if( widthDiff != 0 ) {
+ var $visibleLevelHolders = visibleLevelHolders();
+ if( $visibleLevelHolders )
+ $visibleLevelHolders.each(function(){
+ $( this ).width( $( this ).width() - widthDiff );
+ });
+ }
+ }
+ sizeDOMelements( instance.menuWidth );
+ return $this;
+ }
+
+ // Manage multiple animated events and associated callbacks
+ function animatedEventCallback( animatedObjects, callbacks ) {
+ var doCallBack = true;
+ $.each( animatedObjects, function( key, val ){
+ doCallBack = doCallBack && val;
+ });
+ if( doCallBack )
+ window.setTimeout(function(){
+ $.each( callbacks, function( key, val ){
+ val['method'].apply( this, Array.prototype.slice.call( val['args'] ) );
+ });
+ }, 1);
+ }
+
+ // Get/set settings options
+ function manageOptions() {
+ var response = false;
+ if( instance.settings[arguments[0]] != undefined ) {
+ if( arguments[1] != undefined )
+ instance.settings[arguments[0]] = arguments[1];
+ response = instance.settings[arguments[0]];
+ } else {
+ $.error('No option ' + arguments[0] + ' found in jQuery.multilevelpushmenu');
+ }
+ return response;
+ }
+
+ // Mobile check
+ // http://coveroverflow.com/a/11381730/989439
+ function mobileCheck() {
+ var check = false;
+ (function(a){if(/(android|ipad|playbook|silk|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera);
+ return check;
+ }
+
+ if( mobileCheck() ) {
+ clickEventType = 'touchend';
+ dragEventType = 'touchmove';
+ }
+ else {
+ clickEventType = 'click';
+ dragEventType = 'mousedown';
+ }
+
+ // Invoke called method or init
+ if ( methods[options] ) {
+ returnValue = methods[options].apply(this, Array.prototype.slice.call(args, 1));
+ return returnValue;
+ } else if (typeof options === 'object' || !options) {
+ returnValue = methods.init.apply(this, arguments);
+ return returnValue;
+ } else {
+ $.error('No ' + options + ' method found in jQuery.multilevelpushmenu');
+ }
+
+ // Return object instance or option value
+ if (!returnValue) {
+ returnValue = this;
+ }
+ });
+ return returnValue;
+ }
+}( jQuery ));
diff --git a/ui/client/main.html b/ui/client/main.html
new file mode 100644
index 0000000..5aabed7
--- /dev/null
+++ b/ui/client/main.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 #
+########################################################################################
+ -->
+<head>
+ <title>Cisco Calipso</title>
+ <link rel="shortcut icon" href="/cisco-favicon.ico">
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <!-- TODO: this was need because d3.event was null when included as a module. research other ways -->
+ <script src="https://d3js.org/d3.v4.js"></script>
+</head>
diff --git a/ui/client/main.js b/ui/client/main.js
new file mode 100644
index 0000000..1502661
--- /dev/null
+++ b/ui/client/main.js
@@ -0,0 +1,10 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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/startup/client';
+import '/imports/startup/both';
diff --git a/ui/client/main.styl b/ui/client/main.styl
new file mode 100644
index 0000000..e142eda
--- /dev/null
+++ b/ui/client/main.styl
@@ -0,0 +1,85 @@
+/* CSS declarations go here */
+normalize-css()
+
+// Colors
+black = #222
+grey = #D8D8D8
+purp = #524763
+dark-purp = #373142
+red = #E54B4B
+teal = #82D8D8
+dark-teal = #66A6A6
+green = #CBE56C
+blue = #16528E
+bright-blue = #0082C8
+maroon = #6A2140
+light-red = #CA5765
+pink = #CF256D
+orange = #E8846B
+
+// cisco brand Colors
+brand-blue = #2196F3
+spark-blue = #0A9AD7
+spark-green = #73BE46
+spark-grey = #D1D2D1
+dk-blue = #303f9f
+light-grey = #efefef
+dk-gray1 = #58585b
+// cisco brand status Colors
+status-blue = #64bbe3
+status-turq = #14a792
+status-green = #6cc04a
+status-yellow = #ffcc00
+status-orange = #ff7300
+status-red = #cf2030
+// cisco accepteble colors
+
+// Material Shadows
+shadow-level(l = 1)
+ if l == -1
+ box-shadow: inset -2px 7px 9px -7px rgba(0,0,0,0.7);
+ if l == 0
+ box-shadow: none;
+ if l == 1
+ box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
+ if l == 2
+ box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
+ if l == 3
+ box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
+ if l == 4
+ box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
+ if l == 5
+ box-shadow: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22);
+
+// Common Transition
+trans($sec = 0.3s)
+ transition: all $sec ease
+
+// Grid
+$xsmall = 340px
+$small = 500px
+$med = 700px
+$large = 960px
+$wide = 1200px
+
+z = {
+ neg: -7,
+ lowest: 1,
+ low: 4,
+ mid: 5,
+ video: 6,
+ header: 7,
+ nav: 8,
+ mini: 9,
+ modal: 10,
+ highest: 11
+}
+
+$side-nav = 100px
+$side-nav-large = 200px
+
+body{
+ background light-grey
+}
+
+@import 'css/*'
diff --git a/ui/client/style/d3plusgraph.css b/ui/client/style/d3plusgraph.css
new file mode 100644
index 0000000..f811c80
--- /dev/null
+++ b/ui/client/style/d3plusgraph.css
@@ -0,0 +1,14 @@
+/****************************************************************************************/
+/* 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 */
+/****************************************************************************************/
+#viz{
+ /*margin: 166px 134px;*/
+ color: white;
+ width: 90%;
+ height: 90%;
+} \ No newline at end of file
diff --git a/ui/client/style/landingpage.css b/ui/client/style/landingpage.css
new file mode 100644
index 0000000..1fd3035
--- /dev/null
+++ b/ui/client/style/landingpage.css
@@ -0,0 +1,138 @@
+/****************************************************************************************/
+/* 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 */
+/****************************************************************************************/
+/* wizard */
+.mdl-dialog {
+ width: 90vw !important;
+}
+
+.mdl-tabs__panel{
+ margin-top: 30px;
+}
+
+.mdl-dialog__content {
+ border-left: 5px solid #2196F3;
+}
+
+
+/* ======= Home page =======*/
+.background-blue{
+ background-color: #2196F3;
+ color: #f9f9f9;
+}
+.background-white {
+ border-color: #FFF;
+}
+
+.background-grey {
+ background-color: #f9f9f9;
+}
+
+.home-fullscreen {
+ /* padding: 25px; */
+}
+
+.mtop-50{
+ margin-top: 100px;
+}
+
+.font20{
+ font-size: 20px;
+}
+
+.title-border-bottom{
+ border-bottom: 5px solid #2196F3;
+}
+.text-align-center{
+ text-align: center;
+}
+
+/*
+.home_mockups {
+ position: relative;
+ margin-top: 4rem;
+ margin-bottom: -1rem;
+ z-index: 3;
+}
+
+.home_mockups_1 {
+ position: relative;
+ animation-delay: 0.5s;
+}
+
+.home_mockups_2 {
+ animation-delay: 1s;
+}
+
+.home_mockups_2, .home_mockups_3 {
+ position: absolute;
+ margin: 0 auto;
+ top: 0;
+ right: 0;
+ left: 0;
+ bottom: 0;
+}
+*/
+
+.box {
+ margin: 50px;
+}
+
+.box-side{
+ margin: 10px 50px;
+}
+/* ======= End of section =========*/
+
+
+/* ======= Login - Register pages ======= */
+.login-bg-primary {
+ width: 100%;
+ height: 30vh;
+ background-color: #2196F3;
+}
+
+.login-bg-accent {
+ width: 100%;
+ height: 30vh;
+ background-color: #9C27B0;
+}
+
+.login-card{
+ margin-top: -70px;
+ padding: 10px;
+}
+/* ======= End of section =======*/
+
+/*====== Main page =======*/
+.logo{
+ padding: 10px;
+}
+
+.view-100{
+ height: 100vh;
+}
+
+.user-bar{
+ background: #42A5F5;
+ color: #fff;
+ padding-left: 15px;
+}
+/* ======= End of section =======*/
+
+/*// Rules for sizing the icon.*/
+.material-icons.md-18 { font-size: 21px; }
+.material-icons.md-24 { font-size: 24px; }
+.material-icons.md-36 { font-size: 36px; }
+.material-icons.md-48 { font-size: 48px; }
+/*// Rules for using icons as black on a light background.*/
+.material-icons.md-dark { color: rgba(0, 0, 0, 0.54); }
+.material-icons.md-dark.md-inactive { color: rgba(0, 0, 0, 0.26); }
+/*// Rules for using icons as white on a dark background.*/
+.material-icons.md-light { color: rgba(255, 255, 255, 1); }
+.material-icons.md-light.md-inactive { color: rgba(255, 255, 255, 0.3); }
+/* ======= End of section ===========*/
diff --git a/ui/client/style/multilevelorig.css b/ui/client/style/multilevelorig.css
new file mode 100644
index 0000000..c73c7e6
--- /dev/null
+++ b/ui/client/style/multilevelorig.css
@@ -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 */
+/****************************************************************************************/
+/*
+body {
+ margin: 0;
+ padding: 0;
+ background: rgb(161, 183, 206);
+ overflow: hidden;
+}
+*/
+#menu{
+ margin-top: -50px;
+}
+#pushobj {
+ position: absolute;
+ top: 92px;
+ left: 384px;
+ color: #fff;
+ background: rgb(161, 183, 206);
+ font-family: 'Open Sans Condensed', sans-serif;
+ font-size: 2em;
+ z-index: 1;
+ width: 100%;
+ height: 89%;
+}
+/*
+.breadcrumb > li + li:before {
+ color: #CCCCCC;
+ content: "/ ";
+ padding: 0 5px;
+}
+.breadcrumb{
+ margin-bottom: 0px;
+ padding: 14px 15px;
+ font-size: 0.6em;
+}
+*/
diff --git a/ui/client/style/threeTest.css b/ui/client/style/threeTest.css
new file mode 100644
index 0000000..7a04d9c
--- /dev/null
+++ b/ui/client/style/threeTest.css
@@ -0,0 +1,65 @@
+/****************************************************************************************/
+/* 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 */
+/****************************************************************************************/
+/*
+body, html {
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ margin: 0;
+ position: relative;
+ overflow: hidden;
+ background: rgb(161, 183, 206);
+}
+*/
+/*
+svg {
+ !*background-color: antiquewhite;*!
+ background-color: rgb(161, 183, 206);
+ padding-top: 100px;
+
+}
+
+.node circle {
+ fill: steelblue;
+}
+.node text {
+ font: bold 12px sans-serif;
+ fill: rgb(16, 15, 15);
+ text-shadow: 0 0px 0 #fff;
+ font-size: 0.6em;
+}
+.link {
+ fill: #1313AB;
+ stroke: #1313AB;
+}
+.default {
+ stroke: #fbb;
+ stroke-width: 4px;
+}
+.outline {
+ stroke: #844;
+ stroke-width: 6px;
+}
+.link-group text{
+ font-size: 0.5em;
+ fill: rgba(8, 8, 8, 0.73);
+}
+div.tooltip {
+ position: absolute;
+ text-align: left;
+ font: bold 18px sans-serif !important;
+ !* width: 60px; *!
+ !* height: 28px; *!
+ padding: 2px;
+ font: 16px sans-serif;
+ background: #50DFE0;
+ border: 0px;
+ border-radius: 10px;
+ pointer-events: none;
+}*/
diff --git a/ui/client/style/threeTest2.css b/ui/client/style/threeTest2.css
new file mode 100644
index 0000000..83ec163
--- /dev/null
+++ b/ui/client/style/threeTest2.css
@@ -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 */
+/****************************************************************************************/
+#canvas {
+}
+
+#canvas-force {
+ width: 100%;
+ height: 100%;
+ /*float: left;*/
+}
+
+/*
+body {
+ margin: 0px;
+}
+*/
+
+#tooltip-container {
+ position: absolute;
+ background-color: #404040;
+ color: #fff;
+ opacity: 0.9;
+ padding-left: 10px;
+ padding-right: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ border: 1px solid;
+ display: none;
+ font-family: "Arial";
+ font-size: 12px;
+}
+
+.tooltip_key {
+ font-weight: bold;
+} \ No newline at end of file
diff --git a/ui/client/templates/envdialog.html b/ui/client/templates/envdialog.html
new file mode 100644
index 0000000..77d8375
--- /dev/null
+++ b/ui/client/templates/envdialog.html
@@ -0,0 +1,131 @@
+<!--
+########################################################################################
+# 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="envdialog">
+ <!-- <button id="show-dialog" type="button" class="mdl-button">Show Dialog</button> -->
+ <!-- Colored FAB button with ripple -->
+ <button id="show-dialog" class="mdl-button mdl-js-button mdl-button--fab mdl-js-ripple-effect mdl-button--colored">
+ <i class="material-icons">add</i>
+ </button>
+ <div class="mdl-tooltip" for="show-dialog">
+ Add new environment
+ </div>
+
+ <dialog class="mdl-dialog">
+ <h5 class="mdl-dialog__title">Add new environment</h5>
+
+ <div class="mdl-dialog__content">
+
+ <div class="mdl-tabs mdl-js-tabs mdl-js-ripple-effect">
+ <div class="mdl-tabs__tab-bar">
+ <a href="#main-info-panel" class="mdl-tabs__tab is-active">Main Info</a>
+ <a href="#endpoin-panel" class="mdl-tabs__tab">OS API endpoin</a>
+ <a href="#db-credentials-panel" class="mdl-tabs__tab">OpenStack DB Credentials</a>
+ <a href="#master-host-panel" class="mdl-tabs__tab">Master Host Credentials</a>
+ <a href="#nfv-panel" class="mdl-tabs__tab">NFV Credentials</a>
+ </div>
+
+ <div class="mdl-tabs__panel is-active" id="main-info-panel">
+ <!-- Textfield with Floating Label -->
+ <form action="#">
+ <div class="mdl-grid">
+ <div class="mdl-cell mdl-cell--4-col">
+ <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+ <input class="mdl-textfield__input" type="text" id="ownerName" value="Koren Lev">
+ <label class="mdl-textfield__label" for="ownerName">Owner</label>
+ </div>
+ </div>
+ <div class="mdl-cell mdl-cell--8-col">
+ <h6>Admin name, owner of project</h6>
+ </div>
+ <div class="mdl-cell mdl-cell--4-col">
+ <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+ <input class="mdl-textfield__input" type="text" id="initialEnvName">
+ <label class="mdl-textfield__label" for="initialEnvName">Enviroment name</label>
+ </div>
+ </div>
+ <div class="mdl-cell mdl-cell--8-col">
+ <h6>Enter name of your project</h6>
+ </div>
+ </div>
+ </form>
+ </div>
+
+ <div class="mdl-tabs__panel" id="endpoin-panel">
+ <form action="#">
+ <div class="mdl-grid">
+ <div class="mdl-cell mdl-cell--4-col">
+ <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+ <input class="mdl-textfield__input" type="text" id="apiHost" pattern="-?[0-9]*(\.[0-9]+)?">
+ <label class="mdl-textfield__label" for="apiHost">API host</label>
+ <span class="mdl-textfield__error">Input is not a number!</span>
+ </div>
+ </div>
+ <div class="mdl-cell mdl-cell--8-col">
+ <h6>This is API bla bla...</h6>
+ </div>
+ <div class="mdl-cell mdl-cell--4-col">
+ <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+ <input class="mdl-textfield__input" type="text" id="adminToken">
+ <label class="mdl-textfield__label" for="adminToken">Admin token</label>
+ </div>
+ </div>
+ <div class="mdl-cell mdl-cell--8-col">
+ <h6>You can find token ..</h6>
+ </div>
+ <div class="mdl-cell mdl-cell--4-col">
+ <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+ <input class="mdl-textfield__input" type="text" id="apiUsername">
+ <label class="mdl-textfield__label" for="apiUsername">Username</label>
+ </div>
+ </div>
+ <div class="mdl-cell mdl-cell--8-col">
+ <h6>Some info about user name</h6>
+ </div>
+ <div class="mdl-cell mdl-cell--4-col">
+ <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+ <input class="mdl-textfield__input" type="password" id="apiPassword">
+ <label class="mdl-textfield__label" for="apiPassword">Password</label>
+ </div>
+ </div>
+ <div class="mdl-cell mdl-cell--8-col">
+ <h6></h6>
+ </div>
+ </div>
+ </form>
+ </div>
+
+ <div class="mdl-tabs__panel" id="db-credentials-panel">
+ <ul>
+ <li>Viserys</li>
+ </ul>
+ </div>
+
+ <div class="mdl-tabs__panel" id="master-host-panel">
+ <ul>
+ <li>St</li>
+ </ul>
+ </div>
+
+ <div class="mdl-tabs__panel" id="nfv-panel">
+ <ul>
+ <li>Kor</li>
+ </ul>
+ <div class="mdl-dialog__actions">
+ <button type="button" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored">Submit</button>
+ <button type="button" class="mdl-button close">Close</button>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ </dialog>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/dialog-polyfill/0.4.3/dialog-polyfill.min.js"></script>
+</template>
diff --git a/ui/client/templates/header.html b/ui/client/templates/header.html
new file mode 100644
index 0000000..0bfda0b
--- /dev/null
+++ b/ui/client/templates/header.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 ="header">
+
+</template> \ No newline at end of file
diff --git a/ui/client/templates/mainPage.html b/ui/client/templates/mainPage.html
new file mode 100644
index 0000000..1b85c15
--- /dev/null
+++ b/ui/client/templates/mainPage.html
@@ -0,0 +1,207 @@
+<!--
+########################################################################################
+# 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="mainPage">
+ <header>
+ <div class="navbar" role="navigation">
+ <div class="container">
+ <div class="navbar-header">
+ {{#if currentUser}}
+ <a class="navbar-brand" id="logo" href="{{pathFor route='home'}}">Calipso - Dashboard</a>
+ {{/if }}
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#mynav">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ </div>
+ <div class="collapse navbar-collapse" id="mynav">
+ <ul class="nav navbar-nav navbar-right">
+ <li><a href="#about">About</a></li>
+ <li><a href="#clients">Clients</a></li>
+ <li><a href="#process">Process</a></li>
+ <li><a href="#testimonials">Testimonials</a></li>
+ <li><a href="#blogs">Blogs</a></li>
+ <li><a href="{{pathFor route='landingpage'}}">LandingPage</a></li>
+ <li><a href="#">{{> loginButtons}}</a></li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ <div class="banner">
+ <h2>Calipso - OpenStack Network Discovery N Assurance</h2>
+ <div class="info">
+ <a href="#process" title="">See our process</a>
+ <a href="#clients" title="">See our clients</a>
+ </div>
+ </div>
+ </header>
+ <section class="about" id="about">
+ <div class="container">
+ <div class="row">
+ <div class="col-xs-12">
+ <h2>About</h2>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-xs-12 col-sm-4">
+ <h4>Our history</h4>
+ <p class="visible-lg">We are going to enhance the way Cloud Network Administrators (CNA) and Tenant Network Administrators (TNA) Understands, Monitors and Troubleshoot highly distributed OpenStack Environments.</p>
+ </div>
+ <div class="col-xs-12 col-sm-4">
+ <h4>Our vision & Strategy</h4>
+ <p>Vision: Substantially Simplifying OpenStack networking operations: maintenance, troubleshooting and monitoring, with added-value analytics.</p>
+ <p>Strategy: Drive the adoption of a commercial, cisco-supported neutron networking assurance application.Create Operations application that dynamically discovers, display, monitors and analyze virtual networks in any OpenStack distribution with any Neutron plugin and dynamically analyze the information for assurance reporting, potentially a commercial offering (with limited open sourced functionality), to be sold to any OpenStack customers, admins and operators.</p>
+ <p class="visible-lg">Focus on multiple distributions and multiple plugins per distribution at High performance and scale.</p>
+ </div>
+ <div class="col-xs-12 col-sm-4">
+ <h4>Application Intent</h4>
+ <p>Provide CNA and TNA with support for: </p>
+ <p>1. Building virtual Network inventory and visualizing all low level details, inter-connections in real-time. </p>
+ <p>2. Monitor virtual network objects.</p>
+ <p class="visible-lg">3. Troubleshoot failures and analyzing the root cause of failures in virtual networks. </p>
+ <p class="visible-lg">4. Assess impact of failure in virtual networks.</p>
+
+ </div>
+ </div>
+ </div>
+ </section> <!-- end of about section-->
+ <hr>
+ <section class="clients" id="clients">
+ <div class="container">
+ <div class="row">
+ <div class="col-xs-12">
+ <h2>Our Clients</h2>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3">
+ <div class="clients-info">
+ <h3>Client1</h3>
+ </div>
+ </div>
+ <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3">
+ <div class="clients-info">
+ <h3>Client2</h3>
+ </div>
+ </div>
+ <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3">
+ <div class="clients-info">
+ <h3>Client3</h3>
+ </div>
+ </div>
+ <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3">
+ <div class="clients-info">
+ <h3>Client4</h3>
+ </div>
+ </div>
+ </div>
+ </div>
+ </section> <!-- end of clients section-->
+ <hr>
+ <section class="process" id="process">
+ <div class="container">
+ <div class="row">
+ <div class="col-xs-12">
+ <h2>Our Process</h2>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-xs-12 col-sm-6 col-md-3">
+ <div class="method">
+ <h3>Analyze</h3>
+ <hr>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
+ <h4>1</h4>
+ </div>
+ </div>
+ <div class="col-xs-12 col-sm-6 col-md-3">
+ <div class="method">
+ <h3>Develop</h3>
+ <hr>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
+ <h4>2</h4>
+ </div>
+ </div>
+ <div class="col-xs-12 col-sm-6 col-md-3">
+ <div class="method">
+ <h3>Test</h3>
+ <hr>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
+ <h4>3</h4>
+ </div>
+ </div>
+ <div class="col-xs-12 col-sm-6 col-md-3">
+ <div class="method">
+ <h3>Deploy</h3>
+ <hr>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
+ <h4>4</h4>
+ </div>
+ </div>
+ </div>
+ </div>
+ </section> <!-- end of method section-->
+ <hr>
+ <section class="testimonials" id="testimonials">
+ <div class="container">
+ <div class="row">
+ <div class="col-xs-12">
+ <h2>Our Testimonials</h2>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-xs-12 col-sm-4">
+ <blockquote>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore eligendi a officiis aspernatur nesciunt ducimus, placeat. Eum, ratione maxime fugiat deserunt doloremque laborum hic quisquam in, libero voluptatibus! Assumenda, eligendi.
+ </p>
+ </blockquote>
+ </div>
+ <div class="col-xs-12 col-sm-4">
+ <blockquote>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore eligendi a officiis aspernatur nesciunt ducimus, placeat. Eum, ratione maxime fugiat deserunt doloremque laborum hic quisquam in, libero voluptatibus! Assumenda, eligendi.
+ </p>
+ </blockquote>
+ </div>
+ <div class="col-xs-12 col-sm-4">
+ <blockquote>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore eligendi a officiis aspernatur nesciunt ducimus, placeat. Eum, ratione maxime fugiat deserunt doloremque laborum hic quisquam in, libero voluptatibus! Assumenda, eligendi.
+ </p>
+ </blockquote>
+ </div>
+ </div>
+ </div>
+ </section><!-- end of testimonials section-->
+ <hr>
+ <section class="blogs" id="blogs">
+ <div class="container">
+ <div class="row">
+ <div class="col-xs-12">
+ <h2>Latest Blogs</h2>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-xs-12 col-lg-8">
+ <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin pulvinar scelerisque aliquet. Aliquam eu ultrices nisl. Mauris sit amet odio tincidunt, congue magna a, malesuada odio. Maecenas egestas metus leo, nec vulputate odio pulvinar sodales. Aenean a nunc nisi. Donec eleifend sodales mauris, ut egestas tellus ultrices nec. Integer in nisl vitae libero lobortis egestas. Proin nisi lacus, iaculis sit amet nibh vel, fringilla pellentesque est.</p>
+ <p class="visible-lg">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut delectus ratione aut est, minus eius, dolorem blanditiis culpa ad iusto, accusamus iure alias nihil voluptatem soluta esse velit laudantium eum.</p>
+ <p class="visible-lg">Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur.</p>
+ <button type="button" class="btn btn-primary">Learn more</button>
+ </div>
+ <div class="visible-lg col-lg-4">
+ <img class="img-responsive" src="http://placehold.it/450x300" />
+ </div>
+ </div>
+ </div>
+ </section><!-- end of blogs section-->
+ <footer>
+ <p>Made by Ofir Ashery.</p>
+ </footer>
+</template>
diff --git a/ui/contributors.md b/ui/contributors.md
new file mode 100644
index 0000000..7453147
--- /dev/null
+++ b/ui/contributors.md
@@ -0,0 +1,6 @@
+meteor Calipso UI branch
+================
+
+contacts:
+* Ofir Ashery
+* Stas Isakov
diff --git a/ui/doc/tasks/refactor-tree-node-to-component.md b/ui/doc/tasks/refactor-tree-node-to-component.md
new file mode 100644
index 0000000..ccdb870
--- /dev/null
+++ b/ui/doc/tasks/refactor-tree-node-to-component.md
@@ -0,0 +1,13 @@
+# Task
+
+## Libraries
+
+### Added
+
+#### Meteor
+
+reactive-dict
+tmeasday:publish-counts
+
+#### Node
+bcrypt - nodejs
diff --git a/ui/imports/api/accounts/methods.js b/ui/imports/api/accounts/methods.js
new file mode 100644
index 0000000..4e1c40a
--- /dev/null
+++ b/ui/imports/api/accounts/methods.js
@@ -0,0 +1,196 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 { ValidatedMethod } from 'meteor/mdg:validated-method';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import * as R from 'ramda';
+import { Roles } from 'meteor/alanning:roles';
+import { Environments } from '/imports/api/environments/environments';
+
+let userSchema = new SimpleSchema({
+ _id: { type: String },
+ username: { type: String },
+ password: { type: String },
+ viewEnvs: { type: [ String ] },
+ editEnvs: { type: [ String ] },
+});
+
+export const insert = new ValidatedMethod({
+ name: 'accounts.insert',
+ validate: userSchema
+ .pick([
+ 'username',
+ 'password',
+ 'viewEnvs',
+ 'viewEnvs.$',
+ 'editEnvs',
+ 'editEnvs.$',
+ ]).validator({ clean: true, filter: false }),
+ run({
+ username,
+ password,
+ viewEnvs,
+ editEnvs,
+ }) {
+ if (! Roles.userIsInRole(Meteor.userId(), 'manage-users', Roles.GLOBAL_GROUP)) {
+ throw new Meteor.Error('unauthorized for removing users');
+ }
+
+ let userId = Accounts.createUser({
+ username: username,
+ password: password
+ });
+
+ addRole(viewEnvs, 'view-env', userId);
+ addRole(editEnvs, 'edit-env', userId);
+ }
+});
+
+
+
+export const update = new ValidatedMethod({
+ name: 'accounts.update',
+ validate: userSchema
+ .pick([
+ '_id',
+ // 'password',
+ 'viewEnvs',
+ 'viewEnvs.$',
+ 'editEnvs',
+ 'editEnvs.$',
+ ]).validator({ clean: true, filter: false }),
+ run({
+ _id,
+ //_password,
+ viewEnvs,
+ editEnvs,
+ }) {
+ console.log('accounts - methods - update - start');
+ //throw new Meteor.Error('unimplemented');
+ if (! Roles.userIsInRole(Meteor.userId(), 'manage-users', Roles.GLOBAL_GROUP)) {
+ throw new Meteor.Error('unauthorized for updating users');
+ }
+
+ /*
+ let item = Meteor.users.findOne({ _id: _id });
+ console.log('user for update: ', item);
+
+ item = R.merge(R.pick([
+ 'password',
+ ], item), {
+ password
+ });
+ */
+
+ /*
+ let item = {
+ //password
+ };
+
+ Meteor.users.update({ _id: _id }, { $set: item });
+ */
+
+ let currentViewEnvs = R.map((env) => {
+ return env.name;
+ }, Environments.find({ 'auth.view-env': { $in: [ _id ] }}).fetch());
+
+ let viewEnvsForDelete = R.difference(currentViewEnvs, viewEnvs);
+ let viewEnvsForAdd = R.difference(viewEnvs, currentViewEnvs);
+
+ removeRole(viewEnvsForDelete, 'view-env', _id);
+ addRole(viewEnvsForAdd, 'view-env', _id);
+
+ //
+
+ let currentEditEnvs = R.map((env) => {
+ return env.name;
+ }, Environments.find({ 'auth.edit-env': { $in: [ _id ] }}).fetch());
+
+ let editEnvsForDelete = R.difference(currentEditEnvs, editEnvs);
+ let editEnvsForAdd = R.difference(editEnvs, currentEditEnvs);
+
+ removeRole(editEnvsForDelete, 'edit-env', _id);
+ addRole(editEnvsForAdd, 'edit-env', _id);
+
+ console.log('accounts - methods - update - end');
+ }
+});
+
+export const remove = new ValidatedMethod({
+ name: 'accounts.remove',
+ validate: userSchema
+ .pick([
+ '_id',
+ ]).validator({ clean: true, filter: false }),
+ run({
+ _id
+ }) {
+ if (! Roles.userIsInRole(Meteor.userId(), 'manage-users', Roles.GLOBAL_GROUP)) {
+ throw new Meteor.Error('unauthorized for removing users');
+ }
+
+ let user = Meteor.users.findOne({ _id: _id });
+ console.log('user for remove: ', user);
+
+ Meteor.users.remove({ _id: _id });
+ }
+});
+
+function removeRole(rolesForRemoval, roleName, userId) {
+ R.forEach((envName) => {
+ let env = Environments.findOne({ name: envName });
+ let auth = env.auth;
+ if (R.isNil(auth)) { auth = { }; }
+ if (R.isNil(R.path([roleName], auth))) {
+ auth = R.assoc(roleName, [], auth);
+ }
+ auth = R.assoc(roleName, R.reject(R.equals(userId), auth[roleName]), auth);
+
+ updateEnv(auth, env);
+ //let newEnv = R.merge(env, { auth: auth });
+
+ }, rolesForRemoval);
+}
+
+function addRole(rolesForAdd, roleName, userId) {
+ R.forEach((envName) => {
+ let env = Environments.findOne({ name: envName });
+ let auth = env.auth;
+ if (R.isNil(auth)) { auth = { }; }
+ if (R.isNil(R.path([roleName], auth))) {
+ auth = R.assoc(roleName, [], auth);
+ }
+ auth = R.assoc(roleName, R.append(userId, auth[roleName]), auth);
+
+ updateEnv(auth, env);
+ //let newEnv = R.merge(env, { auth: auth });
+
+ }, rolesForAdd);
+}
+
+function updateEnv(auth, env) {
+ console.log('update env. set: ' + R.toString(auth));
+ try {
+ Environments.update(env._id, {
+ $set: {
+ auth: auth,
+ configuration: env.configuration,
+ //distribution: distribution,
+ //name: name,
+ type_drivers: env.type_drivers,
+ mechanism_drivers: env.mechanism_drivers,
+ listen: env.listen,
+ enable_monitoring: env.enable_monitoring,
+ }
+ });
+ } catch(e) {
+ console.error('error in update: ' + R.toString(e));
+ throw new Meteor.Error('enviornment update error',
+ `unable to update ACL for environment - ${env.name}. Please check envrironment info. ${e.message}`);
+ }
+}
diff --git a/ui/imports/api/accounts/server/publications.js b/ui/imports/api/accounts/server/publications.js
new file mode 100644
index 0000000..47718d3
--- /dev/null
+++ b/ui/imports/api/accounts/server/publications.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { Meteor } from 'meteor/meteor';
+//import * as R from 'ramda';
+//import { Environments } from '/imports/api/environments/environments';
+//import { Roles } from 'meteor/alanning:roles';
+
+Meteor.publish('users', function () {
+ console.log('server subscribtion to: users');
+ /*
+ let that = this;
+
+ let query = {};
+
+ if (! Roles.userIsInRole(that.userId, 'manage-users', 'default-group')) {
+ query = {
+ _id: that.userId
+ };
+ }
+ */
+
+ return Meteor.users.find({});
+});
diff --git a/ui/imports/api/attributes_for_hover_on_data/attributes_for_hover_on_data.js b/ui/imports/api/attributes_for_hover_on_data/attributes_for_hover_on_data.js
new file mode 100644
index 0000000..ec2f6cd
--- /dev/null
+++ b/ui/imports/api/attributes_for_hover_on_data/attributes_for_hover_on_data.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { Mongo } from 'meteor/mongo';
+
+export const NodeHoverAttr = new Mongo.Collection(
+ 'attributes_for_hover_on_data', { idGeneration: 'MONGO' });
diff --git a/ui/imports/api/attributes_for_hover_on_data/methods.js b/ui/imports/api/attributes_for_hover_on_data/methods.js
new file mode 100644
index 0000000..1eda375
--- /dev/null
+++ b/ui/imports/api/attributes_for_hover_on_data/methods.js
@@ -0,0 +1,8 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 /
+/////////////////////////////////////////////////////////////////////////////////////////
diff --git a/ui/imports/api/attributes_for_hover_on_data/server/publications.js b/ui/imports/api/attributes_for_hover_on_data/server/publications.js
new file mode 100644
index 0000000..bc42d58
--- /dev/null
+++ b/ui/imports/api/attributes_for_hover_on_data/server/publications.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 { Meteor } from 'meteor/meteor';
+
+import { NodeHoverAttr } from '../attributes_for_hover_on_data.js';
+
+Meteor.publish('attributes_for_hover_on_data', function () {
+ console.log('server subscribtion to: attributes_for_hover_on_data');
+ //return Inventory.find({$where: 'this.id_path.match('^/WebEX-Mirantis@Cisco/')'});
+ return NodeHoverAttr.find({});
+});
+
+Meteor.publish('attributes_for_hover_on_data?type', function (type) {
+ console.log('server subscribtion to: attributes_for_hover_on_data?type');
+ console.log('- type: ' + type);
+
+ //return Inventory.find({$where: 'this.id_path.match('^/WebEX-Mirantis@Cisco/')'});
+ return NodeHoverAttr.find({ 'type': type});
+});
diff --git a/ui/imports/api/clique-constraints/clique-constraints.js b/ui/imports/api/clique-constraints/clique-constraints.js
new file mode 100644
index 0000000..8641715
--- /dev/null
+++ b/ui/imports/api/clique-constraints/clique-constraints.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 { Mongo } from 'meteor/mongo';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import * as R from 'ramda';
+import { Constants } from '/imports/api/constants/constants';
+
+export const CliqueConstraints = new Mongo.Collection(
+ 'clique_constraints', { idGeneration: 'MONGO' });
+
+let schema = {
+ _id: { type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } } },
+
+ focal_point_type: {
+ type: String,
+ custom: function () {
+ let that = this;
+ let values = Constants.findOne({ name: 'object_types_for_links' }).data;
+
+ if (R.isNil(R.find(R.propEq('value', that.value), values))) {
+ return 'notAllowed';
+ }
+ }
+ },
+
+ constraints: {
+ type: [String],
+ minCount: 1,
+ custom: function () {
+ let that = this;
+ let objectTypes = Constants.findOne({ name: 'object_types_for_links' }).data;
+
+ let findResult = R.intersection(that.value, R.pluck('value', objectTypes));
+ if (findResult.length !== that.value.length) { return 'notAllowed'; }
+
+ return;
+ },
+ },
+};
+
+CliqueConstraints.schema = new SimpleSchema(schema);
+CliqueConstraints.attachSchema(CliqueConstraints.schema);
diff --git a/ui/imports/api/clique-constraints/methods.js b/ui/imports/api/clique-constraints/methods.js
new file mode 100644
index 0000000..c9ae997
--- /dev/null
+++ b/ui/imports/api/clique-constraints/methods.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 { ValidatedMethod } from 'meteor/mdg:validated-method';
+import * as R from 'ramda';
+import { Roles } from 'meteor/alanning:roles';
+
+import { CliqueConstraints } from './clique-constraints';
+
+export const insert = new ValidatedMethod({
+ name: 'clique_constraints.insert',
+ validate: CliqueConstraints.simpleSchema()
+ .pick([
+// 'environment',
+ 'focal_point_type',
+ 'constraints',
+ 'constraints.$',
+ ]).validator({ clean: true, filter: false }),
+ run({
+ // environment,
+ focal_point_type,
+ constraints,
+ }) {
+ if (! Roles.userIsInRole(Meteor.userId(), 'manage-clique-constraints', Roles.GLOBAL_GROUP)) {
+ throw new Meteor.Error('unauthorized for inserting clique constraints');
+ }
+
+ let cliqueConstraint = CliqueConstraints.schema.clean({});
+
+ cliqueConstraint = R.merge(cliqueConstraint, {
+ // environment,
+ focal_point_type,
+ constraints,
+ });
+
+ CliqueConstraints.insert(cliqueConstraint);
+ }
+});
+
+export const remove = new ValidatedMethod({
+ name: 'clique_constraints.remove',
+ validate: CliqueConstraints.simpleSchema()
+ .pick([
+ '_id',
+ ]).validator({ clean: true, filter: false }),
+ run({
+ _id
+ }) {
+ if (! Roles.userIsInRole(Meteor.userId(), 'manage-clique-constraints', Roles.DEFAULT_GROUP)) {
+ throw new Meteor.Error('unauthorized for removing clique constraints');
+ }
+
+ let cliqueConstraint = CliqueConstraints.findOne({ _id: _id });
+ console.log('clique constraint for remove: ', cliqueConstraint);
+
+ CliqueConstraints.remove({ _id: _id });
+ }
+});
+
+export const update = new ValidatedMethod({
+ name: 'clique_constraints.update',
+ validate: CliqueConstraints.simpleSchema()
+ .pick([
+ '_id',
+ 'focal_point_type',
+ 'constraints',
+ 'constraints.$',
+ ]).validator({ clean: true, filter: false }),
+ run({
+ _id,
+ focal_point_type,
+ constraints,
+ }) {
+
+ if (! Roles.userIsInRole(Meteor.userId(), 'manage-clique-constraints', Roles.DEFAULT_GROUP)) {
+ throw new Meteor.Error('unauthorized for removing clique constraints');
+ }
+
+ let item = CliqueConstraints.findOne({ _id: _id });
+ console.log('clique constraints for update: ', item);
+ console.log('current user', Meteor.userId());
+
+ item = R.merge(
+ R.pick([
+ 'focal_point_type',
+ 'constraints',
+ ], item), {
+ focal_point_type,
+ constraints,
+ });
+
+ CliqueConstraints.update({ _id: _id }, { $set: item });
+ }
+});
diff --git a/ui/imports/api/clique-constraints/server/publications.js b/ui/imports/api/clique-constraints/server/publications.js
new file mode 100644
index 0000000..6e4ae1a
--- /dev/null
+++ b/ui/imports/api/clique-constraints/server/publications.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 { Meteor } from 'meteor/meteor';
+
+import { CliqueConstraints } from '../clique-constraints.js';
+
+Meteor.publish('clique_constraints', function () {
+ console.log('server subscribtion: clique_constraints');
+
+ //let that = this;
+
+ let query = {};
+ return CliqueConstraints.find(query);
+});
+
+Meteor.publish('clique_constraints?_id', function (_id) {
+ console.log('server subscribtion: clique_constraints?_id');
+ console.log(_id);
+
+ //let that = this;
+
+ let query = { _id: _id };
+ return CliqueConstraints.find(query);
+});
diff --git a/ui/imports/api/clique-types/clique-types.js b/ui/imports/api/clique-types/clique-types.js
new file mode 100644
index 0000000..852c319
--- /dev/null
+++ b/ui/imports/api/clique-types/clique-types.js
@@ -0,0 +1,107 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import * as R from 'ramda';
+import { Constants } from '/imports/api/constants/constants';
+import { Environments } from '/imports/api/environments/environments';
+import { LinkTypes } from '/imports/api/link-types/link-types';
+
+export const CliqueTypes = new Mongo.Collection(
+ 'clique_types', { idGeneration: 'MONGO' });
+
+let schema = {
+ _id: { type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } } },
+
+ environment: {
+ type: String,
+ custom: function () {
+ let that = this;
+ let env = Environments.findOne({ name: that.value });
+
+ if (R.isNil(env)) {
+ return 'notAllowed';
+ }
+ }
+ },
+
+ focal_point_type: {
+ type: String,
+ custom: function () {
+ let that = this;
+ let values = Constants.findOne({ name: 'object_types_for_links' }).data;
+
+ if (R.isNil(R.find(R.propEq('value', that.value), values))) {
+ return 'notAllowed';
+ }
+ }
+ },
+
+ link_types: {
+ type: [String],
+ minCount: 1,
+ custom: function () {
+ let that = this;
+ let findResult = R.all(function (pLinkType) {
+ if (R.isNil(LinkTypes.findOne({ type: pLinkType }))) {
+ return false;
+ }
+
+ return true;
+ }, that.value);
+
+ if (! findResult) { return 'notAllowed'; }
+
+ return;
+ },
+ },
+
+ name: {
+ type: String
+ },
+};
+
+let simpleSchema = new SimpleSchema(schema);
+
+simpleSchema.addValidator(function () {
+ let that = this;
+
+ let existing = CliqueTypes.findOne({
+ environment: that.field('environment').value,
+ focal_point_type: that.field('focal_point_type').value
+ });
+
+ if (R.allPass([
+ R.pipe(R.isNil, R.not),
+ R.pipe(R.propEq('_id', that.docId), R.not)
+ ])(existing)) {
+
+ return 'alreadyExists';
+ }
+});
+
+simpleSchema.addValidator(function () {
+ let that = this;
+
+ let existing = CliqueTypes.findOne({
+ environment: that.field('environment').value,
+ name: that.field('name').value
+ });
+
+ if (R.allPass([
+ R.pipe(R.isNil, R.not),
+ R.pipe(R.propEq('_id', that.docId), R.not)
+ ])(existing)) {
+
+ return 'alreadyExists';
+ }
+});
+
+CliqueTypes.schema = simpleSchema;
+CliqueTypes.attachSchema(CliqueTypes.schema);
diff --git a/ui/imports/api/clique-types/methods.js b/ui/imports/api/clique-types/methods.js
new file mode 100644
index 0000000..a62c22f
--- /dev/null
+++ b/ui/imports/api/clique-types/methods.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { ValidatedMethod } from 'meteor/mdg:validated-method';
+import * as R from 'ramda';
+import { Roles } from 'meteor/alanning:roles';
+
+import { CliqueTypes } from './clique-types';
+
+export const insert = new ValidatedMethod({
+ name: 'clique_types.insert',
+ validate: CliqueTypes.simpleSchema()
+ .pick([
+ 'environment',
+ 'focal_point_type',
+ 'link_types',
+ 'link_types.$',
+ 'name',
+ ]).validator({ clean: true, filter: false }),
+ run({
+ environment,
+ focal_point_type,
+ link_types,
+ name,
+ }) {
+ if (! Roles.userIsInRole(Meteor.userId(), 'manage-clique-types', Roles.DEFAULT_GROUP)) {
+ throw new Meteor.Error('unauthorized for adding clique type');
+ }
+
+ let cliqueType = CliqueTypes.schema.clean({});
+
+ cliqueType = R.merge(cliqueType, {
+ environment,
+ focal_point_type,
+ link_types,
+ name,
+ });
+
+ CliqueTypes.insert(cliqueType);
+ }
+});
+
+export const remove = new ValidatedMethod({
+ name: 'clique_types.remove',
+ validate: CliqueTypes.simpleSchema()
+ .pick([
+ '_id',
+ ]).validator({ clean: true, filter: false }),
+ run({
+ _id
+ }) {
+
+ if (! Roles.userIsInRole(Meteor.userId(), 'manage-clique-types', Roles.DEFAULT_GROUP)) {
+ throw new Meteor.Error('unauthorized for removing clique type');
+ }
+
+ let cliqueType = CliqueTypes.findOne({ _id: _id });
+ console.log('clique type for remove: ', cliqueType);
+
+ CliqueTypes.remove({ _id: _id });
+ }
+});
+
+export const update = new ValidatedMethod({
+ name: 'clique_types.update',
+ validate: CliqueTypes.simpleSchema()
+ .pick([
+ '_id',
+ 'environment',
+ 'focal_point_type',
+ 'link_types',
+ 'link_types.$',
+ 'name',
+ ]).validator({ clean: true, filter: false }),
+ run({
+ _id,
+ environment,
+ focal_point_type,
+ link_types,
+ name,
+ }) {
+ if (! Roles.userIsInRole(Meteor.userId(), 'manage-clique-types', Roles.DEFAULT_GROUP)) {
+ throw new Meteor.Error('unauthorized for updating clique type');
+ }
+
+ let cliqueType = CliqueTypes.findOne({ _id: _id });
+ console.log('clique type for remove: ', cliqueType);
+
+ cliqueType = R.merge(R.pick([
+ 'environment',
+ 'focal_point_type',
+ 'link_types',
+ 'name', ],
+ cliqueType), {
+ environment,
+ focal_point_type,
+ link_types,
+ name,
+ });
+
+ CliqueTypes.update({ _id: _id }, { $set: cliqueType });
+ }
+});
diff --git a/ui/imports/api/clique-types/server/publications.js b/ui/imports/api/clique-types/server/publications.js
new file mode 100644
index 0000000..95274b9
--- /dev/null
+++ b/ui/imports/api/clique-types/server/publications.js
@@ -0,0 +1,34 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 * as R from 'ramda';
+
+import { CliqueTypes } from '../clique-types.js';
+
+Meteor.publish('clique_types?env*', function (env) {
+ console.log('server subscribtion: clique_types?env*');
+ console.log(env);
+
+ //let that = this;
+
+ let query = {};
+ if (! R.isNil(env)) { query = R.assoc('environment', env, query); }
+ console.log('-query: ', query);
+ return CliqueTypes.find(query);
+});
+
+Meteor.publish('clique_types?_id', function (_id) {
+ console.log('server subscribtion: clique_types?_id');
+ console.log(_id);
+
+ //let that = this;
+
+ let query = { _id: _id };
+ return CliqueTypes.find(query);
+});
diff --git a/ui/imports/api/cliques/cliques.js b/ui/imports/api/cliques/cliques.js
new file mode 100644
index 0000000..78fb7ad
--- /dev/null
+++ b/ui/imports/api/cliques/cliques.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { Mongo } from 'meteor/mongo';
+
+export const Cliques = new Mongo.Collection(
+ 'cliques', { idGeneration: 'MONGO' });
diff --git a/ui/imports/api/cliques/methods.js b/ui/imports/api/cliques/methods.js
new file mode 100644
index 0000000..1eda375
--- /dev/null
+++ b/ui/imports/api/cliques/methods.js
@@ -0,0 +1,8 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 /
+/////////////////////////////////////////////////////////////////////////////////////////
diff --git a/ui/imports/api/cliques/server/publications.js b/ui/imports/api/cliques/server/publications.js
new file mode 100644
index 0000000..16a4644
--- /dev/null
+++ b/ui/imports/api/cliques/server/publications.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 { Meteor } from 'meteor/meteor';
+
+import { Cliques } from '../cliques.js';
+
+Meteor.publish('cliques', function () {
+ console.log('server subscribtion to: cliques');
+ //return Inventory.find({$where: 'this.id_path.match('^/WebEX-Mirantis@Cisco/')'});
+ return Cliques.find({});
+});
+
+Meteor.publish('cliques?focal_point', function (objId) {
+ var query = {
+ focal_point: new Mongo.ObjectID(objId)
+ };
+/*
+ var counterName = 'inventory?env+type!counter?env=' + env + '&type=' + type;
+
+ console.log('server subscribing to counter: ' + counterName);
+ Counts.publish(this, counterName, Inventory.find(query));
+*/
+
+ console.log('server subscribtion to: cliques?focal_point');
+ console.log('- focal_point: ' + objId);
+ return Cliques.find(query);
+});
diff --git a/ui/imports/api/constants/constants.js b/ui/imports/api/constants/constants.js
new file mode 100644
index 0000000..b3f0407
--- /dev/null
+++ b/ui/imports/api/constants/constants.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { Mongo } from 'meteor/mongo';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+//import * as R from 'ramda';
+
+export const Constants = new Mongo.Collection('constants', { idGeneration: 'MONGO' });
+
+let schema = {
+ _id: { type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } } },
+ name: { type: String },
+ data: { type: [Object], blackbox: true },
+};
+
+Constants.schema = schema;
+Constants.attachSchema(schema);
diff --git a/ui/imports/api/constants/data/distributions.js b/ui/imports/api/constants/data/distributions.js
new file mode 100644
index 0000000..97ecdb4
--- /dev/null
+++ b/ui/imports/api/constants/data/distributions.js
@@ -0,0 +1,64 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+export const Distributions = [{
+ label: 'Mirantis-6.0',
+ value: 'Mirantis-6.0',
+}, {
+ label: 'Mirantis-7.0',
+ value: 'Mirantis-7.0',
+}, {
+ label: 'Mirantis-8.0',
+ value: 'Mirantis-8.0',
+}, {
+ label: 'Mirantis-9.0',
+ value: 'Mirantis-9.0',
+}, {
+ label: 'RDO-Mitaka',
+ value: 'RDO-Mitaka',
+}, {
+ label: 'RDO-Liberty',
+ value: 'RDO-Liberty',
+}, {
+ label: 'RDO-Juno',
+ value: 'RDO-Juno',
+}, {
+ label: 'RDO-kilo',
+ value: 'RDO-kilo',
+}, {
+ label: 'devstack-liberty',
+ value: 'devstack-liberty',
+}, {
+ label: 'Canonical-icehouse',
+ value: 'Canonical-icehouse',
+}, {
+ label: 'Canonical-juno',
+ value: 'Canonical-juno',
+}, {
+ label: 'Canonical-liberty',
+ value: 'Canonical-liberty',
+}, {
+ label: 'Canonical-mitaka',
+ value: 'Canonical-mitaka',
+}, {
+ label: 'Apex-Mitaka',
+ value: 'Apex-Mitaka',
+}, {
+ label: 'Devstack-Mitaka',
+ value: 'Devstack-Mitaka',
+}, {
+ label: 'packstack-7.0.0-0.10.dev1682',
+ value: 'packstack-7.0.0-0.10.dev1682',
+}, {
+ label: 'Stratoscale-v2.1.6',
+ value: 'Stratoscale-v2.1.6',
+}, {
+ label: 'Mirantis-9.1',
+ value: 'Mirantis-9.1',
+}
+];
diff --git a/ui/imports/api/constants/data/env-types.js b/ui/imports/api/constants/data/env-types.js
new file mode 100644
index 0000000..00b0aaf
--- /dev/null
+++ b/ui/imports/api/constants/data/env-types.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+export const EnvTypes = [{
+ label: 'Production',
+ value: 'production',
+}, {
+ label: 'Development',
+ value: 'development',
+}];
diff --git a/ui/imports/api/constants/data/environment-monitoring-types.js b/ui/imports/api/constants/data/environment-monitoring-types.js
new file mode 100644
index 0000000..e3a573a
--- /dev/null
+++ b/ui/imports/api/constants/data/environment-monitoring-types.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+export const EnvironmentMonitoringTypes = [{
+ label: 'Sensu',
+ value: 'Sensu',
+}];
diff --git a/ui/imports/api/constants/data/environment-provision-types.js b/ui/imports/api/constants/data/environment-provision-types.js
new file mode 100644
index 0000000..5139266
--- /dev/null
+++ b/ui/imports/api/constants/data/environment-provision-types.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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+export const EnvProvisionTypes = [{
+ label: 'None',
+ value: 'None',
+}, {
+ label: 'Deploy',
+ value: 'Deploy',
+}, {
+ label: 'Files',
+ value: 'Files',
+}, {
+ label: 'DB',
+ value: 'DB',
+}];
diff --git a/ui/imports/api/constants/data/log-levels.js b/ui/imports/api/constants/data/log-levels.js
new file mode 100644
index 0000000..dee6b6d
--- /dev/null
+++ b/ui/imports/api/constants/data/log-levels.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+export const LogLevels = [{
+ label: 'CRITICAL',
+ value: 'critical',
+}, {
+ label: 'ERROR',
+ value: 'error',
+}, {
+ label: 'WARNING',
+ value: 'warning',
+}, {
+ label: 'INFO',
+ value: 'info',
+}, {
+ label: 'DEBUG',
+ value: 'debug',
+}, {
+ label: 'NOTSET',
+ value: 'notset',
+}];
diff --git a/ui/imports/api/constants/data/mechanism-drivers.js b/ui/imports/api/constants/data/mechanism-drivers.js
new file mode 100644
index 0000000..afa8b01
--- /dev/null
+++ b/ui/imports/api/constants/data/mechanism-drivers.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+export const MechanismDrivers = [{
+ 'label' : 'ovs',
+ 'value' : 'ovs'
+}, {
+ 'label' : 'vpp',
+ 'value' : 'vpp'
+}, {
+ 'label' : 'lxb',
+ 'value' : 'lxb'
+}, {
+ 'label' : 'Arista',
+ 'value' : 'Arista'
+}, {
+ 'label' : 'Nexus',
+ 'value' : 'Nexus'
+}];
diff --git a/ui/imports/api/constants/data/message-source-systems.js b/ui/imports/api/constants/data/message-source-systems.js
new file mode 100644
index 0000000..77ec901
--- /dev/null
+++ b/ui/imports/api/constants/data/message-source-systems.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+export const MessageSourceSystems = [{
+ label: 'OpenStack',
+ value: 'OpenStack',
+}, {
+ label: 'OSDNA_Sensu',
+ value: 'OSDNA_Sensu',
+}];
diff --git a/ui/imports/api/constants/data/network-plugins.js b/ui/imports/api/constants/data/network-plugins.js
new file mode 100644
index 0000000..c89be26
--- /dev/null
+++ b/ui/imports/api/constants/data/network-plugins.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+export const NetworkPlugins = [{
+ label: 'OVS',
+ value: 'OVS',
+}, {
+ label: 'VPP',
+ value: 'VPP',
+}];
diff --git a/ui/imports/api/constants/data/object-types-for-links.js b/ui/imports/api/constants/data/object-types-for-links.js
new file mode 100644
index 0000000..35f1805
--- /dev/null
+++ b/ui/imports/api/constants/data/object-types-for-links.js
@@ -0,0 +1,39 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+export const ObjectTypesForLinks = [{
+ label: 'vnic',
+ value: 'vnic',
+}, {
+ label: 'vconnector',
+ value: 'vconnector',
+}, {
+ label: 'vedge',
+ value: 'vedge',
+}, {
+ label: 'instance',
+ value: 'instance',
+}, {
+ label: 'vservice',
+ value: 'vservice',
+}, {
+ label: 'pnic',
+ value: 'pnic',
+}, {
+ label: 'network',
+ value: 'network',
+}, {
+ label: 'port',
+ value: 'port',
+}, {
+ label: 'otep',
+ value: 'otep',
+}, {
+ label: 'agent',
+ value: 'agent',
+}];
diff --git a/ui/imports/api/constants/data/scans-statuses.js b/ui/imports/api/constants/data/scans-statuses.js
new file mode 100644
index 0000000..778f256
--- /dev/null
+++ b/ui/imports/api/constants/data/scans-statuses.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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+export const Statuses = [{
+ value: 'draft',
+ label: 'Draft',
+}, {
+ value: 'pending',
+ label: 'Pending',
+}, {
+ value: 'running',
+ label: 'Running',
+}, {
+ value: 'completed',
+ label: 'Completed',
+}, {
+ value: 'failed',
+ label: 'Failed',
+}, {
+ value: 'aborted',
+ label: 'Aborted',
+}
+];
+
+export const StatusesInOperation = ['pending', 'running'];
diff --git a/ui/imports/api/constants/data/type-drivers.js b/ui/imports/api/constants/data/type-drivers.js
new file mode 100644
index 0000000..efc7f7d
--- /dev/null
+++ b/ui/imports/api/constants/data/type-drivers.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+export const TypeDrivers = [{
+ 'label' : 'local',
+ 'value' : 'local'
+}, {
+ 'label' : 'vlan',
+ 'value' : 'vlan'
+}, {
+ 'label' : 'vxlan',
+ 'value' : 'vxlan'
+}, {
+ 'label' : 'gre',
+ 'value' : 'gre'
+}, {
+ 'label' : 'flat',
+ 'value' : 'flat'
+}];
diff --git a/ui/imports/api/constants/server/publications.js b/ui/imports/api/constants/server/publications.js
new file mode 100644
index 0000000..3ace17f
--- /dev/null
+++ b/ui/imports/api/constants/server/publications.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { Meteor } from 'meteor/meteor';
+
+import { Constants } from '../constants.js';
+
+Meteor.publish('constants', function () {
+ console.log('server subscribtion to: constants');
+ return Constants.find({});
+});
diff --git a/ui/imports/api/environments/configuration-groups/aci-configuration.js b/ui/imports/api/environments/configuration-groups/aci-configuration.js
new file mode 100644
index 0000000..10b749e
--- /dev/null
+++ b/ui/imports/api/environments/configuration-groups/aci-configuration.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+
+export const AciSchema = new SimpleSchema({
+ name: {
+ type: String,
+ autoValue: function () { return 'ACI'; }
+ },
+ host: {
+ type: String,
+ regEx: SimpleSchema.RegEx.IP,
+ defaultValue: '10.56.0.104',
+ },
+ user: {
+ type: String,
+ defaultValue: 'admin'
+ },
+ pwd: {
+ type: String,
+ defaultValue: 'C1sco12345'
+ },
+});
diff --git a/ui/imports/api/environments/configuration-groups/amqp-configuration.js b/ui/imports/api/environments/configuration-groups/amqp-configuration.js
new file mode 100644
index 0000000..83a15cf
--- /dev/null
+++ b/ui/imports/api/environments/configuration-groups/amqp-configuration.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { portRegEx } from '/imports/lib/general-regex';
+
+export const AMQPSchema = new SimpleSchema({
+ name: { type: String, autoValue: function () { return 'AMQP'; } },
+ host: {
+ type: String,
+ regEx: SimpleSchema.RegEx.IP,
+ defaultValue: '10.0.0.1',
+ },
+ port: {
+ type: String,
+ regEx: portRegEx,
+ defaultValue: '5673',
+ },
+ user: {
+ type: String,
+ defaultValue: 'rabbitmquser'
+ },
+ password: { type: String },
+});
diff --git a/ui/imports/api/environments/configuration-groups/cli-configuration.js b/ui/imports/api/environments/configuration-groups/cli-configuration.js
new file mode 100644
index 0000000..c651359
--- /dev/null
+++ b/ui/imports/api/environments/configuration-groups/cli-configuration.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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import * as R from 'ramda';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { pathRegEx } from '/imports/lib/general-regex';
+
+export const CLISchema = new SimpleSchema({
+ name: { type: String, autoValue: function () { return 'CLI'; } },
+ host: {
+ type: String,
+ defaultValue: '10.0.0.1'
+ },
+ key: {
+ type: String,
+ regEx: pathRegEx,
+ optional: true
+ },
+ user: {
+ type: String,
+ defaultValue: 'sshuser'
+ },
+ pwd: {
+ type: String,
+ optional: true
+ },
+});
+
+CLISchema.addValidator(function () {
+ let that = this;
+
+ let conf = {};
+ if (isConfEmpty(conf)) {
+ return;
+ }
+
+ let validationResult = R.find((validationFn) => {
+ return validationFn(that).isError;
+ }, [ keyPasswordValidation ]);
+
+ if (R.isNil(validationResult)) { return; }
+
+ throw validationResult(that);
+});
+
+function keyPasswordValidation(schemaItem) {
+ let password = schemaItem.field('pwd');
+ let key = schemaItem.field('key');
+
+ if (key.value || password.value) { return { isError: false }; }
+
+ return {
+ isError: true,
+ type: 'subGroupError',
+ data: [],
+ message: 'Master Host Group: At least one required: key or password'
+ };
+}
+
+function isConfEmpty(conf) {
+ return R.find((key) => {
+ return !(R.isNil(conf[key]));
+ }, R.keys(conf));
+}
diff --git a/ui/imports/api/environments/configuration-groups/monitoring-configuration.js b/ui/imports/api/environments/configuration-groups/monitoring-configuration.js
new file mode 100644
index 0000000..2b27f8a
--- /dev/null
+++ b/ui/imports/api/environments/configuration-groups/monitoring-configuration.js
@@ -0,0 +1,119 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import * as R from 'ramda';
+import { Constants } from '/imports/api/constants/constants';
+import { portRegEx } from '/imports/lib/general-regex';
+import { hostnameRegex } from '/imports/lib/general-regex';
+import { ipAddressRegex } from '/imports/lib/general-regex';
+import { pathRegEx } from '/imports/lib/general-regex';
+
+export const MonitoringSchema = new SimpleSchema({
+ name: { type: String, autoValue: function () { return 'Monitoring'; } },
+ //app_path: { type: String, autoValue: function () { return '/etc/calipso/monitoring'; } },
+
+ config_folder: {
+ type: String,
+ defaultValue: '/local_dir/sensu_config',
+ regEx: pathRegEx,
+ },
+
+ env_type: {
+ type: String,
+ defaultValue: 'production',
+ custom: function () {
+ let that = this;
+ let EnvTypesRec = Constants.findOne({ name: 'env_types' });
+
+ if (R.isNil(EnvTypesRec.data)) { return 'notAllowed'; }
+ let EnvTypes = EnvTypesRec.data;
+
+ if (R.isNil(R.find(R.propEq('value', that.value), EnvTypes))) {
+ return 'notAllowed';
+ }
+ },
+ },
+
+ rabbitmq_port: {
+ type: String,
+ defaultValue: '5671',
+ regEx: portRegEx,
+ },
+
+ rabbitmq_user: {
+ type: String,
+ defaultValue: 'sensu'
+ },
+
+ rabbitmq_pass: { type: String },
+
+ server_ip: {
+ type: String,
+ regEx: new RegExp(hostnameRegex.source + '|' + ipAddressRegex.soure),
+ defaultValue: '10.0.0.1',
+ },
+
+ server_name: {
+ type: String,
+ defaultValue: 'sensu_server',
+ },
+
+ type: {
+ type: String,
+ defaultValue: 'Sensu',
+ custom: function () {
+ let that = this;
+ let values = Constants.findOne({ name: 'environment_monitoring_types' }).data;
+
+ if (R.isNil(values)) { return 'notAllowed'; }
+
+ if (R.isNil(R.find(R.propEq('value', that.value), values))) {
+ return 'notAllowed';
+ }
+ },
+ },
+
+ provision: {
+ type: String,
+ defaultValue: 'None',
+ custom: function () {
+ let that = this;
+ let values = Constants.findOne({ name: 'environment_provision_types' }).data;
+
+ if (R.isNil(values)) { return 'notAllowed'; }
+
+ if (R.isNil(R.find(R.propEq('value', that.value), values))) {
+ return 'notAllowed';
+ }
+ },
+ },
+
+ ssh_port: {
+ type: String,
+ defaultValue: '20022',
+ optional: true
+ },
+
+ ssh_user: {
+ type: String,
+ defaultValue: 'root',
+ optional: true
+ },
+
+ ssh_password: {
+ type: String,
+ defaultValue: 'calipso',
+ optional: true
+ },
+
+ api_port: {
+ type: Number,
+ defaultValue: 4567,
+ },
+});
diff --git a/ui/imports/api/environments/configuration-groups/mysql-configuration.js b/ui/imports/api/environments/configuration-groups/mysql-configuration.js
new file mode 100644
index 0000000..1921432
--- /dev/null
+++ b/ui/imports/api/environments/configuration-groups/mysql-configuration.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 { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { portRegEx } from '/imports/lib/general-regex';
+
+export const MysqlSchema = new SimpleSchema({
+ name: {
+ type: String,
+ autoValue: function () { return 'mysql'; }
+ },
+ host: {
+ type: String,
+ regEx: SimpleSchema.RegEx.IP,
+ defaultValue: '10.0.0.1'
+ },
+ password: { type: String },
+ port: {
+ type: String,
+ regEx: portRegEx,
+ defaultValue: '3307'
+ },
+ user: {
+ type: String,
+ min: 3,
+ defaultValue: 'mysqluser'
+ },
+});
diff --git a/ui/imports/api/environments/configuration-groups/nfv-provider-configuration.js b/ui/imports/api/environments/configuration-groups/nfv-provider-configuration.js
new file mode 100644
index 0000000..3638e3b
--- /dev/null
+++ b/ui/imports/api/environments/configuration-groups/nfv-provider-configuration.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 { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { portRegEx } from '/imports/lib/general-regex';
+
+export const NfvProviderSchema = new SimpleSchema({
+ name: { type: String, autoValue: function () { return 'NFV_provider'; } },
+ host: {
+ type: String,
+ regEx: SimpleSchema.RegEx.IP,
+ },
+ nfv_token: { type: String },
+ port: {
+ type: String,
+ regEx: portRegEx
+ },
+ user: { type: String },
+ pwd: { type: String },
+});
diff --git a/ui/imports/api/environments/configuration-groups/open-stack-configuration.js b/ui/imports/api/environments/configuration-groups/open-stack-configuration.js
new file mode 100644
index 0000000..a0d710f
--- /dev/null
+++ b/ui/imports/api/environments/configuration-groups/open-stack-configuration.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 { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { portRegEx } from '/imports/lib/general-regex';
+
+export const OpenStackSchema = new SimpleSchema({
+ name: { type: String, autoValue: function () { return 'OpenStack'; } },
+ host: {
+ type: String,
+ regEx: SimpleSchema.RegEx.IP,
+ defaultValue: '10.0.0.1',
+ },
+ admin_token: { type: String },
+ port: {
+ type: String,
+ regEx: portRegEx,
+ defaultValue: '5000',
+ },
+ user: {
+ type: String,
+ defaultValue: 'adminuser'
+ },
+ pwd: { type: String },
+});
diff --git a/ui/imports/api/environments/environments.js b/ui/imports/api/environments/environments.js
new file mode 100644
index 0000000..d616960
--- /dev/null
+++ b/ui/imports/api/environments/environments.js
@@ -0,0 +1,457 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import * as R from 'ramda';
+import { Constants } from '/imports/api/constants/constants';
+import { MysqlSchema } from './configuration-groups/mysql-configuration';
+import { OpenStackSchema } from './configuration-groups/open-stack-configuration';
+import { MonitoringSchema } from './configuration-groups/monitoring-configuration';
+import { CLISchema } from './configuration-groups/cli-configuration';
+import { AMQPSchema } from './configuration-groups/amqp-configuration';
+//import { NfvProviderSchema } from './configuration-groups/nfv-provider-configuration';
+import { AciSchema } from './configuration-groups/aci-configuration';
+import {
+ isMonitoringSupported,
+ isListeningSupported,
+} from '/imports/api/supported_environments/supported_environments';
+
+export const Environments = new Mongo.Collection(
+ 'environments_config', { idGeneration: 'MONGO' });
+
+export const requiredConfGroups = [
+ 'mysql',
+ 'OpenStack',
+ 'CLI',
+];
+
+export const optionalConfGroups = [
+ // 'NFV_provider',
+ 'AMQP',
+ 'Monitoring',
+ 'ACI',
+];
+
+let simpleSchema = new SimpleSchema({
+ _id: { type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } } },
+ auth: {
+ type: Object,
+ blackbox: true,
+ defaultValue: {
+ 'view-env': [
+ ],
+ 'edit-env': [
+ ]
+ }
+ },
+ configuration: {
+ type: [Object],
+ blackbox: true,
+ autoValue: function () {
+ console.log('start - autovalue - environment - configuration');
+ //console.log(this);
+ let that = this;
+
+ if (that.isSet) {
+ let confGroups = that.value;
+
+ let {
+ isMonitoringSupportedRes,
+ isListeningSupportedRes,
+ enable_monitoring,
+ listen
+ } = extractCalcEnvSupportedRelatedValues(that);
+ let dbNode = getDbNode(that);
+ let aci = extractValue('aci', that, dbNode);
+
+ if (enable_monitoring && isMonitoringSupportedRes) {
+ if (! R.find(R.propEq('name', 'Monitoring'), confGroups)) {
+ confGroups = R.append(createNewConfGroup('Monitoring'), confGroups);
+ }
+ } else {
+ console.log('env - configurations - autovalue - monitoring not supported');
+ confGroups = R.reject(R.propEq('name', 'Monitoring'), confGroups);
+ }
+
+ if (listen && isListeningSupportedRes) {
+ if (! R.find(R.propEq('name', 'AMQP'), confGroups)) {
+ confGroups = R.append(createNewConfGroup('AMQP'), confGroups);
+ }
+ } else {
+ console.log('env - configurations - autovalue - listening not supported');
+ confGroups = R.reject(R.propEq('name', 'AMQP'), confGroups);
+ }
+
+ if (aci) {
+ if (! R.find(R.propEq('name', 'ACI'), confGroups)) {
+ confGroups = R.append(createNewConfGroup('ACI'), confGroups);
+ }
+ } else {
+ console.log('env - configurations - autovalue - aci not requested');
+ confGroups = R.reject(R.propEq('name', 'ACI'), confGroups);
+ }
+
+ confGroups = cleanOptionalGroups(confGroups, optionalConfGroups);
+ console.log('env - configurations - autovalue - after clean optional groups');
+
+ let newValue = R.map(function(confGroup) {
+ let schema = getSchemaForGroupName(confGroup.name);
+ return schema.clean(confGroup);
+ }, confGroups);
+
+ console.log('end - autovalue - environment - configurations');
+ console.log(newValue);
+ return newValue;
+
+ } else {
+ console.log('env - configurations - autovalue - is not set');
+ let newValue = R.map((confName) => {
+ let schema = getSchemaForGroupName(confName);
+ return schema.clean({});
+ }, requiredConfGroups);
+ console.log('end - autovalue - environment - configurations');
+ console.log(newValue);
+ return newValue;
+ }
+ },
+ custom: function () {
+ console.log('start - custom - environment - configurations');
+ //console.log(this);
+ let that = this;
+ let configurationGroups = that.value;
+
+ let subErrors = [];
+
+ let {
+ isMonitoringSupportedRes,
+ isListeningSupportedRes,
+ enable_monitoring,
+ listen
+ } = extractCalcEnvSupportedRelatedValues(that);
+
+ let requiredConfGroupsTemp = R.clone(requiredConfGroups);
+ if (enable_monitoring && isMonitoringSupportedRes) {
+ requiredConfGroupsTemp = R.append('Monitoring', requiredConfGroupsTemp);
+ }
+ if (listen && isListeningSupportedRes) {
+ requiredConfGroupsTemp = R.append('AMQP', requiredConfGroupsTemp);
+ }
+
+ console.log('env - configurations - custom - after mon & listen check');
+
+ let invalidResult = R.find(function(groupName) {
+ subErrors = checkGroup(groupName, configurationGroups, true);
+ if (subErrors.length > 0) { return true; }
+ return false;
+ }, requiredConfGroupsTemp);
+
+ console.log(`env - configurations - custom - after require groups check`);
+
+ if (R.isNil(invalidResult)) {
+ invalidResult = R.find(function(groupName) {
+ subErrors = checkGroup(groupName, configurationGroups, false);
+ if (subErrors.length > 0) { return true; }
+ return false;
+ }, optionalConfGroups);
+ }
+
+ console.log(`env - configurations - custom - after optional groups check`);
+
+ if (! R.isNil(invalidResult)) {
+ console.log(`env - configrations - custom - invalid result end: ${R.toString(subErrors)}`);
+ throw {
+ isError: true,
+ type: 'subGroupError',
+ data: subErrors,
+ message: constructSubGroupErrorMessage(subErrors)
+ };
+ }
+ },
+
+ },
+ user: {
+ type: String,
+ },
+ distribution: {
+ type: String,
+ defaultValue: 'Mirantis-8.0',
+ custom: function () {
+ let that = this;
+ let constsDist = Constants.findOne({ name: 'distributions' });
+
+ if (R.isNil(constsDist.data)) { return 'notAllowed'; }
+ let distributions = constsDist.data;
+
+ if (R.isNil(R.find(R.propEq('value', that.value), distributions))) {
+ return 'notAllowed';
+ }
+ },
+ },
+ last_scanned: {
+ type: String, defaultValue: ''
+ },
+ name: {
+ type: String,
+ defaultValue: 'MyEnvironmentName',
+ min: 6,
+ },
+ type_drivers: {
+ type: String,
+ defaultValue: 'gre',
+ custom: function () {
+ let that = this;
+ let TypeDriversRec = Constants.findOne({ name: 'type_drivers' });
+
+ if (R.isNil(TypeDriversRec.data)) { return 'notAllowed'; }
+ let TypeDrivers = TypeDriversRec.data;
+
+ if (R.isNil(R.find(R.propEq('value', that.value), TypeDrivers))) {
+ return 'notAllowed';
+ }
+ },
+ },
+
+ mechanism_drivers: {
+ type: [String],
+ defaultValue: ['ovs'],
+ minCount: 1,
+ custom: function () {
+ let that = this;
+ let consts = Constants.findOne({ name: 'mechanism_drivers' });
+
+ if (R.isNil(consts.data)) { return 'notAllowed'; }
+ let mechanismDrivers = consts.data;
+
+ let result = R.find((driver) => {
+ if (R.find(R.propEq('value', driver), mechanismDrivers)) {
+ return false;
+ }
+ return true;
+ }, that.value);
+
+ if (result) { return 'notAllowed'; }
+
+ },
+ },
+
+ operational: {
+ type: String,
+ allowedValues: ['stopped', 'running', 'error'],
+ defaultValue: 'stopped'
+ },
+
+ scanned: { type: Boolean, defaultValue: false },
+
+ type: {
+ type: String,
+ autoValue: function () {
+ return 'environment';
+ },
+ },
+
+ app_path: {
+ type: String,
+ autoValue: function () {
+ return '/home/scan/calipso_prod/app';
+ }
+ },
+
+ listen: {
+ type: Boolean,
+ autoValue: function () {
+ console.log('env - listen - autoValue - start');
+ let that = this;
+ let newValue = that.value;
+ console.log(`- current value: ${R.toString(newValue)}`);
+
+ let { isListeningSupportedRes } = extractCalcEnvSupportedRelatedValues(that);
+
+ if (!isListeningSupportedRes) {
+ console.log('* listening not supported');
+ console.log(`* ${R.toString(isListeningSupportedRes)}`);
+ newValue = false;
+ }
+
+ return newValue;
+ },
+ },
+
+ enable_monitoring: {
+ type: Boolean,
+ autoValue: function () {
+ console.log('env - enable_monitoring - autoValue - start');
+ let that = this;
+ let newValue = that.value;
+ console.log(`- current value: ${R.toString(newValue)}`);
+
+ let { isMonitoringSupportedRes } = extractCalcEnvSupportedRelatedValues(that);
+
+ if (!isMonitoringSupportedRes) {
+ console.log('* monitoring not supported');
+ console.log(`* ${R.toString(isMonitoringSupportedRes)}`);
+ newValue = false;
+ }
+
+ return newValue;
+ },
+ },
+ aci: {
+ type: Boolean,
+ defaultValue: false,
+ },
+});
+
+/*
+simpleSchema.addValidator(function () {
+ //let that = this;
+});
+*/
+
+// Bug in simple schema. cant add custom message to instance specific
+// schema.
+// https://github.com/aldeed/meteor-simple-schema/issues/559
+// Version 2 fixes it but it is rc.
+//Environments.schema.messages({
+SimpleSchema.messages({
+ confGroupInvalid: 'Configuration group is invalid.'
+});
+
+Environments.schema = simpleSchema;
+Environments.attachSchema(Environments.schema);
+
+function getSchemaForGroupName(groupName) {
+ switch (groupName) {
+ case 'mysql':
+ return MysqlSchema;
+ case 'OpenStack':
+ return OpenStackSchema;
+ case 'CLI':
+ return CLISchema;
+ case 'AMQP':
+ return AMQPSchema;
+// case 'NFV_provider':
+// return NfvProviderSchema;
+ case 'ACI':
+ return AciSchema;
+ case 'Monitoring':
+ return MonitoringSchema;
+ default:
+ throw 'group name is not recognized. group: ' + groupName;
+ }
+}
+
+function constructSubGroupErrorMessage(errors) {
+ let message = 'Validation errors on sub groups:';
+ message = message + R.reduce((acc, item) => {
+ return acc + '\n- ' + item.group + ': ' + item.message;
+ }, '', errors);
+
+ return message;
+}
+
+function checkGroup(groupName, configurationGroups, groupRequired) {
+ let subErrors = [];
+ let confGroup = R.find(R.propEq('name', groupName), configurationGroups);
+
+ if (R.isNil(confGroup)) {
+ if (groupRequired) {
+ subErrors = R.append({
+ field: 'configuration',
+ group: groupName,
+ message: 'group ' + groupName + ' is required'
+ }, subErrors);
+ }
+ return subErrors;
+ }
+
+ let validationContext = getSchemaForGroupName(groupName).newContext();
+
+ if (! validationContext.validate(confGroup)) {
+ subErrors = R.reduce(function (acc, invalidField) {
+ return R.append({
+ field: invalidField,
+ group: groupName,
+ message: validationContext.keyErrorMessage(invalidField.name),
+ }, acc);
+ }, [], validationContext.invalidKeys());
+
+ return subErrors;
+ }
+
+ return subErrors;
+}
+
+export function createNewConfGroup(groupName) {
+ let schema = getSchemaForGroupName(groupName);
+ return schema.clean({});
+}
+
+function cleanOptionalGroups(confGroups, optionalConfGroups) {
+ return R.filter((conf) => {
+ if (R.contains(conf.name, optionalConfGroups)) {
+ return !isConfEmpty(conf);
+ }
+
+ return true;
+ }, confGroups);
+}
+
+function isConfEmpty(conf) {
+ return ! R.any((key) => {
+ if (key === 'name') { return false; } // We ignore the key 'name'. It is a 'type' key.
+ let val = conf[key];
+ return ! ( R.isNil(val) || R.isEmpty(val));
+ })(R.keys(conf));
+}
+
+function extractValue(name, schemaValidator, dbNode) {
+ console.log('env - extract value');
+ console.log(`-name: ${R.toString(name)}`);
+ //console.log(`-schemaValidator: ${R.toString(schemaValidator)}`);
+ console.log(`-dbNode: ${R.toString(dbNode)}`);
+
+ let field = schemaValidator.field(name);
+ let value = field.value;
+
+ console.log(`extract value - schema value: ${R.toString(value)}`);
+
+ if (R.isNil(field.value) && !field.isSet && dbNode) {
+ console.log(`extract value - db value: ${R.toString(dbNode[name])}`);
+ value = dbNode[name];
+ }
+
+ console.log(`extract value - result: ${R.toString(value)}`);
+ return value;
+}
+
+function getDbNode(schemaHelper) {
+ let _id = R.defaultTo(schemaHelper.docId, R.path(['value'], schemaHelper.field('_id')));
+ let dbNode = R.defaultTo(null, Environments.findOne({ _id: _id }));
+ return dbNode;
+}
+
+function extractCalcEnvSupportedRelatedValues(schemaHelper) {
+ let dbNode = getDbNode(schemaHelper);
+
+ let dist = extractValue('distribution', schemaHelper, dbNode);
+ let typeDrivers = extractValue('type_drivers', schemaHelper, dbNode);
+ let mechDrivers = extractValue('mechanism_drivers', schemaHelper, dbNode);
+ let enable_monitoring = extractValue('enable_monitoring', schemaHelper, dbNode);
+ let listen = extractValue('listen', schemaHelper, dbNode);
+
+ let isMonitoringSupportedRes = isMonitoringSupported(dist, typeDrivers, mechDrivers);
+ let isListeningSupportedRes = isListeningSupported(dist, typeDrivers, mechDrivers);
+
+ return {
+ enable_monitoring,
+ listen,
+ isMonitoringSupportedRes,
+ isListeningSupportedRes,
+ };
+}
diff --git a/ui/imports/api/environments/methods.js b/ui/imports/api/environments/methods.js
new file mode 100644
index 0000000..22a1e8b
--- /dev/null
+++ b/ui/imports/api/environments/methods.js
@@ -0,0 +1,154 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 * as R from 'ramda';
+import { ValidatedMethod } from 'meteor/mdg:validated-method';
+
+//import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+
+import { Environments } from './environments';
+import { Inventory } from '/imports/api/inventories/inventories';
+import { Links } from '/imports/api/links/links';
+import { Cliques } from '/imports/api/cliques/cliques';
+import { CliqueTypes } from '/imports/api/clique-types/clique-types';
+import { Messages } from '/imports/api/messages/messages';
+import { Scans } from '/imports/api/scans/scans';
+import { Roles } from 'meteor/alanning:roles';
+
+export const insert = new ValidatedMethod({
+ name: 'environments.insert',
+ validate: Environments.simpleSchema()
+ .pick([
+ 'configuration',
+ 'configuration.$',
+ 'distribution',
+ 'name',
+ 'type_drivers',
+ 'mechanism_drivers',
+ 'mechanism_drivers.$',
+ 'listen',
+ 'enable_monitoring',
+ 'aci',
+ ]).validator({ clean: true, filter: false }),
+ //validate: null,
+ run({
+ configuration,
+ distribution,
+ name,
+ type_drivers,
+ mechanism_drivers,
+ listen,
+ enable_monitoring,
+ aci,
+ }) {
+ // todo: create clean object instance.
+ let environment = Environments.schema.clean({
+ user: Meteor.userId()
+ });
+
+ let auth = {
+ 'view-env': [
+ Meteor.userId()
+ ],
+ 'edit-env': [
+ Meteor.userId()
+ ]
+ };
+
+ environment = R.merge(environment, {
+ configuration,
+ distribution,
+ name,
+ type_drivers,
+ mechanism_drivers,
+ listen,
+ enable_monitoring,
+ auth,
+ aci,
+ });
+
+ Environments.insert(environment);
+ },
+});
+
+export const update = new ValidatedMethod({
+ name: 'environments.update',
+ validate: Environments.simpleSchema().pick([
+ '_id',
+ 'configuration',
+ 'configuration.$',
+ //'distribution',
+ //'name',
+ 'type_drivers',
+ 'mechanism_drivers',
+ 'mechanism_drivers.$',
+ 'listen',
+ 'enable_monitoring',
+ 'aci',
+ ]).validator({ clean: true, filter: false }),
+ run({
+ _id,
+ configuration,
+ //distribution,
+ //name,
+ type_drivers,
+ mechanism_drivers,
+ listen,
+ enable_monitoring,
+ aci,
+ }) {
+ let env = Environments.findOne({ _id: _id });
+
+ if (! Roles.userIsInRole(Meteor.userId(), 'edit-env', 'default-group')) {
+ if (! R.contains(Meteor.userId(), R.path(['auth', 'edit-env'], env) )) {
+ throw new Meteor.Error('not-auth', 'unauthorized for updating env');
+ }
+ }
+
+ Environments.update(_id, {
+ $set: {
+ configuration: configuration,
+ //distribution: distribution,
+ //name: name,
+ type_drivers,
+ mechanism_drivers,
+ listen,
+ enable_monitoring,
+ aci,
+ },
+ });
+ }
+});
+
+export const remove = new ValidatedMethod({
+ name: 'environments.remove',
+ validate: Environments.simpleSchema().pick([
+ '_id',
+ ]).validator({ clean: true, filter: false }),
+ run({
+ _id,
+ }) {
+ const env = Environments.findOne({ _id: _id });
+ console.log('environment for remove: ', env);
+
+ if (! Roles.userIsInRole(Meteor.userId(), 'edit-env', 'default-group')) {
+ if (! R.contains(Meteor.userId(), R.path(['auth', 'edit-env'], env) )) {
+ throw new Meteor.Error('not-auth', 'unauthorized for updating env');
+ }
+ }
+
+ Inventory.remove({ environment: env.name });
+ Links.remove({ environment: env.name });
+ Cliques.remove({ environment: env.name });
+ CliqueTypes.remove({ environment: env.name });
+ Messages.remove({ environment: env.name });
+ Scans.remove({ environment: env.name });
+ Environments.remove({ _id: _id });
+ }
+});
diff --git a/ui/imports/api/environments/server/publications.js b/ui/imports/api/environments/server/publications.js
new file mode 100644
index 0000000..667ee8e
--- /dev/null
+++ b/ui/imports/api/environments/server/publications.js
@@ -0,0 +1,102 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 * as R from 'ramda';
+import { Roles } from 'meteor/alanning:roles';
+
+import { Environments } from '../environments.js';
+
+Meteor.publish('environments_config', function () {
+ console.log('server subscribtion to: environments_config');
+ let userId = this.userId;
+
+ let query = {
+ type: 'environment',
+ };
+
+ if (! Roles.userIsInRole(userId, 'view-env', null)) {
+ query = R.merge(query, {
+ 'auth.view-env': {
+ $in: [ userId ]
+ }
+ });
+ }
+
+ console.log('-query: ', R.toString(query));
+ return Environments.find(query);
+});
+
+const subsEnvViewEnvUserId = 'environments.view-env&userId';
+Meteor.publish(subsEnvViewEnvUserId, function (userId) {
+ console.log(`subscription - ${subsEnvViewEnvUserId} `);
+ console.log(`-userId: ${R.toString(userId)}`);
+
+ let query = {};
+
+ let currentUser = this.userId;
+ if (! Roles.userIsInRole(currentUser, 'manage-users', Roles.GLOBAL_GROUP)) {
+ console.log(`* error: unauth`);
+ console.log(`- currentUser: ${R.toString(currentUser)}`);
+ this.error('unauthorized for this subscription');
+ return;
+ }
+
+ query = R.merge(query, {
+ 'auth.view-env': {
+ $in: [ userId ]
+ }
+ });
+
+ console.log(`* query: ${R.toString(query)}`);
+ return Environments.find(query);
+});
+
+const subsEnvEditEnvUserId = 'environments.edit-env&userId';
+Meteor.publish(subsEnvEditEnvUserId, function (userId) {
+ console.log(`subscription - ${subsEnvEditEnvUserId} `);
+ console.log(`-userId: ${R.toString(userId)}`);
+ let query = {};
+
+ let currentUser = this.userId;
+ if (! Roles.userIsInRole(currentUser, 'manage-users', Roles.GLOBAL_GROUP)) {
+ console.log(`* error: unauth`);
+ console.log(`- currentUser: ${R.toString(currentUser)}`);
+ this.error('unauthorized for this subscription');
+ return;
+ }
+
+ query = R.merge(query, {
+ 'auth.edit-env': {
+ $in: [ userId ]
+ }
+ });
+
+ console.log(`* query: ${R.toString(query)}`);
+ return Environments.find(query);
+});
+
+Meteor.publish('environments?name', function (name) {
+ console.log('server subscribtion to: environments?name=' + name.toString());
+ let query = {
+ name: name,
+ user: this.userId
+ };
+ return Environments.find(query);
+});
+
+Meteor.publish('environments?_id', function (_id) {
+ console.log('server subscribtion to: environments?_id');
+ console.log('-_id: ', R.toString(_id));
+
+ let query = {
+ _id: _id,
+ user: this.userId
+ };
+ return Environments.find(query);
+});
diff --git a/ui/imports/api/inventories/inventories.js b/ui/imports/api/inventories/inventories.js
new file mode 100644
index 0000000..114f5ef
--- /dev/null
+++ b/ui/imports/api/inventories/inventories.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 { Mongo } from 'meteor/mongo';
+
+export const Inventory = new Mongo.Collection('inventory', { idGeneration: 'MONGO' });
diff --git a/ui/imports/api/inventories/server/methods.js b/ui/imports/api/inventories/server/methods.js
new file mode 100644
index 0000000..ec2f27d
--- /dev/null
+++ b/ui/imports/api/inventories/server/methods.js
@@ -0,0 +1,151 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 { check } from 'meteor/check';
+import * as R from 'ramda';
+import { Inventory } from '../inventories';
+import { Environments } from '/imports/api/environments/environments';
+import { regexEscape } from '/imports/lib/regex-utils';
+import { NodeHoverAttr } from '/imports/api/attributes_for_hover_on_data/attributes_for_hover_on_data';
+const AUTO_COMPLETE_RESULTS_LIMIT = 15;
+
+Meteor.methods({
+ 'inventorySearch': function(searchTerm, envId, opCounter) {
+ console.log('inventorySearch');
+ console.log('searchTerm', R.toString(searchTerm));
+ console.log('envId', R.toString(envId));
+ console.log('opCounter', R.toString(opCounter));
+
+ this.unblock();
+
+ if (R.anyPass([R.isNil, R.isEmpty])(searchTerm)) {
+ return {
+ searchResults: [],
+ opCounter: opCounter
+ };
+ }
+
+ let searchExp = new RegExp(regexEscape(searchTerm), 'i');
+
+ let query = {
+ name: searchExp
+ };
+
+ if (! R.isNil(envId)) {
+ let env = Environments.findOne({ _id: envId });
+ query = R.merge(query, {
+ environment: env.name
+ });
+ }
+
+ let searchResults = Inventory.find(query, {
+ limit: AUTO_COMPLETE_RESULTS_LIMIT
+ }).fetch();
+
+ searchResults = R.map((inventory) => {
+ console.log('search result');
+ console.log(R.toString(inventory));
+
+ let itemEnv = Environments.findOne({ name: inventory.environment });
+
+ return R.merge(inventory, {
+ _envId: itemEnv._id
+ });
+ }, searchResults);
+
+ return {
+ opCounter: opCounter,
+ searchResults: searchResults,
+ };
+ },
+
+ 'expandNodePath': function(nodeId) {
+ console.log('method server: expandNodePath', R.toString(nodeId));
+
+ //check(nodeId, MongoI);
+ this.unblock();
+
+ let node = Inventory.findOne({ _id: nodeId });
+ if (R.isNil(node)) {
+ console.log('method server: expandNodePath - no node');
+ return null;
+ }
+
+ let idList = R.pipe(R.split('/'), R.drop(2))(node.id_path);
+ let result = R.map((partId) => {
+ return Inventory.findOne({ environment: node.environment, id: partId });
+ }, idList);
+
+ console.log('method server: expandNodePath - results', result);
+ return result;
+ },
+
+ 'inventoryFindNode?type&env&name': function(type, envName, nodeName) {
+ console.log('method server: inventoryFindNode',
+ R.toString(type), R.toString(envName), R.toString(nodeName));
+
+ check(envName, String);
+ check(nodeName, String);
+ this.unblock();
+
+ let query = { type: type, environment: envName, name: nodeName };
+ let node = Inventory.findOne(query);
+
+ return {
+ node: node
+ };
+ },
+
+ 'inventoryFindNode?env&id': function (envName, nodeId) {
+ console.log('method server: inventoryFindNode?env&id',
+ R.toString(envName), R.toString(nodeId));
+
+ check(envName, String);
+ check(nodeId, String);
+ this.unblock();
+
+ let query = { environment: envName, id: nodeId };
+ let node = Inventory.findOne(query);
+
+ return {
+ node: node
+ };
+ },
+
+ 'inventoryFindNode?DataAndAttrs': function (nodeId) {
+ console.log(`method server: inventoryFindNode?DataAndAttrs. ${R.toString(nodeId)}`);
+ //check(nodeId, ObjectId);
+ this.unblock();
+
+ let query = { _id: nodeId };
+ let node = Inventory.findOne(query);
+ let attrsDefs = NodeHoverAttr.findOne({ 'type': node.type });
+ let attributes = calcAttrsForNode(node, attrsDefs);
+
+ return {
+ node: node,
+ nodeName: node.name,
+ attributes: attributes
+ };
+ },
+});
+
+function calcAttrsForNode(node, attrsDefsRec) {
+ if (R.isNil(attrsDefsRec)) {
+ return [];
+ }
+
+ let attrsDefs = attrsDefsRec.attributes;
+
+ return R.reduce((acc, attrDef) => {
+ return R.ifElse(R.isNil,
+ R.always(acc),
+ (attrVal) => R.append(R.assoc(attrDef, attrVal, {}), acc)
+ )(R.prop(attrDef, node));
+ }, [], attrsDefs);
+}
diff --git a/ui/imports/api/inventories/server/publications.js b/ui/imports/api/inventories/server/publications.js
new file mode 100644
index 0000000..f35ff30
--- /dev/null
+++ b/ui/imports/api/inventories/server/publications.js
@@ -0,0 +1,250 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 { Counts } from 'meteor/tmeasday:publish-counts';
+import { check } from 'meteor/check';
+import * as R from 'ramda';
+
+import { Inventory } from '../inventories.js';
+import { regexEscape } from '/imports/lib/regex-utils';
+
+Meteor.publish('inventory', function () {
+ console.log('server subscribtion to: inventory');
+ //return Inventory.find({$where: 'this.id_path.match('^/WebEX-Mirantis@Cisco/')'});
+ //return Inventory.find({ 'show_in_tree': true });
+ return Inventory.find({});
+});
+
+Meteor.publish('inventory?_id', function (_id) {
+ console.log('server subscribtion to: inventory?_id');
+ console.log('_id:', R.toString(_id));
+
+ return Inventory.find({ _id: _id });
+});
+
+Meteor.publish('inventory?id', function (id) {
+ console.log('server subscribtion to: inventory?id');
+ return Inventory.find({id: id});
+});
+
+Meteor.publish('inventory?env&id', function (env, id) {
+ console.log('server subscribtion to: inventory?env&id');
+ console.log(`-env: ${R.toString(env)}`);
+ console.log(`-id: ${R.toString(id)}`);
+
+ return Inventory.find({environment: env, id: id});
+});
+
+Meteor.publish('inventory?id_path', function (id_path) {
+ console.log('server subscribtion to: inventory?id_path');
+ return Inventory.find({id_path: id_path});
+});
+
+Meteor.publish('inventory?name&env&type', function (name, env, type) {
+ console.log('server subscribtion to: inventory?name&env&type');
+ console.log('-name:', R.toString(name));
+ console.log('-env:', R.toString(env));
+ console.log('-type:', R.toString(type));
+
+ let query = {
+ name: name,
+ environment: env,
+ type: type
+ };
+
+ console.log('query', R.toString(query));
+ return Inventory.find(query);
+});
+
+Meteor.publish('inventory?_id-in', function (idsList) {
+ var query = {
+ _id: { $in: idsList }
+ };
+ /*
+ var counterName = 'inventory?env+type!counter?env=' + env + '&type=' + type;
+
+ console.log('server subscribing to counter: ' + counterName);
+ Counts.publish(this, counterName, Inventory.find(query));
+ */
+ console.log('server subscribtion to: inventory?_id-in');
+ console.log('- id-in: ' + idsList);
+
+ return Inventory.find(query);
+});
+
+Meteor.publish('inventory?env+type', function (env, type) {
+ var query = {
+ environment: env,
+ type: type
+ };
+ var counterName = 'inventory?env+type!counter?env=' + env + '&type=' + type;
+
+ console.log('server subscribing to counter: ' + counterName);
+ Counts.publish(this, counterName, Inventory.find(query));
+
+ console.log('server subscribtion to: inventory-by-env-and-type');
+ console.log('-env: ' + env);
+ console.log('-type: ' + type);
+
+ return Inventory.find(query);
+});
+
+Meteor.publish('inventory?env&binding:host_id&type', function (env, host_id, type) {
+ var query = {
+ environment: env,
+ 'binding:host_id': host_id,
+ type: type
+ };
+ console.log('server subscribtion to: inventory?env&binding:host_id&type');
+ console.log('-env: ' + env);
+ console.log('-binding:host_id: ' + host_id);
+ console.log('-type: ' + type);
+
+ return Inventory.find(query);
+});
+
+Meteor.publish('inventory?env+name', function (env, name) {
+ var query = {
+ name: name,
+ environment: env
+ };
+
+ console.log('server subscribtion to: inventory?env+name');
+ console.log('- name: ' + name);
+ console.log('- env: ' + env);
+
+ return Inventory.find(query);
+});
+
+Meteor.publish('inventory?type+host', function (type, host) {
+ var query = {
+ type: type,
+ host: host
+ };
+/*
+ var counterName = 'inventory?env+type!counter?env=' + env + '&type=' + type;
+
+ console.log('server subscribing to counter: ' + counterName);
+ Counts.publish(this, counterName, Inventory.find(query));
+*/
+
+ console.log('server subscribtion to: inventory?type+host');
+ console.log('- type: ' + type);
+ console.log('- host: ' + host);
+ return Inventory.find(query);
+});
+
+Meteor.publish('inventory?id_path_start&type', function (id_path, type) {
+ check(id_path, String);
+ check(type, String);
+
+ let idPathExp = new RegExp(`^${regexEscape(id_path)}`);
+
+ let query = {
+ id_path: idPathExp,
+ type: type
+ };
+
+ var counterName = 'inventory?id_path_start&type!counter?id_path_start=' +
+ id_path + '&type=' + type;
+
+ console.log('server subscribing to counter: ' + counterName);
+ Counts.publish(this, counterName, Inventory.find(query));
+
+ console.log('server subscribtion to: inventory?id_path_start&type');
+ console.log('-id_path_start: ' + id_path);
+ console.log('-type: ' + type);
+ return Inventory.find(query);
+});
+
+
+Meteor.publish('inventory.children', function (id, type, name, env) {
+ console.log('server subscribtion to: inventory.children');
+ console.log('node id: ' + R.toString(id));
+ console.log('node type: ' + R.toString(type));
+ console.log('node name: ' + R.toString(name));
+ console.log('node env: ' + R.toString(env));
+
+ let query = {
+ $or:
+ [
+ {
+ environment: env,
+ parent_id: id
+ },
+ ]
+ };
+
+ if (R.equals('host_ref', type)) {
+ let realParent = Inventory.findOne({
+ name: name,
+ environment: env,
+ type: 'host'
+ });
+
+ query = R.merge(query, {
+ $or: R.append({
+ environment: env,
+ parent_id: realParent.id
+ }, query.$or)
+ });
+ }
+
+ console.log('query: ', R.toString(query));
+
+ return Inventory.find(query);
+});
+
+Meteor.publish('inventory.first-child', function (id, type, name, env) {
+ console.log('server subscribing to: inventory.first-child');
+ console.log('node id: ' + R.toString(id));
+ console.log('node type: ' + R.toString(type));
+ console.log('node name: ' + R.toString(name));
+ console.log('node env: ' + R.toString(env));
+
+ var counterName = 'inventory.first-child!counter!id=' + id;
+ var query = {
+ $or: [
+ {
+ environment: env,
+ parent_id: id
+ }
+ ]
+ };
+
+ if (R.equals('host_ref', type)) {
+ let realParent = Inventory.findOne({
+ name: name,
+ environment: env,
+ type: 'host'
+ });
+
+ query = R.merge(query, {
+ $or: R.append({
+ environment: env,
+ parent_id: realParent.id
+ }, query.$or)
+ });
+ }
+
+ Counts.publish(this, counterName, Inventory.find(query, { limit: 1 }));
+ console.log('server subscribing to counter: ' + counterName);
+
+// todo: eyaltask: all criteria
+ console.log('query: ', R.toString(query));
+ return Inventory.find(query, { limit: 1 });
+});
+
+Meteor.publish('inventoryByEnv', function (env) {
+ console.log('server subscribtion to: inventoryByEnv');
+ //return Inventory.find({$where: 'this.id_path.match('^/WebEX-Mirantis@Cisco/')'});
+ //return Inventory.find({ 'show_in_tree': true });
+ return Inventory.find({'environment':env});
+});
+
diff --git a/ui/imports/api/link-types/link-types.js b/ui/imports/api/link-types/link-types.js
new file mode 100644
index 0000000..94d6ddd
--- /dev/null
+++ b/ui/imports/api/link-types/link-types.js
@@ -0,0 +1,86 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import * as R from 'ramda';
+import { Constants } from '/imports/api/constants/constants';
+//import { Environments } from '/imports/api/environments/environments';
+
+export const LinkTypes = new Mongo.Collection(
+ 'link_types', { idGeneration: 'MONGO' });
+
+let schema = {
+ _id: { type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } } },
+ description: {
+ type: String
+ },
+ type: {
+ type: String
+ },
+ endPointA: {
+ type: String,
+ custom: function () {
+ let that = this;
+ let values = Constants.findOne({ name: 'object_types_for_links' }).data;
+
+ if (R.isNil(R.find(R.propEq('value', that.value), values))) {
+ return 'notAllowed';
+ }
+ }
+ },
+ endPointB: {
+ type: String,
+ custom: function () {
+ let that = this;
+ let values = Constants.findOne({ name: 'object_types_for_links' }).data;
+
+ if (R.isNil(R.find(R.propEq('value', that.value), values))) {
+ return 'notAllowed';
+ }
+ }
+ }
+};
+
+let simpleSchema = new SimpleSchema(schema);
+
+simpleSchema.addValidator(function () {
+ let that = this;
+
+ let existing = LinkTypes.findOne({
+ _id: { $ne: that.docId },
+ endPointA: that.field('endPointA').value,
+ endPointB: that.field('endPointB').value
+ });
+
+ if (R.allPass([
+ R.pipe(R.isNil, R.not),
+ R.pipe(R.propEq('_id', that.docId), R.not)
+ ])(existing)) {
+
+ return 'alreadyExists';
+ }
+
+ existing = LinkTypes.findOne({
+ _id: { $ne: that.docId },
+ endPointA: that.field('endPointB').value,
+ endPointB: that.field('endPointA').value
+ });
+
+ if (R.allPass([
+ R.pipe(R.isNil, R.not),
+ R.pipe(R.propEq('_id', that.docId), R.not)
+ ])(existing)) {
+
+ return 'alreadyExists';
+ }
+});
+
+LinkTypes.schema = simpleSchema;
+
+LinkTypes.attachSchema(LinkTypes.schema);
diff --git a/ui/imports/api/link-types/methods.js b/ui/imports/api/link-types/methods.js
new file mode 100644
index 0000000..846c28b
--- /dev/null
+++ b/ui/imports/api/link-types/methods.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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { ValidatedMethod } from 'meteor/mdg:validated-method';
+import * as R from 'ramda';
+import { Roles } from 'meteor/alanning:roles';
+
+import { LinkTypes } from './link-types';
+
+export const insert = new ValidatedMethod({
+ name: 'links_types.insert',
+ validate: LinkTypes.simpleSchema()
+ .pick([
+ //'environment',
+ 'description',
+ 'endPointA',
+ 'endPointB',
+ ]).validator({ clean: true, filter: false }),
+ run({
+ //environment,
+ description,
+ endPointA,
+ endPointB
+ }) {
+ if (! Roles.userIsInRole(Meteor.userId(), 'manage-link-types', Roles.GLOBAL_GROUP)) {
+ throw new Meteor.Error('unauthorized for inserting link type');
+ }
+
+ let linkType = LinkTypes.schema.clean({
+ });
+
+ let type = calcTypeFromEndPoints(endPointA, endPointB);
+
+ linkType = R.merge(linkType, {
+ description,
+ endPointA,
+ endPointB,
+ type
+ });
+
+ LinkTypes.insert(linkType);
+ }
+});
+
+export const remove = new ValidatedMethod({
+ name: 'links_types.remove',
+ validate: LinkTypes.simpleSchema()
+ .pick([
+ '_id',
+ ]).validator({ clean: true, filter: false }),
+ run({
+ _id
+ }) {
+ if (! Roles.userIsInRole(Meteor.userId(), 'manage-link-types', Roles.DEFAULT_GROUP)) {
+ throw new Meteor.Error('unauthorized for removing link type');
+ }
+
+ let linkType = LinkTypes.findOne({ _id: _id });
+ console.log('link type for remove: ', linkType);
+ console.log('current user', Meteor.userId());
+
+ LinkTypes.remove({ _id: _id });
+ }
+});
+
+export const update = new ValidatedMethod({
+ name: 'links_types.update',
+ validate: LinkTypes.simpleSchema()
+ .pick([
+ '_id',
+ 'description',
+ 'endPointA',
+ 'endPointB',
+ ]).validator({ clean: true, filter: false }),
+ run({
+ _id,
+ description,
+ endPointA,
+ endPointB
+ }) {
+ if (! Roles.userIsInRole(Meteor.userId(), 'manage-link-types', Roles.DEFAULT_GROUP)) {
+ throw new Meteor.Error('unauthorized for updating link type');
+ }
+
+ let linkType = LinkTypes.findOne({ _id: _id });
+ console.log('link type for update: ', linkType);
+ console.log('current user', Meteor.userId());
+
+ let type = calcTypeFromEndPoints(endPointA, endPointB);
+
+ linkType = R.merge(R.pick([
+ 'description',
+ 'endPointA',
+ 'endPointB',
+ 'type'
+ ], linkType), {
+ description,
+ endPointA,
+ endPointB,
+ type
+ });
+
+ LinkTypes.update({ _id: _id }, { $set: linkType });
+ }
+});
+
+function calcTypeFromEndPoints(endPointA, endPointB) {
+ return `${endPointA}-${endPointB}`;
+}
diff --git a/ui/imports/api/link-types/server/publications.js b/ui/imports/api/link-types/server/publications.js
new file mode 100644
index 0000000..6c6278f
--- /dev/null
+++ b/ui/imports/api/link-types/server/publications.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 { Meteor } from 'meteor/meteor';
+import * as R from 'ramda';
+
+import { LinkTypes } from '../link-types.js';
+
+Meteor.publish('link_types', function () {
+ console.log('server subscribtion: link_types');
+
+ //let that = this;
+
+ let query = {};
+ return LinkTypes.find(query);
+});
+
+Meteor.publish('link_types?env*', function (env) {
+ console.log('server subscribtion: link_types?env*');
+ console.log(env);
+
+ //let that = this;
+
+ let query = {};
+ if (! R.isNil(env)) { query = R.assoc('environment', env, query); }
+ console.log('-query: ', query);
+ return LinkTypes.find(query);
+});
+
+Meteor.publish('link_types?_id', function (_id) {
+ console.log('server subscribtion: link_types?_id');
+ console.log(_id);
+
+ //let that = this;
+
+ let query = {
+ _id: _id,
+ };
+ console.log('-query: ', query);
+ return LinkTypes.find(query);
+});
diff --git a/ui/imports/api/links/links.js b/ui/imports/api/links/links.js
new file mode 100644
index 0000000..2baf58c
--- /dev/null
+++ b/ui/imports/api/links/links.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 { Mongo } from 'meteor/mongo';
+
+export const Links = new Mongo.Collection('links', { idGeneration: 'MONGO' });
diff --git a/ui/imports/api/links/methods.js b/ui/imports/api/links/methods.js
new file mode 100644
index 0000000..1eda375
--- /dev/null
+++ b/ui/imports/api/links/methods.js
@@ -0,0 +1,8 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 /
+/////////////////////////////////////////////////////////////////////////////////////////
diff --git a/ui/imports/api/links/server/publications.js b/ui/imports/api/links/server/publications.js
new file mode 100644
index 0000000..78d0c26
--- /dev/null
+++ b/ui/imports/api/links/server/publications.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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { Meteor } from 'meteor/meteor';
+import { Links } from '../links.js';
+
+Meteor.publish('links', function () {
+ console.log('server subscribtion to: links');
+ //return Inventory.find({$where: 'this.id_path.match('^/WebEX-Mirantis@Cisco/')'});
+ return Links.find({});
+});
+
+Meteor.publish('links?_id-in', function (idsList) {
+ var query = {
+ _id: { $in: idsList}
+ };
+/*
+ var counterName = 'inventory?env+type!counter?env=' + env + '&type=' + type;
+
+ console.log('server subscribing to counter: ' + counterName);
+ Counts.publish(this, counterName, Inventory.find(query));
+*/
+
+ console.log('server subscribtion to: links?_id-in');
+ console.log('- _id-in: ' + idsList);
+ return Links.find(query);
+});
diff --git a/ui/imports/api/messages/messages.js b/ui/imports/api/messages/messages.js
new file mode 100644
index 0000000..5a028b0
--- /dev/null
+++ b/ui/imports/api/messages/messages.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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { Mongo } from 'meteor/mongo';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import * as R from 'ramda';
+import { Environments } from '/imports/api/environments/environments';
+import { Constants } from '/imports/api/constants/constants';
+
+export const Messages = new Mongo.Collection('messages', { idGeneration: 'MONGO' });
+
+let schema = {
+ _id: { type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } } },
+
+ environment: {
+ type: String,
+ custom: function () {
+ let that = this;
+ let env = Environments.findOne({ name: that.value });
+
+ if (R.isNil(env)) {
+ return 'notAllowed';
+ }
+ }
+ },
+
+ id: {
+ type: String
+ },
+
+ viewed: {
+ type: Boolean,
+ defaultValue: false
+ },
+
+ display_context: {
+ type: String
+ },
+
+ message: {
+ type: Object,
+ blackbox: true
+ },
+
+ source_system: {
+ type: String,
+ custom: function () {
+ let that = this;
+ let values = Constants.findOne({ name: 'message_source_systems' }).data;
+
+ if (R.isNil(R.find(R.propEq('value', that.value), values))) {
+ return 'notAllowed';
+ }
+ }
+ },
+
+ level: {
+ type: String
+ },
+
+ timestamp: {
+ type: Date
+ },
+
+ related_object_type: {
+ type: String
+ },
+
+ related_object: {
+ type: String
+ },
+
+ scan_id: {
+ type: Date
+ }
+};
+
+let simpleSchema = new SimpleSchema(schema);
+
+Messages.schema = simpleSchema;
+Messages.attachSchema(Messages.schema);
+
+export function calcIconForMessageLevel(level) {
+ switch (level) {
+ case 'info':
+ return 'notifications';
+ case 'warning':
+ return 'warning';
+ case 'error':
+ return 'error';
+ default:
+ return 'notifications';
+ }
+}
+
+export function lastMessageTimestamp (level, envName) {
+ let query = { level: level };
+ query = R.ifElse(R.isNil, R.always(query), R.assoc('environment', R.__, query))(envName);
+
+ let message = Messages.findOne(query, {
+ sort: { timestamp: -1 }
+ });
+
+ let res = R.path(['timestamp'], message);
+ if (R.isNil(res)) { return null; }
+ return (res instanceof String) ? res : res.toString();
+}
+
+export function calcColorClassForMessagesInfoBox(level) {
+ switch (level) {
+ case 'info':
+ return 'green-text';
+ case 'warning':
+ return 'orange-text';
+ case 'error':
+ return 'red-text';
+ default:
+ return 'green-text';
+ }
+}
diff --git a/ui/imports/api/messages/methods.js b/ui/imports/api/messages/methods.js
new file mode 100644
index 0000000..1eda375
--- /dev/null
+++ b/ui/imports/api/messages/methods.js
@@ -0,0 +1,8 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 /
+/////////////////////////////////////////////////////////////////////////////////////////
diff --git a/ui/imports/api/messages/server/methods.js b/ui/imports/api/messages/server/methods.js
new file mode 100644
index 0000000..119e6b0
--- /dev/null
+++ b/ui/imports/api/messages/server/methods.js
@@ -0,0 +1,49 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 { Messages } from '/imports/api/messages/messages';
+
+Meteor.methods({
+ 'messages/get?level&env&page&amountPerPage&sortField&sortDirection': function (
+ level, env, page, amountPerPage, sortField, sortDirection) {
+
+ logMethodCall('messages/get?level&env&page&amountPerPage&sortField&sortDirection',
+ {level, env, page, amountPerPage});
+
+ this.unblock();
+
+ let skip = (page - 1) * amountPerPage;
+
+ let query = {};
+ let sortParams = {};
+
+ query = R.ifElse(R.isNil, R.always(query),R.assoc('environment', R.__, query))(env);
+ query = R.ifElse(R.isNil, R.always(query),R.assoc('level', R.__, query))(level);
+
+ sortParams = R.ifElse(R.isNil, R.always(sortParams),
+ R.assoc(R.__, sortDirection, sortParams))(sortField);
+
+ console.log('sort params:', sortParams);
+
+ let qParams = {
+ limit: amountPerPage,
+ skip: skip,
+ sort: sortParams,
+ };
+
+ return Messages.find(query, qParams).fetch();
+ }
+});
+
+function logMethodCall(name, args) {
+ console.log(`method call: ${name}`);
+ R.forEachObjIndexed((value, key) => {
+ console.log(`${key}: ${R.toString(value)}`);
+ }, args);
+}
diff --git a/ui/imports/api/messages/server/publications.js b/ui/imports/api/messages/server/publications.js
new file mode 100644
index 0000000..13c7c50
--- /dev/null
+++ b/ui/imports/api/messages/server/publications.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 { Meteor } from 'meteor/meteor';
+//import { Counts } from 'meteor/tmeasday:publish-counts';
+import { Counter } from 'meteor/natestrauser:publish-performant-counts';
+import { Messages } from '../messages.js';
+import * as R from 'ramda';
+
+Meteor.publish('messages', function () {
+ console.log('server subscribtion to: messages');
+ //return Inventory.find({$where: 'this.id_path.match('^/WebEX-Mirantis@Cisco/')'});
+ //return Inventory.find({ 'show_in_tree': true });
+ return Messages.find({});
+});
+
+Meteor.publish('messages?_id', function (_id) {
+ console.log('server subscribtion to: messages?_id');
+ console.log('_id', _id);
+
+ let query = { _id: _id };
+ return Messages.find(query);
+});
+
+Meteor.publish('messages?level', function (level) {
+ var query = {
+ level: level
+ };
+
+ /*
+ var counterName = 'messages?level!counter?' +
+ 'level=' + level;
+
+ console.log('server subscription to: ' + counterName);
+ Counts.publish(this, counterName, Messages.find(query));
+ */
+
+ console.log('server subscribtion to: messages?level');
+ console.log('- level: ' + level);
+ return Messages.find(query);
+});
+
+Meteor.publish('messages?env+level', function (env, level) {
+ var query = {
+ environment: env,
+ level: level
+ };
+ /*
+ var counterName = 'messages?env+level!counter?env=' +
+ env + '&level=' + level;
+
+ console.log('server subscription to: messages - counter');
+ console.log(' - name: ' + counterName);
+ Counts.publish(this, counterName, Messages.find(query));
+ */
+
+ console.log('server subscribtion to: messages');
+ console.log('- env: ' + env);
+ console.log('- level: ' + level);
+ return Messages.find(query);
+});
+
+Meteor.publish('messages/count', function () {
+ const counterName = `messages/count`;
+ console.log(`subscribe - counter: ${counterName}`);
+
+ return new Counter(counterName, Messages.find({ }));
+});
+
+Meteor.publish('messages/count?env', function (env) {
+ const counterName = `messages/count?env`;
+ console.log(`subscribe - counter: ${counterName}`);
+
+ let query = {};
+ query = R.ifElse(R.isNil, R.always(query), R.assoc('environment', R.__, query))(env);
+ return new Counter(counterName, Messages.find(query));
+});
+
+Meteor.publish('messages/count?level', function (level) {
+ const counterName = `messages/count?level=${level}`;
+ console.log(`subscribe - counter: ${counterName}`);
+
+ return new Counter(counterName, Messages.find({ level: level }));
+});
+
+Meteor.publish('messages/count?level&env', function (level, env) {
+ const counterName = `messages/count?level=${level}&env=${env}`;
+ console.log(`subscribe - counter: ${counterName}`);
+
+ let query = { level: level };
+ query = R.ifElse(R.isNil, R.always(query), R.assoc('environment', R.__, query))(env);
+
+ return new Counter(counterName, Messages.find(query)); });
diff --git a/ui/imports/api/migrations/migrations.js b/ui/imports/api/migrations/migrations.js
new file mode 100644
index 0000000..79411b1
--- /dev/null
+++ b/ui/imports/api/migrations/migrations.js
@@ -0,0 +1,20 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 { CliqueTypes } from '/imports/api/clique-types/clique-types';
+
+Migrations.add({
+ version: 1,
+ up: () => {
+ console.log('migrating: add clique type constaints for env+name, env+focal_point_type');
+ CliqueTypes._ensureIndex({ environment: 1, name: 1 });
+ CliqueTypes._ensureIndex({ environment: 1, focal_point_type: 1 });
+ },
+ down: () => {
+ }
+});
diff --git a/ui/imports/api/scans/methods.js b/ui/imports/api/scans/methods.js
new file mode 100644
index 0000000..82af820
--- /dev/null
+++ b/ui/imports/api/scans/methods.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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { ValidatedMethod } from 'meteor/mdg:validated-method';
+import * as R from 'ramda';
+
+import { Scans } from './scans';
+
+export const insert = new ValidatedMethod({
+ name: 'scans.insert',
+ validate: Scans.simpleSchema()
+ .pick([
+ 'environment',
+ 'object_id',
+ 'log_level',
+ 'clear',
+ 'loglevel',
+ 'scan_only_inventory',
+ 'scan_only_links',
+ 'scan_only_cliques',
+ ]).validator({ clean: true, filter: false }),
+ run({
+ environment,
+ object_id,
+ log_level,
+ clear,
+ loglevel,
+ scan_only_inventory,
+ scan_only_links,
+ scan_only_cliques,
+ }) {
+ let scan = Scans.schema.clean({
+ status: 'pending'
+ });
+ scan = R.merge(scan, {
+ environment,
+ object_id,
+ log_level,
+ clear,
+ loglevel,
+ scan_only_inventory,
+ scan_only_links,
+ scan_only_cliques,
+ submit_timestamp: Date.now()
+ });
+
+ Scans.insert(scan);
+ },
+
+});
diff --git a/ui/imports/api/scans/scans.js b/ui/imports/api/scans/scans.js
new file mode 100644
index 0000000..857c2ea
--- /dev/null
+++ b/ui/imports/api/scans/scans.js
@@ -0,0 +1,159 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import * as R from 'ramda';
+
+import { Constants } from '/imports/api/constants/constants';
+import { StatusesInOperation } from '/imports/api/constants/data/scans-statuses';
+
+export const Scans = new Mongo.Collection('scans', { idGeneration: 'MONGO' });
+
+Scans.schemaRelated = {
+ environment: {
+ label: 'Environment',
+ description: 'Name of environment to scan',
+ disabled: true,
+ },
+ status: {
+ label: 'Status',
+ description: 'Scan lifecycle status',
+ subtype: 'select',
+ options: 'scans_statuses',
+ disabled: true,
+ },
+ object_id: {
+ label: 'Scan specific object',
+ description: 'Object ID',
+ },
+ log_level: {
+ label: 'Log level',
+ description: 'logging level',
+ subtype: 'select',
+ options: 'log_levels',
+ },
+ clear: {
+ label: 'Clear data',
+ description: 'clear all data prior to scanning',
+ },
+ scan_only_inventory: {
+ label: 'Scan only inventory',
+ description: 'do only scan to inventory',
+ },
+ scan_only_links: {
+ label: 'Scan only links',
+ description: 'do only links creation',
+ },
+ scan_only_cliques: {
+ label: 'Scan only cliques',
+ description: 'do only cliques creation',
+ },
+};
+
+Scans.scansOnlyFields = ['scan_only_inventory', 'scan_only_links', 'scan_only_cliques'];
+
+let schema = {
+ _id: { type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } } },
+ environment: {
+ type: String
+ },
+ status: {
+ type: String,
+ defaultValue: 'draft',
+ custom: function () {
+ let that = this;
+ let statuses = Constants.findOne({ name: 'scans_statuses' }).data;
+
+ if (R.isNil(R.find(R.propEq('value', that.value), statuses))) {
+ return 'notAllowed';
+ }
+ },
+ },
+ object_id: {
+ type: String,
+ optional: true,
+ },
+ log_level: {
+ type: String,
+ defaultValue: 'warning',
+ custom: function () {
+ let that = this;
+ let logLevels = Constants.findOne({ name: 'log_levels' }).data;
+
+ if (R.isNil(R.find(R.propEq('value', that.value), logLevels))) {
+ return 'notAllowed';
+ }
+ },
+ },
+ clear: {
+ type: Boolean,
+ defaultValue: true,
+ },
+ scan_only_inventory: {
+ type: Boolean,
+ defaultValue: false,
+ },
+ scan_only_links: {
+ type: Boolean,
+ defaultValue: false,
+ },
+ scan_only_cliques: {
+ type: Boolean,
+ defaultValue: false,
+ },
+ submit_timestamp: {
+ type: Date,
+ defaultValue: null
+ },
+
+};
+
+Scans.schema = new SimpleSchema(schema);
+Scans.schema.addValidator(function () {
+ let that = this;
+ let env = that.field('environment').value;
+
+ let currentScansCount = Scans.find({
+ environment: env,
+ status: { $in: StatusesInOperation }
+ }).count();
+
+ if (currentScansCount > 0) {
+ throw {
+ isError: true,
+ type: 'notUinque',
+ data: [],
+ message: 'There is already a scan in progress.'
+ };
+ }
+
+ let scanOnlyFields = R.filter( f => that.field(f).value, Scans.scansOnlyFields);
+
+ if(scanOnlyFields.length > 1) {
+ throw {
+ isError: true,
+ type: 'conflict',
+ data: scanOnlyFields,
+ message: 'Only one of the scan only fields can be selected'
+ };
+ }
+
+});
+
+Scans.attachSchema(Scans.schema);
+
+Scans.schemaRelated = R.mapObjIndexed((relatedItem, key) => {
+ return R.merge(relatedItem, {
+ type: schema[key].type
+ });
+
+}, Scans.schemaRelated);
+
+export const subsScansEnvPageAmountSorted = 'scans?env*&page&amount&sortField&sortDirection';
+export const subsScansEnvPageAmountSortedCounter = `${subsScansEnvPageAmountSorted}!counter`;
diff --git a/ui/imports/api/scans/server/methods.js b/ui/imports/api/scans/server/methods.js
new file mode 100644
index 0000000..0fe43c2
--- /dev/null
+++ b/ui/imports/api/scans/server/methods.js
@@ -0,0 +1,44 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 { check } from 'meteor/check';
+import * as R from 'ramda';
+import { Scans } from '../scans';
+import { Environments } from '/imports/api/environments/environments';
+
+Meteor.methods({
+ 'scansFind?start-timestamp-before': function (startTimestamp) {
+ console.log('method server: scanFind?start-timestamp-before',
+ R.toString(startTimestamp));
+
+ check(startTimestamp, Date);
+ this.unblock();
+
+ let query = { start_timestamp: { $lt: startTimestamp }};
+ let scan = Scans.findOne(query, {
+ sort: { start_timestamp: -1 }
+ });
+
+ let environment = R.ifElse(
+ R.isNil,
+ R.always(null),
+ (scan) => {
+ console.log('finding environment:', scan.environment);
+ let env = Environments.findOne({ name: scan.environment });
+ console.log('found env:', env);
+ return env;
+ })(scan);
+
+ console.log('found scan', scan);
+
+ return {
+ environment: environment,
+ scan: scan,
+ };
+ },
+});
diff --git a/ui/imports/api/scans/server/publications.js b/ui/imports/api/scans/server/publications.js
new file mode 100644
index 0000000..774fe3d
--- /dev/null
+++ b/ui/imports/api/scans/server/publications.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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { Meteor } from 'meteor/meteor';
+import * as R from 'ramda';
+import { Counts } from 'meteor/tmeasday:publish-counts';
+
+import { Scans,
+ subsScansEnvPageAmountSorted,
+ subsScansEnvPageAmountSortedCounter,
+} from '../scans.js';
+
+Meteor.publish('scans?env', function (env_name) {
+ console.log('server subscribtion: scans?env');
+ console.log(env_name);
+
+ return Scans.find({
+ environment: env_name,
+ });
+});
+
+Meteor.publish('scans?env*', function (env) {
+ console.log('server subscribtion: scans?env*');
+ console.log(env);
+
+ //let that = this;
+
+ let query = {};
+ if (! R.isNil(env)) { query = R.assoc('environment', env, query); }
+ console.log('-query: ', query);
+ return Scans.find(query);
+});
+
+Meteor.publish(subsScansEnvPageAmountSorted, function (
+ env, page, amountPerPage, sortField, sortDirection) {
+
+ console.log(`server subscribtion: ${subsScansEnvPageAmountSorted}`);
+ console.log(env);
+ console.log('page: ', page);
+ console.log('amount: ', amountPerPage);
+ console.log('sortField: ', sortField, R.isNil(sortField));
+ console.log('sortDirection: ', sortDirection);
+
+ let skip = (page - 1) * amountPerPage;
+
+ let query = {};
+ if (! R.isNil(env)) { query = R.assoc('environment', env, query); }
+ console.log('-query: ', query);
+ let sortParams = {};
+
+ sortParams = R.ifElse(R.isNil, R.always(sortParams),
+ R.assoc(R.__, sortDirection, sortParams))(sortField);
+
+ console.log('sort params:', sortParams);
+
+ let qParams = {
+ limit: amountPerPage,
+ skip: skip,
+ sort: sortParams,
+ };
+
+ Counts.publish(this, subsScansEnvPageAmountSortedCounter, Scans.find(query), {
+ noReady: true
+ });
+
+ return Scans.find(query, qParams);
+});
+
+Meteor.publish('scans?id', function (id) {
+ console.log('server subscribtion: scans?id');
+ console.log('-id: ', id);
+
+ //let that = this;
+
+ let query = { _id: id };
+ return Scans.find(query);
+});
diff --git a/ui/imports/api/scheduled-scans/methods.js b/ui/imports/api/scheduled-scans/methods.js
new file mode 100644
index 0000000..22f8110
--- /dev/null
+++ b/ui/imports/api/scheduled-scans/methods.js
@@ -0,0 +1,131 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 { ValidatedMethod } from 'meteor/mdg:validated-method';
+import * as R from 'ramda';
+
+import { ScheduledScans } from './scheduled-scans';
+
+export const insert = new ValidatedMethod({
+ name: 'scheduled-scans.insert',
+ validate: ScheduledScans.simpleSchema()
+ .pick([
+ 'environment',
+ 'object_id',
+ 'log_level',
+ 'clear',
+ 'loglevel',
+ 'scan_only_inventory',
+ 'scan_only_links',
+ 'scan_only_cliques',
+ 'freq',
+ ]).validator({ clean: true, filter: false }),
+ run({
+ environment,
+ object_id,
+ log_level,
+ clear,
+ loglevel,
+ scan_only_inventory,
+ scan_only_links,
+ scan_only_cliques,
+ freq,
+ }) {
+ let scan = ScheduledScans.schema.clean({ });
+
+ scan = R.merge(scan, {
+ environment,
+ object_id,
+ log_level,
+ clear,
+ loglevel,
+ scan_only_inventory,
+ scan_only_links,
+ scan_only_cliques,
+ freq,
+ submit_timestamp: Date.now()
+ });
+
+ ScheduledScans.insert(scan);
+ },
+
+});
+
+export const update = new ValidatedMethod({
+ name: 'scheduled_scans.update',
+ validate: ScheduledScans.simpleSchema()
+ .pick([
+ '_id',
+ 'environment',
+ 'object_id',
+ 'log_level',
+ 'clear',
+ 'loglevel',
+ 'scan_only_inventory',
+ 'scan_only_links',
+ 'scan_only_cliques',
+ 'freq',
+ ]).validator({ clean: true, filter: false }),
+ run({
+ _id,
+ environment,
+ object_id,
+ log_level,
+ clear,
+ loglevel,
+ scan_only_inventory,
+ scan_only_links,
+ scan_only_cliques,
+ freq,
+ }) {
+ let item = ScheduledScans.findOne({ _id: _id });
+ console.log('scheduled scan for update: ', item);
+
+ item = R.merge(R.pick([
+ 'environment',
+ 'object_id',
+ 'log_level',
+ 'clear',
+ 'loglevel',
+ 'scan_only_inventory',
+ 'scan_only_links',
+ 'scan_only_cliques',
+ 'submit_timestamp',
+ 'freq',
+ ], item), {
+ environment,
+ object_id,
+ log_level,
+ clear,
+ loglevel,
+ scan_only_inventory,
+ scan_only_links,
+ scan_only_cliques,
+ freq,
+ submit_timestamp: Date.now()
+ });
+
+ ScheduledScans.update({ _id: _id }, { $set: item });
+ }
+});
+
+export const remove = new ValidatedMethod({
+ name: 'scheduled_scans.remove',
+ validate: ScheduledScans.simpleSchema()
+ .pick([
+ '_id',
+ ]).validator({ clean: true, filter: false }),
+ run({
+ _id
+ }) {
+ let item = ScheduledScans.findOne({ _id: _id });
+ console.log('scheduled scan for remove: ', item);
+
+ ScheduledScans.remove({ _id: _id });
+ }
+});
diff --git a/ui/imports/api/scheduled-scans/scheduled-scans.js b/ui/imports/api/scheduled-scans/scheduled-scans.js
new file mode 100644
index 0000000..66ae5d1
--- /dev/null
+++ b/ui/imports/api/scheduled-scans/scheduled-scans.js
@@ -0,0 +1,91 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { Constants } from '/imports/api/constants/constants';
+import * as R from 'ramda';
+
+export const ScheduledScans = new Mongo.Collection('scheduled_scans', { idGeneration: 'MONGO' });
+
+export const scansOnlyFields = ['scan_only_inventory', 'scan_only_links', 'scan_only_cliques'];
+
+let schema = new SimpleSchema({
+ _id: { type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } } },
+ environment: {
+ type: String
+ },
+ object_id: {
+ type: String,
+ optional: true,
+ },
+ log_level: {
+ type: String,
+ defaultValue: 'warning',
+ custom: function () {
+ let that = this;
+ let logLevels = Constants.findOne({ name: 'log_levels' }).data;
+
+ if (R.isNil(R.find(R.propEq('value', that.value), logLevels))) {
+ return 'notAllowed';
+ }
+ },
+ },
+ clear: {
+ type: Boolean,
+ defaultValue: true,
+ },
+ scan_only_inventory: {
+ type: Boolean,
+ defaultValue: true,
+ },
+ scan_only_links: {
+ type: Boolean,
+ defaultValue: false,
+ },
+ scan_only_cliques: {
+ type: Boolean,
+ defaultValue: false,
+ },
+ freq: {
+ type: String,
+ defaultValue: 'WEEKLY',
+ },
+ submit_timestamp: {
+ type: Date,
+ defaultValue: null
+ },
+ scheduled_timestamp: {
+ type: Date,
+ defaultValue: null,
+ optional: true,
+ }
+});
+
+schema.addValidator(function () {
+ let that = this;
+ let currentScansOnlyFields =
+ R.reject( f => that.field(f).value == false, scansOnlyFields);
+
+ if(currentScansOnlyFields.length > 1) {
+ throw {
+ isError: true,
+ type: 'conflict',
+ data: currentScansOnlyFields,
+ message: `Only one of the scan only fields can be selected. ${R.toString(currentScansOnlyFields)}`
+ };
+ }
+});
+
+ScheduledScans.schema = schema;
+ScheduledScans.attachSchema(ScheduledScans.schema);
+
+export const subsScheduledScansPageAmountSorted = 'scheduled_scans?page&amount&sortField&sortDirection';
+export const subsScheduledScansPageAmountSortedCounter = `${subsScheduledScansPageAmountSorted}!counter`;
+
+export const subsScheduledScansId = 'scheduled_scans?_id';
diff --git a/ui/imports/api/scheduled-scans/server/methods.js b/ui/imports/api/scheduled-scans/server/methods.js
new file mode 100644
index 0000000..17ed990
--- /dev/null
+++ b/ui/imports/api/scheduled-scans/server/methods.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { check } from 'meteor/check';
+import * as R from 'ramda';
+import { ScheduledScans } from '../scheduled-scans';
+
+Meteor.methods({
+ 'scheduledScansFind?env': function (env) {
+ console.log('method server: scheduledScansFind?env', R.toString(env));
+
+ check(env, String);
+ this.unblock();
+
+ let query = { environment: env };
+ let scheduledScan = ScheduledScans.findOne(query, {});
+
+ return {
+ item: scheduledScan
+ };
+ }
+});
diff --git a/ui/imports/api/scheduled-scans/server/publications.js b/ui/imports/api/scheduled-scans/server/publications.js
new file mode 100644
index 0000000..97acc21
--- /dev/null
+++ b/ui/imports/api/scheduled-scans/server/publications.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { Meteor } from 'meteor/meteor';
+import * as R from 'ramda';
+import { Counts } from 'meteor/tmeasday:publish-counts';
+
+import { ScheduledScans,
+ subsScheduledScansPageAmountSorted,
+ subsScheduledScansPageAmountSortedCounter,
+ subsScheduledScansId,
+} from '../scheduled-scans.js';
+
+Meteor.publish(subsScheduledScansPageAmountSorted, function (
+ page, amountPerPage, sortField, sortDirection) {
+
+ console.log(`server subscribtion: ${subsScheduledScansPageAmountSorted}`);
+ console.log('page: ', page);
+ console.log('amount: ', amountPerPage);
+ console.log('sortField: ', sortField, R.isNil(sortField));
+ console.log('sortDirection: ', sortDirection);
+
+ let skip = (page - 1) * amountPerPage;
+
+ let query = {};
+ console.log('-query: ', query);
+ let sortParams = {};
+
+ sortParams = R.ifElse(R.isNil, R.always(sortParams),
+ R.assoc(R.__, sortDirection, sortParams))(sortField);
+
+ console.log('sort params:', sortParams);
+
+ let qParams = {
+ limit: amountPerPage,
+ skip: skip,
+ sort: sortParams,
+ };
+
+ Counts.publish(this, subsScheduledScansPageAmountSortedCounter, ScheduledScans.find(query), {
+ noReady: true
+ });
+
+ return ScheduledScans.find(query, qParams);
+});
+
+Meteor.publish(subsScheduledScansId, function (_id) {
+ console.log(`server subscribtion: ${subsScheduledScansId}`);
+ console.log('-id: ', _id);
+
+ //let that = this;
+
+ let query = { _id: _id };
+ return ScheduledScans.find(query);
+});
diff --git a/ui/imports/api/simple-schema.init.js b/ui/imports/api/simple-schema.init.js
new file mode 100644
index 0000000..4f5addb
--- /dev/null
+++ b/ui/imports/api/simple-schema.init.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+
+SimpleSchema.messages({
+ 'alreadyExists': 'item already exists'
+});
diff --git a/ui/imports/api/statistics/helpers.js b/ui/imports/api/statistics/helpers.js
new file mode 100644
index 0000000..7cb78e8
--- /dev/null
+++ b/ui/imports/api/statistics/helpers.js
@@ -0,0 +1,64 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 function createGraphQuerySchema(
+ env,
+ object_id,
+ type,
+ flowType,
+ timeStart,
+ timeEnd,
+ sourceMacAddress,
+ destinationMacAddress,
+ sourceIPv4Address,
+ destinationIPv4Address) {
+
+ let schema = {
+ environment: env,
+ object_id: object_id,
+ type: type,
+ flowType: flowType,
+ /*
+ averageArrivalNanoSeconds: {
+ $gte: timeStart,
+ //$lt: timeEnd
+ }
+ */
+ data_arrival_avg: {
+ $gte: timeStart,
+ }
+ };
+
+ if (! R.isNil(timeEnd)) {
+ //schema = R.assocPath(['averageArrivalNanoSeconds', '$lt'], timeEnd, schema);
+ schema = R.assocPath(['data_arrival_avg', '$lt'], timeEnd, schema);
+ }
+
+ switch (flowType) {
+ case 'L2':
+ schema = R.merge(schema, {
+ sourceMacAddress: sourceMacAddress,
+ destinationMacAddress: destinationMacAddress
+ });
+ break;
+
+ case 'L3':
+ schema = R.merge(schema, {
+ sourceIPv4Address: sourceIPv4Address,
+ destinationIPv4Address: destinationIPv4Address
+ });
+ break;
+
+ default:
+ break;
+ }
+
+ return schema;
+}
diff --git a/ui/imports/api/statistics/methods.js b/ui/imports/api/statistics/methods.js
new file mode 100644
index 0000000..23a216d
--- /dev/null
+++ b/ui/imports/api/statistics/methods.js
@@ -0,0 +1,159 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 { Statistics } from './statistics';
+import { createGraphQuerySchema } from './helpers';
+
+Meteor.methods({
+ 'statistics.flowTypes?env&object_id&type'({ env, object_id, type}) {
+ console.log('subscribe: statistics.flowTypes?env&object_id&type');
+ console.log(`- env: ${env}`);
+ console.log(`- object_id: ${object_id}`);
+ console.log(`- type: ${type}`);
+
+ let pipeline = [{
+ $match: {
+ environment: env,
+ object_id: object_id,
+ type: type
+ }
+ }, {
+ $group: {
+ _id: { flowType: '$flowType' },
+ flowType: { $first: '$flowType' }
+ }
+ }];
+
+ return Statistics.aggregate(pipeline);
+ },
+
+ 'statistics.srcMacAddresses?env&object_id&type&flowType'(
+ { env, object_id, type, flowType }) {
+
+ let pipeline = [{
+ $match: {
+ environment: env,
+ object_id: object_id,
+ type: type,
+ flowType: flowType
+ }
+ }, {
+ $group: {
+ _id: { sourceMacAddress: '$sourceMacAddress' },
+ sourceMacAddress: { $first: '$sourceMacAddress' }
+ }
+ }];
+
+ return Statistics.aggregate(pipeline);
+ },
+
+ 'statistics.dstMacAddresses?env&object_id&type&flowType'(
+ { env, object_id, type, flowType }) {
+
+ let pipeline = [{
+ $match: {
+ environment: env,
+ object_id: object_id,
+ type: type,
+ flowType: flowType
+ }
+ }, {
+ $group: {
+ _id: { destinationMacAddress: '$destinationMacAddress' },
+ destinationMacAddress: { $first: '$destinationMacAddress' }
+ }
+ }];
+
+ return Statistics.aggregate(pipeline);
+ },
+
+ 'statistics.srcIPv4Addresses?env&object_id&type&flow_typw'(
+ { env, object_id, type, flowType }) {
+ let pipeline = [{
+ $match: {
+ environment: env,
+ object_id: object_id,
+ type: type,
+ flowType: flowType
+ }
+ }, {
+ $group: {
+ _id: { sourceIPv4Address: '$sourceIPv4Address' },
+ sourceIPv4Address: { $first: '$sourceIPv4Address' }
+ }
+ }];
+
+ return Statistics.aggregate(pipeline);
+ },
+
+ 'statistics.dstIPv4Addresses?env&object_id&type&flowType'(
+ { env, object_id, type, flowType }) {
+ let pipeline = [{
+ $match: {
+ environment: env,
+ object_id: object_id,
+ type: type,
+ flowType: flowType
+ }
+ }, {
+ $group: {
+ _id: { destinationIPv4Address: '$destinationIPv4Addres' },
+ destinationIPv4Address: { $first: '$destinationIPv4Addres' }
+ }
+ }];
+
+ return Statistics.aggregate(pipeline);
+ },
+
+ 'statistics!graph-frames'({
+ env,
+ object_id,
+ type,
+ flowType,
+ timeStart,
+ timeEnd,
+ sourceMacAddress,
+ destinationMacAddress,
+ sourceIPv4Address,
+ destinationIPv4Address
+ }) {
+ let schema = createGraphQuerySchema(
+ env,
+ object_id,
+ type,
+ flowType,
+ timeStart,
+ timeEnd,
+ sourceMacAddress,
+ destinationMacAddress,
+ sourceIPv4Address,
+ destinationIPv4Address);
+
+ console.log('statistics!graph-frames');
+ console.log(`- env: ${env}`);
+ console.log(`- object_id: ${object_id}`);
+ console.log(`- type: ${type}`);
+ console.log(`- flowType: ${flowType}`);
+ console.log(`- timeStart: ${timeStart}`);
+ console.log(`- timeEnd: ${timeEnd}`);
+ console.log(`- sourceMacAddress: ${sourceMacAddress}`);
+ console.log(`- destinationMacAddress: ${destinationMacAddress}`);
+ console.log(`- sourceIPv4Address: ${sourceIPv4Address}`);
+ console.log(`- destinationIPv4Address: ${destinationIPv4Address}`);
+
+ //let data = Statistics.find(schema).fetch();
+ let data = Statistics.findOne(schema);
+ console.log(`- averageArrivalNanoSeconds: ${R.path([0, 'averageArrivalNanoSeconds'], data)}`);
+
+ return data;
+ }
+});
+
+
+
diff --git a/ui/imports/api/statistics/server/publications.js b/ui/imports/api/statistics/server/publications.js
new file mode 100644
index 0000000..f69be56
--- /dev/null
+++ b/ui/imports/api/statistics/server/publications.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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { Meteor } from 'meteor/meteor';
+//import { Counts } from 'meteor/tmeasday:publish-counts';
+import { Statistics } from '../statistics.js';
+import { createGraphQuerySchema } from '../helpers';
+
+Meteor.publish('statistics!graph-frames', function ({
+ env,
+ object_id,
+ type,
+ flowType,
+ timeStart,
+ sourceMacAddress,
+ destinationMacAddress,
+ sourceIPv4Address,
+ destinationIPv4Address
+}) {
+ console.log('server subscribe: statistics?graph-frames');
+
+ let schema = createGraphQuerySchema(
+ env,
+ object_id,
+ type,
+ flowType,
+ timeStart,
+ null,
+ sourceMacAddress,
+ destinationMacAddress,
+ sourceIPv4Address,
+ destinationIPv4Address);
+
+ console.log('statistics!graph-frames');
+ console.log(`- env: ${env}`);
+ console.log(`- object_id: ${object_id}`);
+ console.log(`- type: ${type}`);
+ console.log(`- flowType: ${flowType}`);
+ console.log(`- timeStart: ${timeStart}`);
+ console.log(`- sourceMacAddress: ${sourceMacAddress}`);
+ console.log(`- destinationMacAddress: ${destinationMacAddress}`);
+ console.log(`- sourceIPv4Address: ${sourceIPv4Address}`);
+ console.log(`- destinationIPv4Address: ${destinationIPv4Address}`);
+
+ return Statistics.find(schema);
+});
+
diff --git a/ui/imports/api/statistics/statistics.js b/ui/imports/api/statistics/statistics.js
new file mode 100644
index 0000000..3391933
--- /dev/null
+++ b/ui/imports/api/statistics/statistics.js
@@ -0,0 +1,14 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 { SimpleSchema } from 'meteor/aldeed:simple-schema';
+//import * as R from 'ramda';
+
+export const Statistics = new Mongo.Collection(
+ 'statistics', { idGeneration: 'MONGO' });
diff --git a/ui/imports/api/supported_environments/methods.js b/ui/imports/api/supported_environments/methods.js
new file mode 100644
index 0000000..1eda375
--- /dev/null
+++ b/ui/imports/api/supported_environments/methods.js
@@ -0,0 +1,8 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 /
+/////////////////////////////////////////////////////////////////////////////////////////
diff --git a/ui/imports/api/supported_environments/server/publications.js b/ui/imports/api/supported_environments/server/publications.js
new file mode 100644
index 0000000..8fef880
--- /dev/null
+++ b/ui/imports/api/supported_environments/server/publications.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { Meteor } from 'meteor/meteor';
+import { SupportedEnvironments,
+ subsNameSupportedEnvs
+} from '../supported_environments.js';
+
+Meteor.publish(subsNameSupportedEnvs, function () {
+ console.log(`server subscribtion to: ${subsNameSupportedEnvs}`);
+ return SupportedEnvironments.find({});
+});
diff --git a/ui/imports/api/supported_environments/supported_environments.js b/ui/imports/api/supported_environments/supported_environments.js
new file mode 100644
index 0000000..55c5745
--- /dev/null
+++ b/ui/imports/api/supported_environments/supported_environments.js
@@ -0,0 +1,49 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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';
+
+export const SupportedEnvironments = new Mongo.Collection(
+ 'supported_environments', { idGeneration: 'MONGO' });
+
+export const subsNameSupportedEnvs = 'supported-environments';
+
+export function isMonitoringSupported(distribution, type_drivers, mechanism_drivers) {
+ console.log('isMonitoringSupported');
+ console.log(`distribution: ${R.toString(distribution)}`);
+ console.log(`type_drivers: ${R.toString(type_drivers)}`);
+ console.log(`mechanism_drivers: ${R.toString(mechanism_drivers)}`);
+
+ let result = SupportedEnvironments.find({
+ 'environment.distribution': distribution,
+ 'environment.type_drivers': type_drivers,
+ 'environment.mechanism_drivers': { $in: mechanism_drivers },
+ 'features.monitoring': true
+ }).count() > 0;
+
+ console.log(`result: ${R.toString(result)}`);
+ return result;
+}
+
+export function isListeningSupported(distribution, type_drivers, mechanism_drivers) {
+ console.log('isListeningSupported');
+ console.log(`distribution: ${R.toString(distribution)}`);
+ console.log(`type_drivers: ${R.toString(type_drivers)}`);
+ console.log(`mechanism_drivers: ${R.toString(mechanism_drivers)}`);
+
+ let result = SupportedEnvironments.find({
+ 'environment.distribution': distribution,
+ 'environment.type_drivers': type_drivers,
+ 'environment.mechanism_drivers': { $in: mechanism_drivers },
+ 'features.listening': true
+ }).count() > 0;
+
+ console.log(`result: ${R.toString(result)}`);
+ return result;
+}
diff --git a/ui/imports/index.styl b/ui/imports/index.styl
new file mode 100644
index 0000000..d810c63
--- /dev/null
+++ b/ui/imports/index.styl
@@ -0,0 +1 @@
+@import 'ui/*'
diff --git a/ui/imports/lib/d3-graph.js b/ui/imports/lib/d3-graph.js
new file mode 100644
index 0000000..311ad95
--- /dev/null
+++ b/ui/imports/lib/d3-graph.js
@@ -0,0 +1,573 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 { Cliques } from '/imports/api/cliques/cliques';
+import { Links } from '/imports/api/links/links';
+import { NodeHoverAttr } from '/imports/api/attributes_for_hover_on_data/attributes_for_hover_on_data';
+import * as cola from 'webcola';
+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 * as R from 'ramda';
+
+let d3Graph = {
+ color:'',
+
+ zoomer:function(){
+ var width = '100%',
+ height = '100%';
+ var xScale = d3.scale.linear()
+ .domain([0,width]).range([0,width]);
+ var yScale = d3.scale.linear()
+ .domain([0,height]).range([0, height]);
+ return d3.behavior.zoom().
+ scaleExtent([0.1,10]).
+ x(xScale).
+ y(yScale).
+ on('zoomstart', d3Graph.zoomstart).
+ on('zoom', d3Graph.redraw);
+ },
+
+ svg:'',
+ force:'',
+ link:'',
+ node:'',
+ linkText:'',
+
+ graph:{
+ nodes:[],
+ links:[],
+ },
+
+ zoomstart:function () {
+ var node = d3Graph.svg.selectAll('.node');
+ node.each(function(d) {
+ d.selected = false;
+ d.previouslySelected = false;
+ });
+ node.classed('selected', false);
+ },
+
+ /* depreacted - not used ?
+ getGraphData:function(nodeId){
+
+ var invNodes = Inventory.find({ 'type': 'instance', $and: [ { 'host': nodeId } ] });
+
+ var edges = [];
+ var nodes = [];
+
+ invNodes.forEach(function(n){
+ nodes = n['Entities'];
+ edges = n['Relations'];
+ });
+
+ nodes.forEach(function(n){
+ n.name = n.object_name;
+ });
+
+ var edges_new = [];
+ edges.forEach(function(e) {
+ var sourceNode = nodes.filter(function(n) { return n.id === e.from; })[0],
+ targetNode = nodes.filter(function(n) { return n.id === e.to; })[0];
+
+ edges_new.push({source: sourceNode, target: targetNode, value: 1,label: e.label,attributes: e.attributes});
+ });
+//any links with duplicate source and target get an incremented 'linknum'
+ for (var i=0; i<edges_new.length; i++) {
+ if (i != 0 &&
+ edges_new[i].source == edges_new[i-1].source &&
+ edges_new[i].target == edges_new[i-1].target) {
+
+ edges_new[i].linknum = edges_new[i-1].linknum + 1;
+ }
+ else {edges_new[i].linknum = 1;}
+ }
+ //var graph = {};
+ this.graph.nodes = nodes;
+ this.graph.links = edges_new;
+
+ },
+ */
+
+ getGraphDataByClique:function(nodeObjId){
+ // Clique: one instance per graph. A focal point describing a node, and with links data.
+ // TODO: findOne or .each.
+ var cliques = Cliques.find({ focal_point: new Mongo.ObjectID(nodeObjId) }).fetch();
+
+
+ var nodes = [];
+ var edges_new = [];
+
+ if (R.length(cliques) === 0) {
+ return;
+ }
+
+ // CliquesLinks: All the links for a specific clique
+ var cliquesLinks = [];
+ cliques[0].links.forEach(function(n){
+ cliquesLinks.push(n);
+ });
+
+ // LinksList = Map(Clique.links -> links collection)
+ var linksList = Links.find({ _id: {$in: cliquesLinks}}).fetch();
+ //console.log(linksList);
+
+ // Create nodes from the links endpoints.
+ // Nodes = link source & target (objectid)
+ linksList.forEach(function(linkItem){
+ nodes.push(linkItem['source']);
+ nodes.push(linkItem['target']);
+ });
+
+ // NodesList = Nodes exapneded.
+ var nodesList = Inventory.find({ _id: {$in: nodes}}).fetch();
+
+ // Links list: expanend source/target nodes to create in memory data graph - links,nodes.
+ linksList.forEach(function(linkItem){
+ var sourceNode = nodesList.filter(function(n) {
+ return n._id._str === linkItem.source._str;
+ })[0];
+
+ var targetNode = nodesList.filter(function(n) {
+ return n._id._str === linkItem.target._str;
+ })[0];
+
+ edges_new.push({
+ source: sourceNode,
+ target: targetNode,
+ value: 1,
+ label: linkItem.link_name,
+ attributes: linkItem
+ });
+
+ });
+
+ // Expend nodeslist to include linked attributes.
+ nodesList.forEach(function(nodeItem){
+ nodeItem.attributes = [];
+ var attrHoverFields = NodeHoverAttr.find({ 'type': nodeItem['type']}).fetch();
+ if(attrHoverFields.length){
+ attrHoverFields[0].attributes.forEach(function(field){
+ if(nodeItem[field]){
+ var object = {};
+ object[field] = nodeItem[field];
+ nodeItem.attributes.push(object);
+ }
+ });
+ }
+ });
+
+ this.graph.nodes = nodesList;
+ this.graph.links = edges_new;
+
+ },
+
+ createGraphData: function (width, height){
+ //var self = this;
+ //var width = 500;
+ //var height = 900;
+
+ this.color = d3.scale.category20();
+ /*
+ this.svg = d3.select('#dgraphid').append('svg')
+ .attr('width', '100%')
+ .attr('height', '100%')
+ .attr('pointer-events', 'all')
+ //.attr('transform', 'translate(250,250) scale(0.3)')
+ .call(d3.behavior.zoom().on('zoom', this.redraw))
+ .append('svg:g');
+
+ //.append('g');
+
+ this.force = cola.d3adaptor().convergenceThreshold(0.1)
+ //.linkDistance(200)
+ .size([width, height]);
+ */
+ //var focused = null;
+
+ this.force = cola.d3adaptor().convergenceThreshold(0.1)
+ //.linkDistance(200)
+ .size([width, height]);
+
+ var outer = d3.select('#dgraphid')
+ .append('svg')
+ .attr({
+ width: '100%',
+ height: '100%',
+ 'pointer-events': 'all',
+ class: 'os-d3-graph'
+ });
+
+ outer.append('rect')
+ .attr({ class: 'background', width: '100%', height: '100%' })
+ .call(this.zoomer());
+ /*.call(d3.behavior.zoom()
+ .on('zoom', function(d) {
+ d3Graph.svg.attr('transform', 'translate(' + d3.event.translate + ')' + ' scale(' + d3.event.scale + ')');
+ }))*/
+ //.on('mouseover', function () { focused = this; });
+
+ //d3.select('body').on('keydown', function () { d3.select(focused); /* then do something with it here */ });
+ //d3.select('#dgraphid').on('keydown', d3Graph.keydown());
+
+ let scale = 0.5;
+
+ this.svg = outer
+ .append('g')
+ //.attr('transform', 'translate(250,250) scale(0.3)');
+ .attr('transform', 'translate(250,250) scale(' + scale.toString() + ')');
+
+ let fontSize = Math.floor(16 / scale);
+ d3Graph.svg.selectAll('.link-group text')
+ .style('font-size', fontSize + 'px');
+ d3Graph.svg.selectAll('.node text')
+ .style('font-size', fontSize + 'px');
+
+ },
+
+ redraw: function(){
+ //console.log('here', d3.event.translate, d3.event.scale);
+
+ d3Graph.svg.attr('transform',
+ 'translate(' + d3.event.translate + ')'
+ + ' scale(' + d3.event.scale + ')');
+
+ let fontSize = Math.floor(16 / d3.event.scale);
+ d3Graph.svg.selectAll('.link-group text')
+ .style('font-size', fontSize + 'px');
+ d3Graph.svg.selectAll('.node text')
+ .style('font-size', fontSize + 'px');
+
+ },
+
+ updateNetworkGraph:function (){
+ var self = this;
+
+ if (R.isNil(this.svg)) {
+ return;
+ }
+
+ this.svg.selectAll('g').remove();
+ //this.svg.exit().remove();
+
+ this.force
+ .nodes(this.graph.nodes)
+ .links(this.graph.links)
+ .symmetricDiffLinkLengths(250)
+ //.jaccardLinkLengths(300)
+ //.jaccardLinkLengths(80,0.7)
+ .handleDisconnected(true)
+ .avoidOverlaps(true)
+ .start(50, 100, 200);
+
+ /*
+ this.force
+ .on('dragstart', function (d) { d3.event.sourceEvent.stopPropagation(); d3.select(this).classed('dragging', true); } )
+ .on('drag', function (d) { d3.select(this).attr('cx', d.x = d3.event.x).attr('cy', d.y = d3.event.y); } )
+ .on('dragend', function (d) { d3.select(this).classed('dragging', false); });
+ */
+
+
+ // Define the div for the tooltip
+
+ //svg.exit().remove();
+ //graph.constraints = [{'axis':'y', 'left':0, 'right':1, 'gap':25},];
+
+ //.start(10,15,20);
+ /*var path = svg.append('svg:g')
+ .selectAll('path')
+ .data(force.links())
+ .enter().append('svg:path')
+ .attr('class', 'link');;
+ */
+ var link = this.svg.selectAll('.link')
+ .data(this.force.links())
+ .enter()
+ .append('g')
+ .attr('class', 'link-group')
+ .append('line')
+ .attr('class', 'link')
+ .style('stroke-width', function(_d) { return 3; })
+ //.style('stroke-width', function(d) { return Math.sqrt(d.stroke); })
+ .attr('stroke', function (d) {
+ if(d.attributes.state == 'error'){
+ self.blinkLink(d);
+ return 'red';
+ }
+ else if(d.attributes.state == 'warn'){
+ self.blinkLink(d);
+ return 'orange';
+ }
+ else if(d.source.level === d.target.level) {
+ return self.color(d.source.level);
+ }
+ else {
+ return self.color(d.level);
+ //d3.select(this).classed('different-groups', true);
+ }
+ });
+ /*.style('stroke', function(d) {
+ if(d.label == 'net-103'){
+ self.blinkLink(d);
+ return 'red';
+ }
+ //return 'red';
+ //return self.color(d.level);
+ })*/
+
+ var linkText = this.svg.selectAll('.link-group')
+ .append('text')
+ .data(this.force.links())
+ .text(function(d) { return d.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')
+ .on('mouseover', function(d) {
+ store.dispatch(activateGraphTooltipWindow(
+ d.label,
+ d.attributes,
+ d3.event.pageX,
+ d3.event.pageY
+ ));
+ })
+ .on('mouseout', function(_d) {
+ store.dispatch(closeGraphTooltipWindow());
+ });
+
+ var node = this.svg.selectAll('.node')
+ .data(this.force.nodes())
+ .enter().append('g')
+ .attr('class', 'node')
+ .call(this.force.drag);
+
+ // A map from group ID to image URL.
+ var imageByGroup = {
+ 'instance': 'ic_computer_black_48dp_2x.png',
+ 'pnic': 'ic_dns_black_48dp_2x.png',
+ 'vconnector': 'ic_settings_input_composite_black_48dp_2x.png',
+ // 'network': 'ic_cloud_queue_black_48dp_2x.png',
+ 'network': 'ic_cloud_queue_black_48dp_2x.png',
+ 'vedge': 'ic_gamepad_black_48dp_2x.png',
+ 'vservice': 'ic_storage_black_48dp_2x.png',
+ 'vnic': 'ic_settings_input_hdmi_black_48dp_2x.png',
+ 'otep':'ic_keyboard_return_black_48dp_2x.png',
+ 'default':'ic_lens_black_48dp_2x.png'
+ };
+
+ node.append('image')
+ //.attr('xlink:href', 'https://github.com/favicon.ico')
+ .attr('xlink:href', function(d) {
+ if(imageByGroup[d.type]){
+ return `/${imageByGroup[d.type]}`;
+ }
+ else{
+ return `/${imageByGroup['default']}`;
+ }
+
+ })
+ .attr('x', -8)
+ .attr('y', -8)
+ .attr('width', 36)
+ .attr('height', 36)
+ //node.append('circle')
+ .attr('class', 'node')
+ //.attr('r', function(d){return 13;})
+ .on('mouseover', function(d) {
+ store.dispatch(activateGraphTooltipWindow(
+ d.name,
+ d.attributes,
+ d3.event.pageX,
+ d3.event.pageY));
+ })
+ .on('mouseout', function(_d) {
+ store.dispatch(closeGraphTooltipWindow());
+ })
+ .on('click', function(d) {
+ if (d.type === 'vedge') {
+ store.dispatch(activateVedgeInfoWindow(
+ d,
+ d3.event.pageX,
+ d3.event.pageY));
+ }
+ })
+ .style('fill', function(d) {
+ if(d.state == 'error'){
+ self.blinkNode(d);
+ return 'red';
+ }
+ return self.color(d.group);
+ })
+ .call(this.force.drag);
+
+
+ /*
+ .each(function() {
+ var sel = d3.select(this);
+ var state = false;
+ sel.on('dblclick', function () {
+ state = !state;
+ if (state) {
+ sel.style('fill', 'black');
+ } else {
+ sel.style('fill', function (d) {
+ return d.colr;
+ });
+ }
+ });
+ });
+ */
+
+ node.append('text')
+ .attr('dx', 0)
+ .attr('dy', 40)
+ .text(function(d) { return d.object_name; });
+
+
+ this.force.on('tick', function() {
+ link.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; });
+ /*
+ .attr('dr1', function(d) { return 75/d.source.linknum; })
+ .attr('dr2', function(d) { return 75/d.target.linknum; });
+ */
+
+ node.attr('transform', function(d) {
+ return 'translate(' + d.x + ',' + d.y + ')';
+ });
+
+ linkText
+ .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);
+ });
+ });
+
+ },
+
+ centerview: function () {
+ // Center the view on the molecule(s) and scale it so that everything
+ // fits in the window
+ var width = 500;
+ var height = 500;
+
+ if (this.graph === null) return;
+
+ var nodes = this.graph.nodes;
+
+ //no molecules, nothing to do
+ if (nodes.length === 0) return;
+
+ // Get the bounding box
+ var min_x = d3.min(nodes.map(function(d) {return d.x;}));
+ var min_y = d3.min(nodes.map(function(d) {return d.y;}));
+
+ var max_x = d3.max(nodes.map(function(d) {return d.x;}));
+ var max_y = d3.max(nodes.map(function(d) {return d.y;}));
+
+
+ // The width and the height of the graph
+ var mol_width = max_x - min_x;
+ var mol_height = max_y - min_y;
+
+ // how much larger the drawing area is than the width and the height
+ var width_ratio = width / mol_width;
+ var height_ratio = height / mol_height;
+
+ // we need to fit it in both directions, so we scale according to
+ // the direction in which we need to shrink the most
+ var min_ratio = Math.min(width_ratio, height_ratio) * 0.8;
+
+ // the new dimensions of the molecule
+ var new_mol_width = mol_width * min_ratio;
+ var new_mol_height = mol_height * min_ratio;
+
+ // translate so that it's in the center of the window
+ var x_trans = -(min_x) * min_ratio + (width - new_mol_width) / 2;
+ var y_trans = -(min_y) * min_ratio + (height - new_mol_height) / 2;
+
+
+ // do the actual moving
+ d3Graph.svg.attr('transform',
+ 'translate(' + [x_trans, y_trans] + ')' + ' scale(' + min_ratio + ')');
+
+ // tell the zoomer what we did so that next we zoom, it uses the
+ // transformation we entered here
+ //d3Graph.zoomer.translate([x_trans, y_trans ]);
+ //d3Graph.zoomer.scale(min_ratio);
+ },
+
+ keydown:function() {
+/*
+ shiftKey = d3.event.shiftKey || d3.event.metaKey;
+ ctrlKey = d3.event.ctrlKey;
+*/
+ if(d3.event===null) return;
+
+ console.log('d3.event', d3.event);
+
+ if (d3.event.keyCode == 67) { //the 'c' key
+ this.centerview();
+ }
+
+ },
+
+ blinkNode: function(node){
+ var nodeList = this.svg.selectAll('.node');
+ var selected = nodeList.filter(function (d, _i) {
+ return d.id == node.id;
+ //return d.name != findFromParent;
+ });
+ selected.forEach(function(n){
+ for (var i = 0; i != 30; i++) {
+ $(n[1]).fadeTo('slow', 0.1).fadeTo('slow', 5.0);
+ }
+ });
+ },
+
+ blinkLink: function(link){
+ var linkList = this.svg.selectAll('.link');
+ var selected = linkList.filter(function (d, _i) {
+ return d.label == link.label;
+ //return d.id == link.id;
+ //return d.name != findFromParent;
+ });
+ selected.forEach(function(n){
+ for (var i = 0; i != 30; i++) {
+ $(n[0]).fadeTo('slow', 0.1).fadeTo('slow', 5.0);
+ }
+ });
+ },
+
+ tick:function(obj){
+ obj.link.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; });
+
+ obj.node.attr('transform', function(d) {
+ return 'translate(' + d.x + ',' + d.y + ')';
+ });
+
+ obj.linkText
+ .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);
+ });
+ }
+};
+
+export { d3Graph };
diff --git a/ui/imports/lib/d3three.js b/ui/imports/lib/d3three.js
new file mode 100644
index 0000000..51493f2
--- /dev/null
+++ b/ui/imports/lib/d3three.js
@@ -0,0 +1,789 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+var chartOffset = 0;
+
+// D3.layout.force3d.js
+// (C) 2012 ziggy.jonsson.nyc@gmail.com
+// BSD license (http://opensource.org/licenses/BSD-3-Clause)
+
+d3.layout.force3d = function() {
+ var forceXY = d3.layout.force()
+ ,forceZ = d3.layout.force()
+ ,zNodes = {}
+ ,zLinks = {}
+ ,nodeID = 1
+ ,linkID = 1
+ ,tickFunction = Object
+
+ var force3d = {}
+
+ Object.keys(forceXY).forEach(function(d) {
+ force3d[d] = function() {
+ var result = forceXY[d].apply(this,arguments)
+ if (d !="nodes" && d!="links") forceZ[d].apply(this,arguments)
+ return (result == forceXY) ? force3d : result
+ }
+ })
+
+
+ force3d.on = function(name,fn) {
+ tickFunction = fn
+ return force3d
+ }
+
+
+ forceXY.on("tick",function() {
+
+ // Refresh zNodes add new, delete removed
+ var _zNodes = {}
+ forceXY.nodes().forEach(function(d,i) {
+ if (!d.id) d.id = nodeID++
+ _zNodes[d.id] = zNodes[d.id] || {x:d.z,px:d.z,py:d.z,y:d.z,id:d.id}
+ d.z = _zNodes[d.id].x
+ })
+ zNodes = _zNodes
+
+ // Refresh zLinks add new, delete removed
+ var _zLinks = {}
+ forceXY.links().forEach(function(d) {
+ var nytt = false
+ if (!d.linkID) { d.linkID = linkID++;nytt=true}
+ _zLinks[d.linkID] = zLinks[d.linkID] || {target:zNodes[d.target.id],source:zNodes[d.source.id]}
+
+ })
+ zLinks = _zLinks
+
+ // Update the nodes/links in forceZ
+ forceZ.nodes(d3.values(zNodes))
+ forceZ.links(d3.values(zLinks))
+ forceZ.start() // Need to kick forceZ so we don't lose the update mechanism
+
+ // And run the user defined function, if defined
+ tickFunction()
+ })
+
+ // Expose the sub-forces for debugging purposes
+ force3d.xy = forceXY
+ force3d.z = forceZ
+
+ return force3d
+}
+// end of d3.layout.force3d.js
+
+// Override default functions for d3
+THREE.Object3D.prototype.appendChild = function (c) {
+ this.add(c);
+ return c;
+};
+THREE.Object3D.prototype.querySelectorAll = function () { return []; };
+
+// this one is to use D3's .attr() on THREE's objects
+THREE.Object3D.prototype.setAttribute = function (name, value) {
+ var chain = name.split('.');
+ var object = this;
+ for (var i = 0; i < chain.length - 1; i++) {
+ object = object[chain[i]];
+ }
+ object[chain[chain.length - 1]] = value;
+}
+
+// d3three object
+D3THREE = function(singleton) {
+ this.labelGroup = new THREE.Object3D();
+ this.maxY = 0;
+ this.axisObjects = {};
+
+ this.running = true;
+
+ if (singleton) {
+ if (typeof(d3three) !== 'undefined') {
+ d3three.stop();
+ }
+ d3three = this;
+ }
+
+ //if (!singleton) {
+ // d3threes.push(this);
+ //}
+}
+
+D3THREE.prototype.init = function(divId) {
+ // standard THREE stuff, straight from examples
+ this.renderer = new THREE.WebGLRenderer({antialias: true, alpha : true, preserveDrawingBuffer: true});
+ this.renderer.shadowMap.enabled = true;
+ this.renderer.shadowMap.type = THREE.PCFSoftShadow;
+ this.renderer.shadowMapSoft = true;
+ this.renderer.shadowCameraNear = 1000;
+ this.renderer.shadowCameraFar = 10000;
+ this.renderer.shadowCameraFov = 50;
+ this.renderer.shadowMapBias = 0.0039;
+ this.renderer.shadowMapDarkness = 0.25;
+ this.renderer.shadowMapWidth = 10000;
+ this.renderer.shadowMapHeight = 10000;
+ this.renderer.physicallyBasedShading = true;
+
+ this.divId = divId;
+ this.width = document.getElementById(divId).offsetWidth;
+ this.height = document.getElementById(divId).offsetHeight;
+
+ this.renderer.setSize( this.width, this.height );
+
+ document.getElementById(divId).appendChild( this.renderer.domElement );
+
+ this.camera = new THREE.PerspectiveCamera( 30, this.width / this.height, 1, 100000 );
+ this.camera.position.z = -1000;
+ this.camera.position.x = -800;
+ this.camera.position.y = 600;
+
+ this.controls = new THREE.OrbitControls( this.camera, this.renderer.domElement );
+
+ this.scene = new THREE.Scene();
+
+ this.defaultLight = new THREE.AmbientLight( 0xbbbbb ); // soft white light
+ this.scene.add( this.defaultLight );
+
+ this.scene.add(this.labelGroup);
+
+ var self = this;
+ window.addEventListener( 'resize', self.onWindowResize.bind(self), false );
+}
+
+D3THREE.prototype.onWindowResize = function() {
+ var self = this;
+ self.camera.aspect = self.width / self.height;
+ self.camera.updateProjectionMatrix();
+
+ self.renderer.setSize( self.width, self.height );
+}
+
+D3THREE.prototype.animate = function() {
+ var self = this;
+ if (this.running) {
+ setTimeout( function() {
+ this.requestId = requestAnimationFrame( self.animate.bind(self) );
+ }, 1000 / 15 );
+
+ self.renderer.render( self.scene, self.camera );
+ self.controls.update();
+
+ self.labelGroup.children.forEach(function(l){
+ l.rotation.setFromRotationMatrix(self.camera.matrix, "YXZ");
+ l.rotation.x = 0;
+ l.rotation.z = 0;
+ });
+ } else {
+ window.removeEventListener( 'resize', self.onWindowResize.bind(self) );
+ while (self.scene.children.length > 0) {
+ var childObject = self.scene.children[0];
+ if (childObject.geometry) {
+ childObject.geometry.dispose();
+ }
+ if (childObject.material) {
+ childObject.material.dispose();
+ }
+ self.scene.remove(childObject);
+ delete(childObject);
+ }
+
+ self.renderer.context = null;
+ self.renderer.domElement = null;
+ self.renderer = null;
+
+ self.camera = null;
+ self.controls = null;
+ self.scene = null;
+ self.labelGroup = null;
+
+ cancelAnimationFrame(self.requestId);
+ }
+}
+
+D3THREE.prototype.stop = function() {
+ this.running = false;
+}
+
+D3THREE.prototype.render = function(element, data) {
+ element.render(data);
+}
+
+D3THREE.createAxis = function(dt) {
+ return new D3THREE.Axis(dt);
+}
+
+// d3three axis
+D3THREE.Axis = function(dt) {
+ this._scale = d3.scale.linear();
+ this._orient = "x";
+ this._tickFormat = function(d) { return d };
+ this._dt = dt;
+}
+
+D3THREE.Axis.prototype.orient = function(o) {
+ if (o) {
+ this._dt.axisObjects[o] = this;
+ this._orient = o;
+ }
+ return this;
+}
+
+D3THREE.Axis.prototype.scale = function(s) {
+ if (s) {
+ this._scale = s;
+ }
+ return this;
+}
+
+D3THREE.Axis.prototype.tickFormat = function(f) {
+ if (f) {
+ this._tickFormat = f;
+ }
+ return this;
+}
+
+D3THREE.Axis.prototype.interval = function() {
+ var interval;
+ if (typeof(this._scale.rangeBand) === 'function') {
+ // ordinal scale
+ interval = this._scale.range()[1];
+ } else {
+ interval = this._scale.range()[1] / (this._scale.ticks().length - 1);
+ }
+ return interval;
+}
+
+D3THREE.Axis.prototype.ticks = function() {
+ var ticks;
+ if (typeof(this._scale.rangeBand) === 'function') {
+ // ordinal scale
+ ticks = this._scale.domain();
+ } else {
+ ticks = this._scale.ticks();
+ }
+ return ticks;
+}
+
+D3THREE.Axis.prototype.getRotationShift = function() {
+ return this.interval() * (this.ticks().length - 1) / 2;
+}
+
+D3THREE.Axis.prototype.render = function() {
+ var material = new THREE.LineBasicMaterial({
+ color: 0xbbbbbb,
+ linewidth: 2
+ });
+
+ var tickMaterial = new THREE.LineBasicMaterial({
+ color: 0xbbbbbb,
+ linewidth: 1
+ });
+
+ var geometry = new THREE.Geometry();
+
+ interval = this.interval();
+
+ var interval = this.interval(), ticks = this.ticks();
+
+ // x,y axis shift, so rotation is from center of screen
+ var xAxisShift = this._dt.axisObjects.x.getRotationShift(),
+ yAxisShift = this._dt.axisObjects.y.getRotationShift();
+
+ for (var i = 0; i < ticks.length; i++) {
+ var tickMarGeometry = new THREE.Geometry();
+
+ var shape = new THREE.TextGeometry(this._tickFormat(ticks[i]),
+ {
+ size: 5,
+ height: 1,
+ curveSegments: 20
+ });
+ var wrapper = new THREE.MeshBasicMaterial({color: 0xbbbbbb});
+ var words = new THREE.Mesh(shape, wrapper);
+
+ if (this._orient === "y") {
+ // tick
+ geometry.vertices.push(new THREE.Vector3(i * interval - yAxisShift, chartOffset, 0 - xAxisShift));
+
+ tickMarGeometry.vertices.push(new THREE.Vector3(i * interval - yAxisShift, chartOffset, 0 - xAxisShift));
+ tickMarGeometry.vertices.push(new THREE.Vector3(i * interval - yAxisShift, -10 + chartOffset, 0 - xAxisShift));
+ var tickLine = new THREE.Line(tickMarGeometry, tickMaterial);
+ this._dt.scene.add(tickLine);
+
+ if (i * interval > this._dt.maxY) {
+ this._dt.maxY = i * interval;
+ }
+
+ words.position.set(i * interval - yAxisShift, -20 + chartOffset, 0 - xAxisShift);
+ } else if (this._orient === "z") {
+ // tick
+ geometry.vertices.push(new THREE.Vector3(0 + this._dt.maxY - yAxisShift, i * interval + chartOffset, 0 - xAxisShift));
+
+ tickMarGeometry.vertices.push(new THREE.Vector3(0 + this._dt.maxY - yAxisShift, i * interval + chartOffset, 0 - xAxisShift));
+ tickMarGeometry.vertices.push(new THREE.Vector3(10 + this._dt.maxY - yAxisShift, i * interval + chartOffset, 0 - xAxisShift));
+ var tickLine = new THREE.Line(tickMarGeometry, tickMaterial);
+ this._dt.scene.add(tickLine);
+
+ words.position.set(20 + this._dt.maxY - yAxisShift, i * interval + chartOffset, 0 - xAxisShift);
+ } else if (this._orient === "x") {
+ // tick
+ geometry.vertices.push(new THREE.Vector3(0 - yAxisShift, chartOffset, i * interval - xAxisShift));
+
+ tickMarGeometry.vertices.push(new THREE.Vector3(0 - yAxisShift, 0 + chartOffset, i * interval - xAxisShift));
+ tickMarGeometry.vertices.push(new THREE.Vector3(0 - yAxisShift, -10 + chartOffset, i * interval - xAxisShift));
+ var tickLine = new THREE.Line(tickMarGeometry, tickMaterial);
+ this._dt.scene.add(tickLine);
+
+ words.position.set(0 - yAxisShift, -20 + chartOffset, i * interval - xAxisShift);
+ }
+
+ this._dt.labelGroup.add(words);
+ }
+
+ var line = new THREE.Line(geometry, material);
+
+ this._dt.scene.add(line);
+}
+
+// Chart object
+D3THREE.Chart = function() {
+}
+
+D3THREE.Chart.prototype.config = function(c) {
+ this._config = $.extend(this._config, c);
+}
+
+D3THREE.Chart.prototype.init = function(dt) {
+ this._dt = dt;
+ // mouse move
+ var self = this;
+ this._dt.renderer.domElement.addEventListener( 'mousemove', function(e) {
+ self.onDocumentMouseMove(e);
+ }, false );
+}
+
+var cumulativeOffset = function(element) {
+ var top = 0, left = 0;
+ do {
+ top += element.offsetTop || 0;
+ left += element.offsetLeft || 0;
+ element = element.offsetParent;
+ } while(element);
+
+ return {
+ top: top,
+ left: left
+ };
+};
+
+D3THREE.Chart.prototype.detectNodeHover = function(e) {
+ var boundingRect = this._dt.renderer.domElement.getBoundingClientRect();
+
+ var vector = new THREE.Vector3();
+ vector.x = ( (e.clientX - boundingRect.left) / this._dt.renderer.domElement.width ) * 2 - 1;
+ vector.y = 1 - ( (e.clientY - boundingRect.top) / this._dt.renderer.domElement.height ) * 2;
+ vector.z = 1;
+
+ // create a check ray
+ vector.unproject( this._dt.camera );
+ var ray = new THREE.Raycaster( this._dt.camera.position,
+ vector.sub( this._dt.camera.position ).normalize() );
+
+ var intersects = ray.intersectObjects( this._nodeGroup.children );
+
+ for (var i = 0; i < this._nodeGroup.children.length; i++) {
+ this._nodeGroup.children[i].material.opacity = 1;
+ }
+
+ if (intersects.length > 0) {
+ var obj = intersects[0].object;
+ obj.material.opacity = 0.5;
+
+ var html = "";
+
+ html += "<div class=\"tooltip_kv\">";
+ html += "<span>";
+ html += "x: " + this._dt.axisObjects.x._tickFormat(obj.userData.x);
+ html += "</span><br>";
+ html += "<span>";
+ html += "y: " + this._dt.axisObjects.y._tickFormat(obj.userData.y);
+ html += "</span><br>";
+ html += "<span>";
+ html += "z: " + this._dt.axisObjects.z._tickFormat(obj.userData.z);
+ html += "</span><br>";
+ html += "</div>";
+
+ document.getElementById("tooltip-container").innerHTML = html;
+ document.getElementById("tooltip-container").style.display = "block";
+
+ document.getElementById("tooltip-container").style.top = (e.pageY + 10) + "px";
+ document.getElementById("tooltip-container").style.left = (e.pageX + 10) + "px";
+ } else {
+ document.getElementById("tooltip-container").style.display = "none";
+ }
+}
+
+// Scatter plot
+D3THREE.Scatter = function(dt) {
+ this.init(dt);
+
+ this._nodeGroup = new THREE.Object3D();
+
+ this._config = {color: 0x4682B4, pointRadius: 5};
+}
+
+D3THREE.Scatter.prototype = new D3THREE.Chart();
+
+D3THREE.Scatter.prototype.onDocumentMouseMove = function(e) {
+ // detect intersected spheres
+ this.detectNodeHover(e);
+}
+
+D3THREE.Scatter.prototype.render = function(data) {
+ var geometry = new THREE.SphereGeometry( this._config.pointRadius, 32, 32 );
+
+ this._dt.scene.add(this._nodeGroup);
+
+ // x,y axis shift, so rotation is from center of screen
+ var xAxisShift = this._dt.axisObjects.x.getRotationShift(),
+ yAxisShift = this._dt.axisObjects.y.getRotationShift();
+
+ var self = this;
+ d3.select(this._nodeGroup)
+ .selectAll()
+ .data(data)
+ .enter().append( function(d) {
+ var material = new THREE.MeshBasicMaterial( {
+ color: self._config.color } );
+ var mesh = new THREE.Mesh( geometry, material );
+ mesh.userData = {x: d.x, y: d.y, z: d.z};
+ return mesh;
+ } )
+ .attr("position.z", function(d) {
+ return self._dt.axisObjects.x._scale(d.x) - xAxisShift;
+ })
+ .attr("position.x", function(d) {
+ return self._dt.axisObjects.y._scale(d.y) - yAxisShift;
+ })
+ .attr("position.y", function(d) {
+ return self._dt.axisObjects.z._scale(d.z) + chartOffset;
+ });
+}
+
+// Surface plot
+D3THREE.Surface = function(dt) {
+ this.init(dt);
+
+ this._nodeGroup = new THREE.Object3D();
+
+ this._config = {color: 0x4682B4, pointColor: 0xff7f0e, pointRadius: 2};
+}
+
+D3THREE.Surface.prototype = new D3THREE.Chart();
+
+D3THREE.Surface.prototype.onDocumentMouseMove = function(e) {
+ // detect intersected spheres
+ var boundingRect = this._dt.renderer.domElement.getBoundingClientRect();
+
+ var vector = new THREE.Vector3();
+ vector.x = ( (e.clientX - boundingRect.left) / this._dt.renderer.domElement.width ) * 2 - 1;
+ vector.y = 1 - ( (e.clientY - boundingRect.top) / this._dt.renderer.domElement.height ) * 2;
+ vector.z = 1;
+
+ // create a check ray
+ vector.unproject( this._dt.camera );
+ var ray = new THREE.Raycaster( this._dt.camera.position,
+ vector.sub( this._dt.camera.position ).normalize() );
+
+ var meshIntersects = ray.intersectObjects( [this._meshSurface] );
+
+ if (meshIntersects.length > 0) {
+ for (var i = 0; i < this._nodeGroup.children.length; i++) {
+ this._nodeGroup.children[i].visible = true;
+ this._nodeGroup.children[i].material.opacity = 1;
+ }
+
+ this.detectNodeHover(e);
+ } else {
+ // hide nodes
+ for (var i = 0; i < this._nodeGroup.children.length; i++) {
+ this._nodeGroup.children[i].visible = false;
+ }
+ }
+}
+
+D3THREE.Surface.prototype.render = function(threeData) {
+ /* render data points */
+ var geometry = new THREE.SphereGeometry( this._config.pointRadius, 32, 32 );
+
+ this._dt.scene.add(this._nodeGroup);
+
+ // x,y axis shift, so rotation is from center of screen
+ var xAxisShift = this._dt.axisObjects.x.getRotationShift(),
+ yAxisShift = this._dt.axisObjects.y.getRotationShift();
+
+ var self = this;
+ d3.select(this._nodeGroup)
+ .selectAll()
+ .data(threeData)
+ .enter().append( function(d) {
+ var material = new THREE.MeshBasicMaterial( {
+ color: self._config.pointColor } );
+ var mesh = new THREE.Mesh( geometry, material );
+ mesh.userData = {x: d.x, y: d.y, z: d.z};
+ mesh.visible = false;
+ return mesh;
+ } )
+ .attr("position.z", function(d) {
+ return self._dt.axisObjects.x._scale(d.x) - xAxisShift;
+ })
+ .attr("position.x", function(d) {
+ return self._dt.axisObjects.y._scale(d.y) - yAxisShift;
+ })
+ .attr("position.y", function(d) {
+ return self._dt.axisObjects.z._scale(d.z) + chartOffset;
+ });
+
+ /* custom surface */
+ function distance (v1, v2)
+ {
+ var dx = v1.x - v2.x;
+ var dy = v1.y - v2.y;
+ var dz = v1.z - v2.z;
+
+ return Math.sqrt(dx*dx+dz*dz);
+ }
+
+ var vertices = [];
+ var holes = [];
+ var triangles, mesh;
+ var geometry = new THREE.Geometry();
+ var material = new THREE.MeshBasicMaterial({color: this._config.color});
+
+ for (var i = 0; i < threeData.length; i++) {
+ vertices.push(new THREE.Vector3(
+ self._dt.axisObjects.y._scale(threeData[i].y) - yAxisShift,
+ self._dt.axisObjects.z._scale(threeData[i].z) + chartOffset,
+ self._dt.axisObjects.x._scale(threeData[i].x) - xAxisShift));
+ }
+
+ geometry.vertices = vertices;
+
+ for (var i = 0; i < vertices.length; i++) {
+ // find three closest vertices to generate surface
+ var v1, v2, v3;
+ var distances = [];
+
+ // find vertices in same y or y + 1 row
+ var minY = Number.MAX_VALUE;
+ for (var j = i + 1; j < vertices.length; j++) {
+ if (i !== j && vertices[j].x > vertices[i].x) {
+ if (vertices[j].x < minY) {
+ minY = vertices[j].x;
+ }
+ }
+ }
+
+ var rowVertices = [], row2Vertices = [];
+ for (var j = i + 1; j < vertices.length; j++) {
+ if (i !== j && (vertices[j].x === vertices[i].x)) {
+ rowVertices.push({index: j, v: vertices[j]});
+ }
+ if (i !== j && (vertices[j].x === minY)) {
+ row2Vertices.push({index: j, v: vertices[j]});
+ }
+ }
+
+ if (rowVertices.length >= 1 && row2Vertices.length >= 2) {
+ // find smallest x
+ rowVertices.sort(function(a, b) {
+ if (a.v.z < b.v.z) {
+ return -1;
+ } else if (a.v.z === b.v.z) {
+ return 0;
+ } else {
+ return 1;
+ }
+ });
+
+ v1 = rowVertices[0].index;
+
+ row2Vertices.sort(function(a, b) {
+ if (a.v.z < b.v.z) {
+ return -1;
+ } else if (a.v.z === b.v.z) {
+ return 0;
+ } else {
+ return 1;
+ }
+ });
+
+ v2 = row2Vertices[0].index;
+ v3 = row2Vertices[1].index;
+
+ var fv = [i, v1, v2, v3];
+ fv = fv.sort(function(a, b) {
+ if (a < b) return -1;
+ else if (a === b) return 0;
+ else return 1;
+ });
+
+ geometry.faces.push( new THREE.Face3(fv[1], fv[0], fv[3]));
+ geometry.faces.push( new THREE.Face3(fv[0], fv[2], fv[3]));
+ }
+ }
+
+ this._meshSurface = new THREE.Mesh( geometry, material );
+ this._dt.scene.add(this._meshSurface);
+}
+
+// Bar plot
+D3THREE.Bar = function(dt) {
+ this.init(dt);
+
+ this._nodeGroup = new THREE.Object3D();
+
+ this._config = {color: 0x4682B4, barSize: 5};
+}
+
+D3THREE.Bar.prototype = new D3THREE.Chart();
+
+D3THREE.Bar.prototype.onDocumentMouseMove = function(e) {
+ this.detectNodeHover(e);
+}
+
+D3THREE.Bar.prototype.render = function(threeData) {
+ /* render data points */
+ this._dt.scene.add(this._nodeGroup);
+
+ // x,y axis shift, so rotation is from center of screen
+ var xAxisShift = this._dt.axisObjects.x.getRotationShift(),
+ yAxisShift = this._dt.axisObjects.y.getRotationShift();
+
+ var self = this;
+ d3.select(this._nodeGroup)
+ .selectAll()
+ .data(threeData)
+ .enter().append( function(d) {
+ var height = self._dt.axisObjects.z._scale(d.z) + chartOffset;
+ var geometry = new THREE.BoxGeometry( self._config.barSize, height, self._config.barSize );
+ var material = new THREE.MeshBasicMaterial( {
+ color: self._config.color } );
+ var mesh = new THREE.Mesh( geometry, material );
+ mesh.userData = {x: d.x, y: d.y, z: d.z};
+ return mesh;
+ } )
+ .attr("position.z", function(d) {
+ return self._dt.axisObjects.x._scale(d.x) - xAxisShift;
+ })
+ .attr("position.x", function(d) {
+ return self._dt.axisObjects.y._scale(d.y) - yAxisShift;
+ })
+ .attr("position.y", function(d) {
+ var height = self._dt.axisObjects.z._scale(d.z) + chartOffset;
+ return height / 2;
+ });
+}
+
+// Force layout plot
+D3THREE.Force = function(dt) {
+ this.init(dt);
+
+ this._nodeGroup = new THREE.Object3D();
+
+ this._config = {color: 0x4682B4, linkColor: 0xcccccc, linkWidth: 1};
+}
+
+D3THREE.Force.prototype = new D3THREE.Chart();
+
+D3THREE.Force.prototype.onDocumentMouseMove = function(e) {
+}
+
+D3THREE.Force.prototype.render = function(threeData) {
+ var spheres = [], three_links = [];
+ // Define the 3d force
+ var force = d3.layout.force3d()
+ .nodes(sort_data=[])
+ .links(links=[])
+ .size([50, 50])
+ .gravity(0.3)
+ .charge(-400)
+
+ var DISTANCE = 1;
+
+ for (var i = 0; i < threeData.nodes.length; i++) {
+ sort_data.push({x:threeData.nodes.x + DISTANCE,y:threeData.nodes.y + DISTANCE,z:0})
+
+ // set up the sphere vars
+ var radius = 5,
+ segments = 16,
+ rings = 16;
+
+ // create the sphere's material
+ var sphereMaterial = new THREE.MeshLambertMaterial({ color: this._config.color });
+
+ var sphere = new THREE.Mesh(
+ new THREE.SphereGeometry(
+ radius,
+ segments,
+ rings),
+ sphereMaterial);
+
+ spheres.push(sphere);
+
+ // add the sphere to the scene
+ this._dt.scene.add(sphere);
+ }
+
+ for (var i = 0; i < threeData.links.length; i++) {
+ links.push({target:sort_data[threeData.links[i].target],source:sort_data[threeData.links[i].source]});
+
+ var material = new THREE.LineBasicMaterial({ color: this._config.linkColor,
+ linewidth: this._config.linkWidth});
+ var geometry = new THREE.Geometry();
+
+ geometry.vertices.push( new THREE.Vector3( 0, 0, 0 ) );
+ geometry.vertices.push( new THREE.Vector3( 0, 0, 0 ) );
+ var line = new THREE.Line( geometry, material );
+ line.userData = { source: threeData.links[i].source,
+ target: threeData.links[i].target };
+ three_links.push(line);
+ this._dt.scene.add(line);
+
+ force.start();
+ }
+
+ // set up the axes
+ var x = d3.scale.linear().domain([0, 350]).range([0, 10]),
+ y = d3.scale.linear().domain([0, 350]).range([0, 10]),
+ z = d3.scale.linear().domain([0, 350]).range([0, 10]);
+
+ var self = this;
+ force.on("tick", function(e) {
+ for (var i = 0; i < sort_data.length; i++) {
+ spheres[i].position.set(x(sort_data[i].x) * 40 - 40, y(sort_data[i].y) * 40 - 40,z(sort_data[i].z) * 40 - 40);
+
+ for (var j = 0; j < three_links.length; j++) {
+ var line = three_links[j];
+ var vi = -1;
+ if (line.userData.source === i) {
+ vi = 0;
+ }
+ if (line.userData.target === i) {
+ vi = 1;
+ }
+
+ if (vi >= 0) {
+ line.geometry.vertices[vi].x = x(sort_data[i].x) * 40 - 40;
+ line.geometry.vertices[vi].y = y(sort_data[i].y) * 40 - 40;
+ line.geometry.vertices[vi].z = y(sort_data[i].z) * 40 - 40;
+ line.geometry.verticesNeedUpdate = true;
+ }
+ }
+ }
+ });
+}
diff --git a/ui/imports/lib/general-regex.js b/ui/imports/lib/general-regex.js
new file mode 100644
index 0000000..184a63a
--- /dev/null
+++ b/ui/imports/lib/general-regex.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+export const portRegEx = /^0*(?:6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])$/;
+
+export const pathRegEx = /^(\/){1}([^\/\0]+(\/)?)+$/;
+
+export const hostnameRegex= new RegExp('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$');
+
+export const ipAddressRegex = new RegExp('(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}');
diff --git a/ui/imports/lib/icon.js b/ui/imports/lib/icon.js
new file mode 100644
index 0000000..1653bc2
--- /dev/null
+++ b/ui/imports/lib/icon.js
@@ -0,0 +1,14 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+export class Icon {
+ constructor({type, name}) {
+ this.type = type;
+ this.name = name;
+ }
+}
diff --git a/ui/imports/lib/images-for-node-type.js b/ui/imports/lib/images-for-node-type.js
new file mode 100644
index 0000000..5846f46
--- /dev/null
+++ b/ui/imports/lib/images-for-node-type.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+export let imagesForNodeType = {
+ 'instance': 'ic_computer_black_48dp_2x.png',
+ 'pnic': 'ic_dns_black_48dp_2x.png',
+ 'vconnector': 'ic_settings_input_composite_black_48dp_2x.png',
+ // 'network': 'ic_cloud_queue_black_48dp_2x.png',
+ 'network': 'ic_cloud_queue_black_48dp_2x.png',
+ 'vedge': 'ic_gamepad_black_48dp_2x.png',
+ 'vservice': 'ic_storage_black_48dp_2x.png',
+ 'vnic': 'ic_settings_input_hdmi_black_48dp_2x.png',
+ 'otep':'ic_keyboard_return_black_48dp_2x.png',
+};
+
+export let defaultNodeTypeImage = 'ic_lens_black_48dp_2x.png';
+
diff --git a/ui/imports/lib/regex-utils.js b/ui/imports/lib/regex-utils.js
new file mode 100644
index 0000000..fd9bce2
--- /dev/null
+++ b/ui/imports/lib/regex-utils.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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+export function regexEscape(s) {
+ return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+}
diff --git a/ui/imports/lib/simple-schema-utils.js b/ui/imports/lib/simple-schema-utils.js
new file mode 100644
index 0000000..3f2840b
--- /dev/null
+++ b/ui/imports/lib/simple-schema-utils.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+
+export let _idFieldDef = {
+ type: {
+ _str: { type: String, regEx: SimpleSchema.RegEx.Id }
+ }
+};
diff --git a/ui/imports/lib/utilities.js b/ui/imports/lib/utilities.js
new file mode 100644
index 0000000..e1143a3
--- /dev/null
+++ b/ui/imports/lib/utilities.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import * as R from 'ramda';
+
+export function idToStr(orgId) {
+ return R.ifElse(R.is(Mongo.ObjectID),
+ function (id) { return id.toHexString() + ':' + 'objectid'; },
+ R.identity
+ )(orgId);
+}
+
+export function parseReqId(pId) {
+ let idMatch = R.match(/(.*):objectid$/, pId);
+ if (idMatch.length === 0) {
+ return {
+ type: 'string',
+ id: pId
+ };
+ } else {
+ return {
+ type: 'objectid',
+ id: new Mongo.ObjectID(idMatch[1])
+ };
+ }
+}
+
+function calcColor(level) {
+ let r = 11;
+ let g = 122;
+ let b = 209;
+ //let a = 1;
+ let factor = level / 15;
+ factor = factor < 0 ? 0 : 1 - factor;
+
+ let nR = Math.floor(r * factor);
+ let nG = Math.floor(g * factor);
+ let nB = Math.floor(b * factor);
+ //let nA = a;
+ let colorStr = R.reduce((acc, colorPart) => {
+ let digits = colorPart.toString(16);
+ if (colorPart < 16) { digits = '0' + digits; }
+ return acc + digits;
+ }, '#', [nR, nG, nB]);
+
+ return colorStr;
+}
+
+export let calcColorMem = R.memoize(calcColor);
diff --git a/ui/imports/startup/both/config.js b/ui/imports/startup/both/config.js
new file mode 100644
index 0000000..ac7c9a2
--- /dev/null
+++ b/ui/imports/startup/both/config.js
@@ -0,0 +1,9 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 './configs/accounts';
diff --git a/ui/imports/startup/both/configs/accounts.js b/ui/imports/startup/both/configs/accounts.js
new file mode 100644
index 0000000..2653abe
--- /dev/null
+++ b/ui/imports/startup/both/configs/accounts.js
@@ -0,0 +1,9 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+
diff --git a/ui/imports/startup/both/index.js b/ui/imports/startup/both/index.js
new file mode 100644
index 0000000..62d0ef5
--- /dev/null
+++ b/ui/imports/startup/both/index.js
@@ -0,0 +1,10 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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/api/simple-schema.init';
+import './config';
diff --git a/ui/imports/startup/client/index.js b/ui/imports/startup/client/index.js
new file mode 100644
index 0000000..93da904
--- /dev/null
+++ b/ui/imports/startup/client/index.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import './template-helpers.js';
+
+import '/imports/ui/store/store';
+import '/imports/ui/components/landing/landing';
+import '/imports/ui/components/main/main';
+import '/imports/ui/components/loading/loading';
+import '/imports/ui/components/top-navbar-menu/top-navbar-menu';
+import '/imports/ui/components/environment/environment';
+import '/imports/ui/components/environment-wizard/environment-wizard';
+import '/imports/ui/components/scanning-request/scanning-request';
+import '/imports/ui/components/scheduled-scan/scheduled-scan';
+import '/imports/ui/components/project-dashboard/project-dashboard';
+import '/imports/ui/components/region-dashboard/region-dashboard';
+import '/imports/ui/components/zone-dashboard/zone-dashboard';
+import '/imports/ui/components/host-dashboard/host-dashboard';
+import '/imports/ui/components/aggregate-dashboard/aggregate-dashboard';
+import '/imports/ui/components/scans-list/scans-list';
+import '/imports/ui/components/scheduled-scans-list/scheduled-scans-list';
+import '/imports/ui/components/link-types-list/link-types-list';
+import '/imports/ui/components/link-type/link-type';
+import '/imports/ui/components/clique-types-list/clique-types-list';
+import '/imports/ui/components/clique-type/clique-type';
+import '/imports/ui/components/clique-constraints-list/clique-constraints-list';
+import '/imports/ui/components/clique-constraint/clique-constraint';
+import '/imports/ui/components/user-list/user-list';
+import '/imports/ui/components/user/user';
+import '/imports/ui/components/messages-list/messages-list';
+import '/imports/ui/components/message/message';
+import '/imports/ui/components/dashboard/dashboard';
+import '/imports/ui/components/new-scanning/new-scanning';
diff --git a/ui/imports/startup/client/template-helpers.js b/ui/imports/startup/client/template-helpers.js
new file mode 100644
index 0000000..89023b6
--- /dev/null
+++ b/ui/imports/startup/client/template-helpers.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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import * as R from 'ramda';
+import * as utils from '/imports/lib/utilities';
+import { Counter } from 'meteor/natestrauser:publish-performant-counts';
+
+Template.registerHelper('asHash', function (params) {
+ return params.hash;
+});
+
+Template.registerHelper('idToStr', utils.idToStr);
+
+Template.registerHelper('rPath', function (source, pathStr) {
+ let path = R.split('.', pathStr);
+ return R.path(path, source);
+});
+
+Template.registerHelper('asArray', function (val) {
+ return [val];
+});
+
+Template.registerHelper('countOf', function (name) {
+ if (name) {
+ return Counter.get(name);
+ }
+});
diff --git a/ui/imports/startup/server/config.js b/ui/imports/startup/server/config.js
new file mode 100644
index 0000000..ac7c9a2
--- /dev/null
+++ b/ui/imports/startup/server/config.js
@@ -0,0 +1,9 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 './configs/accounts';
diff --git a/ui/imports/startup/server/configs/accounts.js b/ui/imports/startup/server/configs/accounts.js
new file mode 100644
index 0000000..f098233
--- /dev/null
+++ b/ui/imports/startup/server/configs/accounts.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+Accounts.validateNewUser((_user) => {
+ let loggedInUser = Meteor.user();
+ if (Roles.userIsInRole(loggedInUser, 'manage-users', Roles.GLOBAL_GROUP)) {
+ return true;
+ }
+
+ throw new Meteor.Error(403, 'NotAuthorized to create new users');
+});
diff --git a/ui/imports/startup/server/index.js b/ui/imports/startup/server/index.js
new file mode 100644
index 0000000..ee22e45
--- /dev/null
+++ b/ui/imports/startup/server/index.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+// This defines all the collections, publications and methods that the application provides
+// as an API to the client.
+import './register-api.js';
+import './seeds.js';
+import './config.js';
diff --git a/ui/imports/startup/server/register-api.js b/ui/imports/startup/server/register-api.js
new file mode 100644
index 0000000..3475c53
--- /dev/null
+++ b/ui/imports/startup/server/register-api.js
@@ -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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import '../../api/constants/server/publications';
+
+import '../../api/environments/server/publications.js';
+import '../../api/environments/methods.js';
+
+import '../../api/inventories/server/publications.js';
+import '../../api/inventories/server/methods.js';
+
+import '../../api/scans/server/publications.js';
+import '../../api/scans/methods.js';
+import '../../api/scans/server/methods.js';
+
+import '../../api/scheduled-scans/server/publications.js';
+import '../../api/scheduled-scans/methods.js';
+import '../../api/scheduled-scans/server/methods.js';
+
+import '../../api/messages/server/publications';
+import '../../api/messages/server/methods';
+import '../../api/messages/methods.js';
+
+import '../../api/cliques/server/publications';
+import '../../api/cliques/methods.js';
+
+import '../../api/links/server/publications';
+import '../../api/links/methods.js';
+
+import '../../api/statistics/server/publications';
+import '../../api/statistics/methods.js';
+
+import '../../api/attributes_for_hover_on_data/server/publications';
+import '../../api/attributes_for_hover_on_data/methods.js';
+
+import '../../api/link-types/server/publications';
+import '../../api/link-types/methods.js';
+
+import '../../api/clique-types/server/publications';
+import '../../api/clique-types/methods.js';
+
+import '../../api/clique-constraints/server/publications';
+import '../../api/clique-constraints/methods.js';
+
+import '../../api/accounts/server/publications';
+import '../../api/accounts/methods';
+
+import '../../api/supported_environments/server/publications';
+import '../../api/supported_environments/methods';
+
+import '../../api/migrations/migrations';
diff --git a/ui/imports/startup/server/seeds.js b/ui/imports/startup/server/seeds.js
new file mode 100644
index 0000000..a6132bf
--- /dev/null
+++ b/ui/imports/startup/server/seeds.js
@@ -0,0 +1,10 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 './seeds/constants'; //disabled as of US2758.
+import './seeds/users';
diff --git a/ui/imports/startup/server/seeds/constants.js b/ui/imports/startup/server/seeds/constants.js
new file mode 100644
index 0000000..2d59d99
--- /dev/null
+++ b/ui/imports/startup/server/seeds/constants.js
@@ -0,0 +1,68 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 { Constants } from '/imports/api/constants/constants';
+import * as R from 'ramda';
+import { Distributions } from '/imports/api/constants/data/distributions';
+//import { NetworkPlugins } from './data/network-plugins';
+import { LogLevels } from '/imports/api/constants/data/log-levels';
+import { MechanismDrivers } from '/imports/api/constants/data/mechanism-drivers';
+import { ObjectTypesForLinks } from '/imports/api/constants/data/object-types-for-links';
+import { TypeDrivers } from '/imports/api/constants/data/type-drivers';
+import { EnvTypes } from '/imports/api/constants/data/env-types';
+import { Statuses as ScansStatuses } from '/imports/api/constants/data/scans-statuses';
+import { EnvironmentMonitoringTypes } from '/imports/api/constants/data/environment-monitoring-types';
+import { EnvProvisionTypes } from '/imports/api/constants/data/environment-provision-types';
+import { MessageSourceSystems } from '/imports/api/constants/data/message-source-systems';
+
+let constantsDefaults = [{
+ name: 'env_types',
+ values: EnvTypes
+}, {
+ name: 'scans_statuses',
+ values: ScansStatuses
+}, {
+ name: 'environment_monitoring_types',
+ values: EnvironmentMonitoringTypes
+}, {
+ name: 'distributions',
+ values: Distributions
+}, {
+ name: 'log_levels',
+ values: LogLevels
+}, {
+ name: 'mechanism_drivers',
+ values: MechanismDrivers
+}, {
+ name: 'object_types_for_links',
+ values: ObjectTypesForLinks
+}, {
+ name: 'type_drivers',
+ values: TypeDrivers
+}, {
+ name: 'environment_provision_types',
+ values: EnvProvisionTypes
+}, {
+ name: 'message_source_systems',
+ values: MessageSourceSystems
+}];
+
+if (Meteor.server) {
+ R.forEach((def) => {
+ insertConstants(Constants, def.name, def.values);
+ }, constantsDefaults);
+}
+
+function insertConstants(collection, name, values) {
+ if (collection.find({ name: name}).count() === 0) {
+ Constants.insert({
+ name: name,
+ data: values
+ });
+ }
+}
diff --git a/ui/imports/startup/server/seeds/users.js b/ui/imports/startup/server/seeds/users.js
new file mode 100644
index 0000000..34c20c6
--- /dev/null
+++ b/ui/imports/startup/server/seeds/users.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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+import * as R from 'ramda';
+import { Roles } from 'meteor/alanning:roles';
+
+let users = [
+ {
+ username: 'admin',
+ name: 'admin',
+ email: 'admin@example.com',
+ password: '123456',
+ roles: [
+ { role: 'manage-users', group: Roles.GLOBAL_GROUP },
+ { role: 'manage-link-types', group: Roles.GLOBAL_GROUP },
+ { role: 'manage-clique-types', group: Roles.GLOBAL_GROUP },
+ { role: 'manage-clique-constraints', group: Roles.GLOBAL_GROUP },
+ { role: 'view-env', group: Roles.GLOBAL_GROUP },
+ { role: 'edit-env', group: Roles.GLOBAL_GROUP },
+ ]
+ }
+];
+
+R.forEach((user) => {
+ let id;
+ let userDb = Meteor.users.findOne({ username: user.username });
+ if (R.isNil(userDb)) {
+ console.log('creating user', user);
+ id = Accounts.createUser({
+ username: user.username,
+ email: user.email,
+ password: user.password,
+ profile: { name: user.name }
+ });
+ } else {
+ id = userDb._id;
+ }
+
+ if (user.roles.length > 0) {
+ console.log('adding roles to user', user, user.roles);
+
+ R.forEach((roleItem) => {
+ Roles.addUsersToRoles(id, roleItem.role, roleItem.group);
+ }, user.roles);
+ }
+}, users);
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
+};
diff --git a/ui/lib/router.js b/ui/lib/router.js
new file mode 100644
index 0000000..37e8195
--- /dev/null
+++ b/ui/lib/router.js
@@ -0,0 +1,363 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/* Created by oashery on 3/2/2016. Modified by sisakov on 9/7/2016*/
+import * as R from 'ramda';
+import { Environments } from '/imports/api/environments/environments';
+import { parseReqId } from '/imports/lib/utilities';
+
+/*
+ *
+ */
+
+if (Meteor.isClient){
+ Accounts.onLogin(function () {
+ Router.go('/Dashboard');
+ });
+
+ Accounts.onLogout(function () {
+ Router.go('/');
+ });
+}
+
+Router.configure({
+ layoutTemplate: 'main',
+ loadingTemplate: 'loading'
+});
+
+if (Meteor.isClient) {
+ let originalDispatch = Router.dispatch;
+
+ Router.dispatch = function (_href, _a, _onDispatchComplete) {
+ let that = this;
+ let args = arguments;
+ let controller = that.current();
+ if (controller) {
+ let isDirty = Session.get('isDirty');
+ let needsConfirmation = controller.state.get('needsConfirmation');
+
+ if (needsConfirmation && isDirty) {
+ let confirmResult = confirm('Changes are not saved. Are you sure you want to leave?');
+ if (confirmResult) {
+ Session.set('isDirty', false);
+ } else {
+ let loc = Iron.Location.get();
+ loc.cancelUrlChange();
+ return;
+ }
+ }
+ }
+
+ originalDispatch.apply(that, args);
+ };
+}
+
+function confirm(msg) {
+ return window.confirm(msg);
+}
+/*
+ * Hooks
+ */
+
+Router.onBeforeAction(function () {
+ if (Meteor.userId()) {
+ this.next();
+ } else {
+ this.layout('landing');
+ this.render('landing');
+ }
+});
+
+/*
+ * Routes
+ */
+
+Router.route('/', {
+ name: 'landing',
+ path: '/',
+ action: function () {
+ if (Meteor.userId()) {
+ Router.go('/Dashboard');
+ }
+ if (this.ready())
+ this.layout('landing');
+ else
+ this.render('loading');
+ }
+});
+
+Router.route('home', {
+ path: '/home',
+ /* refactor to component. home not in use ?
+ waitOn: function () {
+ return Meteor.subscribe('inventory');
+ },
+ */
+ action: function () {
+ if (this.ready()){
+
+ this.state.set('envName', this.params.query.env);
+ /*
+ if(query){
+ //return Inventory.find({$where: "this.id_path.match('^/WebEX-Mirantis@Cisco/')"});
+ console.log(query);
+ this.render('home', {
+ data: function () {
+ return Inventory.find({environment: query, parent_id: query});
+ }
+ });
+ //
+ }
+ */
+
+ // if the sub handle returned from waitOn ready() method returns
+ // true then we're ready to go ahead and render the page.
+ this.render('home');
+
+ }
+ else{
+ this.render('loading');
+ }
+ }
+});
+
+Router.route('getstarted', {
+ name: 'getstarted',
+ path: '/getstarted'
+});
+
+Router.route('/wizard', function () {
+ this.state.set('env', null);
+ Session.set('wizardEnv', null);
+ this.render('EnvironmentWizard');
+});
+
+Router.route('/wizard/:env', function () {
+ this.state.set('env', this.params.env);
+ Session.set('wizardEnv', this.params.env);
+ this.render('EnvironmentWizard');
+});
+
+Router.route('/scans-list', function () {
+ this.render('ScansList');
+}, { });
+
+Router.route('/scheduled-scans-list', function () {
+ this.render('ScheduledScansList');
+}, {
+ name: 'scheduled-scans-list',
+ data: function () {
+ //let that = this;
+ let data = {};
+ return data;
+ }
+});
+
+Router.route('/link-types-list', function () {
+ this.render('LinkTypesList');
+}, { });
+
+Router.route('/link-type', function () {
+ this.render('LinkType');
+}, { });
+
+Router.route('/clique-types-list', function () {
+ this.render('CliqueTypesList');
+}, { });
+
+Router.route('/clique-type', function () {
+ this.render('CliqueType');
+}, { });
+
+Router.route('/clique-constraints-list', function () {
+ this.render('CliqueConstraintsList');
+}, { });
+
+Router.route('/clique-constraint', function () {
+ this.render('CliqueConstraint');
+}, { });
+
+Router.route('/messages-list', function () {
+ this.render('MessagesList');
+}, { });
+
+Router.route('/message', function () {
+ let that = this;
+ let params = that.params;
+ let query = params.query;
+
+ this.render('Message', {
+ data: function () {
+ return {
+ id: query.id,
+ action: query.action
+ };
+ }
+ });
+}, { });
+
+Router.route('/user-list', function () {
+ this.render('UserList');
+}, { });
+
+Router.route('/user', function () {
+ this.render('User');
+}, { });
+
+Router.route('/scanning-request', function () {
+ this.render('ScanningRequest');
+}, {
+ name: 'scanning-request',
+ data: function () {
+ let that = this;
+
+ let _id =
+ R.when(R.pipe(R.isNil, R.not),
+ (idObj) => R.prop('id', parseReqId(idObj))
+ )(R.path(['params', 'query', '_id'], that));
+
+ let data = {
+ _id: _id,
+ env: that.params.query.env,
+ action: that.params.query.action,
+ };
+
+ return data;
+ }
+});
+
+
+Router.route('/scheduled-scan', function () {
+ this.render('ScheduledScan');
+}, {
+ name: 'scheduled-scan',
+ data: function () {
+ let that = this;
+
+ let _id =
+ R.when(R.pipe(R.isNil, R.not),
+ (idObj) => R.prop('id', parseReqId(idObj))
+ )(R.path(['params', 'query', '_id'], that));
+
+ let data = {
+ _id: _id,
+ env: that.params.query.env,
+ action: that.params.query.action,
+ };
+
+ return data;
+ }
+});
+
+Router.route('/new-scanning', function () {
+ this.render('NewScanning');
+}, {
+ name: 'new-scanning',
+ data: function () {
+ let that = this;
+
+ let data = {
+ env: that.params.query.env,
+ };
+
+ return data;
+ }
+});
+
+Router.route('Dashboard', {
+ name: 'Dashboard',
+ path: '/Dashboard',
+ /* eyaltask
+ waitOn: function () {
+ return Meteor.subscribe('inventory');
+ },
+ */
+ action: function () {
+ if (this.ready()){
+ this.render('Dashboard');
+
+ }
+ else{
+ this.render('loading');
+ }
+ }
+});
+
+Router.route('environment', {
+ name: 'environment',
+ path: '/environment/:_id',
+ action: function () {
+ if (this.ready()){
+ this.render('Environment');
+ }
+ else{
+ this.render('loading');
+ }
+ },
+ data: function () {
+ let that = this;
+
+ let _id = parseReqId(that.params._id).id;
+ let selectedNodeId = R.ifElse(R.isNil,
+ R.always(null),
+ R.pipe(
+ R.curry(parseReqId),
+ R.prop('id')
+ )
+ )(that.params.query.selectedNodeId);
+
+ let data = { _id: _id };
+
+ if (! R.isNil(selectedNodeId)) {
+ data = R.assoc('selectedNodeId', selectedNodeId, data);
+ }
+
+ return data;
+ }
+});
+
+
+Router.route('migrateEnvToUserId', {
+ name: 'migrateEnvToUserId',
+ where: 'server',
+ action: function () {
+ console.log('migrate env to user id');
+
+ //let request = this.request;
+ let response = this.response;
+
+ let envs = Environments.find({}).fetch();
+ R.forEach((env) => {
+ console.log('found env: ' + env.name + ' ' + R.toString(env._id));
+
+ let user = Meteor.users.findOne({ username: env.user });
+ if (R.isNil(user)) {
+ console.log('not migrated: ' + env.name);
+ return;
+ }
+ console.log('found user: ' + user._id + ' ' + user.username);
+
+ try {
+ let result = Environments.update(
+ { _id : env._id },
+ {
+ $set: {
+ user: user._id
+ }
+ });
+ console.log('result', R.toString(result));
+ console.log('migrated: ' + env.name);
+ } catch(e) {
+ console.log('exception', R.toString(e));
+ }
+
+ }, envs);
+
+ response.end('migration end');
+ }
+});
diff --git a/ui/osdna.js b/ui/osdna.js
new file mode 100644
index 0000000..8eda6eb
--- /dev/null
+++ b/ui/osdna.js
@@ -0,0 +1,9 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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 /
+/////////////////////////////////////////////////////////////////////////////////////////
+cola = require('webcola'); \ No newline at end of file
diff --git a/ui/package-source.sh b/ui/package-source.sh
new file mode 100755
index 0000000..1f89901
--- /dev/null
+++ b/ui/package-source.sh
@@ -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 #
+########################################################################################
+#!/usr/bin/env bash
+
+OS_TARGET_PATH=".."
+
+# Compress current source to a tar object.
+# -exclude: Excluding specific folders and files
+# -z: use gzip
+# -c: create new archive
+# -v: verbose log file processed
+# -f: use archive file as target to build the tar to.
+tar \
+ --exclude='./.meteor/local' \
+ --exclude='./node_modules' \
+ --exclude='./.git' \
+ -zcvf $OS_TARGET_PATH/calipso-source-$(date +%Y-%m-%d-%s).tar.gz .
diff --git a/ui/package.json b/ui/package.json
new file mode 100644
index 0000000..531452b
--- /dev/null
+++ b/ui/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "calipso",
+ "version": "1.0.0",
+ "description": "",
+ "dependencies": {
+ "babel-runtime": "^6.23.0",
+ "bcrypt": "^1.0.2",
+ "bson": "^1.0.4",
+ "d3": "^4.5.0",
+ "jquery": "^1.9.1",
+ "jquery.scrollto": "^2.1.2",
+ "meteor-node-stubs": "^0.2.1",
+ "ramda": "^0.24.1",
+ "reactive-redux": "^1.1.0",
+ "redux": "^3.6.0",
+ "redux-thunk": "^2.1.0",
+ "rrule": "^2.2.0",
+ "webcola": "^3.1.1"
+ },
+ "devDependencies": {},
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC"
+}
diff --git a/ui/public/cisco-favicon.ico b/ui/public/cisco-favicon.ico
new file mode 100644
index 0000000..7cdb889
--- /dev/null
+++ b/ui/public/cisco-favicon.ico
Binary files differ
diff --git a/ui/public/cisco-logo-load.png b/ui/public/cisco-logo-load.png
new file mode 100644
index 0000000..6186978
--- /dev/null
+++ b/ui/public/cisco-logo-load.png
Binary files differ
diff --git a/ui/public/dna-718905_600.png b/ui/public/dna-718905_600.png
new file mode 100644
index 0000000..4c99cdb
--- /dev/null
+++ b/ui/public/dna-718905_600.png
Binary files differ
diff --git a/ui/public/get-started/dashboard-notifications.png b/ui/public/get-started/dashboard-notifications.png
new file mode 100644
index 0000000..695d783
--- /dev/null
+++ b/ui/public/get-started/dashboard-notifications.png
Binary files differ
diff --git a/ui/public/get-started/env-dashboard.png b/ui/public/get-started/env-dashboard.png
new file mode 100644
index 0000000..cb9ba7c
--- /dev/null
+++ b/ui/public/get-started/env-dashboard.png
Binary files differ
diff --git a/ui/public/get-started/env-delete-action.png b/ui/public/get-started/env-delete-action.png
new file mode 100644
index 0000000..79dc801
--- /dev/null
+++ b/ui/public/get-started/env-delete-action.png
Binary files differ
diff --git a/ui/public/get-started/env-diting-os-db-credentails.png b/ui/public/get-started/env-diting-os-db-credentails.png
new file mode 100644
index 0000000..62bf19c
--- /dev/null
+++ b/ui/public/get-started/env-diting-os-db-credentails.png
Binary files differ
diff --git a/ui/public/get-started/env-edit-action.png b/ui/public/get-started/env-edit-action.png
new file mode 100644
index 0000000..04cd439
--- /dev/null
+++ b/ui/public/get-started/env-edit-action.png
Binary files differ
diff --git a/ui/public/get-started/env-editing-amqp-credentials.png b/ui/public/get-started/env-editing-amqp-credentials.png
new file mode 100644
index 0000000..53c77ad
--- /dev/null
+++ b/ui/public/get-started/env-editing-amqp-credentials.png
Binary files differ
diff --git a/ui/public/get-started/env-editing-master-host-credentials.png b/ui/public/get-started/env-editing-master-host-credentials.png
new file mode 100644
index 0000000..c069c37
--- /dev/null
+++ b/ui/public/get-started/env-editing-master-host-credentials.png
Binary files differ
diff --git a/ui/public/get-started/env-editing-monitoring.png b/ui/public/get-started/env-editing-monitoring.png
new file mode 100644
index 0000000..0dc87bc
--- /dev/null
+++ b/ui/public/get-started/env-editing-monitoring.png
Binary files differ
diff --git a/ui/public/get-started/env-editing-os-end-point.png b/ui/public/get-started/env-editing-os-end-point.png
new file mode 100644
index 0000000..6221ec5
--- /dev/null
+++ b/ui/public/get-started/env-editing-os-end-point.png
Binary files differ
diff --git a/ui/public/get-started/env-editing-provision.png b/ui/public/get-started/env-editing-provision.png
new file mode 100644
index 0000000..5048ed0
--- /dev/null
+++ b/ui/public/get-started/env-editing-provision.png
Binary files differ
diff --git a/ui/public/get-started/env-editing.png b/ui/public/get-started/env-editing.png
new file mode 100644
index 0000000..2db9fc1
--- /dev/null
+++ b/ui/public/get-started/env-editing.png
Binary files differ
diff --git a/ui/public/get-started/environments-pick.png b/ui/public/get-started/environments-pick.png
new file mode 100644
index 0000000..8f87f71
--- /dev/null
+++ b/ui/public/get-started/environments-pick.png
Binary files differ
diff --git a/ui/public/get-started/main-screen-explained.png b/ui/public/get-started/main-screen-explained.png
new file mode 100644
index 0000000..a23e568
--- /dev/null
+++ b/ui/public/get-started/main-screen-explained.png
Binary files differ
diff --git a/ui/public/get-started/navbar-explained.png b/ui/public/get-started/navbar-explained.png
new file mode 100644
index 0000000..cd1ca80
--- /dev/null
+++ b/ui/public/get-started/navbar-explained.png
Binary files differ
diff --git a/ui/public/get-started/new-environment-action.png b/ui/public/get-started/new-environment-action.png
new file mode 100644
index 0000000..21e7f2d
--- /dev/null
+++ b/ui/public/get-started/new-environment-action.png
Binary files differ
diff --git a/ui/public/get-started/scan-action.png b/ui/public/get-started/scan-action.png
new file mode 100644
index 0000000..b64c18e
--- /dev/null
+++ b/ui/public/get-started/scan-action.png
Binary files differ
diff --git a/ui/public/get-started/scan-request-screen.png b/ui/public/get-started/scan-request-screen.png
new file mode 100644
index 0000000..b5859c4
--- /dev/null
+++ b/ui/public/get-started/scan-request-screen.png
Binary files differ
diff --git a/ui/public/get-started/selecting-dashboard.png b/ui/public/get-started/selecting-dashboard.png
new file mode 100644
index 0000000..0583123
--- /dev/null
+++ b/ui/public/get-started/selecting-dashboard.png
Binary files differ
diff --git a/ui/public/get-started/selecting-env.png b/ui/public/get-started/selecting-env.png
new file mode 100644
index 0000000..646830e
--- /dev/null
+++ b/ui/public/get-started/selecting-env.png
Binary files differ
diff --git a/ui/public/get-started/setting-action.png b/ui/public/get-started/setting-action.png
new file mode 100644
index 0000000..c6b6296
--- /dev/null
+++ b/ui/public/get-started/setting-action.png
Binary files differ
diff --git a/ui/public/get-started/setting-scans-action.png b/ui/public/get-started/setting-scans-action.png
new file mode 100644
index 0000000..cab6e39
--- /dev/null
+++ b/ui/public/get-started/setting-scans-action.png
Binary files differ
diff --git a/ui/public/ic_cloud_queue_black_48dp_2x.png b/ui/public/ic_cloud_queue_black_48dp_2x.png
new file mode 100644
index 0000000..ce8ffa5
--- /dev/null
+++ b/ui/public/ic_cloud_queue_black_48dp_2x.png
Binary files differ
diff --git a/ui/public/ic_computer_black_48dp_2x.png b/ui/public/ic_computer_black_48dp_2x.png
new file mode 100644
index 0000000..04755ea
--- /dev/null
+++ b/ui/public/ic_computer_black_48dp_2x.png
Binary files differ
diff --git a/ui/public/ic_dns_black_48dp_2x.png b/ui/public/ic_dns_black_48dp_2x.png
new file mode 100644
index 0000000..186be89
--- /dev/null
+++ b/ui/public/ic_dns_black_48dp_2x.png
Binary files differ
diff --git a/ui/public/ic_gamepad_black_48dp_2x.png b/ui/public/ic_gamepad_black_48dp_2x.png
new file mode 100644
index 0000000..d558cd5
--- /dev/null
+++ b/ui/public/ic_gamepad_black_48dp_2x.png
Binary files differ
diff --git a/ui/public/ic_keyboard_return_black_48dp_2x.png b/ui/public/ic_keyboard_return_black_48dp_2x.png
new file mode 100644
index 0000000..7b27ff1
--- /dev/null
+++ b/ui/public/ic_keyboard_return_black_48dp_2x.png
Binary files differ
diff --git a/ui/public/ic_lens_black_48dp_2x.png b/ui/public/ic_lens_black_48dp_2x.png
new file mode 100644
index 0000000..d4ce2cf
--- /dev/null
+++ b/ui/public/ic_lens_black_48dp_2x.png
Binary files differ
diff --git a/ui/public/ic_settings_input_composite_black_48dp_2x.png b/ui/public/ic_settings_input_composite_black_48dp_2x.png
new file mode 100644
index 0000000..80407f9
--- /dev/null
+++ b/ui/public/ic_settings_input_composite_black_48dp_2x.png
Binary files differ
diff --git a/ui/public/ic_settings_input_hdmi_black_48dp_2x.png b/ui/public/ic_settings_input_hdmi_black_48dp_2x.png
new file mode 100644
index 0000000..b7427a8
--- /dev/null
+++ b/ui/public/ic_settings_input_hdmi_black_48dp_2x.png
Binary files differ
diff --git a/ui/public/ic_storage_black_48dp_2x.png b/ui/public/ic_storage_black_48dp_2x.png
new file mode 100644
index 0000000..3a4c6b0
--- /dev/null
+++ b/ui/public/ic_storage_black_48dp_2x.png
Binary files differ
diff --git a/ui/public/images/calipso-logo.jpg b/ui/public/images/calipso-logo.jpg
new file mode 100644
index 0000000..9a47732
--- /dev/null
+++ b/ui/public/images/calipso-logo.jpg
Binary files differ
diff --git a/ui/public/instance.png b/ui/public/instance.png
new file mode 100644
index 0000000..06e2c1c
--- /dev/null
+++ b/ui/public/instance.png
Binary files differ
diff --git a/ui/public/landing/calipso-logo.png b/ui/public/landing/calipso-logo.png
new file mode 100644
index 0000000..b93de04
--- /dev/null
+++ b/ui/public/landing/calipso-logo.png
Binary files differ
diff --git a/ui/public/landing/docker.png b/ui/public/landing/docker.png
new file mode 100644
index 0000000..884beb7
--- /dev/null
+++ b/ui/public/landing/docker.png
Binary files differ
diff --git a/ui/public/landing/eye.png b/ui/public/landing/eye.png
new file mode 100644
index 0000000..c2e587f
--- /dev/null
+++ b/ui/public/landing/eye.png
Binary files differ
diff --git a/ui/public/landing/first.png b/ui/public/landing/first.png
new file mode 100644
index 0000000..ff5b6ec
--- /dev/null
+++ b/ui/public/landing/first.png
Binary files differ
diff --git a/ui/public/landing/graph.png b/ui/public/landing/graph.png
new file mode 100644
index 0000000..7a70522
--- /dev/null
+++ b/ui/public/landing/graph.png
Binary files differ
diff --git a/ui/public/landing/main-screen.png b/ui/public/landing/main-screen.png
new file mode 100644
index 0000000..258c093
--- /dev/null
+++ b/ui/public/landing/main-screen.png
Binary files differ
diff --git a/ui/public/landing/openstack.png b/ui/public/landing/openstack.png
new file mode 100644
index 0000000..2603a9d
--- /dev/null
+++ b/ui/public/landing/openstack.png
Binary files differ
diff --git a/ui/public/logo-cisco-png.png b/ui/public/logo-cisco-png.png
new file mode 100644
index 0000000..55cff5f
--- /dev/null
+++ b/ui/public/logo-cisco-png.png
Binary files differ
diff --git a/ui/public/logo-cisco.svg b/ui/public/logo-cisco.svg
new file mode 100644
index 0000000..bc541f0
--- /dev/null
+++ b/ui/public/logo-cisco.svg
@@ -0,0 +1,37 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 234.9 612 323" enable-background="new 0 234.9 612 323" xml:space="preserve">
+<g>
+ <rect x="173.4" y="449.1" fill="#fff" width="27.2" height="106.2"/>
+ <path fill="#fff" d="M416.5,479.7c-0.8-0.9-9.3-6-23-6c-17,0-29.8,11.9-29.8,28.1c0,16.1,11.9,28,29.8,28
+ c12.8,0,21.2-5.1,23-6v28.9c-3.4,0.8-12.8,3.4-24.7,3.4c-30.6,0-56.9-20.4-56.9-55.2c0-31.5,23.8-55.2,56.9-55.2
+ c12.8,0,22.1,3.4,24.7,3.4V479.7z"/>
+ <path fill="#fff" d="M136,479.7c-0.9-0.9-9.4-6-22.9-6c-17,0-29.8,11.9-29.8,28.1c0,16.1,11.9,28,29.8,28
+ c12.8,0,21.2-5.1,22.9-6v28.9c-3.4,0.8-12.8,3.4-24.6,3.4c-29.8,0-57-20.4-57-55.2c0-31.5,23.8-55.2,57-55.2
+ c12.8,0,22.1,3.4,24.6,3.4V479.7z"/>
+ <path fill="#fff" d="M556.8,502.7c0,30.6-23.8,55.2-56.1,55.2s-56.1-24.7-56.1-55.2s23.8-55.2,56.1-55.2
+ C533.8,447.4,556.8,472.1,556.8,502.7z M500.7,474.6c-16.2,0-28.1,12.8-28.1,28s11.9,28,28.1,28c16.2,0,28-12.8,28-28
+ S516.8,474.6,500.7,474.6z"/>
+ <path fill="#fff" d="M301.8,473.8c0,0-11.9-3.4-21.2-3.4c-11,0-17,3.4-17,8.5c0,6.8,7.6,8.5,11.9,10.2l7.6,2.5
+ c17,6,25.5,17.8,25.5,30.6c0,26.3-23.8,35.7-44.2,35.7c-14.4,0-28-2.5-28.9-2.5v-24.7c2.5,0.8,13.6,4.2,25.5,4.2
+ c13.6,0,19.6-4.2,19.6-10.2c0-5.1-5.1-8.5-11.9-10.2c-1.7-0.8-4.2-1.7-6-1.7c-15.3-5.1-27.2-13.6-27.2-31.4
+ c0-19.5,15.3-33.1,39.9-33.1c12.8,0,25.5,3.4,26.3,3.4V473.8z"/>
+ <path fill="#fff" d="M26.3,335.2c0-7.6-5.9-13.6-13.6-13.6s-13.6,5.9-13.6,13.6v28.1c0,7.6,5.9,13.6,13.6,13.6s13.6-6,13.6-13.6
+ V335.2z"/>
+ <path fill="#fff" d="M99.4,298.7c0-7.6-5.9-13.6-13.6-13.6s-12.7,6-12.7,13.6v64.6c0,7.6,5.9,13.6,13.6,13.6s13.6-6,13.6-13.6
+ v-64.6H99.4z"/>
+ <path fill="#fff" d="M173.4,248.5c0-7.7-5.9-13.6-13.6-13.6c-7.6,0-13.6,5.9-13.6,13.6v141.1c0,7.6,5.9,13.6,13.6,13.6
+ c7.7,0,13.6-6,13.6-13.6V248.5z"/>
+ <path fill="#fff" d="M246.5,298.7c0-7.6-6-13.6-13.6-13.6s-13.6,6-13.6,13.6v64.6c0,7.6,6,13.6,13.6,13.6s13.6-6,13.6-13.6
+ V298.7z"/>
+ <path fill="#fff" d="M319.6,335.2c0-7.6-5.9-13.6-13.6-13.6c-7.7,0-13.6,5.9-13.6,13.6v28.1c0,7.6,5.9,13.6,13.6,13.6
+ c7.7,0,13.6-6,13.6-13.6V335.2z"/>
+ <path fill="#fff" d="M392.7,298.7c0-7.6-6-13.6-13.6-13.6s-13.6,6-13.6,13.6v64.6c0,7.6,6,13.6,13.6,13.6
+ c7.7,0,13.6-6,13.6-13.6V298.7z"/>
+ <path fill="#fff" d="M465.8,248.5c0-7.7-6-13.6-13.6-13.6c-7.7,0-13.6,5.9-13.6,13.6v141.1c0,7.6,6,13.6,13.6,13.6
+ s13.6-6,13.6-13.6V248.5z"/>
+ <path fill="#fff" d="M538.9,298.7c0-7.6-6-13.6-13.6-13.6c-7.7,0-13.6,6-13.6,13.6v64.6c0,7.6,5.9,13.6,13.6,13.6
+ c7.6,0,13.6-6,13.6-13.6V298.7z"/>
+ <path fill="#fff" d="M612,335.2c0-7.6-5.9-13.6-13.6-13.6s-13.6,5.9-13.6,13.6v28.1c0,7.6,5.9,13.6,13.6,13.6s13.6-6,13.6-13.6
+ V335.2z"/>
+</g>
+</svg>
diff --git a/ui/public/logo_microscope_3.png b/ui/public/logo_microscope_3.png
new file mode 100644
index 0000000..315594c
--- /dev/null
+++ b/ui/public/logo_microscope_3.png
Binary files differ
diff --git a/ui/public/network.png b/ui/public/network.png
new file mode 100644
index 0000000..60fc85d
--- /dev/null
+++ b/ui/public/network.png
Binary files differ
diff --git a/ui/public/particle.png b/ui/public/particle.png
new file mode 100644
index 0000000..4ed564a
--- /dev/null
+++ b/ui/public/particle.png
Binary files differ
diff --git a/ui/public/pnic.jpg b/ui/public/pnic.jpg
new file mode 100644
index 0000000..0068850
--- /dev/null
+++ b/ui/public/pnic.jpg
Binary files differ
diff --git a/ui/public/vconnector.png b/ui/public/vconnector.png
new file mode 100644
index 0000000..f41080d
--- /dev/null
+++ b/ui/public/vconnector.png
Binary files differ
diff --git a/ui/public/vnic.png b/ui/public/vnic.png
new file mode 100644
index 0000000..660328d
--- /dev/null
+++ b/ui/public/vnic.png
Binary files differ
diff --git a/ui/public/vservice.png b/ui/public/vservice.png
new file mode 100644
index 0000000..86168de
--- /dev/null
+++ b/ui/public/vservice.png
Binary files differ
diff --git a/ui/public/wiki/addNewEnv.png b/ui/public/wiki/addNewEnv.png
new file mode 100644
index 0000000..463ada7
--- /dev/null
+++ b/ui/public/wiki/addNewEnv.png
Binary files differ
diff --git a/ui/public/wiki/addNewEnv1.png b/ui/public/wiki/addNewEnv1.png
new file mode 100644
index 0000000..41300b7
--- /dev/null
+++ b/ui/public/wiki/addNewEnv1.png
Binary files differ
diff --git a/ui/public/wiki/wizard1.png b/ui/public/wiki/wizard1.png
new file mode 100644
index 0000000..1d925a4
--- /dev/null
+++ b/ui/public/wiki/wizard1.png
Binary files differ
diff --git a/ui/public/wiki/wizard2.png b/ui/public/wiki/wizard2.png
new file mode 100644
index 0000000..d756ca3
--- /dev/null
+++ b/ui/public/wiki/wizard2.png
Binary files differ
diff --git a/ui/public/wiki/wizard3.png b/ui/public/wiki/wizard3.png
new file mode 100644
index 0000000..6735146
--- /dev/null
+++ b/ui/public/wiki/wizard3.png
Binary files differ
diff --git a/ui/public/wiki/wizard4.png b/ui/public/wiki/wizard4.png
new file mode 100644
index 0000000..42b57a6
--- /dev/null
+++ b/ui/public/wiki/wizard4.png
Binary files differ
diff --git a/ui/readme.md b/ui/readme.md
new file mode 100644
index 0000000..7dfb79e
--- /dev/null
+++ b/ui/readme.md
@@ -0,0 +1,43 @@
+# Calipso
+
+## Run
+
+```bash
+./run.sh
+```
+
+## Build
+
+```bash
+meteor build --architecture=os.linux.x86_64 ./
+```
+
+### Soruce Build
+
+```bash
+tar --exclude='./.meteor/local' --exclude='./node_modules' --exclude='./.git' -zcvf ../calipso-source-$(date +%Y-%m-%d).tar.gz .
+```
+## Testing - Build with Docker
+
+Testing on staging
+
+```bash
+docker run -d \
+ -e ROOT_URL=http://testing-server-example.com \
+ -e MONGO_URL=mongodb://testing-server-example.com:27017/calipso \
+ -v /home/ofir/calipso:/bundle \
+ -p 80:80 \
+ kadirahq/meteord:base
+```
+
+Testing on local
+
+```bash
+docker run \
+ --net=host \
+ -e ROOT_URL=http://localhost \
+ -e MONGO_URL=mongodb://localhost:27017/calipso \
+ -v /home/eyal_work/projects/cisco/output:/bundle \
+ kadirahq/meteord:base
+```
+
diff --git a/ui/run b/ui/run
new file mode 100755
index 0000000..ec4aa7c
--- /dev/null
+++ b/ui/run
@@ -0,0 +1,2 @@
+# run meteor with different mongo
+MONGO_URL=mongodb://localhost:27017/osdna meteor run
diff --git a/ui/run-dev.sh b/ui/run-dev.sh
new file mode 100755
index 0000000..d35659e
--- /dev/null
+++ b/ui/run-dev.sh
@@ -0,0 +1,10 @@
+########################################################################################
+# 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 #
+########################################################################################
+# run meteor with different mongo
+MONGO_URL=mongodb://osdna:Oki9wse3@10.56.20.80:27017/osdna meteor run
diff --git a/ui/server/main.js b/ui/server/main.js
new file mode 100644
index 0000000..ad0bdf3
--- /dev/null
+++ b/ui/server/main.js
@@ -0,0 +1,10 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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/startup/server';
+import '/imports/startup/both';