http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity/table.html ---------------------------------------------------------------------- diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity/table.html b/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity/table.html new file mode 100644 index 0000000..13d5807 --- /dev/null +++ b/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity/table.html @@ -0,0 +1,150 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<div ng-controller="classification_sensitivityViewTable"> + <p ng-show="databases._promise.$$state.status !== 1"> + <span class="fa fa-refresh fa-spin"> </span> + Loading... + </p> + + <div ng-show="databases._promise.$$state.status === 1" class="row"> + <div class="col-md-4"> + <label> + Databases + ({{databases.length}}) + </label> + <ul class="tree tree-bordered" style="max-height: 500px; overflow-y: auto;"> + <li ng-repeat="db in databases"> + <span class="tree-item box-clickable text-primary" ng-click="db.show = !db.show; loadTables(db);"> + <span ng-class="{'text-warning' : db.sensitiveType}"> + <span class="fa fa-database"> </span> + {{db.database}} + <span ng-show="db.tables._promise.$$state.status === 1">({{db.tables.length}})</span> + + <span ng-show="Auth.isRole('ROLE_ADMIN')"> + <a class="fa fa-eye text-muted hover" ng-click="markSensitivity(db, $event)" ng-show="!db.sensitiveType" + uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="right"></a> + <a class="fa fa-eye-slash text-muted hover" ng-click="unmarkSensitivity(db, $event)" ng-show="db.sensitiveType" + uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="right"></a> + </span> + + <span class="pull-right" ng-show="db.childSensitiveTypes.length"> + <span class="fa fa-dot-circle-o" uib-tooltip="Contain child sensitivity defination" tooltip-placement="right" tooltip-append-to-body="true"> </span> + </span> + <span ng-show="db.sensitiveType" class="pull-right">[{{db.sensitiveType}}]</span> + </span> + </span> + <ul ng-show="db.show"> + <li ng-show="db.tables._promise.$$state.status !== 1"> + <span> + <span class="fa fa-refresh fa-spin"> </span> + Loading... + </span> + </li> + <li ng-repeat="tb in db.tables" ng-class="{active : tb === table}"> + <span class="tree-item box-clickable text-primary" ng-click="loadColumns(db, tb)"> + <span ng-class="{'text-warning' : tb.sensitiveType}"> + <span class="fa fa-table"> </span> + {{tb.table}} + + <span ng-show="Auth.isRole('ROLE_ADMIN')"> + <a class="fa fa-eye text-muted hover" ng-click="markSensitivity(tb, $event)" ng-show="!tb.sensitiveType" + uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="right"></a> + <a class="fa fa-eye-slash text-muted hover" ng-click="unmarkSensitivity(tb, $event)" ng-show="tb.sensitiveType" + uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="right"></a> + </span> + + <span class="pull-right" ng-show="tb.childSensitiveTypes.length"> + <span class="fa fa-dot-circle-o" uib-tooltip="Contain child sensitivity defination" tooltip-placement="right" tooltip-append-to-body="true"> </span> + </span> + <span ng-show="tb.sensitiveType" class="pull-right">[{{tb.sensitiveType}}]</span> + </span> + </span> + </li> + </ul> + </li> + </ul> + </div> + <div class="col-md-8"> + <label ng-show="table">Route: {{table.database}} > {{table.table}}</label> + <p ng-show="table && table.columns._promise.$$state.status !== 1"> + <span class="fa fa-refresh fa-spin"> </span> + Loading... + </p> + <div ng-show="table && table.columns._promise.$$state.status === 1"> + <table class="table table-bordered"> + <thead> + <tr> + <th width="40%">Column Name</th> + <th>Sensitivity Type</th> + <th width="10" ng-show="Auth.isRole('ROLE_ADMIN')"> </th> + </tr> + </thead> + <tbody> + <tr ng-repeat="col in table.columns" ng-class="{warning : col.sensitiveType}"> + <td>{{col.column}}</td> + <td>{{col.sensitiveType}}</td> + <td ng-show="Auth.isRole('ROLE_ADMIN')"> + <button class="fa fa-eye btn btn-primary btn-xs" ng-click="markSensitivity(col)" ng-show="!col.sensitiveType" + uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="left"> </button> + <button class="fa fa-eye-slash btn btn-warning btn-xs" ng-click="unmarkSensitivity(col)" ng-show="col.sensitiveType" + uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="left"> </button> + </td> + </tr> + </tbody> + </table> + </div> + </div> + </div> + + + + + + + <!-- Modal: Create / Edit site --> + <div class="modal fade" id="sensitivityMDL" tabindex="-1" role="dialog"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + <h4 class="modal-title">Mark Sensitivity Data</h4> + </div> + <div class="modal-body"> + <div class="form-group"> + <label>Resource</label> + <input type="text" readonly="readonly" class="form-control" ng-model="_markItem.tags[viewConfig.keys[0]]" /> + </div> + <div class="form-group"> + <label>* Sensitivity Type</label> + <input type="text" class="form-control" ng-model="_markItem.sensitivityType" id="sensitiveType" /> + </div> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal"> + Close + </button> + <button type="button" class="btn btn-primary" ng-click="confirmUpateSensitivity()" ng-disabled="!_markItem.sensitivityType"> + Update + </button> + </div> + </div> + </div> + </div> +</div> \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/common/controller.js ---------------------------------------------------------------------- diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/common/controller.js b/eagle-webservice/src/main/webapp/_app/public/feature/common/controller.js new file mode 100644 index 0000000..207c8df --- /dev/null +++ b/eagle-webservice/src/main/webapp/_app/public/feature/common/controller.js @@ -0,0 +1,1224 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +(function() { + 'use strict'; + + var featureControllers = angular.module("featureControllers"); + var feature = featureControllers.register("common"); + + // ============================================================== + // = Function = + // ============================================================== + feature.service("Policy", function(Entities) { + var Policy = function () {}; + + Policy.updatePolicyStatus = function(policy, status) { + $.dialog({ + title: "Confirm", + content: "Do you want to " + (status ? "enable" : "disable") + " policy[" + policy.tags.policyId + "]?", + confirm: true + }, function(ret) { + if(ret) { + policy.enabled = status; + Entities.updateEntity("AlertDefinitionService", policy); + } + }); + }; + Policy.deletePolicy = function(policy, callback) { + $.dialog({ + title: "Confirm", + content: "Do you want to delete policy[" + policy.tags.policyId + "]?", + confirm: true + }, function(ret) { + if(ret) { + policy.enabled = status; + Entities.deleteEntity("AlertDefinitionService", policy)._promise.finally(function() { + if(callback) { + callback(policy); + } + }); + } + }); + }; + return Policy; + }); + + feature.service("Notification", function(Entities) { + var Notification = function () {}; + Notification.map = {}; + + Notification.list = Entities.queryEntities("AlertNotificationService"); + Notification.list._promise.then(function () { + $.each(Notification.list, function (i, notification) { + // Parse fields + notification.fieldList = common.parseJSON(notification.fields, []); + + // Fill map + Notification.map[notification.tags.notificationType] = notification; + }); + }); + + Notification.promise = Notification.list._promise; + + return Notification; + }); + + // ============================================================== + // = Policies = + // ============================================================== + + // ========================= Policy List ======================== + feature.navItem("policyList", "Policies", "list"); + feature.controller('policyList', function(PageConfig, Site, $scope, Application, Entities, Policy) { + PageConfig.pageTitle = "Policy List"; + PageConfig.pageSubTitle = Site.current().tags.site; + + // Initial load + $scope.policyList = []; + $scope.application = Application.current(); + + // List policies + var _policyList = Entities.queryEntities("AlertDefinitionService", {site: Site.current().tags.site, application: $scope.application.tags.application}); + _policyList._promise.then(function() { + $.each(_policyList, function(i, policy) { + policy.__expression = common.parseJSON(policy.policyDef, {}).expression; + + $scope.policyList.push(policy); + }); + }); + $scope.policyList._promise = _policyList._promise; + + // Function + $scope.searchFunc = function(item) { + var key = $scope.search; + if(!key) return true; + + var _key = key.toLowerCase(); + function _hasKey(item, path) { + var _value = common.getValueByPath(item, path, "").toLowerCase(); + return _value.indexOf(_key) !== -1; + } + return _hasKey(item, "tags.policyId") || _hasKey(item, "__expression") || _hasKey(item, "description") || _hasKey(item, "owner"); + }; + + $scope.updatePolicyStatus = Policy.updatePolicyStatus; + $scope.deletePolicy = function(policy) { + Policy.deletePolicy(policy, function(policy) { + var _index = $scope.policyList.indexOf(policy); + $scope.policyList.splice(_index, 1); + }); + }; + }); + + // ======================= Policy Detail ======================== + feature.controller('policyDetail', function(PageConfig, Site, $scope, $wrapState, $interval, Entities, Policy, nvd3) { + var MAX_PAGESIZE = 10000; + var seriesRefreshInterval; + + PageConfig.pageTitle = "Policy Detail"; + PageConfig.lockSite = true; + PageConfig + .addNavPath("Policy List", "/common/policyList") + .addNavPath("Policy Detail"); + + $scope.chartConfig = { + chart: "line", + xType: "time" + }; + + // Query policy + if($wrapState.param.filter) { + $scope.policyList = Entities.queryEntity("AlertDefinitionService", $wrapState.param.filter); + } else { + $scope.policyList = Entities.queryEntities("AlertDefinitionService", { + policyId: $wrapState.param.policy, + site: $wrapState.param.site, + alertExecutorId: $wrapState.param.executor + }); + } + + $scope.policyList._promise.then(function() { + var policy = null; + + if($scope.policyList.length === 0) { + $.dialog({ + title: "OPS!", + content: "Policy not found!" + }, function() { + location.href = "#/common/policyList"; + }); + return; + } else { + policy = $scope.policyList[0]; + + policy.__notificationList = common.parseJSON(policy.notificationDef, []); + + policy.__expression = common.parseJSON(policy.policyDef, {}).expression; + + $scope.policy = policy; + Site.current(Site.find($scope.policy.tags.site)); + console.log($scope.policy); + } + + // Visualization + var _intervalType = 0; + var _intervalList = [ + ["Daily", 1440, function() { + var _endTime = app.time.now().hour(23).minute(59).second(59).millisecond(0); + var _startTime = _endTime.clone().subtract(1, "month").hour(0).minute(0).second(0).millisecond(0); + return [_startTime, _endTime]; + }], + ["Hourly", 60, function() { + var _endTime = app.time.now().minute(59).second(59).millisecond(0); + var _startTime = _endTime.clone().subtract(48, "hour").minute(0).second(0).millisecond(0); + return [_startTime, _endTime]; + }], + ["Every 5 minutes", 5, function() { + var _endTime = app.time.now().second(59).millisecond(0); + var _minute = Math.floor(_endTime.minute() / 5) * 5; + var _startTime = _endTime.clone().minute(_minute).subtract(5 * 30, "minute").second(0).millisecond(0); + _endTime.minute(_minute + 4); + return [_startTime, _endTime]; + }] + ]; + + function _loadSeries(seriesName, metricName, condition) { + var list = Entities.querySeries("GenericMetricService", $.extend({_metricName: metricName}, condition), "@site", "sum(value)", _intervalList[_intervalType][1]); + var seriesList = nvd3.convert.eagle([list]); + if(!$scope[seriesName]) $scope[seriesName] = seriesList; + list._promise.then(function() { + $scope[seriesName] = seriesList; + }); + } + + function refreshSeries() { + var _timeRange = _intervalList[_intervalType][2](); + var _startTime = _timeRange[0]; + var _endTime = _timeRange[1]; + var _cond = { + application: policy.tags.application, + policyId: policy.tags.policyId, + _startTime: _startTime, + _endTime: _endTime + }; + console.log("Range:", common.format.date(_startTime, "datetime"), common.format.date(_endTime, "datetime")); + + // > eagle.policy.eval.count + _loadSeries("policyEvalSeries", "eagle.policy.eval.count", _cond); + + // > eagle.policy.eval.fail.count + _loadSeries("policyEvalFailSeries", "eagle.policy.eval.fail.count", _cond); + + // > eagle.alert.count + _loadSeries("alertSeries", "eagle.alert.count", _cond); + + // > eagle.alert.fail.count + _loadSeries("alertFailSeries", "eagle.alert.fail.count", _cond); + } + + // Alert list + $scope.alertList = Entities.queryEntities("AlertService", { + site: Site.current().tags.site, + application: policy.tags.application, + policyId: policy.tags.policyId, + _pageSize: MAX_PAGESIZE, + _duration: 1000 * 60 * 60 * 24 * 30, + __ETD: 1000 * 60 * 60 * 24 + }); + + refreshSeries(); + seriesRefreshInterval = $interval(refreshSeries, 60000); + + // Menu + $scope.visualizationMenu = [ + {icon: "clock-o", title: "Interval", list: + $.map(_intervalList, function(item, index) { + var _item = {icon: "clock-o", title: item[0], func: function() { + _intervalType = index; + refreshSeries(); + }}; + Object.defineProperty(_item, 'strong', { + get: function() {return _intervalType === index;} + }); + return _item; + }) + } + ]; + }); + + // Function + $scope.updatePolicyStatus = Policy.updatePolicyStatus; + $scope.deletePolicy = function(policy) { + Policy.deletePolicy(policy, function() { + location.href = "#/common/policyList"; + }); + }; + + // Clean up + $scope.$on('$destroy', function() { + $interval.cancel(seriesRefreshInterval); + }); + }); + + // ======================== Policy Edit ========================= + function policyCtrl(create, PageConfig, Site, Policy, $scope, $wrapState, $q, UI, Entities, Application, Authorization, Notification) { + PageConfig.pageTitle = create ? "Policy Create" : "Policy Edit"; + PageConfig.pageSubTitle = Site.current().tags.site; + PageConfig + .addNavPath("Policy List", "/common/policyList") + .addNavPath("Policy Edit"); + + var _winTimeDesc = "Number unit[millisecond, sec, min, hour, day, month, year]. e.g. 23 sec"; + var _winTimeRegex = /^\d+\s+(millisecond|milliseconds|second|seconds|sec|minute|minutes|min|hour|hours|day|days|week|weeks|month|months|year|years)$/; + var _winTimeDefaultValue = '10 min'; + $scope.config = { + window: [ + // Display name, window type, required columns[Title, Description || "LONG_FIELD", Regex check, default value] + { + title: "Message Time Slide", + description: "Using timestamp filed from input is used as event's timestamp", + type: "externalTime", + fields:[ + {title: "Field", defaultValue: "timestamp", hide: true}, + {title: "Time", description: _winTimeDesc, regex: _winTimeRegex, defaultValue: _winTimeDefaultValue} + ] + }, + { + title: "System Time Slide", + description: "Using System time is used as timestamp for event's timestamp", + type: "time", + fields:[ + {title: "Time", description: _winTimeDesc, regex: _winTimeRegex, defaultValue: _winTimeDefaultValue} + ] + }, + { + title: "System Time Batch", + description: "Same as System Time Window except the policy is evaluated at fixed duration", + type: "timeBatch", + fields:[ + {title: "Time", description: _winTimeDesc, regex: _winTimeRegex, defaultValue: _winTimeDefaultValue} + ] + }, + { + title: "Length Slide", + description: "The slide window has a fixed length", + type: "length", + fields:[ + {title: "Number", description: "Number only. e.g. 1023", regex: /^\d+$/} + ] + }, + { + title: "Length Batch", + description: "Same as Length window except the policy is evaluated in batch mode when fixed event count reached", + type: "lengthBatch", + fields:[ + {title: "Number", description: "Number only. e.g. 1023", regex: /^\d+$/} + ] + } + ] + }; + + $scope.Notification = Notification; + + $scope.create = create; + $scope.encodedRowkey = $wrapState.param.filter; + + $scope.step = 0; + $scope.applications = {}; + $scope.policy = {}; + + // ========================================== + // = Notification = + // ========================================== + $scope.notificationTabHolder = null; + + $scope.newNotification = function (notificationType) { + var __notification = { + notificationType: notificationType + }; + + $.each(Notification.map[notificationType].fieldList, function (i, field) { + __notification[field.name] = field.value || ""; + }); + + $scope.policy.__.notification.push(__notification); + }; + + Notification.promise.then(function () { + $scope.menu = Authorization.isRole('ROLE_ADMIN') ? [ + {icon: "cog", title: "Configuration", list: [ + {icon: "trash", title: "Delete", danger: true, func: function () { + var notification = $scope.notificationTabHolder.selectedPane.data; + UI.deleteConfirm(notification.notificationType).then(null, null, function(holder) { + common.array.remove(notification, $scope.policy.__.notification); + holder.closeFunc(); + }); + }} + ]}, + {icon: "plus", title: "New", list: $.map(Notification.list, function(notification) { + return { + icon: "plus", + title: notification.tags.notificationType, + func: function () { + $scope.newNotification(notification.tags.notificationType); + setTimeout(function() { + $scope.notificationTabHolder.setSelect(-1); + $scope.$apply(); + }, 0); + } + }; + })} + ] : []; + }); + + // ========================================== + // = Data Preparation = + // ========================================== + // Steam list + var _streamList = Entities.queryEntities("AlertStreamService", {application: Application.current().tags.application}); + var _executorList = Entities.queryEntities("AlertExecutorService", {application: Application.current().tags.application}); + $scope.streamList = _streamList; + $scope.executorList = _executorList; + $scope.streamReady = false; + + $q.all([_streamList._promise, _executorList._promise]).then(function() { + // Map executor with stream + $.each(_executorList, function(i, executor) { + $.each(_streamList, function(j, stream) { + if(stream.tags.application === executor.tags.application && stream.tags.streamName === executor.tags.streamName) { + stream.alertExecutor = executor; + return false; + } + }); + }); + + // Fill stream list + $.each(_streamList, function(i, unit) { + var _srcStreamList = $scope.applications[unit.tags.application] = $scope.applications[unit.tags.application] || []; + _srcStreamList.push(unit); + }); + + $scope.streamReady = true; + + // ========================================== + // = Function = + // ========================================== + function _findStream(application, streamName) { + var _streamList = $scope.applications[application]; + if(!_streamList) return null; + + for(var i = 0 ; i < _streamList.length ; i += 1) { + if(_streamList[i].tags.streamName === streamName) { + return _streamList[i]; + } + } + return null; + } + + // ========================================== + // = Step control = + // ========================================== + $scope.steps = [ + // >> Select stream + { + title: "Select Stream", + ready: function() { + return $scope.streamReady; + }, + init: function() { + $scope.policy.__.streamName = $scope.policy.__.streamName || + common.array.find($scope.policy.tags.application, _streamList, "tags.application").tags.streamName; + }, + nextable: function() { + var _streamName = common.getValueByPath($scope.policy, "__.streamName"); + if(!_streamName) return false; + + // Detect stream in current data source list + return !!common.array.find(_streamName, $scope.applications[$scope.policy.tags.application], "tags.streamName"); + } + }, + + // >> Define Alert Policy + { + title: "Define Alert Policy", + init: function() { + // Normal mode will fetch meta list + if(!$scope.policy.__.advanced) { + var _stream = _findStream($scope.policy.tags.application, $scope.policy.__.streamName); + $scope._stream = _stream; + + if(!_stream) { + $.dialog({ + title: "OPS", + content: "Stream not found! Current application don't match any stream." + }); + return; + } + + if(!_stream.metas) { + _stream.metas = Entities.queryEntities("AlertStreamSchemaService", {application: $scope.policy.tags.application, streamName: $scope.policy.__.streamName}); + _stream.metas._promise.then(function() { + _stream.metas.sort(function(a, b) { + if(a.tags.attrName < b.tags.attrName) { + return -1; + } else if(a.tags.attrName > b.tags.attrName) { + return 1; + } + return 0; + }); + }); + } + } + }, + ready: function() { + if(!$scope.policy.__.advanced) { + return $scope._stream.metas._promise.$$state.status === 1; + } + return true; + }, + nextable: function() { + if($scope.policy.__.advanced) { + // Check stream source + $scope._stream = null; + $.each($scope.applications[$scope.policy.tags.application], function(i, stream) { + if(($scope.policy.__._expression || "").indexOf(stream.tags.streamName) !== -1) { + $scope._stream = stream; + return false; + } + }); + return $scope._stream; + } else { + // Window + if($scope.policy.__.windowConfig) { + var _winMatch = true; + var _winConds = $scope.getWindow().fields; + $.each(_winConds, function(i, cond) { + if(!(cond.val || "").match(cond.regex)) { + _winMatch = false; + return false; + } + }); + if(!_winMatch) return false; + + // Aggregation + if($scope.policy.__.groupAgg) { + if(!$scope.policy.__.groupAggPath || + !$scope.policy.__.groupCondOp || + !$scope.policy.__.groupCondVal) { + return false; + } + } + } + } + return true; + } + }, + + // >> Configuration & Notification + { + title: "Configuration & Notification", + nextable: function() { + return !!$scope.policy.tags.policyId; + } + } + ]; + + // ========================================== + // = Policy Logic = + // ========================================== + _streamList._promise.then(function() { + // Initial policy entity + if(create) { + $scope.policy = { + __: { + toJSON: jQuery.noop, + conditions: {}, + notification: [], + dedupe: { + alertDedupIntervalMin: 10, + fields: [] + }, + _dedupTags: {}, + policy: {}, + window: "externalTime", + group: "", + groupAgg: "count", + groupAggPath: "timestamp", + groupCondOp: ">=", + groupCondVal: "2" + }, + description: "", + enabled: true, + prefix: "alertdef", + remediationDef: "", + tags: { + application: Application.current().tags.application, + policyType: "siddhiCEPEngine" + } + }; + + // If configured data source + if($wrapState.param.app) { + $scope.policy.tags.application = $wrapState.param.app; + if(common.array.find($wrapState.param.app, Site.current().applicationList, "tags.application")) { + setTimeout(function() { + $scope.changeStep(0, 2, false); + $scope.$apply(); + }, 1); + } + } + + // Start step + $scope.changeStep(0, 1, false); + console.log($scope.policy); + } else { + var _policy = Entities.queryEntity("AlertDefinitionService", $scope.encodedRowkey); + _policy._promise.then(function() { + if(_policy.length) { + $scope.policy = _policy[0]; + $scope.policy.__ = { + toJSON: jQuery.noop + }; + + Site.current(Site.find($scope.policy.tags.site)); + } else { + $.dialog({ + title: "OPS", + content: "Policy not found!" + }, function() { + $wrapState.path("/common/policyList"); + $scope.$apply(); + }); + return; + } + + var _application = Application.current(); + if(_application.tags.application !== $scope.policy.tags.application) { + _application = Application.find($scope.policy.tags.application); + if(_application) { + Application.current(_application, false); + console.log("Application not match. Do reload..."); + $wrapState.reload(); + } else { + $.dialog({ + title: "OPS", + content: "Application not found! Current policy don't match any application." + }, function() { + $location.path("/common/policyList"); + $scope.$apply(); + }); + } + return; + } + + // === Revert inner data === + // >> De-dupe + $scope.policy.__._dedupTags = {}; + $scope.policy.__.dedupe = common.parseJSON($scope.policy.dedupeDef, {}); + $.each($scope.policy.__.dedupe.fields || [], function (i, field) { + $scope.policy.__._dedupTags[field] = true; + }); + + // >> Notification + $scope.policy.__.notification = common.parseJSON($scope.policy.notificationDef, []); + + // >> Policy + var _policyUnit = $scope.policy.__.policy = common.parseJSON($scope.policy.policyDef); + + // >> Parse expression + $scope.policy.__.conditions = {}; + var _condition = _policyUnit.expression.match(/from\s+(\w+)(\[(.*)])?(#window[^\)]*\))?\s+(select (\w+, )?(\w+)\((\w+)\) as [\w\d_]+ (group by (\w+) )?having ([\w\d_]+) ([<>=]+) ([^\s]+))?/); + var _cond_stream = _condition[1]; + var _cond_query = _condition[3] || ""; + var _cond_window = _condition[4]; + var _cond_group = _condition[5]; + var _cond_groupUnit = _condition.slice(7,14); + + if(!_condition) { + $scope.policy.__.advanced = true; + } else { + // > StreamName + var _streamName = _cond_stream; + var _cond = _cond_query; + + $scope.policy.__.streamName = _streamName; + + // > Conditions + // Loop condition groups + if(_cond.trim() !== "" && /^\(.*\)$/.test(_cond)) { + var _condGrps = _cond.substring(1, _cond.length - 1).split(/\)\s+and\s+\(/); + $.each(_condGrps, function(i, line) { + // Loop condition cells + var _condCells = line.split(/\s+or\s+/); + $.each(_condCells, function(i, cell) { + var _opMatch = cell.match(/(\S*)\s*(==|!=|>|<|>=|<=|contains)\s*('(?:[^'\\]|\\.)*'|[\w\d]+)/); + if(!common.isEmpty(_opMatch)) { + var _key = _opMatch[1]; + var _op = _opMatch[2]; + var _val = _opMatch[3]; + var _conds = $scope.policy.__.conditions[_key] = $scope.policy.__.conditions[_key] || []; + var _type = ""; + if(_val.match(/'.*'/)) { + _val = _val.slice(1, -1); + _type = "string"; + } else if(_val === "true" || _val === "false") { + var _regexMatch = _key.match(/^str:regexp\((\w+),'(.*)'\)/); + var _containsMatch = _key.match(/^str:contains\((\w+),'(.*)'\)/); + var _mathes = _regexMatch || _containsMatch; + if(_mathes) { + _key = _mathes[1]; + _val = _mathes[2]; + _type = "string"; + _op = _regexMatch ? "regex" : "contains"; + _conds = $scope.policy.__.conditions[_key] = $scope.policy.__.conditions[_key] || []; + } else { + _type = "bool"; + } + } else { + _type = "number"; + } + _conds.push($scope._CondUnit(_key, _op, _val, _type)); + } + }); + }); + } else if(_cond_query !== "") { + $scope.policy.__.advanced = true; + } + } + + if($scope.policy.__.advanced) { + $scope.policy.__._expression = _policyUnit.expression; + } else { + // > window + if(!_cond_window) { + $scope.policy.__.window = "externalTime"; + $scope.policy.__.group = ""; + $scope.policy.__.groupAgg = "count"; + $scope.policy.__.groupAggPath = "timestamp"; + $scope.policy.__.groupCondOp = ">="; + $scope.policy.__.groupCondVal = "2"; + } else { + try { + $scope.policy.__.windowConfig = true; + + var _winCells = _cond_window.match(/\.(\w+)\((.*)\)/); + $scope.policy.__.window = _winCells[1]; + var _winConds = $scope.getWindow().fields; + $.each(_winCells[2].split(","), function(i, val) { + _winConds[i].val = val; + }); + + // Group + if(_cond_group) { + $scope.policy.__.group = _cond_groupUnit[3]; + $scope.policy.__.groupAgg = _cond_groupUnit[0]; + $scope.policy.__.groupAggPath = _cond_groupUnit[1]; + $scope.policy.__.groupAggAlias = _cond_groupUnit[4] === "aggValue" ? "" : _cond_groupUnit[4]; + $scope.policy.__.groupCondOp = _cond_groupUnit[5]; + $scope.policy.__.groupCondVal = _cond_groupUnit[6]; + } else { + $scope.policy.__.group = ""; + $scope.policy.__.groupAgg = "count"; + $scope.policy.__.groupAggPath = "timestamp"; + $scope.policy.__.groupCondOp = ">="; + $scope.policy.__.groupCondVal = "2"; + } + } catch(err) { + $scope.policy.__.window = "externalTime"; + } + } + } + + $scope.changeStep(0, 2, false); + console.log($scope.policy); + }); + } + }); + + // ========================================== + // = Function = + // ========================================== + // UI: Highlight select step + $scope.stepSelect = function(step) { + return step === $scope.step ? "active" : ""; + }; + + // UI: Collapse all + $scope.collapse = function(cntr) { + var _list = $(cntr).find(".collapse").css("height", "auto"); + if(_list.hasClass("in")) { + _list.removeClass("in"); + } else { + _list.addClass("in"); + } + }; + + // Step process. Will fetch target step attribute and return boolean + function _check(key, step) { + var _step = $scope.steps[step - 1]; + if(!_step) return; + + var _value = _step[key]; + if(typeof _value === "function") { + return _value(); + } else if(typeof _value === "boolean") { + return _value; + } + return true; + } + // Check step is ready. Otherwise will display load animation + $scope.stepReady = function(step) { + return _check("ready", step); + }; + // Check whether process next step. Otherwise will disable next button + $scope.checkNextable = function(step) { + return !_check("nextable", step); + }; + // Switch step + $scope.changeStep = function(step, targetStep, check) { + if(check === false || _check("checkStep", step)) { + $scope.step = targetStep; + + _check("init", targetStep); + } + }; + + // Window + $scope.getWindow = function() { + if(!$scope.policy || !$scope.policy.__) return null; + return common.array.find($scope.policy.__.window, $scope.config.window, "type"); + }; + + // Aggregation + $scope.groupAggPathList = function() { + return $.grep(common.getValueByPath($scope, "_stream.metas", []), function(meta) { + return $.inArray(meta.attrType, ['long','integer','number', 'double', 'float']) !== -1; + }); + }; + + $scope.updateGroupAgg = function() { + $scope.policy.__.groupAggPath = $scope.policy.__.groupAggPath || common.getValueByPath($scope.groupAggPathList()[0], "tags.attrName"); + + if($scope.policy.__.groupAgg === 'count') { + $scope.policy.__.groupAggPath = 'timestamp'; + } + }; + + // Resolver + $scope.resolverTypeahead = function(value, resolver) { + var _resolverList = Entities.query("stream/attributeresolve", { + site: Site.current().tags.site, + resolver: resolver, + query: value + }); + return _resolverList._promise.then(function() { + return _resolverList; + }); + }; + + // Used for input box when pressing enter + $scope.conditionPress = function(event) { + if(event.which == 13) { + setTimeout(function() { + $(event.currentTarget).closest(".input-group").find("button").click(); + }, 1); + } + }; + // Check whether has condition + $scope.hasCondition = function(key, type) { + var _list = common.getValueByPath($scope.policy.__.conditions, key, []); + if(_list.length === 0) return false; + + if(type === "bool") { + return !_list[0].ignored(); + } + return true; + }; + // Condition unit definition + $scope._CondUnit = function(key, op, value, type) { + return { + key: key, + op: op, + val: value, + type: type, + ignored: function() { + return this.type === "bool" && this.val === "none"; + }, + getVal: function() { + return this.type === "string" ? "'" + this.val + "'" : this.val; + }, + toString: function() { + return this.op + " " + this.getVal(); + }, + toCondString: function() { + var _op = this.op === "=" ? "==" : this.op; + if(_op === "regex") { + return "str:regexp(" + this.key + "," + this.getVal() + ")==true"; + } else if(_op === "contains") { + return "str:contains(" + this.key + "," + this.getVal() + ")==true"; + } else { + return this.key + " " + _op + " " + this.getVal(); + } + } + }; + }; + + //for maprfs, if key is status or volume or src/dst, convert these names to id. + $scope.convertToID = function(_condList, key, op, name, type, site){ + if(key == "dst" || key == "src") { + Entities.maprfsNameToID("fNameResolver", name, site)._promise.then( + function(response){ + console.log("success"); + console.log(response); + _condList.push($scope._CondUnit(key, op, response.data, type)); + }, + function(error, status){ + console.log("error: " + status); + } + ); + } + else if (key == "status"){ + Entities.maprfsNameToID("sNameResolver", name, site)._promise.then( + function(response){ + console.log("success"); + console.log(response); + _condList.push($scope._CondUnit(key, op, response.data, type)); + }, + function(error, status){ + console.log("error: " + status); + } + ); + } + else if (key == "volume") { + Entities.maprfsNameToID("vNameResolver", name, site)._promise.then( + function(response){ + console.log("success"); + console.log(response); + _condList.push($scope._CondUnit(key, op, response.data, type)); + }, + function(error, status){ + console.log("error: " + status); + } + ); + } + }; + + // Add condition for policy + $scope.addCondition = function(key, op, value, type) { + if(value === "" || value === undefined) return false; + + var _condList = $scope.policy.__.conditions[key] = $scope.policy.__.conditions[key] || []; + _condList.push($scope._CondUnit(key, op, value, type)); + + //if it is mapr application, covert human readable name to ids + if(Application.current().tags.application == "maprFSAuditLog") { + if ( key == "dst" || key == "src" || key == "volume" || key == "status") { + $scope.convertToID(_condList, key, op, value , type, Site.current().tags.site); + } + } + + return true; + }; + // Convert condition list to description string + $scope.parseConditionDesc = function(key) { + return $.map($scope.policy.__.conditions[key] || [], function(cond) { + if(!cond.ignored()) return "[" + cond.toString() + "]"; + }).join(" or "); + }; + + // To query + $scope.toQuery = function() { + if(!$scope.policy.__) return ""; + + if($scope.policy.__.advanced) return $scope.policy.__._expression; + + // > Query + var _query = $.map(common.getValueByPath($scope.policy, "__.conditions", {}), function(list) { + var _conds = $.map(list, function(cond) { + if(!cond.ignored()) return cond.toCondString(); + }); + if(_conds.length) { + return "(" + _conds.join(" or ") + ")"; + } + }).join(" and "); + if(_query) { + _query = "[" + _query + "]"; + } + + // > Window + var _window = $scope.getWindow(); + var _windowStr = ""; + if($scope.policy.__.windowConfig) { + _windowStr = $.map(_window.fields, function(field) { + return field.val; + }).join(","); + _windowStr = "#window." + _window.type + "(" + _windowStr + ")"; + + // > Group + if($scope.policy.__.group) { + _windowStr += common.template(" select ${group}, ${groupAgg}(${groupAggPath}) as ${groupAggAlias} group by ${group} having ${groupAggAlias} ${groupCondOp} ${groupCondVal}", { + group: $scope.policy.__.group, + groupAgg: $scope.policy.__.groupAgg, + groupAggPath: $scope.policy.__.groupAggPath, + groupCondOp: $scope.policy.__.groupCondOp, + groupCondVal: $scope.policy.__.groupCondVal, + groupAggAlias: $scope.policy.__.groupAggAlias || "aggValue" + }); + } else { + _windowStr += common.template(" select ${groupAgg}(${groupAggPath}) as ${groupAggAlias} having ${groupAggAlias} ${groupCondOp} ${groupCondVal}", { + groupAgg: $scope.policy.__.groupAgg, + groupAggPath: $scope.policy.__.groupAggPath, + groupCondOp: $scope.policy.__.groupCondOp, + groupCondVal: $scope.policy.__.groupCondVal, + groupAggAlias: $scope.policy.__.groupAggAlias || "aggValue" + }); + } + } else { + _windowStr = " select *"; + } + + return common.template("from ${stream}${query}${window} insert into outputStream;", { + stream: $scope.policy.__.streamName, + query: _query, + window: _windowStr + }); + }; + + // ========================================== + // = Update Policy = + // ========================================== + // dedupeDef notificationDef policyDef + $scope.finishPolicy = function() { + $scope.lock = true; + + // dedupeDef + $scope.policy.__.dedupe.fields = $.map($scope.policy.__._dedupTags, function (value, key) { + if(value) return key; + }); + $scope.policy.dedupeDef = JSON.stringify($scope.policy.__.dedupe); + + // notificationDef + $scope.policy.__.notification = $scope.policy.__.notification || []; + + $scope.policy.notificationDef = JSON.stringify($scope.policy.__.notification); + + // policyDef + $scope.policy.__.policy = { + expression: $scope.toQuery(), + type: "siddhiCEPEngine" + }; + $scope.policy.policyDef = JSON.stringify($scope.policy.__.policy); + + // alertExecutorId + if($scope._stream.alertExecutor) { + $scope.policy.tags.alertExecutorId = $scope._stream.alertExecutor.tags.alertExecutorId; + } else { + $scope.lock = false; + $.dialog({ + title: "OPS!", + content: "Alert Executor not defined! Please check 'AlertExecutorService'!" + }); + return; + } + + // site + $scope.policy.tags.site = $scope.policy.tags.site || Site.current().tags.site; + + // owner + $scope.policy.owner = Authorization.userProfile.username; + + // Update function + function _updatePolicy() { + Entities.updateEntity("AlertDefinitionService", $scope.policy)._promise.success(function(data) { + $.dialog({ + title: "Success", + content: (create ? "Create" : "Update") + " success!" + }, function() { + if(data.success) { + location.href = "#/common/policyList"; + } else { + $.dialog({ + title: "OPS", + content: (create ? "Create" : "Update") + "failed!" + JSON.stringify(data) + }); + } + }); + + $scope.create = create = false; + $scope.encodedRowkey = data.obj[0]; + }).error(function(data) { + $.dialog({ + title: "OPS", + content: (create ? "Create" : "Update") + "failed!" + JSON.stringify(data) + }); + }).then(function() { + $scope.lock = false; + }); + } + + // Check if already exist + if($scope.create) { + var _checkList = Entities.queryEntities("AlertDefinitionService", { + alertExecutorId: $scope.policy.tags.alertExecutorId, + policyId: $scope.policy.tags.policyId, + policyType: "siddhiCEPEngine", + application: $scope.policy.tags.application + }); + _checkList._promise.then(function() { + if(_checkList.length) { + $.dialog({ + title: "Override Confirm", + content: "Already exists PolicyID '" + $scope.policy.tags.policyId + "'. Do you want to override?", + confirm: true + }, function(ret) { + if(ret) { + _updatePolicy(); + } else { + $scope.lock = false; + $scope.$apply(); + } + }); + } else { + _updatePolicy(); + } + }); + } else { + _updatePolicy(); + } + }; + }); + } + + feature.controller('policyCreate', function(PageConfig, Site, Policy, $scope, $wrapState, $q, UI, Entities, Application, Authorization, Notification) { + var _args = [true]; + _args.push.apply(_args, arguments); + policyCtrl.apply(this, _args); + }, "policyEdit"); + feature.controller('policyEdit', function(PageConfig, Site, Policy, $scope, $wrapState, $q, UI, Entities, Application, Authorization, Notification) { + PageConfig.lockSite = true; + var _args = [false]; + _args.push.apply(_args, arguments); + policyCtrl.apply(this, _args); + }); + + // ============================================================== + // = Alerts = + // ============================================================== + + // ========================= Alert List ========================= + feature.navItem("alertList", "Alerts", "exclamation-triangle"); + feature.controller('alertList', function(PageConfig, Site, $scope, $wrapState, $interval, $timeout, Entities, Application) { + PageConfig.pageSubTitle = Site.current().tags.site; + + var MAX_PAGESIZE = 10000; + + // Initial load + $scope.application = Application.current(); + + $scope.alertList = []; + $scope.alertList.ready = false; + + // Load data + function _loadAlerts() { + if($scope.alertList._promise) { + $scope.alertList._promise.abort(); + } + + var _list = Entities.queryEntities("AlertService", { + site: Site.current().tags.site, + application: $scope.application.tags.application, + _pageSize: MAX_PAGESIZE, + _duration: 1000 * 60 * 60 * 24 * 30, + __ETD: 1000 * 60 * 60 * 24 + }); + $scope.alertList._promise = _list._promise; + _list._promise.then(function() { + var index; + + if($scope.alertList[0]) { + // List new alerts + for(index = 0 ; index < _list.length ; index += 1) { + var _alert = _list[index]; + _alert.__new = true; + if(_alert.encodedRowkey === $scope.alertList[0].encodedRowkey) { + break; + } + } + + if(index > 0) { + $scope.alertList.unshift.apply($scope.alertList, _list.slice(0, index)); + + // Clean up UI highlight + $timeout(function() { + $.each(_list, function(i, alert) { + delete alert.__new; + }); + }, 100); + } + } else { + // List all alerts + $scope.alertList.push.apply($scope.alertList, _list); + } + + $scope.alertList.ready = true; + }); + } + + _loadAlerts(); + var _loadInterval = $interval(_loadAlerts, app.time.refreshInterval); + $scope.$on('$destroy',function(){ + $interval.cancel(_loadInterval); + }); + }); + + // ======================== Alert Detail ======================== + feature.controller('alertDetail', function(PageConfig, Site, $scope, $wrapState, Entities) { + PageConfig.pageTitle = "Alert Detail"; + PageConfig.lockSite = true; + PageConfig + .addNavPath("Alert List", "/common/alertList") + .addNavPath("Alert Detail"); + + $scope.common = common; + + // Query policy + $scope.alertList = Entities.queryEntity("AlertService", $wrapState.param.filter); + $scope.alertList._promise.then(function() { + if($scope.alertList.length === 0) { + $.dialog({ + title: "OPS!", + content: "Alert not found!" + }, function() { + location.href = "#/common/alertList"; + }); + } else { + $scope.alert = $scope.alertList[0]; + $scope.alert.rawAlertContext = JSON.stringify($scope.alert.alertContext, null, "\t"); + Site.current(Site.find($scope.alert.tags.site)); + console.log($scope.alert); + } + }); + + // UI + $scope.getMessageTime = function(alert) { + var _time = common.getValueByPath(alert, "alertContext.timestamp"); + return Number(_time); + }; + }); +})(); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/common/page/alertDetail.html ---------------------------------------------------------------------- diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/common/page/alertDetail.html b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/alertDetail.html new file mode 100644 index 0000000..309fac3 --- /dev/null +++ b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/alertDetail.html @@ -0,0 +1,67 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<div class="box box-info"> + <div class="box-header with-border"> + <h3 class="box-title" id="policyId"> + {{alert.tags.policyId}} + <small>{{common.format.date(alert.timestamp)}}</small> + </h3> + </div><!-- /.box-header --> + + <div class="box-body"> + <a class="btn btn-primary pull-right" href="#/common/policyDetail/?policy={{alert.tags.policyId}}&site={{alert.tags.site}}&executor={{alert.tags.alertExecutorId}}">View Policy</a> + + <div class="inline-group"> + <dl><dt>Site</dt><dd>{{alert.tags.site}}</dd></dl> + <dl><dt>Data Source</dt><dd>{{alert.tags.application}}</dd></dl> + </div> + <div class="inline-group"> + <dl><dt>Alert Time</dt><dd>{{common.format.date(alert.timestamp)}}</dd></dl> + <dl><dt>Message Time</dt><dd>{{common.format.date(alert.alertContext.properties.timestamp)}}</dd></dl> + </div> + <div class="inline-group"> + <dl><dt>Stream Name</dt><dd>{{alert.tags.sourceStreams}}</dd></dl> + </div> + <div class="inline-group"> + <dl><dt>Alert Source</dt><dd>{{alert.tags.alertSource}}</dd></dl> + </div> + <div class="inline-group"> + <dl><dt>User</dt><dd>{{alert.alertContext.properties.user}}</dd></dl> + <dl><dt>Host</dt><dd>{{alert.alertContext.properties.host}}</dd></dl> + </div> + <div class="inline-group"> + <dl><dt>Event</dt><dd>{{alert.alertContext.properties.alertEvent}}</dd></dl> + </div> + <div class="inline-group"> + <dl><dt>Message</dt><dd>{{alert.alertContext.properties.alertMessage}}</dd></dl> + </div> + </div><!-- /.box-body --> + + <div class="overlay" ng-hide="alertList._promise.$$state.status === 1;"> + <i class="fa fa-refresh fa-spin"></i> + </div> + + <div class="box-footer clearfix"> + <a data-toggle="collapse" href="[data-id='rawAlertContext']"> + Raw Alert Context + </a> + <div data-id="rawAlertContext" class="collapse"> + <pre>{{alert.rawAlertContext}}</pre> + </div> + </div> +</div> http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/common/page/alertList.html ---------------------------------------------------------------------- diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/common/page/alertList.html b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/alertList.html new file mode 100644 index 0000000..0415cc0 --- /dev/null +++ b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/alertList.html @@ -0,0 +1,67 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<div class="box box-primary"> + <div class="box-header with-border"> + <i class="fa fa-list-alt"> </i> + <h3 class="box-title"> + {{application.displayName}} + </h3> + </div> + <div class="box-body"> + <p ng-show="!alertList.ready"> + <span class="fa fa-refresh fa-spin"> </span> + Loading... + </p> + + <div sorttable source="alertList" sort="-timestamp"> + <table class="table table-bordered" ng-non-bindable> + <thead> + <tr> + <th width="170" sortpath="timestamp">Alert Time</th> + <th width="170" sortpath="alertContext.properties.timestamp">Message Time</th> + <th width="105" sortpath="tags.application">Application</th> + <th width="150" sortpath="tags.policyId">Policy Name</th> + <th width="60" sortpath="alertContext.properties.user">User</th> + <th width="150" sortpath="tags.alertSource">Source</th> + <th sortpath="alertContext.properties.emailMessage">Description</th> + <th width="50"> </th> + </tr> + </thead> + <tbody> + <tr ng-class="{info : item.__new}"> + <td>{{common.format.date(item.timestamp)}}</td> + <td>{{common.format.date(item.alertContext.properties.timestamp)}}</td> + <td>{{item.tags.application}}</td> + <td class="text-nowrap"> + <a class="fa fa-share-square-o" ng-show="item.tags.policyId" + href="#/common/policyDetail/?policy={{item.tags.policyId}}&site={{item.tags.site}}&executor={{item.tags.alertExecutorId}}"> </a> + {{item.tags.policyId}} + </td> + <td>{{item.alertContext.properties.user}}</td> + <td>{{item.tags.alertSource}}</td> + <td>{{item.alertContext.properties.alertMessage}}</td> + <td><a href="#/common/alertDetail/{{item.encodedRowkey}}">Detail</a></td> + </tr> + </tbody> + </table> + </div> + + </div> + <!--div class="box-footer clearfix"> + </div--> +</div> http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyDetail.html ---------------------------------------------------------------------- diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyDetail.html b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyDetail.html new file mode 100644 index 0000000..cdddc43 --- /dev/null +++ b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyDetail.html @@ -0,0 +1,173 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<div class="box box-info"> + <div class="box-header with-border"> + <h3 class="box-title"> + {{policy.tags.policyId}} + <small>{{policy.tags.site}}</small> + </h3> + </div> + + <div class="box-body"> + <div class="row"> + <div class="col-xs-8"> + <div class="inline-group"> + <dl><dt>Data Source</dt><dd>{{policy.tags.application}}</dd></dl> + <dl><dt>Status</dt><dd> + <span ng-show="policy.enabled" class="text-muted"><i class="fa fa-square text-green"></i> Enabled</span> + <span ng-show="!policy.enabled" class="text-muted"><i class="fa fa-square text-muted"></i> Disabled</span> + </dd></dl> + </div> + <div class="inline-group"> + <dl><dt>Description</dt><dd>{{policy.description}}</dd></dl> + </div> + <!--div class="inline-group"> + <dl><dt>Alert</dt><dd> + <a href="mailto:{{mail}}" ng-repeat="mail in policy.__mailList track by $index" style="margin-right: 10px;">{{mail}}</a> + <div tabs> + <pane ng-repeat="notification in policy.__notificationList track by $index" data-title="{{notification.notificationType}}"> + </pane> + </div> + </dd></dl> + </div--> + <label>Notification</label> + <div tabs> + <pane ng-repeat="notification in policy.__notificationList track by $index" data-title="{{notification.notificationType}}"> + <table class="table table-bordered"> + <tbody> + <tr ng-repeat="(key, value) in notification track by $index"> + <th width="30%">{{key}}</th> + <td>{{value}}</td> + </tr> + </tbody> + </table> + </pane> + </div> + </div> + <div class="col-xs-4 text-right" ng-show="Auth.isRole('ROLE_ADMIN')"> + <a class="btn btn-primary" href="#/common/policyEdit/{{policy.encodedRowkey}}">Edit</a> + <button class="btn btn-warning" ng-show="!policy.enabled" ng-click="updatePolicyStatus(policy, true)">Enable</button> + <button class="btn btn-warning" ng-show="policy.enabled" ng-click="updatePolicyStatus(policy, false)">Disable</button> + <button class="btn btn-danger" ng-click="deletePolicy(policy)">Delete</button> + </div> + </div> + </div> + + <div class="overlay" ng-hide="policyList._promise.$$state.status === 1;"> + <i class="fa fa-refresh fa-spin"></i> + </div> + + <div class="box-footer clearfix"> + <a data-toggle="collapse" href="[data-id='query']"> + View Query + </a> + <div data-id="query" class="collapse in"> + <pre>{{policy.__expression}}</pre> + </div> + </div> +</div> + +<div tabs> + <pane data-title="Visualization" menu="visualizationMenu"> + <div class="row"> + <div class="col-xs-6"> + <div nvd3="policyEvalSeries" data-title="Policy Eval Count" data-config="chartConfig" class="nvd3-chart-cntr"></div> + </div> + <div class="col-xs-6"> + <div nvd3="policyEvalFailSeries" data-title="Policy Eval Fail Count" data-config="chartConfig" class="nvd3-chart-cntr"></div> + </div> + <div class="col-xs-6"> + <div nvd3="alertSeries" data-title="Alert Count" data-config="chartConfig" class="nvd3-chart-cntr"></div> + </div> + <div class="col-xs-6"> + <div nvd3="alertFailSeries" data-title="Alert Fail Count" data-config="chartConfig" class="nvd3-chart-cntr"></div> + </div> + </div> + </pane> + <pane data-title="Statistics"> + <div class="row"> + <div class="col-xs-3"> + <div class="info-box bg-aqua"> + <span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span> + <div class="info-box-content"> + <span class="info-box-text">Policy Eval Count</span> + <span class="info-box-number">{{common.array.sum(policyEvalSeries, "1")}} <small>(Monthly)</small></span> + <span class="info-box-number">{{policyEvalSeries[policyEvalSeries.length - 1][1]}} <small>(Daily)</small></span> + </div> + </div> + </div> + <div class="col-xs-3"> + <div class="info-box bg-red"> + <span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span> + <div class="info-box-content"> + <span class="info-box-text">Policy Eval Fail Count</span> + <span class="info-box-number">{{common.array.sum(policyEvalFailSeries, "1")}} <small>(Monthly)</small></span> + <span class="info-box-number">{{policyEvalFailSeries[policyEvalFailSeries.length - 1][1]}} <small>(Daily)</small></span> + </div> + </div> + </div> + <div class="col-xs-3"> + <div class="info-box bg-aqua"> + <span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span> + <div class="info-box-content"> + <span class="info-box-text">Alert Count</span> + <span class="info-box-number">{{common.array.sum(alertSeries, "1")}} <small>(Monthly)</small></span> + <span class="info-box-number">{{alertSeries[alertSeries.length - 1][1]}} <small>(Daily)</small></span> + </div> + </div> + </div> + <div class="col-xs-3"> + <div class="info-box bg-red"> + <span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span> + <div class="info-box-content"> + <span class="info-box-text">Alert Fail Count</span> + <span class="info-box-number">{{common.array.sum(alertFailSeries, "1")}} <small>(Monthly)</small></span> + <span class="info-box-number">{{alertFailSeries[alertFailSeries.length - 1][1]}} <small>(Daily)</small></span> + </div> + </div> + </div> + </div> + </pane> + <pane data-title="Alerts"> + <div sorttable source="alertList" sort="-timestamp"> + <table class="table table-bordered" ng-non-bindable> + <thead> + <tr> + <th width="170" sortpath="timestamp">Alert Time</th> + <th width="170" sortpath="alertContext.properties.timestamp">Message Time</th> + <th width="60" sortpath="alertContext.properties.user">User</th> + <th width="150" sortpath="alertContext.properties.host">Host</th> + <th sortpath="alertContext.properties.emailMessage">Description</th> + <th width="50"> </th> + </tr> + </thead> + <tbody> + <tr ng-class="{info : item.__new}"> + <td>{{common.format.date(item.timestamp)}}</td> + <td>{{common.format.date(item.alertContext.properties.timestamp)}}</td> + <td>{{item.alertContext.properties.user}}</td> + <td>{{item.alertContext.properties.host}}</td> + <td>{{item.alertContext.properties.alertMessage}}</td> + <td><a href="#/common/alertDetail/{{item.encodedRowkey}}">Detail</a></td> + </tr> + </tbody> + </table> + </div> + </pane> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyEdit.html ---------------------------------------------------------------------- diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyEdit.html b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyEdit.html new file mode 100644 index 0000000..33d4cde --- /dev/null +++ b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyEdit.html @@ -0,0 +1,346 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<div class="progress active" ng-show="!streamReady"> + <div class="progress-bar progress-bar-primary progress-bar-striped" style="width: 100%"> + </div> +</div> + +<!-- Step navigation --> +<div ng-show="streamReady"> + <div class="row step-cntr"> + <div class="col-sm-4" ng-repeat="step in steps"> + <div class="step" ng-class="stepSelect($index + 1)"> + <h1>{{$index + 1}}</h1> + <h2>Step {{$index + 1}}</h2> + <p title="{{step.title}}">{{step.title}}</p> + </div> + </div> + </div> + + <!-- Step container --> + <div class="box box-info"> + <div class="box-header with-border"> + <h3 class="box-title">Step {{step}} - {{steps[step - 1].title}}</h3> + </div><!-- /.box-header --> + + <div class="box-body"> + <!-- ---------------------- Step Body Start ---------------------- --> + + <!-- Step 1: Stream --> + <div ng-show="step === 1"> + <div class="pull-right" ng-show="policy.__.advanced === undefined"> + <span class="text-muted">or</span> + <a ng-click="policy.__.advanced = true;">Advanced</a> + </div> + + <div class="form-group"> + <label>Select Stream</label> + <select class="form-control" ng-model="policy.__.streamName" ng-show="!policy.__.advanced"> + <option ng-repeat="stream in applications[policy.tags.application]">{{stream.tags.streamName}}</option> + </select> + <select class="form-control" ng-show="policy.__.advanced" disabled="disabled"> + <option>[Advanced Mode]</option> + </select> + </div> + + <div class="checkbox" ng-show="policy.__.advanced !== undefined"> + <label> + <input type="checkbox" ng-model="policy.__.advanced"> + Advanced Mode + </label> + </div> + </div> + + <!-- Step 2: Define Alert Policy --> + <div ng-show="step === 2 && !policy.__.advanced"> + <!-- Criteria --> + <div> + <label>Match Criteria</label> + <a ng-click="collapse('.panel-group')">expand / collapse all</a> + + <div class="panel-group panel-group-sm" role="tablist"> + <div class="panel panel-default" + ng-repeat="meta in _stream.metas" + ng-init="op = '=='; val = null; type = (meta.attrType || '').toLowerCase();"> + <div class="panel-heading" role="tab"> + <h4 class="panel-title"> + <span class="bg-navy disabled color-palette pull-right"> + {{parseConditionDesc(meta.tags.attrName)}} + </span> + + <a role="button" data-toggle="collapse" href="[data-name='{{meta.tags.attrName}}']" class="collapsed"> + <span class="fa fa-square" ng-class="hasCondition(meta.tags.attrName, type) ? 'text-green' : 'text-muted'"> </span> + {{meta.attrDisplayName || meta.tags.attrName}} + <span class="fa fa-question-circle" ng-show="meta.attrDescription" + uib-tooltip="{{meta.attrDescription}}" tooltip-placement="right" tooltip-animation="false"> </span> + </a> + </h4> + </div> + <div data-name="{{meta.tags.attrName}}" data-type="{{meta.attrType}}" role="tabpanel" class="collapse"> + <div class="panel-body"> + <ul ng-show="type !== 'bool'"> + <li ng-repeat="cond in policy.__.conditions[meta.tags.attrName]"> + [<a ng-click="policy.__.conditions[meta.tags.attrName].splice($index, 1)">X</a>] + {{cond.toString()}} + </li> + </ul> + + <!-- String --> + <div ng-if="type == 'string'"> + <div class="input-group" style="max-width: 450px;"> + <div class="input-group-btn"> + <select class="form-control" ng-model="op"> + <option ng-repeat="mark in ['==','!=','contains','regex']">{{mark}}</option> + </select> + </div> + + <!-- With resolver --> + <input type="text" class="form-control" autocomplete="off" ng-model="val" ng-show="meta.attrValueResolver" + ng-keypress="conditionPress($event, meta.tags.attrName, op, val, type)" + uib-typeahead="item for item in resolverTypeahead($viewValue, meta.attrValueResolver)"> + <!-- Without resolver --> + <input type="text" class="form-control" autocomplete="off" ng-model="val" ng-show="!meta.attrValueResolver" + ng-keypress="conditionPress($event, meta.tags.attrName, op, val, type)"> + + <span class="input-group-btn"> + <button class="btn btn-info btn-flat" type="button" ng-click="addCondition(meta.tags.attrName, op, val, type);val=null;">Add</button> + </span> + </div> + </div> + + <!-- Number --> + <div ng-if="type == 'long' || type == 'integer' || type == 'number' || type == 'double' || type == 'float'"> + <div class="input-group" style="max-width: 450px;"> + <div class="input-group-btn"> + <select class="form-control" ng-model="op"> + <option ng-repeat="mark in ['==','!=','>','>=','<','<=']">{{mark}}</option> + </select> + </div> + + <input type="number" class="form-control" autocomplete="off" placeholder="Number Only..." ng-model="val" ng-keypress="conditionPress($event, meta.tags.attrName, op, val, type)"> + <span class="input-group-btn"> + <button class="btn btn-info btn-flat" type="button" ng-click="addCondition(meta.tags.attrName, op, val, type) ? val=null : void(0);">Add</button> + </span> + </div> + </div> + + <!-- Boolean --> + <div ng-if="type == 'bool'" ng-init="policy.__.conditions[meta.tags.attrName] = policy.__.conditions[meta.tags.attrName] || [_CondUnit(meta.tags.attrName, '==', 'none', 'bool')]"> + <select class="form-control" ng-model="policy.__.conditions[meta.tags.attrName][0].val" style="max-width: 100px;"> + <option ng-repeat="bool in ['none','true','false']">{{bool}}</option> + </select> + </div> + </div> + </div> + </div> + </div> + </div> + + <!-- Window --> + <div class="checkbox"> + <label> + <input type="checkbox" ng-checked="policy.__.windowConfig" ng-click="policy.__.windowConfig = !policy.__.windowConfig"> Slide Window + </label> + </div> + <div ng-show="policy.__.windowConfig"> + <div class="row"> + <div class="col-md-4"> + <div class="form-group"> + <label>Window</label> + <select class="form-control" ng-model="policy.__.window" + uib-tooltip="{{getWindow().description}}" tooltip-animation="false"> + <option ng-repeat="item in config.window" value="{{item.type}}">{{item.title}}</option> + </select> + </div> + </div> + + <!-- fields --> + <div class="col-md-4" ng-repeat="field in getWindow().fields" ng-init="field.val = field.val || (field.defaultValue || '');" ng-hide="field.hide"> + <div class="form-group" ng-class="{'has-warning' : !field.val || !field.val.match(field.regex)}"> + <label>Window - {{field.title}}</label> + <input type="text" class="form-control" autocomplete="off" placeholder="{{field.description}}" ng-model="field.val" title="{{field.description}}"> + </div> + </div> + </div> + + <!-- Aggregation --> + <div class="row"> + <div class="col-md-4"> + <div class="form-group" ng-class="{'text-yellow' : (policy.__.groupAgg && !policy.__.groupAggPath)}"> + <label>Aggregation</label> + <div class="input-group"> + <div class="input-group-btn"> + <select class="form-control" ng-model="policy.__.groupAgg" ng-change="updateGroupAgg()"> + <option ng-repeat="op in ['max','min','avg','count', 'sum']">{{op}}</option> + </select> + </div> + <select class="form-control" ng-model="policy.__.groupAggPath" ng-class="{'has-warning' : !policy.__.groupAggPath}" id="groupAggPath" + ng-show="policy.__.groupAgg" ng-disabled="policy.__.groupAgg === 'count'"> + <option ng-repeat="meta in groupAggPathList()">{{meta.tags.attrName}}</option> + </select> + </div> + </div> + </div> + + <div class="col-md-4"> + <div class="form-group" ng-class="{'text-yellow' : (!policy.__.groupCondOp || !policy.__.groupCondVal)}"> + <label>Condition</label> + <div class="input-group"> + <div class="input-group-btn"> + <select class="form-control" ng-model="policy.__.groupCondOp" ng-class="{'has-warning' : !policy.__.groupCondOp}"> + <option ng-repeat="op in ['>','<','>=','<=','==']">{{op}}</option> + </select> + </div> + <input type="text" class="form-control" ng-model="policy.__.groupCondVal" ng-class="{'has-warning' : !policy.__.groupCondVal}" /> + </div> + </div> + </div> + + <div class="col-md-4"> + <div class="form-group"> + <label>Alias (Optional)</label> + <input type="text" class="form-control" ng-model="policy.__.groupAggAlias" placeholder="Default: aggValue" /> + </div> + </div> + </div> + + <!-- Group --> + <div class="row"> + <div class="col-md-4"> + <div class="form-group"> + <label>Group By</label> + <select class="form-control" ng-model="policy.__.group"> + <option value="">None</option> + <option ng-repeat="meta in _stream.metas">{{meta.tags.attrName}}</option> + </select> + </div> + </div> + </div> + </div> + </div> + + <!-- Step 2: Define Alert Policy --> + <div ng-show="step === 2 && policy.__.advanced"> + <div class="form-group"> + <label>Query Expression</label> + <textarea class="form-control" ng-model="policy.__._expression" + placeholder="Query expression. e.g. from hdfsAuditLogEventStream[(cmd=='open') and (host=='localhost' or host=='127.0.0.1')]#window.time(2 sec) select * insert into outputStream;" rows="5"></textarea> + </div> + </div> + + <!-- Step 3: Email Notification --> + <div ng-show="step === 3"> + <div class="row"> + <div class="col-xs-4"> + <div class="form-group" ng-class="{'has-warning' : !policy.tags.policyId}"> + <label>Policy Name</label> + <input type="text" class="form-control" placeholder="" ng-model="policy.tags.policyId" ng-disabled="!create"> + </div> + </div> + <div class="col-xs-3"> + <div class="form-group"> + <label> + Alert De-Dup Interval(min) + <span class="fa fa-question-circle" uib-tooltip="Same type alert will be De-dup in configured interval"> </span> + </label> + <input type="number" class="form-control" ng-model="policy.__.dedupe.alertDedupIntervalMin" placeholder="[Minute] Number only. Suggestion: 720"> + </div> + </div> + <div class="col-xs-2"> + <div class="form-group"> + <label> + Enabled + </label> + <p> + <input type="checkbox" checked="checked" ng-model="policy.enabled"> + </p> + </div> + </div> + </div> + + <div> + <a data-toggle="collapse" href="[data-id='advancedDeDup']">Advanced De-Dup</a> + <div data-id='advancedDeDup' class="collapse"> + <label> + De-Dup Key + <span class="fa fa-question-circle" uib-tooltip="Type of grouping alerts. If you don't know how to config, leave it default."> </span> + </label> + <div class="form-group"> + <div class="checkbox" ng-repeat="meta in _stream.metas" ng-init="create ? policy.__._dedupTags[meta.tags.attrName] = !!meta.usedAsTag : void(0);"> + <label> + <input type="checkbox" ng-model="policy.__._dedupTags[meta.tags.attrName]"> + {{meta.tags.attrName}} + </label> + </div> + </div> + </div> + </div> + + <hr/> + + <label>Notification</label> + <div tabs menu="menu" holder="notificationTabHolder" ng-show="policy.__.notification.length"> + <pane ng-repeat="notification in policy.__.notification track by $index" data-data="notification" data-title="{{notification.notificationType}}"> + <div class="form-group" ng-repeat="field in Notification.map[notification.notificationType].fieldList track by $index"> + <label>{{field.name}}</label> + <input type="text" class="form-control" ng-model="notification[field.name]"> + </div> + <p class="text-muted" ng-if="Notification.map[notification.notificationType].fieldList.length === 0">No configuration required</p> + </pane> + </div> + <ul ng-show="policy.__.notification.length === 0"> + <li ng-repeat="notification in Notification.list track by $index"> + <a ng-click="newNotification(notification.tags.notificationType)">+ New {{notification.tags.notificationType}} Notification</a> + </li> + </ul> + + <hr/> + + <div class="form-group"> + <label>Description</label> + <textarea class="form-control" placeholder="Policy description" ng-model="policy.description"></textarea> + </div> + + <a data-toggle="collapse" href="[data-id='policyQuery']"> + View Query + </a> + <div class="collapse in" data-id="policyQuery"> + <pre>{{toQuery()}}</pre> + </div> + </div> + + <!-- ----------------------- Step Body End ----------------------- --> + </div><!-- /.box-body --> + + <div class="overlay" ng-hide="stepReady(step)"> + <span class="fa fa-refresh fa-spin"> </span> + </div> + + <div class="box-footer text-right"> + <button class="btn btn-info" ng-show="step > 1" ng-click="changeStep(step, step - 1, false)" ng-disabled="lock"> + Prev <span class="fa fa-arrow-circle-o-left"> </span> + </button> + <button class="btn btn-info" ng-show="step < steps.length" ng-click="changeStep(step, step + 1)" ng-disabled="checkNextable(step) || lock"> + Next <span class="fa fa-arrow-circle-o-right"> </span> + </button> + <button class="btn btn-info" ng-show="step === steps.length" ng-click="finishPolicy()" ng-disabled="checkNextable(step) || lock"> + Done <span class="fa fa-check-circle-o"> </span> + </button> + </div> + </div> +</div> http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyList.html ---------------------------------------------------------------------- diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyList.html b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyList.html new file mode 100644 index 0000000..20a38dd --- /dev/null +++ b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyList.html @@ -0,0 +1,84 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<div class="box box-primary"> + <div class="box-header with-border"> + <i class="fa fa-list-alt"> </i> + <h3 class="box-title"> + {{application.displayName}} + </h3> + </div> + <div class="box-body"> + <div class="row"> + <div class="col-xs-3"> + <div class="search-box"> + <input type="search" class="form-control input-sm" placeholder="Search" ng-model="search" /> + <span class="fa fa-search"> </span> + </div> + </div> + <div class="col-xs-6"> + <div class="inline-group form-inline text-muted"> + <dl><dt><i class="fa fa-square text-green"> </i></dt><dd>Enabled</dd></dl> + <dl><dt><i class="fa fa-square text-muted"> </i></dt><dd>Disabled</dd></dl> + </div> + </div> + <div class="col-xs-3 text-right"> + <a class="btn btn-primary" href="#/common/policyCreate/{{!application ? '' : '?app=' + application.tags.application}}" ng-show="Auth.isRole('ROLE_ADMIN')"> + New Policy + <i class="fa fa-plus-circle"> </i> + </a> + </div> + </div> + + <p ng-show="policyList._promise.$$state.status !== 1"> + <span class="fa fa-refresh fa-spin"> </span> + Loading... + </p> + + <div sorttable source="policyList" ng-show="policyList._promise.$$state.status === 1" search="false" searchfunc="searchFunc"> + <table class="table table-bordered" ng-non-bindable> + <thead> + <tr> + <th width="30" sortpath="enabled"> </th> + <th width="200" sortpath="tags.policyId">Policy Name</th> + <th sortpath="description">Description</th> + <th width="150" sortpath="owner">Owner</th> + <th width="170" sortpath="lastModifiedDate">Last Modified</th> + <th width="95" ng-show="_parent.Auth.isRole('ROLE_ADMIN')">Action</th> + </tr> + </thead> + <tbody> + <tr> + <td><span class='fa fa-square' ng-class="item.enabled ? 'text-green' : 'text-muted'"> </span></td> + <td><a href="#/common/policyDetail/{{item.encodedRowkey}}" style="width: 200px;" class="text-breakall">{{item.tags.policyId}}</a></td> + <td>{{item.description}}</td> + <td>{{item.owner}}</td> + <td>{{common.format.date(item.lastModifiedDate) || "-"}}</td> + <td ng-show="_parent.Auth.isRole('ROLE_ADMIN')"> + <a class="fa fa-pencil btn btn-default btn-xs" uib-tooltip="Edit" tooltip-animation="false" href="#/common/policyEdit/{{item.encodedRowkey}}"> </a> + <button class="fa fa-play sm btn btn-default btn-xs" uib-tooltip="Enable" tooltip-animation="false" ng-show="!item.enabled" ng-click="_parent.updatePolicyStatus(item, true)"> </button> + <button class="fa fa-pause sm btn btn-default btn-xs" uib-tooltip="Disable" tooltip-animation="false" ng-show="item.enabled" ng-click="_parent.updatePolicyStatus(item, false)"> </button> + <button class="rm fa fa-trash-o btn btn-danger btn-xs" uib-tooltip="Delete" tooltip-animation="false" ng-click="_parent.deletePolicy(item)"> </button> + </td> + </tr> + </tbody> + </table> + </div> + </div> + <!--div class="box-footer clearfix"> + </div--> +</div> http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/metadata/controller.js ---------------------------------------------------------------------- diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/metadata/controller.js b/eagle-webservice/src/main/webapp/_app/public/feature/metadata/controller.js new file mode 100644 index 0000000..c17d612 --- /dev/null +++ b/eagle-webservice/src/main/webapp/_app/public/feature/metadata/controller.js @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +(function() { + 'use strict'; + + var featureControllers = angular.module('featureControllers'); + var feature = featureControllers.register("metadata"); + + // ============================================================== + // = Function = + // ============================================================== + + // ============================================================== + // = Metadata = + // ============================================================== + + // ======================= Metadata List ======================== + feature.navItem("streamList", "Metadata", "bullseye"); + feature.controller('streamList', function(PageConfig, Site, $scope, $q, Application, Entities) { + PageConfig.hideSite = true; + + $scope.streams = {}; + $scope._streamEntity = null; + $scope._streamEntityLock = false; + + // =========================================== List =========================================== + var _streamList = Entities.queryEntities("AlertStreamService", {application: Application.current().tags.application}); + var _streamSchemaList = Entities.queryEntities("AlertStreamSchemaService", {application: Application.current().tags.application}); + $scope.streamList = _streamList; + $scope.streamSchemaList = _streamSchemaList; + + _streamList._promise.then(function() { + $.each(_streamList, function(i, stream) { + stream.metaList = []; + $scope.streams[stream.tags.application + "_" + stream.tags.streamName] = stream; + }); + }); + + $q.all([_streamList._promise, _streamSchemaList._promise]).then(function() { + $.each(_streamSchemaList, function(i, meta) { + var _stream = $scope.streams[meta.tags.application + "_" + meta.tags.streamName]; + if(_stream) { + _stream.metaList.push(meta); + } else { + console.warn("[Meta] Stream not match:", meta.tags.application + "_" + meta.tags.streamName); + } + }); + }); + }); +})(); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/metadata/page/streamList.html ---------------------------------------------------------------------- diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/metadata/page/streamList.html b/eagle-webservice/src/main/webapp/_app/public/feature/metadata/page/streamList.html new file mode 100644 index 0000000..2b73300 --- /dev/null +++ b/eagle-webservice/src/main/webapp/_app/public/feature/metadata/page/streamList.html @@ -0,0 +1,84 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<p ng-show="streamList._promise.$$state.status !== 1"> + <span class="fa fa-refresh fa-spin"> </span> + Loading... +</p> + +<div class="box box-primary" ng-repeat="stream in streams"> + <div class="box-header with-border"> + <h3 class="box-title">{{stream.tags.streamName}}</h3> + </div> + <div class="box-body"> + <div class="inline-group"> + <dl> + <dt> + Data Source + </dt> + <dd> + {{stream.tags.application}} + </dd> + </dl> + <dl> + <dt> + Stream Name + </dt> + <dd> + {{stream.tags.streamName}} + </dd> + </dl> + </div> + <div> + <dl> + <dt> + Description + </dt> + <dd> + {{stream.description}} + </dd> + </dl> + </div> + + <p ng-show="streamSchemaList._promise.$$state.status !== 1"> + <span class="fa fa-refresh fa-spin"> </span> + Loading... + </p> + + <div class="list" ng-show="streamSchemaList._promise.$$state.status === 1"> + <table class="table table-bordered"> + <thead> + <tr> + <th width="10%">Attribute Name</th> + <th width="12%">Display Name</th> + <th width="8%">Type</th> + <th>Description</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="meta in stream.metaList"> + <td>{{meta.tags.attrName}}</td> + <td>{{meta.attrDisplayName}}</td> + <td><span class="label label-warning">{{meta.attrType}}</span></td> + <td>{{meta.attrDescription}}</td> + </tr> + </tbody> + </table> + </div> + </div><!-- /.box-body --> + <!-- Loading (remove the following to stop the loading)--> +</div> \ No newline at end of file