From b88c78e3cf2bef22aa2f1c4d0bf305e303bc15f0 Mon Sep 17 00:00:00 2001 From: Koren Lev Date: Thu, 27 Jul 2017 16:42:15 +0300 Subject: adding calipso ui Change-Id: Ifa6f63daebb07f45580f747341960e898fdb00c4 Signed-off-by: Koren Lev --- ui/imports/api/accounts/methods.js | 196 +++++ ui/imports/api/accounts/server/publications.js | 29 + .../attributes_for_hover_on_data.js | 12 + .../api/attributes_for_hover_on_data/methods.js | 8 + .../server/publications.js | 25 + .../api/clique-constraints/clique-constraints.js | 48 ++ ui/imports/api/clique-constraints/methods.js | 99 +++ .../api/clique-constraints/server/publications.js | 30 + ui/imports/api/clique-types/clique-types.js | 107 +++ ui/imports/api/clique-types/methods.js | 108 +++ ui/imports/api/clique-types/server/publications.js | 34 + ui/imports/api/cliques/cliques.js | 12 + ui/imports/api/cliques/methods.js | 8 + ui/imports/api/cliques/server/publications.js | 33 + ui/imports/api/constants/constants.js | 22 + ui/imports/api/constants/data/distributions.js | 64 ++ ui/imports/api/constants/data/env-types.js | 15 + .../constants/data/environment-monitoring-types.js | 12 + .../constants/data/environment-provision-types.js | 21 + ui/imports/api/constants/data/log-levels.js | 27 + ui/imports/api/constants/data/mechanism-drivers.js | 24 + .../api/constants/data/message-source-systems.js | 15 + ui/imports/api/constants/data/network-plugins.js | 15 + .../api/constants/data/object-types-for-links.js | 39 + ui/imports/api/constants/data/scans-statuses.js | 30 + ui/imports/api/constants/data/type-drivers.js | 24 + ui/imports/api/constants/server/publications.js | 16 + .../configuration-groups/aci-configuration.js | 29 + .../configuration-groups/amqp-configuration.js | 29 + .../configuration-groups/cli-configuration.js | 69 ++ .../monitoring-configuration.js | 119 ++++ .../configuration-groups/mysql-configuration.js | 33 + .../nfv-provider-configuration.js | 25 + .../open-stack-configuration.js | 30 + ui/imports/api/environments/environments.js | 457 ++++++++++++ ui/imports/api/environments/methods.js | 154 ++++ ui/imports/api/environments/server/publications.js | 102 +++ ui/imports/api/inventories/inventories.js | 11 + ui/imports/api/inventories/server/methods.js | 151 ++++ ui/imports/api/inventories/server/publications.js | 250 +++++++ ui/imports/api/link-types/link-types.js | 86 +++ ui/imports/api/link-types/methods.js | 114 +++ ui/imports/api/link-types/server/publications.js | 46 ++ ui/imports/api/links/links.js | 11 + ui/imports/api/links/methods.js | 8 + ui/imports/api/links/server/publications.js | 32 + ui/imports/api/messages/messages.js | 125 ++++ ui/imports/api/messages/methods.js | 8 + ui/imports/api/messages/server/methods.js | 49 ++ ui/imports/api/messages/server/publications.js | 98 +++ ui/imports/api/migrations/migrations.js | 20 + ui/imports/api/scans/methods.js | 55 ++ ui/imports/api/scans/scans.js | 159 +++++ ui/imports/api/scans/server/methods.js | 44 ++ ui/imports/api/scans/server/publications.js | 82 +++ ui/imports/api/scheduled-scans/methods.js | 131 ++++ ui/imports/api/scheduled-scans/scheduled-scans.js | 91 +++ ui/imports/api/scheduled-scans/server/methods.js | 27 + .../api/scheduled-scans/server/publications.js | 60 ++ ui/imports/api/simple-schema.init.js | 13 + ui/imports/api/statistics/helpers.js | 64 ++ ui/imports/api/statistics/methods.js | 159 +++++ ui/imports/api/statistics/server/publications.js | 52 ++ ui/imports/api/statistics/statistics.js | 14 + ui/imports/api/supported_environments/methods.js | 8 + .../supported_environments/server/publications.js | 17 + .../supported_environments.js | 49 ++ ui/imports/index.styl | 1 + ui/imports/lib/d3-graph.js | 573 +++++++++++++++ ui/imports/lib/d3three.js | 789 +++++++++++++++++++++ ui/imports/lib/general-regex.js | 15 + ui/imports/lib/icon.js | 14 + ui/imports/lib/images-for-node-type.js | 22 + ui/imports/lib/regex-utils.js | 11 + ui/imports/lib/simple-schema-utils.js | 15 + ui/imports/lib/utilities.js | 54 ++ ui/imports/startup/both/config.js | 9 + ui/imports/startup/both/configs/accounts.js | 9 + ui/imports/startup/both/index.js | 10 + ui/imports/startup/client/index.js | 38 + ui/imports/startup/client/template-helpers.js | 32 + ui/imports/startup/server/config.js | 9 + ui/imports/startup/server/configs/accounts.js | 16 + ui/imports/startup/server/index.js | 13 + ui/imports/startup/server/register-api.js | 56 ++ ui/imports/startup/server/seeds.js | 10 + ui/imports/startup/server/seeds/constants.js | 68 ++ ui/imports/startup/server/seeds/users.js | 51 ++ ui/imports/ui/actions/environment-panel.actions.js | 225 ++++++ .../ui/actions/graph-tooltip-window.actions.js | 30 + ui/imports/ui/actions/main-app.actions.js | 21 + ui/imports/ui/actions/navigation.js | 84 +++ ui/imports/ui/actions/search-interested-parties.js | 93 +++ ui/imports/ui/actions/tree-node.actions.js | 144 ++++ ui/imports/ui/actions/vedge-info-window.actions.js | 41 ++ .../accordion-nav-menu/accordion-nav-menu.html | 43 ++ .../accordion-nav-menu/accordion-nav-menu.js | 155 ++++ .../accordion-nav-menu/accordion-nav-menu.styl | 11 + .../accordion-wiki-menu/accordion-wiki-menu.html | 42 ++ .../accordion-wiki-menu/accordion-wiki-menu.js | 147 ++++ .../accordionTreeNode/accordion-tree-node.styl | 5 + .../accordionTreeNode/accordionTreeNode.html | 54 ++ .../accordionTreeNode/accordionTreeNode.js | 284 ++++++++ .../accordionTreeNodeChildren.html | 19 + .../accordionTreeNodeChildren.js | 125 ++++ .../aggregate-dashboard/aggregate-dashboard.html | 35 + .../aggregate-dashboard/aggregate-dashboard.js | 212 ++++++ .../aggregate-dashboard/aggregate-dashboard.styl | 10 + .../ui/components/alarm-icons/alarm-icons.html | 78 ++ .../ui/components/alarm-icons/alarm-icons.js | 53 ++ .../ui/components/alarm-icons/alarm-icons.styl | 1 + .../auto-search-result-line.html | 16 + .../auto-search-result-line.js | 51 ++ .../auto-search-result-line.styl | 23 + .../ui/components/breadcrumb/breadcrumb.html | 17 + ui/imports/ui/components/breadcrumb/breadcrumb.js | 83 +++ .../ui/components/breadcrumb/breadcrumb.styl | 3 + .../components/breadcrumbNode/breadcrumbNode.html | 15 + .../ui/components/breadcrumbNode/breadcrumbNode.js | 43 ++ .../components/breadcrumbNode/breadcrumbNode.styl | 4 + .../clique-constraint/clique-constraint.html | 96 +++ .../clique-constraint/clique-constraint.js | 329 +++++++++ .../clique-constraint/clique-constraint.styl | 35 + .../clique-constraints-list.html | 52 ++ .../clique-constraints-list.js | 77 ++ .../clique-constraints-list.styl | 22 + .../ui/components/clique-type/clique-type.html | 100 +++ .../ui/components/clique-type/clique-type.js | 371 ++++++++++ .../ui/components/clique-type/clique-type.styl | 54 ++ .../clique-types-list/clique-types-list.html | 56 ++ .../clique-types-list/clique-types-list.js | 82 +++ .../clique-types-list/clique-types-list.styl | 22 + ui/imports/ui/components/d3graph/d3graph.html | 13 + ui/imports/ui/components/d3graph/d3graph.js | 126 ++++ ui/imports/ui/components/d3graph/d3graph.styl | 12 + ui/imports/ui/components/dashboard/dashboard.html | 157 ++++ ui/imports/ui/components/dashboard/dashboard.js | 222 ++++++ ui/imports/ui/components/dashboard/dashboard.styl | 0 .../ui/components/data-cubic/data-cubic.html | 23 + ui/imports/ui/components/data-cubic/data-cubic.js | 71 ++ .../ui/components/data-cubic/data-cubic.styl | 27 + .../detailed-node-info-box.html | 53 ++ .../detailed-node-info-box.js | 57 ++ .../detailed-node-info-box.styl | 33 + .../ui/components/env-aci-info/env-aci-info.html | 88 +++ .../ui/components/env-aci-info/env-aci-info.js | 61 ++ .../env-amqp-credentials-info.html | 108 +++ .../env-amqp-credentials-info.js | 52 ++ .../env-delete-modal/env-delete-modal.html | 48 ++ .../env-delete-modal/env-delete-modal.js | 55 ++ .../env-delete-modal/env-delete-modal.styl | 2 + ui/imports/ui/components/env-form/env-form.html | 37 + ui/imports/ui/components/env-form/env-form.js | 94 +++ ui/imports/ui/components/env-form/env-form.styl | 0 .../ui/components/env-main-info/env-main-info.html | 193 +++++ .../ui/components/env-main-info/env-main-info.js | 123 ++++ .../env-master-host-credentials-info.html | 109 +++ .../env-master-host-credentials-info.js | 52 ++ .../env-monitoring-info/env-monitoring-info.html | 283 ++++++++ .../env-monitoring-info/env-monitoring-info.js | 76 ++ .../ui/components/env-nfv-info/env-nfv-info.html | 128 ++++ .../ui/components/env-nfv-info/env-nfv-info.js | 63 ++ .../env-open-stack-db-credentials-info.html | 109 +++ .../env-open-stack-db-credentials-info.js | 52 ++ .../env-os-api-endpoint-info.html | 125 ++++ .../env-os-api-endpoint-info.js | 52 ++ .../environment-dashboard.html | 59 ++ .../environment-dashboard/environment-dashboard.js | 380 ++++++++++ .../environment-dashboard.styl | 12 + .../environment-wizard/environment-wizard.html | 83 +++ .../environment-wizard/environment-wizard.js | 452 ++++++++++++ .../environment-wizard/environment-wizard.styl | 27 + .../ui/components/environment/environment.html | 63 ++ .../ui/components/environment/environment.js | 570 +++++++++++++++ .../ui/components/environment/environment.styl | 61 ++ .../ui/components/flow-graph/flow-graph.html | 17 + ui/imports/ui/components/flow-graph/flow-graph.js | 383 ++++++++++ .../ui/components/flow-graph/flow-graph.styl | 18 + .../general-folder-node-dashboard.html | 24 + .../general-folder-node-dashboard.js | 112 +++ .../general-folder-node-dashboard.styl | 12 + .../general-node-dashboard.html | 17 + .../general-node-dashboard.js | 84 +++ .../general-node-dashboard.styl | 6 + .../general-node-info-box.html | 37 + .../general-node-info-box/general-node-info-box.js | 63 ++ .../general-node-info-box.styl | 33 + .../ui/components/get-started/get-started.html | 412 +++++++++++ .../ui/components/get-started/get-started.js | 32 + .../graph-tooltip-window/graph-tooltip-window.html | 17 + .../graph-tooltip-window/graph-tooltip-window.js | 57 ++ .../graph-tooltip-window/graph-tooltip-window.styl | 25 + .../components/host-dashboard/host-dashboard.html | 29 + .../ui/components/host-dashboard/host-dashboard.js | 197 +++++ .../components/host-dashboard/host-dashboard.styl | 6 + ui/imports/ui/components/icon/icon.html | 18 + ui/imports/ui/components/icon/icon.js | 48 ++ ui/imports/ui/components/icon/icon.styl | 2 + ui/imports/ui/components/index.styl | 57 ++ .../ui/components/input-model/input-model.html | 21 + .../ui/components/input-model/input-model.js | 116 +++ .../inventory-properties-display.html | 13 + .../inventory-properties-display.js | 90 +++ .../inventory-properties-display.styl | 2 + ui/imports/ui/components/landing/landing.html | 201 ++++++ ui/imports/ui/components/landing/landing.js | 35 + ui/imports/ui/components/landing/landing.styl | 80 +++ ui/imports/ui/components/link-type/link-type.html | 88 +++ ui/imports/ui/components/link-type/link-type.js | 328 +++++++++ ui/imports/ui/components/link-type/link-type.styl | 34 + .../link-types-list/link-types-list.html | 56 ++ .../components/link-types-list/link-types-list.js | 87 +++ .../link-types-list/link-types-list.styl | 23 + .../ui/components/list-info-box/list-info-box.html | 60 ++ .../ui/components/list-info-box/list-info-box.js | 111 +++ .../ui/components/list-info-box/list-info-box.styl | 4 + ui/imports/ui/components/loading/loading.html | 12 + ui/imports/ui/components/loading/loading.js | 30 + ui/imports/ui/components/loading/loading.styl | 0 ui/imports/ui/components/main/main.html | 15 + ui/imports/ui/components/main/main.js | 98 +++ ui/imports/ui/components/main/main.styl | 0 ui/imports/ui/components/message/message.html | 168 +++++ ui/imports/ui/components/message/message.js | 257 +++++++ ui/imports/ui/components/message/message.styl | 41 ++ .../messages-info-box/messages-info-box.html | 27 + .../messages-info-box/messages-info-box.js | 66 ++ .../messages-info-box/messages-info-box.styl | 2 + .../ui/components/messages-list/messages-list.html | 103 +++ .../ui/components/messages-list/messages-list.js | 291 ++++++++ .../ui/components/messages-list/messages-list.styl | 37 + .../components/messages-modal/messages-modal.html | 78 ++ .../ui/components/messages-modal/messages-modal.js | 285 ++++++++ .../components/messages-modal/messages-modal.styl | 18 + ui/imports/ui/components/mt-input/mt-input.html | 7 + ui/imports/ui/components/mt-input/mt-input.js | 114 +++ ui/imports/ui/components/mt-input/mt-input.styl | 2 + ui/imports/ui/components/mt-radios/mt-radios.html | 22 + ui/imports/ui/components/mt-radios/mt-radios.js | 70 ++ ui/imports/ui/components/mt-radios/mt-radios.styl | 2 + ui/imports/ui/components/mt-select/mt-select.html | 13 + ui/imports/ui/components/mt-select/mt-select.js | 99 +++ ui/imports/ui/components/mt-select/mt-select.styl | 2 + .../network-graph-manager.html | 5 + .../network-graph-manager/network-graph-manager.js | 282 ++++++++ .../network-graph-manager.styl | 2 + .../ui/components/network-graph/network-graph.html | 7 + .../ui/components/network-graph/network-graph.js | 697 ++++++++++++++++++ .../ui/components/network-graph/network-graph.styl | 20 + .../network-info-box/network-info-box.html | 38 + .../network-info-box/network-info-box.js | 69 ++ .../network-info-box/network-info-box.styl | 2 + .../ui/components/new-scanning/new-scanning.html | 53 ++ .../ui/components/new-scanning/new-scanning.js | 73 ++ .../ui/components/new-scanning/new-scanning.styl | 7 + ui/imports/ui/components/pager/pager.html | 42 ++ ui/imports/ui/components/pager/pager.js | 123 ++++ ui/imports/ui/components/pager/pager.styl | 4 + .../project-dashboard/project-dashboard.html | 38 + .../project-dashboard/project-dashboard.js | 149 ++++ .../project-dashboard/project-dashboard.styl | 14 + .../region-dashboard/region-dashboard.html | 38 + .../region-dashboard/region-dashboard.js | 220 ++++++ .../region-dashboard/region-dashboard.styl | 14 + .../scanning-request/scanning-request.html | 74 ++ .../scanning-request/scanning-request.js | 371 ++++++++++ .../scanning-request/scanning-request.styl | 7 + .../ui/components/scans-list/scans-list.html | 88 +++ ui/imports/ui/components/scans-list/scans-list.js | 224 ++++++ .../ui/components/scans-list/scans-list.styl | 33 + .../components/scheduled-scan/scheduled-scan.html | 116 +++ .../ui/components/scheduled-scan/scheduled-scan.js | 511 +++++++++++++ .../components/scheduled-scan/scheduled-scan.styl | 34 + .../scheduled-scans-list/scheduled-scans-list.html | 66 ++ .../scheduled-scans-list/scheduled-scans-list.js | 160 +++++ .../scheduled-scans-list/scheduled-scans-list.styl | 33 + .../search-auto-complete-list.html | 22 + .../search-auto-complete-list.js | 167 +++++ .../search-auto-complete.styl | 28 + .../ui/components/select-model/select-model.html | 23 + .../ui/components/select-model/select-model.js | 79 +++ .../selectable-ordered-input.html | 29 + .../selectable-ordered-input.js | 243 +++++++ .../selectable-ordered-input.styl | 30 + .../time-selection-widget.html | 33 + .../time-selection-widget/time-selection-widget.js | 45 ++ .../time-selection-widget.styl | 5 + .../top-navbar-menu/top-navbar-menu.html | 59 ++ .../components/top-navbar-menu/top-navbar-menu.js | 129 ++++ .../top-navbar-menu/top-navbar-menu.styl | 23 + ui/imports/ui/components/tree-node/tree-node.html | 58 ++ ui/imports/ui/components/tree-node/tree-node.js | 419 +++++++++++ ui/imports/ui/components/tree-node/tree-node.styl | 53 ++ ui/imports/ui/components/user-list/user-list.html | 53 ++ ui/imports/ui/components/user-list/user-list.js | 74 ++ ui/imports/ui/components/user-list/user-list.styl | 22 + ui/imports/ui/components/user/user.html | 111 +++ ui/imports/ui/components/user/user.js | 366 ++++++++++ ui/imports/ui/components/user/user.styl | 34 + .../vedge-info-window/vedge-info-window.html | 114 +++ .../vedge-info-window/vedge-info-window.js | 380 ++++++++++ .../vedge-info-window/vedge-info-window.styl | 43 ++ .../components/zone-dashboard/zone-dashboard.html | 35 + .../ui/components/zone-dashboard/zone-dashboard.js | 214 ++++++ .../components/zone-dashboard/zone-dashboard.styl | 10 + ui/imports/ui/index.styl | 1 + .../ui/lib/environment-tree-node-behavior.js | 46 ++ ui/imports/ui/lib/input-model.js | 31 + ui/imports/ui/lib/inventory-tree-node-behavior.js | 66 ++ ui/imports/ui/lib/select-model.js | 31 + .../ui/reducers/environment-panel.reducer.js | 194 +++++ .../ui/reducers/graph-tooltip-window.reducer.js | 48 ++ ui/imports/ui/reducers/i18n.reducer.js | 180 +++++ ui/imports/ui/reducers/index.js | 33 + ui/imports/ui/reducers/main-app.reducer.js | 28 + ui/imports/ui/reducers/navigation.js | 99 +++ .../ui/reducers/search-interested-parties.js | 76 ++ ui/imports/ui/reducers/tree-node.reducer.js | 232 ++++++ .../ui/reducers/vedge-info-window.reducer.js | 50 ++ ui/imports/ui/store/index.js | 11 + ui/imports/ui/store/store.js | 25 + 321 files changed, 26423 insertions(+) create mode 100644 ui/imports/api/accounts/methods.js create mode 100644 ui/imports/api/accounts/server/publications.js create mode 100644 ui/imports/api/attributes_for_hover_on_data/attributes_for_hover_on_data.js create mode 100644 ui/imports/api/attributes_for_hover_on_data/methods.js create mode 100644 ui/imports/api/attributes_for_hover_on_data/server/publications.js create mode 100644 ui/imports/api/clique-constraints/clique-constraints.js create mode 100644 ui/imports/api/clique-constraints/methods.js create mode 100644 ui/imports/api/clique-constraints/server/publications.js create mode 100644 ui/imports/api/clique-types/clique-types.js create mode 100644 ui/imports/api/clique-types/methods.js create mode 100644 ui/imports/api/clique-types/server/publications.js create mode 100644 ui/imports/api/cliques/cliques.js create mode 100644 ui/imports/api/cliques/methods.js create mode 100644 ui/imports/api/cliques/server/publications.js create mode 100644 ui/imports/api/constants/constants.js create mode 100644 ui/imports/api/constants/data/distributions.js create mode 100644 ui/imports/api/constants/data/env-types.js create mode 100644 ui/imports/api/constants/data/environment-monitoring-types.js create mode 100644 ui/imports/api/constants/data/environment-provision-types.js create mode 100644 ui/imports/api/constants/data/log-levels.js create mode 100644 ui/imports/api/constants/data/mechanism-drivers.js create mode 100644 ui/imports/api/constants/data/message-source-systems.js create mode 100644 ui/imports/api/constants/data/network-plugins.js create mode 100644 ui/imports/api/constants/data/object-types-for-links.js create mode 100644 ui/imports/api/constants/data/scans-statuses.js create mode 100644 ui/imports/api/constants/data/type-drivers.js create mode 100644 ui/imports/api/constants/server/publications.js create mode 100644 ui/imports/api/environments/configuration-groups/aci-configuration.js create mode 100644 ui/imports/api/environments/configuration-groups/amqp-configuration.js create mode 100644 ui/imports/api/environments/configuration-groups/cli-configuration.js create mode 100644 ui/imports/api/environments/configuration-groups/monitoring-configuration.js create mode 100644 ui/imports/api/environments/configuration-groups/mysql-configuration.js create mode 100644 ui/imports/api/environments/configuration-groups/nfv-provider-configuration.js create mode 100644 ui/imports/api/environments/configuration-groups/open-stack-configuration.js create mode 100644 ui/imports/api/environments/environments.js create mode 100644 ui/imports/api/environments/methods.js create mode 100644 ui/imports/api/environments/server/publications.js create mode 100644 ui/imports/api/inventories/inventories.js create mode 100644 ui/imports/api/inventories/server/methods.js create mode 100644 ui/imports/api/inventories/server/publications.js create mode 100644 ui/imports/api/link-types/link-types.js create mode 100644 ui/imports/api/link-types/methods.js create mode 100644 ui/imports/api/link-types/server/publications.js create mode 100644 ui/imports/api/links/links.js create mode 100644 ui/imports/api/links/methods.js create mode 100644 ui/imports/api/links/server/publications.js create mode 100644 ui/imports/api/messages/messages.js create mode 100644 ui/imports/api/messages/methods.js create mode 100644 ui/imports/api/messages/server/methods.js create mode 100644 ui/imports/api/messages/server/publications.js create mode 100644 ui/imports/api/migrations/migrations.js create mode 100644 ui/imports/api/scans/methods.js create mode 100644 ui/imports/api/scans/scans.js create mode 100644 ui/imports/api/scans/server/methods.js create mode 100644 ui/imports/api/scans/server/publications.js create mode 100644 ui/imports/api/scheduled-scans/methods.js create mode 100644 ui/imports/api/scheduled-scans/scheduled-scans.js create mode 100644 ui/imports/api/scheduled-scans/server/methods.js create mode 100644 ui/imports/api/scheduled-scans/server/publications.js create mode 100644 ui/imports/api/simple-schema.init.js create mode 100644 ui/imports/api/statistics/helpers.js create mode 100644 ui/imports/api/statistics/methods.js create mode 100644 ui/imports/api/statistics/server/publications.js create mode 100644 ui/imports/api/statistics/statistics.js create mode 100644 ui/imports/api/supported_environments/methods.js create mode 100644 ui/imports/api/supported_environments/server/publications.js create mode 100644 ui/imports/api/supported_environments/supported_environments.js create mode 100644 ui/imports/index.styl create mode 100644 ui/imports/lib/d3-graph.js create mode 100644 ui/imports/lib/d3three.js create mode 100644 ui/imports/lib/general-regex.js create mode 100644 ui/imports/lib/icon.js create mode 100644 ui/imports/lib/images-for-node-type.js create mode 100644 ui/imports/lib/regex-utils.js create mode 100644 ui/imports/lib/simple-schema-utils.js create mode 100644 ui/imports/lib/utilities.js create mode 100644 ui/imports/startup/both/config.js create mode 100644 ui/imports/startup/both/configs/accounts.js create mode 100644 ui/imports/startup/both/index.js create mode 100644 ui/imports/startup/client/index.js create mode 100644 ui/imports/startup/client/template-helpers.js create mode 100644 ui/imports/startup/server/config.js create mode 100644 ui/imports/startup/server/configs/accounts.js create mode 100644 ui/imports/startup/server/index.js create mode 100644 ui/imports/startup/server/register-api.js create mode 100644 ui/imports/startup/server/seeds.js create mode 100644 ui/imports/startup/server/seeds/constants.js create mode 100644 ui/imports/startup/server/seeds/users.js create mode 100644 ui/imports/ui/actions/environment-panel.actions.js create mode 100644 ui/imports/ui/actions/graph-tooltip-window.actions.js create mode 100644 ui/imports/ui/actions/main-app.actions.js create mode 100644 ui/imports/ui/actions/navigation.js create mode 100644 ui/imports/ui/actions/search-interested-parties.js create mode 100644 ui/imports/ui/actions/tree-node.actions.js create mode 100644 ui/imports/ui/actions/vedge-info-window.actions.js create mode 100644 ui/imports/ui/components/accordion-nav-menu/accordion-nav-menu.html create mode 100644 ui/imports/ui/components/accordion-nav-menu/accordion-nav-menu.js create mode 100644 ui/imports/ui/components/accordion-nav-menu/accordion-nav-menu.styl create mode 100644 ui/imports/ui/components/accordion-wiki-menu/accordion-wiki-menu.html create mode 100644 ui/imports/ui/components/accordion-wiki-menu/accordion-wiki-menu.js create mode 100644 ui/imports/ui/components/accordionTreeNode/accordion-tree-node.styl create mode 100644 ui/imports/ui/components/accordionTreeNode/accordionTreeNode.html create mode 100644 ui/imports/ui/components/accordionTreeNode/accordionTreeNode.js create mode 100644 ui/imports/ui/components/accordionTreeNodeChildren/accordionTreeNodeChildren.html create mode 100644 ui/imports/ui/components/accordionTreeNodeChildren/accordionTreeNodeChildren.js create mode 100644 ui/imports/ui/components/aggregate-dashboard/aggregate-dashboard.html create mode 100644 ui/imports/ui/components/aggregate-dashboard/aggregate-dashboard.js create mode 100644 ui/imports/ui/components/aggregate-dashboard/aggregate-dashboard.styl create mode 100644 ui/imports/ui/components/alarm-icons/alarm-icons.html create mode 100644 ui/imports/ui/components/alarm-icons/alarm-icons.js create mode 100644 ui/imports/ui/components/alarm-icons/alarm-icons.styl create mode 100644 ui/imports/ui/components/auto-search-result-line/auto-search-result-line.html create mode 100644 ui/imports/ui/components/auto-search-result-line/auto-search-result-line.js create mode 100644 ui/imports/ui/components/auto-search-result-line/auto-search-result-line.styl create mode 100644 ui/imports/ui/components/breadcrumb/breadcrumb.html create mode 100644 ui/imports/ui/components/breadcrumb/breadcrumb.js create mode 100644 ui/imports/ui/components/breadcrumb/breadcrumb.styl create mode 100644 ui/imports/ui/components/breadcrumbNode/breadcrumbNode.html create mode 100644 ui/imports/ui/components/breadcrumbNode/breadcrumbNode.js create mode 100644 ui/imports/ui/components/breadcrumbNode/breadcrumbNode.styl create mode 100644 ui/imports/ui/components/clique-constraint/clique-constraint.html create mode 100644 ui/imports/ui/components/clique-constraint/clique-constraint.js create mode 100644 ui/imports/ui/components/clique-constraint/clique-constraint.styl create mode 100644 ui/imports/ui/components/clique-constraints-list/clique-constraints-list.html create mode 100644 ui/imports/ui/components/clique-constraints-list/clique-constraints-list.js create mode 100644 ui/imports/ui/components/clique-constraints-list/clique-constraints-list.styl create mode 100644 ui/imports/ui/components/clique-type/clique-type.html create mode 100644 ui/imports/ui/components/clique-type/clique-type.js create mode 100644 ui/imports/ui/components/clique-type/clique-type.styl create mode 100644 ui/imports/ui/components/clique-types-list/clique-types-list.html create mode 100644 ui/imports/ui/components/clique-types-list/clique-types-list.js create mode 100644 ui/imports/ui/components/clique-types-list/clique-types-list.styl create mode 100644 ui/imports/ui/components/d3graph/d3graph.html create mode 100644 ui/imports/ui/components/d3graph/d3graph.js create mode 100644 ui/imports/ui/components/d3graph/d3graph.styl create mode 100644 ui/imports/ui/components/dashboard/dashboard.html create mode 100644 ui/imports/ui/components/dashboard/dashboard.js create mode 100644 ui/imports/ui/components/dashboard/dashboard.styl create mode 100644 ui/imports/ui/components/data-cubic/data-cubic.html create mode 100644 ui/imports/ui/components/data-cubic/data-cubic.js create mode 100644 ui/imports/ui/components/data-cubic/data-cubic.styl create mode 100644 ui/imports/ui/components/detailed-node-info-box/detailed-node-info-box.html create mode 100644 ui/imports/ui/components/detailed-node-info-box/detailed-node-info-box.js create mode 100644 ui/imports/ui/components/detailed-node-info-box/detailed-node-info-box.styl create mode 100644 ui/imports/ui/components/env-aci-info/env-aci-info.html create mode 100644 ui/imports/ui/components/env-aci-info/env-aci-info.js create mode 100644 ui/imports/ui/components/env-amqp-credentials-info/env-amqp-credentials-info.html create mode 100644 ui/imports/ui/components/env-amqp-credentials-info/env-amqp-credentials-info.js create mode 100644 ui/imports/ui/components/env-delete-modal/env-delete-modal.html create mode 100644 ui/imports/ui/components/env-delete-modal/env-delete-modal.js create mode 100644 ui/imports/ui/components/env-delete-modal/env-delete-modal.styl create mode 100644 ui/imports/ui/components/env-form/env-form.html create mode 100644 ui/imports/ui/components/env-form/env-form.js create mode 100644 ui/imports/ui/components/env-form/env-form.styl create mode 100644 ui/imports/ui/components/env-main-info/env-main-info.html create mode 100644 ui/imports/ui/components/env-main-info/env-main-info.js create mode 100644 ui/imports/ui/components/env-master-host-credentials-info/env-master-host-credentials-info.html create mode 100644 ui/imports/ui/components/env-master-host-credentials-info/env-master-host-credentials-info.js create mode 100644 ui/imports/ui/components/env-monitoring-info/env-monitoring-info.html create mode 100644 ui/imports/ui/components/env-monitoring-info/env-monitoring-info.js create mode 100644 ui/imports/ui/components/env-nfv-info/env-nfv-info.html create mode 100644 ui/imports/ui/components/env-nfv-info/env-nfv-info.js create mode 100644 ui/imports/ui/components/env-open-stack-db-credentials-info/env-open-stack-db-credentials-info.html create mode 100644 ui/imports/ui/components/env-open-stack-db-credentials-info/env-open-stack-db-credentials-info.js create mode 100644 ui/imports/ui/components/env-os-api-endpoint-info/env-os-api-endpoint-info.html create mode 100644 ui/imports/ui/components/env-os-api-endpoint-info/env-os-api-endpoint-info.js create mode 100644 ui/imports/ui/components/environment-dashboard/environment-dashboard.html create mode 100644 ui/imports/ui/components/environment-dashboard/environment-dashboard.js create mode 100644 ui/imports/ui/components/environment-dashboard/environment-dashboard.styl create mode 100644 ui/imports/ui/components/environment-wizard/environment-wizard.html create mode 100644 ui/imports/ui/components/environment-wizard/environment-wizard.js create mode 100644 ui/imports/ui/components/environment-wizard/environment-wizard.styl create mode 100644 ui/imports/ui/components/environment/environment.html create mode 100644 ui/imports/ui/components/environment/environment.js create mode 100644 ui/imports/ui/components/environment/environment.styl create mode 100644 ui/imports/ui/components/flow-graph/flow-graph.html create mode 100644 ui/imports/ui/components/flow-graph/flow-graph.js create mode 100644 ui/imports/ui/components/flow-graph/flow-graph.styl create mode 100644 ui/imports/ui/components/general-folder-node-dashboard/general-folder-node-dashboard.html create mode 100644 ui/imports/ui/components/general-folder-node-dashboard/general-folder-node-dashboard.js create mode 100644 ui/imports/ui/components/general-folder-node-dashboard/general-folder-node-dashboard.styl create mode 100644 ui/imports/ui/components/general-node-dashboard/general-node-dashboard.html create mode 100644 ui/imports/ui/components/general-node-dashboard/general-node-dashboard.js create mode 100644 ui/imports/ui/components/general-node-dashboard/general-node-dashboard.styl create mode 100644 ui/imports/ui/components/general-node-info-box/general-node-info-box.html create mode 100644 ui/imports/ui/components/general-node-info-box/general-node-info-box.js create mode 100644 ui/imports/ui/components/general-node-info-box/general-node-info-box.styl create mode 100644 ui/imports/ui/components/get-started/get-started.html create mode 100644 ui/imports/ui/components/get-started/get-started.js create mode 100644 ui/imports/ui/components/graph-tooltip-window/graph-tooltip-window.html create mode 100644 ui/imports/ui/components/graph-tooltip-window/graph-tooltip-window.js create mode 100644 ui/imports/ui/components/graph-tooltip-window/graph-tooltip-window.styl create mode 100644 ui/imports/ui/components/host-dashboard/host-dashboard.html create mode 100644 ui/imports/ui/components/host-dashboard/host-dashboard.js create mode 100644 ui/imports/ui/components/host-dashboard/host-dashboard.styl create mode 100644 ui/imports/ui/components/icon/icon.html create mode 100644 ui/imports/ui/components/icon/icon.js create mode 100644 ui/imports/ui/components/icon/icon.styl create mode 100644 ui/imports/ui/components/index.styl create mode 100644 ui/imports/ui/components/input-model/input-model.html create mode 100644 ui/imports/ui/components/input-model/input-model.js create mode 100644 ui/imports/ui/components/inventory-properties-display/inventory-properties-display.html create mode 100644 ui/imports/ui/components/inventory-properties-display/inventory-properties-display.js create mode 100644 ui/imports/ui/components/inventory-properties-display/inventory-properties-display.styl create mode 100644 ui/imports/ui/components/landing/landing.html create mode 100644 ui/imports/ui/components/landing/landing.js create mode 100644 ui/imports/ui/components/landing/landing.styl create mode 100644 ui/imports/ui/components/link-type/link-type.html create mode 100644 ui/imports/ui/components/link-type/link-type.js create mode 100644 ui/imports/ui/components/link-type/link-type.styl create mode 100644 ui/imports/ui/components/link-types-list/link-types-list.html create mode 100644 ui/imports/ui/components/link-types-list/link-types-list.js create mode 100644 ui/imports/ui/components/link-types-list/link-types-list.styl create mode 100644 ui/imports/ui/components/list-info-box/list-info-box.html create mode 100644 ui/imports/ui/components/list-info-box/list-info-box.js create mode 100644 ui/imports/ui/components/list-info-box/list-info-box.styl create mode 100644 ui/imports/ui/components/loading/loading.html create mode 100644 ui/imports/ui/components/loading/loading.js create mode 100644 ui/imports/ui/components/loading/loading.styl create mode 100644 ui/imports/ui/components/main/main.html create mode 100644 ui/imports/ui/components/main/main.js create mode 100644 ui/imports/ui/components/main/main.styl create mode 100644 ui/imports/ui/components/message/message.html create mode 100644 ui/imports/ui/components/message/message.js create mode 100644 ui/imports/ui/components/message/message.styl create mode 100644 ui/imports/ui/components/messages-info-box/messages-info-box.html create mode 100644 ui/imports/ui/components/messages-info-box/messages-info-box.js create mode 100644 ui/imports/ui/components/messages-info-box/messages-info-box.styl create mode 100644 ui/imports/ui/components/messages-list/messages-list.html create mode 100644 ui/imports/ui/components/messages-list/messages-list.js create mode 100644 ui/imports/ui/components/messages-list/messages-list.styl create mode 100644 ui/imports/ui/components/messages-modal/messages-modal.html create mode 100644 ui/imports/ui/components/messages-modal/messages-modal.js create mode 100644 ui/imports/ui/components/messages-modal/messages-modal.styl create mode 100644 ui/imports/ui/components/mt-input/mt-input.html create mode 100644 ui/imports/ui/components/mt-input/mt-input.js create mode 100644 ui/imports/ui/components/mt-input/mt-input.styl create mode 100644 ui/imports/ui/components/mt-radios/mt-radios.html create mode 100644 ui/imports/ui/components/mt-radios/mt-radios.js create mode 100644 ui/imports/ui/components/mt-radios/mt-radios.styl create mode 100644 ui/imports/ui/components/mt-select/mt-select.html create mode 100644 ui/imports/ui/components/mt-select/mt-select.js create mode 100644 ui/imports/ui/components/mt-select/mt-select.styl create mode 100644 ui/imports/ui/components/network-graph-manager/network-graph-manager.html create mode 100644 ui/imports/ui/components/network-graph-manager/network-graph-manager.js create mode 100644 ui/imports/ui/components/network-graph-manager/network-graph-manager.styl create mode 100644 ui/imports/ui/components/network-graph/network-graph.html create mode 100644 ui/imports/ui/components/network-graph/network-graph.js create mode 100644 ui/imports/ui/components/network-graph/network-graph.styl create mode 100644 ui/imports/ui/components/network-info-box/network-info-box.html create mode 100644 ui/imports/ui/components/network-info-box/network-info-box.js create mode 100644 ui/imports/ui/components/network-info-box/network-info-box.styl create mode 100644 ui/imports/ui/components/new-scanning/new-scanning.html create mode 100644 ui/imports/ui/components/new-scanning/new-scanning.js create mode 100644 ui/imports/ui/components/new-scanning/new-scanning.styl create mode 100644 ui/imports/ui/components/pager/pager.html create mode 100644 ui/imports/ui/components/pager/pager.js create mode 100644 ui/imports/ui/components/pager/pager.styl create mode 100644 ui/imports/ui/components/project-dashboard/project-dashboard.html create mode 100644 ui/imports/ui/components/project-dashboard/project-dashboard.js create mode 100644 ui/imports/ui/components/project-dashboard/project-dashboard.styl create mode 100644 ui/imports/ui/components/region-dashboard/region-dashboard.html create mode 100644 ui/imports/ui/components/region-dashboard/region-dashboard.js create mode 100644 ui/imports/ui/components/region-dashboard/region-dashboard.styl create mode 100644 ui/imports/ui/components/scanning-request/scanning-request.html create mode 100644 ui/imports/ui/components/scanning-request/scanning-request.js create mode 100644 ui/imports/ui/components/scanning-request/scanning-request.styl create mode 100644 ui/imports/ui/components/scans-list/scans-list.html create mode 100644 ui/imports/ui/components/scans-list/scans-list.js create mode 100644 ui/imports/ui/components/scans-list/scans-list.styl create mode 100644 ui/imports/ui/components/scheduled-scan/scheduled-scan.html create mode 100644 ui/imports/ui/components/scheduled-scan/scheduled-scan.js create mode 100644 ui/imports/ui/components/scheduled-scan/scheduled-scan.styl create mode 100644 ui/imports/ui/components/scheduled-scans-list/scheduled-scans-list.html create mode 100644 ui/imports/ui/components/scheduled-scans-list/scheduled-scans-list.js create mode 100644 ui/imports/ui/components/scheduled-scans-list/scheduled-scans-list.styl create mode 100644 ui/imports/ui/components/search-auto-complete-list/search-auto-complete-list.html create mode 100644 ui/imports/ui/components/search-auto-complete-list/search-auto-complete-list.js create mode 100644 ui/imports/ui/components/search-auto-complete-list/search-auto-complete.styl create mode 100644 ui/imports/ui/components/select-model/select-model.html create mode 100644 ui/imports/ui/components/select-model/select-model.js create mode 100644 ui/imports/ui/components/selectable-ordered-input/selectable-ordered-input.html create mode 100644 ui/imports/ui/components/selectable-ordered-input/selectable-ordered-input.js create mode 100644 ui/imports/ui/components/selectable-ordered-input/selectable-ordered-input.styl create mode 100644 ui/imports/ui/components/time-selection-widget/time-selection-widget.html create mode 100644 ui/imports/ui/components/time-selection-widget/time-selection-widget.js create mode 100644 ui/imports/ui/components/time-selection-widget/time-selection-widget.styl create mode 100644 ui/imports/ui/components/top-navbar-menu/top-navbar-menu.html create mode 100644 ui/imports/ui/components/top-navbar-menu/top-navbar-menu.js create mode 100644 ui/imports/ui/components/top-navbar-menu/top-navbar-menu.styl create mode 100644 ui/imports/ui/components/tree-node/tree-node.html create mode 100644 ui/imports/ui/components/tree-node/tree-node.js create mode 100644 ui/imports/ui/components/tree-node/tree-node.styl create mode 100644 ui/imports/ui/components/user-list/user-list.html create mode 100644 ui/imports/ui/components/user-list/user-list.js create mode 100644 ui/imports/ui/components/user-list/user-list.styl create mode 100644 ui/imports/ui/components/user/user.html create mode 100644 ui/imports/ui/components/user/user.js create mode 100644 ui/imports/ui/components/user/user.styl create mode 100644 ui/imports/ui/components/vedge-info-window/vedge-info-window.html create mode 100644 ui/imports/ui/components/vedge-info-window/vedge-info-window.js create mode 100644 ui/imports/ui/components/vedge-info-window/vedge-info-window.styl create mode 100644 ui/imports/ui/components/zone-dashboard/zone-dashboard.html create mode 100644 ui/imports/ui/components/zone-dashboard/zone-dashboard.js create mode 100644 ui/imports/ui/components/zone-dashboard/zone-dashboard.styl create mode 100644 ui/imports/ui/index.styl create mode 100644 ui/imports/ui/lib/environment-tree-node-behavior.js create mode 100644 ui/imports/ui/lib/input-model.js create mode 100644 ui/imports/ui/lib/inventory-tree-node-behavior.js create mode 100644 ui/imports/ui/lib/select-model.js create mode 100644 ui/imports/ui/reducers/environment-panel.reducer.js create mode 100644 ui/imports/ui/reducers/graph-tooltip-window.reducer.js create mode 100644 ui/imports/ui/reducers/i18n.reducer.js create mode 100644 ui/imports/ui/reducers/index.js create mode 100644 ui/imports/ui/reducers/main-app.reducer.js create mode 100644 ui/imports/ui/reducers/navigation.js create mode 100644 ui/imports/ui/reducers/search-interested-parties.js create mode 100644 ui/imports/ui/reducers/tree-node.reducer.js create mode 100644 ui/imports/ui/reducers/vedge-info-window.reducer.js create mode 100644 ui/imports/ui/store/index.js create mode 100644 ui/imports/ui/store/store.js (limited to 'ui/imports') 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 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 += "
"; + html += ""; + html += "x: " + this._dt.axisObjects.x._tickFormat(obj.userData.x); + html += "
"; + html += ""; + html += "y: " + this._dt.axisObjects.y._tickFormat(obj.userData.y); + html += "
"; + html += ""; + html += "z: " + this._dt.axisObjects.z._tickFormat(obj.userData.z); + html += "
"; + html += "
"; + + 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 @@ + + 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 @@ + + 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('+'); + } + }, + 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(''); + } + 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 @@ + + 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 @@ + + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + + + + 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 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 = '

Loading Calipso

'; +var spinner = '
'; 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 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 @@ + + 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 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + 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 @@ + 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 @@ + 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 @@ + 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 @@ + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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,'
') + .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 +}; -- cgit 1.2.3-korg