diff options
Diffstat (limited to 'ui/imports/ui/components/scheduled-scan')
3 files changed, 661 insertions, 0 deletions
diff --git a/ui/imports/ui/components/scheduled-scan/scheduled-scan.html b/ui/imports/ui/components/scheduled-scan/scheduled-scan.html new file mode 100644 index 0000000..c5c5c72 --- /dev/null +++ b/ui/imports/ui/components/scheduled-scan/scheduled-scan.html @@ -0,0 +1,116 @@ +<!-- +######################################################################################## +# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others # +# # +# All rights reserved. This program and the accompanying materials # +# are made available under the terms of the Apache License, Version 2.0 # +# which accompanies this distribution, and is available at # +# http://www.apache.org/licenses/LICENSE-2.0 # +######################################################################################## + --> +<template name="ScheduledScan"> + <div class="os-scheduled-scan cards white"> + + <h3>{{ getState 'pageHeader' }}</h3> + <div class="sm-form-container"> + <form role="form" class="sm-item-form form-horizontal"> + + <div class="sm-field-group-id cl-field-group"> + <label class="cl-field-label">Id</label> + <input name="id" + disabled="disabled" + value="{{ modelField '_id' }}" + class="sm-input-id cl-input" type="text" placeholder="Id" /> + </div> + + <div class="cl-field-group"> + <label class="cl-field-label">Environment</label> + {{>MtSelect (argsSelect classStr="cl-input" + options=envsAsOptions + selectedValue=(modelField 'environment') + onInput=onInputEnvFn + disabled=isGenDisabled + ) + }} + </div> + + <div class="cl-field-group"> + <label class="cl-field-label">Scan specific object</label> + {{>MtInput (argsInput classStr="cl-input" + placeholder="Object Id" + inputValue=(modelField 'object_id') + inputType="text" onInput=onInputObjectIdFn + disabled=isGenDisabled + ) }} + </div> + + <div class="cl-field-group"> + <label class="cl-field-label">Log level</label> + {{>MtSelect (argsSelect classStr="cl-input" + options=logLevelsAsOptions + selectedValue=(modelField 'log_level') + onInput=onInputLogLevelFn + disabled=isGenDisabled + ) }} + </div> + + <div class="cl-field-group"> + <label class="cl-field-label">Clear data</label> + {{>MtInput (argsInput classStr="cl-input" + inputValue=(modelField 'clear') + inputType="checkbox" + onInput=onInputClearFn + disabled=isGenDisabled + ) }} + </div> + + <div class="cl-field-group"> + <label class="cl-field-label">What to scan</label> + {{>MtSelect (argsSelect classStr="cl-input" + options=scanOnlyFieldOptions + selectedValue=scanOnlyFieldsSelectedValue + onInput=scanOnlyFieldInputFn + disabled=isGenDisabled + ) }} + </div> + + <div class="cl-field-group"> + <label class="cl-field-label">Frequency</label> + {{>MtSelect (argsSelect classStr="cl-input" + options=freqsAsOptions + selectedValue=(modelField 'freq') + onInput=onInputFreqFn + disabled=isGenDisabled + ) }} + </div> + + <div class="cl-field-group"> + <label class="cl-field-label">Recurrence</label> + <div class="cl-info-data">{{ getRecurrenceText (getState 'model') }}</div> + </div> + + <div class="cl-field-group"> + <label class="cl-field-label">Next run</label> + <div class="cl-info-data">{{ getNextRunText (getState 'model') }}</div> + </div> + + {{#if isUpdateableAction }} + <button type="submit" + class="js-submit-button mdl-button mdl-js-button mdl-button--raised + mdl-js-ripple-effect mdl-button--colored" + >{{ actionLabel }}</button> + {{/if }} + + </form> + + {{#if (getState 'isMessage') }} + <div class="js-message-panel alert {{#if (getState 'isError')}}alert-danger{{/if}} + {{#if (getState 'isSuccess')}}alert-success{{/if}}" + role="alert"> + {{ getState 'message' }} + </div> + {{/if }} + + </div> + </div> +</template> diff --git a/ui/imports/ui/components/scheduled-scan/scheduled-scan.js b/ui/imports/ui/components/scheduled-scan/scheduled-scan.js new file mode 100644 index 0000000..3bcc591 --- /dev/null +++ b/ui/imports/ui/components/scheduled-scan/scheduled-scan.js @@ -0,0 +1,511 @@ +///////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others / +// / +// All rights reserved. This program and the accompanying materials / +// are made available under the terms of the Apache License, Version 2.0 / +// which accompanies this distribution, and is available at / +// http://www.apache.org/licenses/LICENSE-2.0 / +///////////////////////////////////////////////////////////////////////////////////////// +/* + * Template Component: ScheduledScan + */ + +//import { Meteor } from 'meteor/meteor'; +import { Template } from 'meteor/templating'; +import { SimpleSchema } from 'meteor/aldeed:simple-schema'; +import { ReactiveDict } from 'meteor/reactive-dict'; +import * as R from 'ramda'; +import { RRule } from 'rrule'; +import { ScheduledScans, + scansOnlyFields, + subsScheduledScansId, +} from '/imports/api/scheduled-scans/scheduled-scans'; +import { Environments } from '/imports/api/environments/environments'; +import { Constants } from '/imports/api/constants/constants'; +import { insert, remove, update } from '/imports/api/scheduled-scans/methods'; + +import '/imports/ui/components/mt-select/mt-select'; +import '/imports/ui/components/mt-input/mt-input'; +import '/imports/ui/components/mt-radios/mt-radios'; + +import './scheduled-scan.html'; + +/* + * Lifecycles + */ + +Template.ScheduledScan.onCreated(function() { + var instance = this; + + instance.state = new ReactiveDict(); + instance.state.setDefault({ + action: null, + _id: null, + model: null, + isError: false, + isSuccess: false, + isMessage: false, + message: null, + envsAsOptions: [], + logLevelsAsOptions: [], + pageHeader: 'Schedule a Scan', + }); + + instance.autorun(function () { + let data = Template.currentData(); + new SimpleSchema({ + _id: { + type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } }, + optional: true + }, + action: { type: String }, + env: { type: String, optional: true }, + }).validate(data); + + instance.state.set('action', data.action); + R.when(R.pipe(R.isNil, R.not), x => instance.state.set('_id', x))(data._id); + R.when(R.pipe(R.isNil, R.not), x => instance.state.set('env', x))(data.env); + }); + + instance.autorun(function () { + let currentOptions = instance.state.get('envsAsOptions'); + instance.subscribe('environments_config'); + let tempOptions = []; + + let addToOptionsDebounced = _.debounce(() => { + if (currentOptions.length === tempOptions.length) { + let result = R.intersectionWith(R.eqBy(R.prop('value')), tempOptions, currentOptions); + if (result.length === currentOptions.length) { + return; + } + } + + instance.state.set('envsAsOptions', tempOptions); + }, 250); + + Environments.find({}).forEach((env) => { + let option = envToOption(env); + tempOptions = R.unionWith(R.eqBy(R.prop('value')), [option], tempOptions); + addToOptionsDebounced(); + }); + }); + + instance.autorun(function () { + let currentOptions = instance.state.get('logLevelsAsOptions'); + instance.subscribe('constants'); + + let tempOptions = []; + + let addToOptionsDebounced = _.debounce(() => { + if (currentOptions.length === tempOptions.length) { + let result = R.intersectionWith(R.eqBy(R.prop('value')), tempOptions, currentOptions); + if (result.length === currentOptions.length) { + return; + } + } + + instance.state.set('logLevelsAsOptions', tempOptions); + }, 250); + + Constants.find({ name: 'log_levels' }).forEach((logLevelsRec) => { + let logLevels = logLevelsRec.data; + R.map((logLevel) => { + let option = logLevelToOption(logLevel); + tempOptions = R.unionWith(R.eqBy(R.prop('value')), [option], tempOptions); + addToOptionsDebounced(); + }, logLevels); + }); + + }); + + instance.autorun(function () { + let action = instance.state.get('action'); + let _id = instance.state.get('_id'); + let env = instance.state.get('env'); + + R.cond([ + [R.equals('insert'), _x => initInsertView(instance, env)], + [R.equals('update'), _x => initUpdateView(instance, _id)], + [R.equals('view'), _x => initViewView(instance, _id)], + [R.equals('remove'), _x => initRemoveView(instance, _id)], + [R.T, x => { throw `unimplemented action: ${R.toString(x)}`; }] + ])(action); + }); +}); + +/* +Template.ScheduledScan.rendered = function() { +}; +*/ + +/* + * Events + */ + +Template.ScheduledScan.events({ + 'submit .sm-item-form': function(event, instance) { + event.preventDefault(); + let model = instance.state.get('model'); + + submitItem(instance, model); + } +}); + +/* + * Helpers + */ + +Template.ScheduledScan.helpers({ + isUpdateableAction() { + let instance = Template.instance(); + let action = instance.state.get('action'); + + return R.contains(action, ['insert', 'update', 'remove']); + }, + + actionLabel: function () { + let instance = Template.instance(); + let action = instance.state.get('action'); + return calcActionLabel(action); + }, + + asJson: function (val) { + return JSON.stringify(val); + }, + + getState: function (key) { + let instance = Template.instance(); + return instance.state.get(key); + }, + + modelField: function (fieldName) { + let instance = Template.instance(); + return R.path([fieldName], instance.state.get('model')); + }, + + envsAsOptions: function () { + let instance = Template.instance(); + return instance.state.get('envsAsOptions'); + }, + + onInputInventoryFn: function () { + let instance = Template.instance(); + return { fn: createSetModelFn(instance, 'inventory') }; + }, + + onInputObjectIdFn: function () { + let instance = Template.instance(); + return { fn: createSetModelFn(instance, 'object_id') }; + }, + + onInputClearFn: function () { + let instance = Template.instance(); + return { fn: createSetModelFn(instance, 'clear') }; + }, + + onInputEnvFn: function () { + let instance = Template.instance(); + return { fn: createSetModelFn(instance, 'environment') }; + }, + + onInputLogLevelFn: function () { + let instance = Template.instance(); + return { fn: createSetModelFn(instance, 'log_level') }; + }, + + onInputFreqFn: function () { + let instance = Template.instance(); + return { fn: createSetModelFn(instance, 'freq') }; + }, + + argsSelect: function (args) { + //let instance = Template.instance(); + let classStr = args.hash.classStr; + let options = args.hash.options; + let selectedValue = args.hash.selectedValue; + let onInput = args.hash.onInput; + let disabled = args.hash.disabled; + + return { + classStr: classStr, + selectedValue: selectedValue, + isDisabled: disabled, + options: options, + onInput: onInput, + }; + }, + + scanOnlyFieldOptions: function () { + return [ + { label: 'Full scan', value: '_full_scan' }, + { label: 'Scan only inventory', value: 'scan_only_inventory' }, + { label: 'Scan only links', value: 'scan_only_links' }, + { label: 'Scan only cliques', value: 'scan_only_cliques' }, + ]; + }, + + scanOnlyFieldInputFn: function () { + let instance = Template.instance(); + + return { + fn: function (newFieldName) { + let model = instance.state.get('model'); + model = R.reduce((acc, fieldName) => { + return R.assoc(fieldName, false, acc); + }, model, scansOnlyFields); + + if (newFieldName === '_full_scan') { + console.log('full scan selected. all scan_only_ fields are reset'); + } else { + model = R.assoc(newFieldName, true, model); + } + instance.state.set('model', model); + } + }; + }, + + scanOnlyFieldsSelectedValue: function () { + let instance = Template.instance(); + let model = instance.state.get('model'); + if (R.isNil(model)) { return null; } + + let selectedValue = R.find((fieldName) => { + return R.prop(fieldName, model) === true; + }, scansOnlyFields); + + if (R.isNil(selectedValue)) { + selectedValue = '_full_scan'; + } + return selectedValue; + }, + + argsRadios: function (options, onInputFn, selectedValue) { + return { + inputClasses: 'cl-input', + options: options, + selectedValue: selectedValue, + onInput: onInputFn, + }; + }, + + freqsAsOptions: function () { + return [ + { label: 'Yearly', value: 'YEARLY' }, + { label: 'Monthly', value: 'MONTHLY' }, + { label: 'Weekly', value: 'WEEKLY' }, + { label: 'Daily', value: 'DAILY' }, + { label: 'Hourly', value: 'HOURLY' }, + ]; + }, + + argsInput: function (args) { + let classStr = args.hash.classStr; + let placeholder = args.hash.placeholder; + let inputValue = args.hash.inputValue; + let inputType = args.hash.inputType; + let onInput = args.hash.onInput; + let disabled = args.hash.disabled; + + return { + inputValue: inputValue, + inputType: inputType, + classStr: classStr, + placeholder: placeholder, + isDisabled: disabled, + onInput: onInput, + }; + }, + + getEnvsAsOptions: function () { + let instance = Template.instance(); + return instance.state.get('envsAsOptions'); + }, + + logLevelsAsOptions: function () { + let instance = Template.instance(); + return instance.state.get('logLevelsAsOptions'); + }, + + isGenDisabled: function () { + let instance = Template.instance(); + let action = instance.state.get('action'); + if (R.contains(action, ['view', 'remove'])) { + return true; + } + + return false; + }, + + getRecurrenceText: function (model) { + if (R.isNil(model)) { return ''; } + + let rule = new RRule({ + freq: RRule[model.freq] + }); + + return rule.toText(); + }, + + getNextRunText: function (model) { + if (R.isNil(model)) { return ''; } + if (R.isNil(model.scheduled_timestamp)) { return ''; } + + let next = moment(model.scheduled_timestamp); + return next.fromNow(); + }, +}); // end: helpers + + +function initInsertView(instance, env) { + instance.state.set('model', ScheduledScans.schema.clean({ + environment: env, + })); + + subscribeToOptionsData(instance); +} + +function initExistingItemView(instance, _id) { + subscribeToOptionsData(instance); + instance.subscribe(subsScheduledScansId, _id); + + ScheduledScans.find({ _id: _id }).forEach((model) => { + instance.state.set('model', model); + }); +} + +function initViewView(instance, _id) { + initExistingItemView(instance, _id); +} + +function initUpdateView(instance, _id) { + initExistingItemView(instance, _id); +} + +function initRemoveView(instance, _id) { + initExistingItemView(instance, _id); +} + +function subscribeToOptionsData(_instance) { + +} + +function envToOption(env) { + return { value: env.name, label: env.name }; +} + +function logLevelToOption(logLevel) { + return { value: logLevel.value, label: logLevel.label }; +} + +function createSetModelFn(instance, fieldName) { + return function (value) { + let model = instance.state.get('model'); + model = R.assoc(fieldName, value, model); + instance.state.set('model', model); + }; +} + +function calcActionLabel(action) { + switch (action) { + case 'insert': + return 'Add'; + case 'remove': + return 'Remove'; + case 'update': + return 'Update'; + default: + return 'Submit'; + } +} + +function submitItem( + instance, + model +) { + + let action = instance.state.get('action'); + + instance.state.set('isError', false); + instance.state.set('isSuccess', false); + instance.state.set('isMessage', false); + instance.state.set('message', null); + + switch (action) { + case 'insert': + insert.call({ + environment: model.environment, + object_id: model.object_id, + log_level: model.log_level, + clear: model.clear, + scan_only_inventory: model.scan_only_inventory, + scan_only_links: model.scan_only_links, + scan_only_cliques: model.scan_only_cliques, + freq: model.freq, + }, processActionResult.bind(null, instance)); + break; + + case 'update': + update.call({ + _id: model._id, + environment: model.environment, + object_id: model.object_id, + log_level: model.log_level, + clear: model.clear, + scan_only_inventory: model.scan_only_inventory, + scan_only_links: model.scan_only_links, + scan_only_cliques: model.scan_only_cliques, + freq: model.freq, + }, processActionResult.bind(null, instance)); + break; + + case 'remove': + remove.call({ + _id: model._id, + }, processActionResult.bind(null, instance)); + break; + + default: + // todo + break; + } +} + +function processActionResult(instance, error) { + let action = instance.state.get('action'); + + if (error) { + instance.state.set('isError', true); + instance.state.set('isSuccess', false); + instance.state.set('isMessage', true); + + if (typeof error === 'string') { + instance.state.set('message', error); + } else { + instance.state.set('message', error.message); + } + + return; + } + + instance.state.set('isError', false); + instance.state.set('isSuccess', true); + instance.state.set('isMessage', true); + + switch (action) { + case 'insert': + instance.state.set('message', 'Record had been added successfully'); + instance.state.set('disabled', true); + break; + + case 'remove': + instance.state.set('message', 'Record had been removed successfully'); + instance.state.set('disabled', true); + break; + + case 'update': + instance.state.set('message', 'Record had been updated successfully'); + break; + } + + //Router.go('/link-types-list'); +} diff --git a/ui/imports/ui/components/scheduled-scan/scheduled-scan.styl b/ui/imports/ui/components/scheduled-scan/scheduled-scan.styl new file mode 100644 index 0000000..ac64dd3 --- /dev/null +++ b/ui/imports/ui/components/scheduled-scan/scheduled-scan.styl @@ -0,0 +1,34 @@ +.os-scheduled-scan + margin: 20px; + + .cl-field-group + display: flex; + flex-flow: row nowrap; + align-items: center; + padding: 5px 0; + + .cl-field-label + width: 170px; + margin: 0 5px; + + .cl-input + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + width: 400px; + margin: 0 5px; + + .cl-field-desc + margin: 0 5px; + + .js-message-panel + margin-top: 20px; |